Merge branch 'backend-rewrite' into 'main'
Rewrote this piece of shit backend See merge request root/fileserver!7
This commit is contained in:
commit
806563de4d
29
.eslintrc.js
29
.eslintrc.js
@ -1,29 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
project: 'tsconfig.json',
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
sourceType: 'module',
|
|
||||||
},
|
|
||||||
plugins: ['@typescript-eslint/eslint-plugin', 'no-relative-import-paths'],
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:prettier/recommended',
|
|
||||||
],
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
jest: true,
|
|
||||||
},
|
|
||||||
ignorePatterns: ['.eslintrc.js'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/interface-name-prefix': 'off',
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'no-relative-import-paths/no-relative-import-paths': [
|
|
||||||
'error',
|
|
||||||
{ 'allowSameFolder': true, 'rootDir': 'src' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
};
|
|
422
.gitignore
vendored
422
.gitignore
vendored
@ -1,37 +1,58 @@
|
|||||||
# Created by .ignore support plugin (hsz.mobi)
|
# Created by https://www.toptal.com/developers/gitignore/api/clion
|
||||||
### JetBrains template
|
# Edit at https://www.toptal.com/developers/gitignore?templates=clion
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
|
||||||
|
### CLion ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
# User-specific stuff:
|
# User-specific stuff
|
||||||
.idea/**/workspace.xml
|
.idea/**/workspace.xml
|
||||||
.idea/**/tasks.xml
|
.idea/**/tasks.xml
|
||||||
.idea/dictionaries
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
# Sensitive or high-churn files:
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
.idea/**/dataSources/
|
.idea/**/dataSources/
|
||||||
.idea/**/dataSources.ids
|
.idea/**/dataSources.ids
|
||||||
.idea/**/dataSources.xml
|
|
||||||
.idea/**/dataSources.local.xml
|
.idea/**/dataSources.local.xml
|
||||||
.idea/**/sqlDataSources.xml
|
.idea/**/sqlDataSources.xml
|
||||||
.idea/**/dynamic.xml
|
.idea/**/dynamic.xml
|
||||||
.idea/**/uiDesigner.xml
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
# Gradle:
|
# Gradle
|
||||||
.idea/**/gradle.xml
|
.idea/**/gradle.xml
|
||||||
.idea/**/libraries
|
.idea/**/libraries
|
||||||
|
|
||||||
# CMake
|
# Gradle and Maven with auto-import
|
||||||
cmake-build-debug/
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
# Mongo Explorer plugin:
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
.idea/**/mongoSettings.xml
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
## File-based project format:
|
# File-based project format
|
||||||
*.iws
|
*.iws
|
||||||
|
|
||||||
## Plugin-specific files:
|
|
||||||
|
|
||||||
# IntelliJ
|
# IntelliJ
|
||||||
out/
|
out/
|
||||||
|
|
||||||
@ -44,358 +65,55 @@ atlassian-ide-plugin.xml
|
|||||||
# Cursive Clojure plugin
|
# Cursive Clojure plugin
|
||||||
.idea/replstate.xml
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
com_crashlytics_export_strings.xml
|
com_crashlytics_export_strings.xml
|
||||||
crashlytics.properties
|
crashlytics.properties
|
||||||
crashlytics-build.properties
|
crashlytics-build.properties
|
||||||
fabric.properties
|
fabric.properties
|
||||||
### VisualStudio template
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
# Editor-based Rest Client
|
||||||
*.suo
|
.idea/httpRequests
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# Android studio 3.1+ serialized cache file
|
||||||
*.userprefs
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
# Build results
|
### CLion Patch ###
|
||||||
[Dd]ebug/
|
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
|
|
||||||
# Visual Studio 2015 cache/options directory
|
# *.iml
|
||||||
.vs/
|
# modules.xml
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
# .idea/misc.xml
|
||||||
#wwwroot/
|
# *.ipr
|
||||||
|
|
||||||
# MSTest test Results
|
# Sonarlint plugin
|
||||||
[Tt]est[Rr]esult*/
|
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||||
[Bb]uild[Ll]og.*
|
.idea/**/sonarlint/
|
||||||
|
|
||||||
# NUNIT
|
# SonarQube Plugin
|
||||||
*.VisualState.xml
|
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||||
TestResult.xml
|
.idea/**/sonarIssues.xml
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
# Markdown Navigator plugin
|
||||||
[Dd]ebugPS/
|
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||||
[Rr]eleasePS/
|
.idea/**/markdown-navigator.xml
|
||||||
dlldata.c
|
.idea/**/markdown-navigator-enh.xml
|
||||||
|
.idea/**/markdown-navigator/
|
||||||
|
|
||||||
# Benchmark Results
|
# Cache file creation bug
|
||||||
BenchmarkDotNet.Artifacts/
|
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||||
|
.idea/$CACHE_FILE$
|
||||||
|
|
||||||
# .NET Core
|
# CodeStream plugin
|
||||||
project.lock.json
|
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||||
project.fragment.lock.json
|
.idea/codestream.xml
|
||||||
artifacts/
|
|
||||||
**/Properties/launchSettings.json
|
|
||||||
|
|
||||||
*_i.c
|
# Azure Toolkit for IntelliJ plugin
|
||||||
*_p.c
|
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||||
*_i.h
|
.idea/**/azureSettings.xml
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
# End of https://www.toptal.com/developers/gitignore/api/clion
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
run/
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# JustCode is a .NET coding add-in
|
|
||||||
.JustCode
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Typescript v1 declaration files
|
|
||||||
typings/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
.idea/
|
|
||||||
*.sln.iml
|
|
||||||
|
|
||||||
# IDE - VSCode
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
|
|
||||||
# CodeRush
|
|
||||||
.cr/
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
coverage/
|
|
||||||
|
|
||||||
### macOS template
|
|
||||||
# General
|
|
||||||
.DS_Store
|
|
||||||
.AppleDouble
|
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Icon must end with two \r
|
|
||||||
Icon
|
|
||||||
|
|
||||||
# Thumbnails
|
|
||||||
._*
|
|
||||||
|
|
||||||
# Files that might appear in the root of a volume
|
|
||||||
.DocumentRevisions-V100
|
|
||||||
.fseventsd
|
|
||||||
.Spotlight-V100
|
|
||||||
.TemporaryItems
|
|
||||||
.Trashes
|
|
||||||
.VolumeIcon.icns
|
|
||||||
.com.apple.timemachine.donotpresent
|
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
|
||||||
.AppleDB
|
|
||||||
.AppleDesktop
|
|
||||||
Network Trash Folder
|
|
||||||
Temporary Items
|
|
||||||
.apdisk
|
|
||||||
|
|
||||||
=======
|
|
||||||
# Local
|
|
||||||
.env
|
|
||||||
dist
|
|
||||||
|
|
||||||
files
|
|
||||||
sqlite.db
|
|
112
.gitlab-ci.yml
112
.gitlab-ci.yml
@ -1,89 +1,42 @@
|
|||||||
image: node:latest
|
image: ubuntu:latest
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- setup
|
|
||||||
- test
|
|
||||||
- build
|
- build
|
||||||
- package
|
- package
|
||||||
|
|
||||||
cache: &global_cache
|
|
||||||
paths:
|
|
||||||
- .yarn
|
|
||||||
- node_modules
|
|
||||||
- frontend/.yarn
|
|
||||||
- frontend/node_modules
|
|
||||||
policy: pull
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- yarn install --cache-folder .yarn --frozen-lockfile
|
|
||||||
- cd frontend
|
|
||||||
- yarn install --cache-folder .yarn --frozen-lockfile
|
|
||||||
- cd ..
|
|
||||||
|
|
||||||
.dto_artifacts_need: &dto_artifacts_need
|
|
||||||
job: test_build_dto
|
|
||||||
artifacts: true
|
|
||||||
|
|
||||||
test_build_dto:
|
|
||||||
stage: setup
|
|
||||||
cache:
|
|
||||||
<<: *global_cache
|
|
||||||
policy: pull-push
|
|
||||||
before_script: []
|
|
||||||
script:
|
|
||||||
- cd dto
|
|
||||||
- yarn install --frozen-lockfile
|
|
||||||
- yarn lint
|
|
||||||
- yarn build
|
|
||||||
- cd ..
|
|
||||||
- yarn install --cache-folder .yarn --frozen-lockfile
|
|
||||||
- yarn add ./dto
|
|
||||||
- cd frontend
|
|
||||||
- yarn install --cache-folder .yarn --frozen-lockfile
|
|
||||||
- yarn add ../dto
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- dto/lib/
|
|
||||||
|
|
||||||
|
|
||||||
test_backend:
|
|
||||||
needs:
|
|
||||||
- *dto_artifacts_need
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- yarn lint
|
|
||||||
|
|
||||||
test_frontend:
|
|
||||||
needs:
|
|
||||||
- *dto_artifacts_need
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- cd frontend
|
|
||||||
- yarn lint
|
|
||||||
|
|
||||||
build_backend:
|
build_backend:
|
||||||
stage: build
|
stage: build
|
||||||
needs:
|
cache:
|
||||||
- *dto_artifacts_need
|
paths:
|
||||||
- job: test_backend
|
- /root/.cache/vcpkg
|
||||||
artifacts: false
|
|
||||||
script:
|
script:
|
||||||
- echo This has to work till I rewrite the backend
|
- apt-get update
|
||||||
- false && echo
|
- apt-get install g++ gcc make cmake git curl zip unzip tar python3 pkg-config -y
|
||||||
- yarn webpack
|
- SRC="$PWD"
|
||||||
|
- TMP=$(mktemp -d)
|
||||||
|
- cd $TMP
|
||||||
|
- git clone https://github.com/Microsoft/vcpkg.git .
|
||||||
|
- ./bootstrap-vcpkg.sh -disableMetrics
|
||||||
|
- cd $SRC
|
||||||
|
- cmake -B build -S backend -DCMAKE_TOOLCHAIN_FILE=$TMP/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release
|
||||||
|
- cmake --build build
|
||||||
|
- cp build/backend server
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- dist/
|
- server
|
||||||
expire_in: 1h
|
expire_in: 1h
|
||||||
|
|
||||||
build_frontend:
|
test_and_build_frontend:
|
||||||
|
image: node:latest
|
||||||
stage: build
|
stage: build
|
||||||
needs:
|
cache:
|
||||||
- *dto_artifacts_need
|
paths:
|
||||||
- job: test_frontend
|
- frontend/.yarn
|
||||||
artifacts: false
|
- frontend/node_modules
|
||||||
script:
|
script:
|
||||||
- cd frontend
|
- cd frontend
|
||||||
|
- yarn install --cache-folder .yarn --frozen-lockfile
|
||||||
|
- yarn lint
|
||||||
- yarn build
|
- yarn build
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
@ -92,23 +45,16 @@ build_frontend:
|
|||||||
|
|
||||||
package_server:
|
package_server:
|
||||||
stage: package
|
stage: package
|
||||||
cache: []
|
|
||||||
before_script: []
|
before_script: []
|
||||||
needs:
|
needs:
|
||||||
- job: build_backend
|
- job: build_backend
|
||||||
artifacts: true
|
artifacts: true
|
||||||
- job: build_frontend
|
- job: test_and_build_frontend
|
||||||
artifacts: true
|
artifacts: true
|
||||||
script:
|
script:
|
||||||
- TMP=$(mktemp -d)
|
- mkdir static
|
||||||
- mv dist/* "$TMP"
|
- mv frontend/dist/* static/
|
||||||
- mkdir "$TMP/frontend"
|
|
||||||
- mv frontend/dist/* "$TMP/frontend"
|
|
||||||
- rm -r *
|
|
||||||
- rm -r .* || true
|
|
||||||
- mv "$TMP/"* .
|
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- package.json
|
- server
|
||||||
- server.js
|
- static/
|
||||||
- frontend/
|
|
||||||
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
1
.idea/.name
Normal file
1
.idea/.name
Normal file
@ -0,0 +1 @@
|
|||||||
|
backend
|
19
.idea/dataSources.xml
Normal file
19
.idea/dataSources.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="sqlite.db" uuid="6e8086dd-b853-422e-b48a-7c96a2403352">
|
||||||
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/old_backend/sqlite.db</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
<data-source source="LOCAL" name="sqlite.db [2]" uuid="788293bd-abec-4b6b-a13e-26da21cb36dd">
|
||||||
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/run/sqlite.db</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
2
.idea/file_server.iml
Normal file
2
.idea/file_server.iml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module classpath="CMake" type="CPP_MODULE" version="4" />
|
11
.idea/misc.xml
Normal file
11
.idea/misc.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$/backend">
|
||||||
|
<contentRoot DIR="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="CidrRootsConfiguration">
|
||||||
|
<libraryRoots>
|
||||||
|
<file path="$PROJECT_DIR$/backend/lib" />
|
||||||
|
</libraryRoots>
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/file_server.iml" filepath="$PROJECT_DIR$/.idea/file_server.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"tabWidth": 4,
|
|
||||||
"useTabs": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"endOfLine": "lf"
|
|
||||||
}
|
|
19
README.md
19
README.md
@ -1,19 +0,0 @@
|
|||||||
# Mutzi's fileserver
|
|
||||||
|
|
||||||
## Description
|
|
||||||
The most crackhead fileserver you will find on the market
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
cd frontend && npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running the app
|
|
||||||
```bash
|
|
||||||
npm run start:dev
|
|
||||||
```
|
|
||||||
Run in parallel for building the frontend:
|
|
||||||
````bash
|
|
||||||
cd frontend && npm run serve
|
|
||||||
````
|
|
70
backend/CMakeLists.txt
Normal file
70
backend/CMakeLists.txt
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
project(backend)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||||
|
|
||||||
|
add_executable(backend
|
||||||
|
src/main.cpp
|
||||||
|
|
||||||
|
src/dto/dto.h
|
||||||
|
src/dto/responses.cpp
|
||||||
|
|
||||||
|
src/db/db.h
|
||||||
|
src/db/db.cpp
|
||||||
|
|
||||||
|
src/db/model/Inode.cc
|
||||||
|
src/db/model/Inode.h
|
||||||
|
src/db/model/Tokens.cc
|
||||||
|
src/db/model/Tokens.h
|
||||||
|
src/db/model/User.cc
|
||||||
|
src/db/model/User.h
|
||||||
|
|
||||||
|
src/controllers/controllers.h
|
||||||
|
src/controllers/admin.cpp
|
||||||
|
src/controllers/fs.cpp
|
||||||
|
src/controllers/user.cpp
|
||||||
|
|
||||||
|
src/controllers/auth/auth_common.cpp
|
||||||
|
src/controllers/auth/auth_basic.cpp
|
||||||
|
src/controllers/auth/auth_2fa.cpp
|
||||||
|
src/controllers/auth/auth_gitlab.cpp
|
||||||
|
|
||||||
|
src/filters/filters.h
|
||||||
|
src/filters/filters.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
find_package(Drogon CONFIG REQUIRED)
|
||||||
|
find_package(CURL CONFIG REQUIRED)
|
||||||
|
find_package(PNG REQUIRED)
|
||||||
|
find_path(JWT_CPP_INCLUDE_DIRS "jwt-cpp/base.h")
|
||||||
|
find_path(BOTAN_INCLUDE_DIRS "botan/botan.h")
|
||||||
|
find_path(QR_INCLUDE_DIRS "qrcodegen.hpp")
|
||||||
|
find_path(PNGPP_INCLUDE_DIRS "png++/color.hpp")
|
||||||
|
find_library(BOTAN_LIBRARY botan-2)
|
||||||
|
find_library(QR_LIBRARY nayuki-qr-code-generator)
|
||||||
|
|
||||||
|
target_include_directories(backend PRIVATE
|
||||||
|
src
|
||||||
|
${JWT_CPP_INCLUDE_DIRS}
|
||||||
|
${BOTAN_INCLUDE_DIRS}
|
||||||
|
${QR_INCLUDE_DIRS}
|
||||||
|
${PNGPP_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(backend
|
||||||
|
Drogon::Drogon
|
||||||
|
CURL::libcurl
|
||||||
|
PNG::PNG
|
||||||
|
${BOTAN_LIBRARY}
|
||||||
|
${QR_LIBRARY}
|
||||||
|
)
|
||||||
|
|
||||||
|
install(TARGETS backend)
|
||||||
|
|
||||||
|
target_compile_options(backend PRIVATE
|
||||||
|
$<$<CONFIG:Debug>:-g -Wall -Wno-unknown-pragmas>
|
||||||
|
$<$<CONFIG:Release>:-O3>
|
||||||
|
)
|
88
backend/src/controllers/admin.cpp
Normal file
88
backend/src/controllers/admin.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma ide diagnostic ignored "performance-unnecessary-value-param"
|
||||||
|
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
|
||||||
|
|
||||||
|
#include "controllers.h"
|
||||||
|
#include "dto/dto.h"
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
void admin::users(req_type, cbk_type cbk) {
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
std::vector<dto::Responses::GetUsersEntry> entries;
|
||||||
|
auto users = user_mapper.findAll();
|
||||||
|
for (const db::User& user : users)
|
||||||
|
entries.emplace_back(
|
||||||
|
user.getValueOfId(),
|
||||||
|
user.getValueOfGitlab() != 0,
|
||||||
|
db::User_getEnumTfaType(user) != db::tfaTypes::NONE,
|
||||||
|
user.getValueOfName(),
|
||||||
|
db::User_getEnumRole(user)
|
||||||
|
);
|
||||||
|
cbk(dto::Responses::get_admin_users_res(entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
void admin::set_role(req_type req, cbk_type cbk) {
|
||||||
|
Json::Value& json = *req->jsonObject();
|
||||||
|
try {
|
||||||
|
uint64_t user_id = dto::json_get<uint64_t>(json, "user").value();
|
||||||
|
db::UserRole role = (db::UserRole)dto::json_get<int>(json, "role").value();
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
auto user = user_mapper.findByPrimaryKey(user_id);
|
||||||
|
user.setRole(role);
|
||||||
|
user_mapper.update(user);
|
||||||
|
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void admin::logout(req_type req, cbk_type cbk) {
|
||||||
|
Json::Value& json = *req->jsonObject();
|
||||||
|
try {
|
||||||
|
uint64_t user_id = dto::json_get<uint64_t>(json, "user").value();
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
auto user = user_mapper.findByPrimaryKey(user_id);
|
||||||
|
auth::revoke_all(user);
|
||||||
|
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void admin::delete_user(req_type req, cbk_type cbk) {
|
||||||
|
Json::Value& json = *req->jsonObject();
|
||||||
|
try {
|
||||||
|
uint64_t user_id = dto::json_get<uint64_t>(json, "user").value();
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
auto user = user_mapper.findByPrimaryKey(user_id);
|
||||||
|
auth::revoke_all(user);
|
||||||
|
fs::delete_node(fs::get_node(user.getValueOfRootId()).value(), true);
|
||||||
|
user_mapper.deleteOne(user);
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void admin::disable_2fa(req_type req, cbk_type cbk) {
|
||||||
|
Json::Value& json = *req->jsonObject();
|
||||||
|
try {
|
||||||
|
uint64_t user_id = dto::json_get<uint64_t>(json, "user").value();
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
auto user = user_mapper.findByPrimaryKey(user_id);
|
||||||
|
user.setTfaType(db::tfaTypes::NONE);
|
||||||
|
user_mapper.update(user);
|
||||||
|
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma clang diagnostic pop
|
103
backend/src/controllers/auth/auth_2fa.cpp
Normal file
103
backend/src/controllers/auth/auth_2fa.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma ide diagnostic ignored "readability-make-member-function-const"
|
||||||
|
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
|
||||||
|
|
||||||
|
#include <botan/base32.h>
|
||||||
|
#include <botan/base64.h>
|
||||||
|
#include <qrcodegen.hpp>
|
||||||
|
#include <png++/png.hpp>
|
||||||
|
|
||||||
|
#include "controllers/controllers.h"
|
||||||
|
#include "db/db.h"
|
||||||
|
#include "dto/dto.h"
|
||||||
|
|
||||||
|
std::string create_totp_qrcode(const db::User& user, const std::string& b32_secret) {
|
||||||
|
const int qrcode_pixel_size = 4;
|
||||||
|
|
||||||
|
std::stringstream code_ss;
|
||||||
|
code_ss << "otpauth://totp/MFileserver:"
|
||||||
|
<< user.getValueOfName()
|
||||||
|
<< "?secret="
|
||||||
|
<< b32_secret
|
||||||
|
<< "&issuer=MFileserver";
|
||||||
|
auto code = qrcodegen::QrCode::encodeText(code_ss.str().c_str(), qrcodegen::QrCode::Ecc::MEDIUM);
|
||||||
|
const int mod_count = code.getSize();
|
||||||
|
png::image<png::gray_pixel> image(mod_count*qrcode_pixel_size, mod_count*qrcode_pixel_size);
|
||||||
|
for (int x = 0; x < mod_count; x++) for (int y = 0; y < mod_count; y++) {
|
||||||
|
const bool mod = code.getModule(x, y);
|
||||||
|
const int x_img_start = x * qrcode_pixel_size, y_img_start = y * qrcode_pixel_size;
|
||||||
|
for (int x_img = x_img_start; x_img < x_img_start + qrcode_pixel_size; x_img++) for (int y_img = y_img_start; y_img < y_img_start + qrcode_pixel_size; y_img++)
|
||||||
|
image[x_img][y_img] = mod ? 0 : 0xff;
|
||||||
|
}
|
||||||
|
std::stringstream image_ss;
|
||||||
|
image.write_stream(image_ss);
|
||||||
|
|
||||||
|
std::string image_str = image_ss.str();
|
||||||
|
std::vector<uint8_t> secret(image_str.data(), image_str.data()+image_str.size());
|
||||||
|
|
||||||
|
return "data:image/png;base64," + Botan::base64_encode(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
void auth::tfa_setup(req_type req, cbk_type cbk) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
Json::Value &json = *req->jsonObject();
|
||||||
|
try {
|
||||||
|
bool mail = dto::json_get<bool>(json, "mail").value();
|
||||||
|
|
||||||
|
auto secret_uchar = rng->random_vec(32);
|
||||||
|
std::vector<char> secret(secret_uchar.data(), secret_uchar.data()+32);
|
||||||
|
user.setTfaSecret(secret);
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
user_mapper.update(user);
|
||||||
|
|
||||||
|
if (mail) {
|
||||||
|
send_mail(user);
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
} else {
|
||||||
|
std::string b32_secret = Botan::base32_encode(secret_uchar);
|
||||||
|
b32_secret.erase(std::remove(b32_secret.begin(), b32_secret.end(), '='), b32_secret.end());
|
||||||
|
std::string code = create_totp_qrcode(user, b32_secret);
|
||||||
|
cbk(dto::Responses::get_tfa_setup_res(b32_secret, code));
|
||||||
|
}
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::tfa_complete(req_type req, cbk_type cbk) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
Json::Value &json = *req->jsonObject();
|
||||||
|
try {
|
||||||
|
bool mail = dto::json_get<bool>(json, "mail").value();
|
||||||
|
uint32_t code = std::stoi(dto::json_get<std::string>(json, "code").value());
|
||||||
|
|
||||||
|
user.setTfaType(mail ? db::tfaTypes::EMAIL : db::tfaTypes::TOTP);
|
||||||
|
|
||||||
|
if (!verify2fa(user, code))
|
||||||
|
return cbk(dto::Responses::get_unauth_res("Incorrect 2fa"));
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
user_mapper.update(user);
|
||||||
|
|
||||||
|
revoke_all(user);
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::tfa_disable(req_type req, cbk_type cbk) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
user.setTfaType(db::tfaTypes::NONE);
|
||||||
|
user_mapper.update(user);
|
||||||
|
|
||||||
|
revoke_all(user);
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
135
backend/src/controllers/auth/auth_basic.cpp
Normal file
135
backend/src/controllers/auth/auth_basic.cpp
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma ide diagnostic ignored "readability-make-member-function-const"
|
||||||
|
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
|
||||||
|
|
||||||
|
#include <botan/argon2.h>
|
||||||
|
#include <botan/totp.h>
|
||||||
|
#include <jwt-cpp/traits/kazuho-picojson/traits.h>
|
||||||
|
#include <jwt-cpp/jwt.h>
|
||||||
|
|
||||||
|
#include "controllers/controllers.h"
|
||||||
|
#include "db/db.h"
|
||||||
|
#include "dto/dto.h"
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
void auth::login(req_type req, cbk_type cbk) {
|
||||||
|
Json::Value &json = *req->jsonObject();
|
||||||
|
try {
|
||||||
|
std::string username = dto::json_get<std::string>(json, "username").value();
|
||||||
|
std::string password = dto::json_get<std::string>(json, "password").value();
|
||||||
|
std::optional<std::string> otp = dto::json_get<std::string>(json, "otp");
|
||||||
|
|
||||||
|
auto db = drogon::app().getDbClient();
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(db);
|
||||||
|
auto db_users = user_mapper.findBy(
|
||||||
|
db::Criteria(db::User::Cols::_name, db::CompareOps::EQ, username) &&
|
||||||
|
db::Criteria(db::User::Cols::_gitlab, db::CompareOps::EQ, 0)
|
||||||
|
);
|
||||||
|
if (db_users.empty()) {
|
||||||
|
cbk(dto::Responses::get_unauth_res("Invalid username or password"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
db::User &db_user = db_users.at(0);
|
||||||
|
if (!Botan::argon2_check_pwhash(password.c_str(), password.size(), db_user.getValueOfPassword())) {
|
||||||
|
cbk(dto::Responses::get_unauth_res("Invalid username or password"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (db::User_getEnumRole(db_user) == db::UserRole::DISABLED) {
|
||||||
|
cbk(dto::Responses::get_unauth_res("Account is disabled"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto tfa = db::User_getEnumTfaType(db_user);
|
||||||
|
if (tfa != db::tfaTypes::NONE) {
|
||||||
|
if (!otp.has_value()) {
|
||||||
|
if (tfa == db::tfaTypes::EMAIL) send_mail(db_user);
|
||||||
|
return cbk(dto::Responses::get_success_res());
|
||||||
|
}
|
||||||
|
if (!verify2fa(db_user, std::stoi(otp.value())))
|
||||||
|
return cbk(dto::Responses::get_unauth_res("Incorrect 2fa"));
|
||||||
|
}
|
||||||
|
|
||||||
|
cbk(dto::Responses::get_login_res(get_token(db_user)));
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::signup(req_type req, cbk_type cbk) {
|
||||||
|
Json::Value &json = *req->jsonObject();
|
||||||
|
try {
|
||||||
|
std::string username = dto::json_get<std::string>(json, "username").value();
|
||||||
|
std::string password = dto::json_get<std::string>(json, "password").value();
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
|
||||||
|
auto existing_users = user_mapper.count(
|
||||||
|
db::Criteria(db::User::Cols::_name, db::CompareOps::EQ, username) &&
|
||||||
|
db::Criteria(db::User::Cols::_gitlab, db::CompareOps::EQ, 0)
|
||||||
|
);
|
||||||
|
if (existing_users != 0) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Username is already taken"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//std::string hash = Botan::argon2_generate_pwhash(password.c_str(), password.size(), *rng, 1, 256*1024, 2);
|
||||||
|
std::string hash = Botan::argon2_generate_pwhash(password.c_str(), password.size(), *rng, 1, 16*1024, 1);
|
||||||
|
|
||||||
|
db::User new_user;
|
||||||
|
new_user.setName(username);
|
||||||
|
new_user.setPassword(hash);
|
||||||
|
new_user.setGitlab(0);
|
||||||
|
new_user.setRole(db::UserRole::DISABLED);
|
||||||
|
new_user.setRootId(0);
|
||||||
|
new_user.setTfaType(db::tfaTypes::NONE);
|
||||||
|
|
||||||
|
user_mapper.insert(new_user);
|
||||||
|
generate_root(new_user);
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::refresh(req_type req, cbk_type cbk) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
db::Token token = dto::get_token(req);
|
||||||
|
|
||||||
|
db::MapperToken token_mapper(drogon::app().getDbClient());
|
||||||
|
token_mapper.deleteOne(token);
|
||||||
|
cbk(dto::Responses::get_login_res( get_token(user)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::logout_all(req_type req, cbk_type cbk) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
revoke_all(user);
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::change_password(req_type req, cbk_type cbk) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
Json::Value &json = *req->jsonObject();
|
||||||
|
try {
|
||||||
|
std::string old_pw = dto::json_get<std::string>(json, "oldPassword").value();
|
||||||
|
std::string new_pw = dto::json_get<std::string>(json, "newPassword").value();
|
||||||
|
|
||||||
|
auto db = drogon::app().getDbClient();
|
||||||
|
db::MapperUser user_mapper(db);
|
||||||
|
|
||||||
|
if (!Botan::argon2_check_pwhash(old_pw.c_str(), old_pw.size(), user.getValueOfPassword()))
|
||||||
|
return cbk(dto::Responses::get_unauth_res("Old password is wrong"));
|
||||||
|
|
||||||
|
std::string hash = Botan::argon2_generate_pwhash(new_pw.c_str(), new_pw.size(), *rng, 1, 256*1024, 2);
|
||||||
|
|
||||||
|
user.setPassword(hash);
|
||||||
|
user_mapper.update(user);
|
||||||
|
revoke_all(user);
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
110
backend/src/controllers/auth/auth_common.cpp
Normal file
110
backend/src/controllers/auth/auth_common.cpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma ide diagnostic ignored "readability-make-member-function-const"
|
||||||
|
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include <botan/argon2.h>
|
||||||
|
#include <botan/uuid.h>
|
||||||
|
#include <botan/totp.h>
|
||||||
|
|
||||||
|
#if defined(BOTAN_HAS_SYSTEM_RNG)
|
||||||
|
#include <botan/system_rng.h>
|
||||||
|
#else
|
||||||
|
#include <botan/auto_rng.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <jwt-cpp/traits/kazuho-picojson/traits.h>
|
||||||
|
#include <jwt-cpp/jwt.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include "controllers/controllers.h"
|
||||||
|
#include "db/db.h"
|
||||||
|
#include "dto/dto.h"
|
||||||
|
|
||||||
|
size_t payload_source(char* ptr, size_t size, size_t nmemb, void* userp) {
|
||||||
|
auto* ss = (std::stringstream*)userp;
|
||||||
|
return ss->readsome(ptr, (long)(size*nmemb));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
#if defined(BOTAN_HAS_SYSTEM_RNG)
|
||||||
|
std::unique_ptr<Botan::RNG> auth::rng = std::make_unique<Botan::System_RNG>();
|
||||||
|
#else
|
||||||
|
std::unique_ptr<Botan::RNG> auth::rng = std::make_unique<Botan::AutoSeeded_RNG>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool auth::verify2fa(const db::User& user, uint32_t totp) {
|
||||||
|
size_t allowed_skew = db::User_getEnumTfaType(user) == db::tfaTypes::TOTP ? 0 : 10;
|
||||||
|
const auto& totp_secret = (const std::vector<uint8_t>&) user.getValueOfTfaSecret();
|
||||||
|
return Botan::TOTP(Botan::OctetString(totp_secret)).verify_totp(totp, std::chrono::system_clock::now(), allowed_skew);
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::send_mail(const db::User& user) {
|
||||||
|
std::stringstream ss;
|
||||||
|
std::time_t t = std::time(nullptr);
|
||||||
|
const auto& totp_secret = (const std::vector<uint8_t>&) user.getValueOfTfaSecret();
|
||||||
|
char totp[16];
|
||||||
|
std::snprintf(totp, 16, "%06d", Botan::TOTP(Botan::OctetString(totp_secret)).generate_totp(t));
|
||||||
|
ss.imbue(std::locale("en_US.utf8"));
|
||||||
|
ss << "Date: " << std::put_time(std::localtime(&t), "%a, %d %b %Y %T %z") << "\r\n";
|
||||||
|
ss << "To: " << user.getValueOfName() << "\r\n";
|
||||||
|
ss << "From: fileserver@mattv.de\r\n";
|
||||||
|
ss << "Message-ID: " << Botan::UUID(*rng).to_string() << "@mattv.de>\r\n";
|
||||||
|
ss << "Subject: Fileserver - EMail 2fa code\r\n";
|
||||||
|
ss << "Your code is: " << totp << "\r\n";
|
||||||
|
ss << "It is valid for 5 Minutes\r\n";
|
||||||
|
|
||||||
|
CURL* curl = curl_easy_init();
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERNAME, "no-reply@mattv.de");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_PASSWORD, "noreplyLONGPASS123");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, "smtp://mail.mattv.de:587");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
|
||||||
|
auto recp = curl_slist_append(nullptr, user.getValueOfName().c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recp);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_READFUNCTION, &payload_source);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_READDATA, &ss);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
|
||||||
|
curl_easy_perform(curl);
|
||||||
|
curl_slist_free_all(recp);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string auth::get_token(const db::User& user) {
|
||||||
|
auto db = drogon::app().getDbClient();
|
||||||
|
|
||||||
|
db::MapperToken token_mapper(db);
|
||||||
|
const auto iat = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||||
|
const auto exp = iat + std::chrono::hours{24};
|
||||||
|
|
||||||
|
db::Token new_token;
|
||||||
|
new_token.setOwnerId(user.getValueOfId());
|
||||||
|
new_token.setExp(exp.count());
|
||||||
|
|
||||||
|
token_mapper.insert(new_token);
|
||||||
|
|
||||||
|
return jwt::create<jwt::traits::kazuho_picojson>()
|
||||||
|
.set_type("JWT")
|
||||||
|
.set_payload_claim("sub", picojson::value((int64_t)user.getValueOfId()))
|
||||||
|
.set_payload_claim("jti", picojson::value((int64_t)new_token.getValueOfId()))
|
||||||
|
.set_issued_at(std::chrono::system_clock::from_time_t(iat.count()))
|
||||||
|
.set_expires_at(std::chrono::system_clock::from_time_t(exp.count()))
|
||||||
|
.sign(jwt::algorithm::hs256{jwt_secret});
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::generate_root(db::User& user) {
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
|
||||||
|
auto node = fs::create_node("", user, false, std::nullopt, true);
|
||||||
|
user.setRootId(std::get<db::INode>(node).getValueOfId());
|
||||||
|
user_mapper.update(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::revoke_all(const db::User& user) {
|
||||||
|
db::MapperToken token_mapper(drogon::app().getDbClient());
|
||||||
|
token_mapper.deleteBy(db::Criteria(db::Token::Cols::_owner_id, db::CompareOps::EQ, user.getValueOfId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
118
backend/src/controllers/auth/auth_gitlab.cpp
Normal file
118
backend/src/controllers/auth/auth_gitlab.cpp
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma ide diagnostic ignored "performance-unnecessary-value-param"
|
||||||
|
#pragma ide diagnostic ignored "readability-make-member-function-const"
|
||||||
|
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "controllers/controllers.h"
|
||||||
|
#include "dto/dto.h"
|
||||||
|
|
||||||
|
const std::string GITLAB_ID = "98bcbad78cb1f880d1d1de62291d70a791251a7bea077bfe7df111ef3c115760";
|
||||||
|
const std::string GITLAB_SECRET = "7ee01d2b204aff3a05f9d028f004d169b6d381ec873e195f314b3935fa150959";
|
||||||
|
const std::string GITLAB_URL = "https://gitlab.mattv.de";
|
||||||
|
const std::string GITLAB_API_URL = "https://ssh.gitlab.mattv.de";
|
||||||
|
|
||||||
|
std::string get_redirect_uri(req_type req) {
|
||||||
|
auto host_header = req->headers().find("host");
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << (req->isOnSecureConnection() ? "https" : "http")
|
||||||
|
<< "://"
|
||||||
|
<< (host_header != req->headers().end() ? host_header->second : "127.0.0.1:1234")
|
||||||
|
<< "/api/auth/gitlab_callback";
|
||||||
|
return drogon::utils::urlEncode(ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const drogon::HttpClientPtr& get_gitlab_client() {
|
||||||
|
static drogon::HttpClientPtr client = drogon::HttpClient::newHttpClient(GITLAB_API_URL, drogon::app().getLoop(), false, false);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
std::optional<auth::gitlab_tokens> auth::get_gitlab_tokens(req_type req, const std::string& code_or_token, bool token) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "/oauth/token"
|
||||||
|
<< "?redirect_uri=" << get_redirect_uri(req)
|
||||||
|
<< "&client_id=" << GITLAB_ID
|
||||||
|
<< "&client_secret=" << GITLAB_SECRET
|
||||||
|
<< (token ? "&refresh_token=" : "&code=") << code_or_token
|
||||||
|
<< "&grant_type=" << (token ? "refresh_token" : "authorization_code");
|
||||||
|
auto gitlab_req = drogon::HttpRequest::newHttpRequest();
|
||||||
|
gitlab_req->setPathEncode(false);
|
||||||
|
gitlab_req->setPath(ss.str());
|
||||||
|
gitlab_req->setMethod(drogon::HttpMethod::Post);
|
||||||
|
auto res_tuple = get_gitlab_client()->sendRequest(gitlab_req);
|
||||||
|
auto res = res_tuple.second;
|
||||||
|
if ((res->statusCode() != drogon::HttpStatusCode::k200OK) && (res->statusCode() != drogon::HttpStatusCode::k201Created))
|
||||||
|
return std::nullopt;
|
||||||
|
auto json = *res->jsonObject();
|
||||||
|
return std::make_optional<gitlab_tokens>(
|
||||||
|
json["access_token"].as<std::string>(),
|
||||||
|
json["refresh_token"].as<std::string>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<auth::gitlab_user> auth::get_gitlab_user(const std::string& at) {
|
||||||
|
auto gitlab_req = drogon::HttpRequest::newHttpRequest();
|
||||||
|
gitlab_req->setPath("/api/v4/user");
|
||||||
|
gitlab_req->addHeader("Authorization", "Bearer " + at);
|
||||||
|
gitlab_req->setMethod(drogon::HttpMethod::Get);
|
||||||
|
auto res_tuple = get_gitlab_client()->sendRequest(gitlab_req);
|
||||||
|
auto res = res_tuple.second;
|
||||||
|
if (res->statusCode() != drogon::HttpStatusCode::k200OK)
|
||||||
|
return std::nullopt;
|
||||||
|
auto json = *res->jsonObject();
|
||||||
|
return std::make_optional<gitlab_user>(
|
||||||
|
json["username"].as<std::string>(),
|
||||||
|
json.get("is_admin", false).as<bool>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::gitlab(req_type req, cbk_type cbk) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << GITLAB_URL << "/oauth/authorize"
|
||||||
|
<< "?redirect_uri=" << get_redirect_uri(req)
|
||||||
|
<< "&client_id=" << GITLAB_ID
|
||||||
|
<< "&scope=read_user&response_type=code";
|
||||||
|
cbk(drogon::HttpResponse::newRedirectionResponse(ss.str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth::gitlab_callback(req_type req, cbk_type cbk, std::string code) {
|
||||||
|
auto tokens = get_gitlab_tokens(req, code, false);
|
||||||
|
if (!tokens.has_value())
|
||||||
|
return cbk(dto::Responses::get_unauth_res("Invalid code"));
|
||||||
|
auto info = get_gitlab_user(tokens->at);
|
||||||
|
if (!info.has_value())
|
||||||
|
return cbk(dto::Responses::get_unauth_res("Invalid code"));
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
auto db_users = user_mapper.findBy(
|
||||||
|
db::Criteria(db::User::Cols::_name, db::CompareOps::EQ, info->name) &&
|
||||||
|
db::Criteria(db::User::Cols::_gitlab, db::CompareOps::EQ, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (db_users.empty()) {
|
||||||
|
db::User new_user;
|
||||||
|
new_user.setName(info->name);
|
||||||
|
new_user.setPassword("");
|
||||||
|
new_user.setGitlab(1);
|
||||||
|
new_user.setRole(info->is_admin ? db::UserRole::ADMIN : db::UserRole::DISABLED);
|
||||||
|
new_user.setRootId(0);
|
||||||
|
new_user.setTfaType(db::tfaTypes::NONE);
|
||||||
|
|
||||||
|
user_mapper.insert(new_user);
|
||||||
|
generate_root(new_user);
|
||||||
|
db_users.push_back(new_user);
|
||||||
|
}
|
||||||
|
db::User& db_user = db_users.at(0);
|
||||||
|
db_user.setGitlabAt(tokens->at);
|
||||||
|
db_user.setGitlabRt(tokens->rt);
|
||||||
|
user_mapper.update(db_user);
|
||||||
|
|
||||||
|
const std::string& token = get_token(db_user);
|
||||||
|
cbk(drogon::HttpResponse::newRedirectionResponse("/set_token?token="+token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
119
backend/src/controllers/controllers.h
Normal file
119
backend/src/controllers/controllers.h
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
#ifndef BACKEND_CONTROLLERS_H
|
||||||
|
#define BACKEND_CONTROLLERS_H
|
||||||
|
#include <drogon/drogon.h>
|
||||||
|
#include <drogon/utils/coroutine.h>
|
||||||
|
#include <botan/rng.h>
|
||||||
|
#include <coroutine>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include "db/db.h"
|
||||||
|
|
||||||
|
using req_type = const drogon::HttpRequestPtr&;
|
||||||
|
using cbk_type = std::function<void(const drogon::HttpResponsePtr &)>&&;
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
class admin : public drogon::HttpController<admin> {
|
||||||
|
public:
|
||||||
|
METHOD_LIST_BEGIN
|
||||||
|
METHOD_ADD(admin::users, "/users", drogon::Get, "Login", "Admin");
|
||||||
|
METHOD_ADD(admin::set_role, "/set_role", drogon::Post, "Login", "Admin");
|
||||||
|
METHOD_ADD(admin::logout, "/logout", drogon::Post, "Login", "Admin");
|
||||||
|
METHOD_ADD(admin::delete_user, "/delete", drogon::Post, "Login", "Admin");
|
||||||
|
METHOD_ADD(admin::disable_2fa, "/disable_2fa", drogon::Post, "Login", "Admin");
|
||||||
|
METHOD_LIST_END
|
||||||
|
|
||||||
|
void users(req_type, cbk_type);
|
||||||
|
void set_role(req_type, cbk_type);
|
||||||
|
void logout(req_type, cbk_type);
|
||||||
|
void delete_user(req_type, cbk_type);
|
||||||
|
void disable_2fa(req_type, cbk_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
class auth : public drogon::HttpController<auth> {
|
||||||
|
public:
|
||||||
|
METHOD_LIST_BEGIN
|
||||||
|
METHOD_ADD(auth::gitlab, "/gitlab", drogon::Get);
|
||||||
|
METHOD_ADD(auth::gitlab_callback, "/gitlab_callback?code={}", drogon::Get);
|
||||||
|
METHOD_ADD(auth::signup, "/signup", drogon::Post);
|
||||||
|
METHOD_ADD(auth::login, "/login", drogon::Post);
|
||||||
|
METHOD_ADD(auth::refresh, "/refresh", drogon::Post, "Login");
|
||||||
|
METHOD_ADD(auth::tfa_setup, "/2fa/setup", drogon::Post, "Login");
|
||||||
|
METHOD_ADD(auth::tfa_complete, "/2fa/complete", drogon::Post, "Login");
|
||||||
|
METHOD_ADD(auth::tfa_disable, "/2fa/disable", drogon::Post, "Login");
|
||||||
|
METHOD_ADD(auth::change_password, "/change_password", drogon::Post, "Login");
|
||||||
|
METHOD_ADD(auth::logout_all, "/logout_all", drogon::Post, "Login");
|
||||||
|
METHOD_LIST_END
|
||||||
|
|
||||||
|
struct gitlab_tokens {
|
||||||
|
gitlab_tokens(std::string at, std::string rt) : at(std::move(at)), rt(std::move(rt)) {}
|
||||||
|
std::string at, rt;
|
||||||
|
};
|
||||||
|
struct gitlab_user {
|
||||||
|
gitlab_user(std::string name, bool isAdmin) : name(std::move(name)), is_admin(isAdmin) {}
|
||||||
|
std::string name;
|
||||||
|
bool is_admin;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unique_ptr<Botan::RNG> rng;
|
||||||
|
|
||||||
|
static std::optional<gitlab_tokens> get_gitlab_tokens(req_type, const std::string&, bool token);
|
||||||
|
static std::optional<gitlab_user> get_gitlab_user(const std::string&);
|
||||||
|
static bool verify2fa(const db::User&, uint32_t totp);
|
||||||
|
static void send_mail(const db::User&);
|
||||||
|
static std::string get_token(const db::User&);
|
||||||
|
static void generate_root(db::User&);
|
||||||
|
static void revoke_all(const db::User&);
|
||||||
|
|
||||||
|
void gitlab(req_type, cbk_type);
|
||||||
|
void gitlab_callback(req_type, cbk_type, std::string code);
|
||||||
|
void signup(req_type, cbk_type);
|
||||||
|
void login(req_type, cbk_type);
|
||||||
|
void refresh(req_type, cbk_type);
|
||||||
|
void tfa_setup(req_type, cbk_type);
|
||||||
|
void tfa_complete(req_type, cbk_type);
|
||||||
|
void tfa_disable(req_type, cbk_type);
|
||||||
|
void change_password(req_type, cbk_type);
|
||||||
|
void logout_all(req_type, cbk_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
class fs : public drogon::HttpController<fs> {
|
||||||
|
public:
|
||||||
|
METHOD_LIST_BEGIN
|
||||||
|
METHOD_ADD(fs::root, "/root", drogon::Get, "Login");
|
||||||
|
METHOD_ADD(fs::node, "/node/{}", drogon::Get, "Login");
|
||||||
|
METHOD_ADD(fs::path, "/path/{}", drogon::Get, "Login");
|
||||||
|
METHOD_ADD(fs::create_node_req<false>, "/createFolder", drogon::Post, "Login");
|
||||||
|
METHOD_ADD(fs::create_node_req<true>, "/createFile", drogon::Post, "Login");
|
||||||
|
METHOD_ADD(fs::delete_node_req, "/delete/{}", drogon::Post, "Login");
|
||||||
|
METHOD_ADD(fs::upload, "/upload/{}", drogon::Post, "Login");
|
||||||
|
METHOD_ADD(fs::download, "/download", drogon::Post, "Login");
|
||||||
|
METHOD_LIST_END
|
||||||
|
|
||||||
|
static std::optional<db::INode> get_node(uint64_t node);
|
||||||
|
static std::optional<db::INode> get_node_and_validate(const db::User& user, uint64_t node);
|
||||||
|
static std::vector<db::INode> get_children(const db::INode& parent);
|
||||||
|
static std::variant<db::INode, std::string> create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force = false);
|
||||||
|
static void delete_node(db::INode node, bool allow_root = false);
|
||||||
|
|
||||||
|
|
||||||
|
void root(req_type, cbk_type);
|
||||||
|
void node(req_type, cbk_type, uint64_t node);
|
||||||
|
void path(req_type, cbk_type, uint64_t node);
|
||||||
|
template<bool file> void create_node_req(req_type req, cbk_type cbk);
|
||||||
|
void delete_node_req(req_type, cbk_type, uint64_t node);
|
||||||
|
void upload(req_type, cbk_type, uint64_t node);
|
||||||
|
void download(req_type, cbk_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
class user : public drogon::HttpController<user> {
|
||||||
|
public:
|
||||||
|
METHOD_LIST_BEGIN
|
||||||
|
METHOD_ADD(user::info, "/info", drogon::Get, "Login");
|
||||||
|
METHOD_ADD(user::delete_user, "/delete", drogon::Post, "Login");
|
||||||
|
METHOD_LIST_END
|
||||||
|
|
||||||
|
void info(req_type, cbk_type);
|
||||||
|
void delete_user(req_type, cbk_type);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif //BACKEND_CONTROLLERS_H
|
211
backend/src/controllers/fs.cpp
Normal file
211
backend/src/controllers/fs.cpp
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma ide diagnostic ignored "performance-unnecessary-value-param"
|
||||||
|
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include "controllers.h"
|
||||||
|
#include "dto/dto.h"
|
||||||
|
|
||||||
|
char windows_invalid_chars[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F<>:\"/\\|";
|
||||||
|
|
||||||
|
std::string generate_path(db::INode node) {
|
||||||
|
db::MapperInode inode_mapper(drogon::app().getDbClient());
|
||||||
|
std::stack<db::INode> path;
|
||||||
|
path.push(node);
|
||||||
|
while (node.getParentId() != nullptr) {
|
||||||
|
node = inode_mapper.findByPrimaryKey(node.getValueOfParentId());
|
||||||
|
path.push(node);
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
while (!path.empty()) {
|
||||||
|
const db::INode& seg = path.top();
|
||||||
|
ss << seg.getValueOfName();
|
||||||
|
if (seg.getValueOfIsFile() == 0) ss << '/';
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
std::optional<db::INode> fs::get_node(uint64_t node) {
|
||||||
|
db::MapperInode inode_mapper(drogon::app().getDbClient());
|
||||||
|
try {
|
||||||
|
return inode_mapper.findByPrimaryKey(node);
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<db::INode> fs::get_node_and_validate(const db::User &user, uint64_t node) {
|
||||||
|
auto inode = get_node(node);
|
||||||
|
if (!inode.has_value()) return std::nullopt;
|
||||||
|
if (inode->getValueOfOwnerId() != user.getValueOfId()) return std::nullopt;
|
||||||
|
return inode;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<db::INode> fs::get_children(const db::INode& parent) {
|
||||||
|
db::MapperInode inode_mapper(drogon::app().getDbClient());
|
||||||
|
return inode_mapper.findBy(db::Criteria(db::INode::Cols::_parent_id, db::CompareOps::EQ, parent.getValueOfId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::variant<db::INode, std::string> fs::create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force) {
|
||||||
|
// Stolen from https://github.com/boostorg/filesystem/blob/develop/src/portability.cpp
|
||||||
|
if (!force)
|
||||||
|
if (name.empty() || name[0] == ' ' || name.find_first_of(windows_invalid_chars, 0, sizeof(windows_invalid_chars)) != std::string::npos || *(name.end() - 1) == ' ' || *(name.end() - 1) == '.' || name == "." || name == "..")
|
||||||
|
return {"Invalid name"};
|
||||||
|
|
||||||
|
db::INode node;
|
||||||
|
node.setIsFile(file ? 1 : 0);
|
||||||
|
node.setName(name);
|
||||||
|
node.setOwnerId(owner.getValueOfId());
|
||||||
|
if (parent.has_value()) {
|
||||||
|
auto parent_node = get_node_and_validate(owner, *parent);
|
||||||
|
if (!parent_node.has_value())
|
||||||
|
return {"Invalid parent"};
|
||||||
|
if (parent_node->getValueOfIsFile() != 0)
|
||||||
|
return {"Can't use file as parent"};
|
||||||
|
auto children = get_children(*parent_node);
|
||||||
|
for (const auto& child : children)
|
||||||
|
if (child.getValueOfName() == name)
|
||||||
|
return {"File/Folder already exists"};
|
||||||
|
node.setParentId(*parent);
|
||||||
|
}
|
||||||
|
db::MapperInode inode_mapper(drogon::app().getDbClient());
|
||||||
|
inode_mapper.insert(node);
|
||||||
|
return {node};
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs::delete_node(db::INode node, bool allow_root) {
|
||||||
|
if (node.getValueOfParentId() == 0 && (!allow_root)) return;
|
||||||
|
if (node.getValueOfIsFile() == 0) {
|
||||||
|
auto children = get_children(node);
|
||||||
|
for (const auto& child : children) delete_node(child, false);
|
||||||
|
} else {
|
||||||
|
std::filesystem::path p("./files");
|
||||||
|
p /= std::to_string(node.getValueOfId());
|
||||||
|
std::filesystem::remove(p);
|
||||||
|
}
|
||||||
|
db::MapperInode inode_mapper(drogon::app().getDbClient());
|
||||||
|
inode_mapper.deleteOne(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs::root(req_type req, cbk_type cbk) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
cbk(dto::Responses::get_root_res(user.getValueOfRootId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs::node(req_type req, cbk_type cbk, uint64_t node) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
auto inode = get_node_and_validate(user, node);
|
||||||
|
if (!inode.has_value())
|
||||||
|
cbk(dto::Responses::get_badreq_res("Unknown node"));
|
||||||
|
else if (inode->getValueOfIsFile() == 0) {
|
||||||
|
std::vector<uint64_t> children;
|
||||||
|
for (const db::INode& child : get_children(*inode)) children.push_back(child.getValueOfId());
|
||||||
|
cbk(dto::Responses::get_node_folder_res(
|
||||||
|
inode->getValueOfId(),
|
||||||
|
inode->getValueOfName(),
|
||||||
|
inode->getParentId(),
|
||||||
|
children
|
||||||
|
));
|
||||||
|
} else
|
||||||
|
cbk(dto::Responses::get_node_file_res(
|
||||||
|
inode->getValueOfId(),
|
||||||
|
inode->getValueOfName(),
|
||||||
|
inode->getParentId(),
|
||||||
|
inode->getValueOfSize()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs::path(req_type req, cbk_type cbk, uint64_t node) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
auto inode = get_node_and_validate(user, node);
|
||||||
|
if (!inode.has_value())
|
||||||
|
cbk(dto::Responses::get_badreq_res("Unknown node"));
|
||||||
|
else
|
||||||
|
cbk(dto::Responses::get_path_res( generate_path(*inode)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool file>
|
||||||
|
void fs::create_node_req(req_type req, cbk_type cbk) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
Json::Value& json = *req->jsonObject();
|
||||||
|
try {
|
||||||
|
uint64_t parent = dto::json_get<uint64_t>(json, "parent").value();
|
||||||
|
std::string name = dto::json_get<std::string>(json, "name").value();
|
||||||
|
|
||||||
|
auto new_node = create_node(name, user, file, std::make_optional(parent));
|
||||||
|
if (std::holds_alternative<std::string>(new_node))
|
||||||
|
cbk(dto::Responses::get_badreq_res(std::get<std::string>(new_node)));
|
||||||
|
else
|
||||||
|
cbk(dto::Responses::get_new_node_res(std::get<db::INode>(new_node).getValueOfId()));
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs::delete_node_req(req_type req, cbk_type cbk, uint64_t node) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
auto inode = get_node_and_validate(user, node);
|
||||||
|
if (!inode.has_value())
|
||||||
|
cbk(dto::Responses::get_badreq_res("Unknown node"));
|
||||||
|
else if (inode->getValueOfParentId() == 0)
|
||||||
|
cbk(dto::Responses::get_badreq_res("Can't delete root"));
|
||||||
|
else {
|
||||||
|
delete_node(*inode);
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs::upload(req_type req, cbk_type cbk, uint64_t node) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
|
||||||
|
auto inode = get_node_and_validate(user, node);
|
||||||
|
if (!inode.has_value())
|
||||||
|
return cbk(dto::Responses::get_badreq_res("Unknown node"));
|
||||||
|
if (inode->getValueOfIsFile() == 0)
|
||||||
|
return cbk(dto::Responses::get_badreq_res("Can't upload to a directory"));
|
||||||
|
|
||||||
|
drogon::MultiPartParser mpp;
|
||||||
|
if (mpp.parse(req) != 0)
|
||||||
|
return cbk(dto::Responses::get_badreq_res("Failed to parse files"));
|
||||||
|
if (mpp.getFiles().size() != 1)
|
||||||
|
return cbk(dto::Responses::get_badreq_res("Exactly 1 file needed"));
|
||||||
|
|
||||||
|
const drogon::HttpFile& file = mpp.getFiles().at(0);
|
||||||
|
|
||||||
|
std::filesystem::path p("./files");
|
||||||
|
p /= std::to_string(inode->getValueOfId());
|
||||||
|
|
||||||
|
file.saveAs(p.string());
|
||||||
|
|
||||||
|
inode->setSize(file.fileLength());
|
||||||
|
db::MapperInode inode_mapper(drogon::app().getDbClient());
|
||||||
|
inode_mapper.update(*inode);
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs::download(req_type req, cbk_type cbk) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
|
||||||
|
auto node_id = req->getOptionalParameter<uint64_t>("id");
|
||||||
|
if (!node_id.has_value()) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Invalid node"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto inode = get_node_and_validate(user, *node_id);
|
||||||
|
if (!inode.has_value()) {
|
||||||
|
cbk(dto::Responses::get_badreq_res("Invalid node"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path p("./files");
|
||||||
|
p /= std::to_string(inode->getValueOfId());
|
||||||
|
|
||||||
|
cbk(drogon::HttpResponse::newFileResponse(
|
||||||
|
p.string(),
|
||||||
|
inode->getValueOfName()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma clang diagnostic pop
|
29
backend/src/controllers/user.cpp
Normal file
29
backend/src/controllers/user.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma ide diagnostic ignored "performance-unnecessary-value-param"
|
||||||
|
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
|
||||||
|
|
||||||
|
#include "controllers.h"
|
||||||
|
#include "dto/dto.h"
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
void user::info(req_type req, cbk_type cbk) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
cbk(dto::Responses::get_user_info_res(
|
||||||
|
user.getValueOfName(),
|
||||||
|
user.getValueOfGitlab() != 0,
|
||||||
|
db::User_getEnumTfaType(user) != db::tfaTypes::NONE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void user::delete_user(req_type req, cbk_type cbk) {
|
||||||
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
|
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
auth::revoke_all(user);
|
||||||
|
fs::delete_node((fs::get_node(user.getValueOfRootId())).value(), true);
|
||||||
|
user_mapper.deleteOne(user);
|
||||||
|
|
||||||
|
cbk(dto::Responses::get_success_res());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma clang diagnostic pop
|
11
backend/src/db/db.cpp
Normal file
11
backend/src/db/db.cpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#include "db.h"
|
||||||
|
|
||||||
|
namespace db {
|
||||||
|
UserRole User_getEnumRole(const User& user) noexcept {
|
||||||
|
return (UserRole)user.getValueOfRole();
|
||||||
|
}
|
||||||
|
|
||||||
|
tfaTypes User_getEnumTfaType(const User& user) noexcept {
|
||||||
|
return (tfaTypes)user.getValueOfTfaType();
|
||||||
|
}
|
||||||
|
}
|
43
backend/src/db/db.h
Normal file
43
backend/src/db/db.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef BACKEND_DB_H
|
||||||
|
#define BACKEND_DB_H
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <drogon/utils/coroutine.h>
|
||||||
|
#include <drogon/drogon.h>
|
||||||
|
|
||||||
|
#include "model/Inode.h"
|
||||||
|
#include "model/Tokens.h"
|
||||||
|
#include "model/User.h"
|
||||||
|
|
||||||
|
const std::string jwt_secret = "CUM";
|
||||||
|
|
||||||
|
namespace db {
|
||||||
|
enum UserRole : int {
|
||||||
|
ADMIN = 2,
|
||||||
|
USER = 1,
|
||||||
|
DISABLED = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
enum tfaTypes : int {
|
||||||
|
NONE = 0,
|
||||||
|
EMAIL = 1,
|
||||||
|
TOTP = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
using INode = drogon_model::sqlite3::Inode;
|
||||||
|
using Token = drogon_model::sqlite3::Tokens;
|
||||||
|
using User = drogon_model::sqlite3::User;
|
||||||
|
|
||||||
|
using MapperInode = drogon::orm::Mapper<INode>;
|
||||||
|
using MapperToken = drogon::orm::Mapper<Token>;
|
||||||
|
using MapperUser = drogon::orm::Mapper<User>;
|
||||||
|
|
||||||
|
using Criteria = drogon::orm::Criteria;
|
||||||
|
using CompareOps = drogon::orm::CompareOperator;
|
||||||
|
|
||||||
|
UserRole User_getEnumRole(const User&) noexcept;
|
||||||
|
tfaTypes User_getEnumTfaType(const User&) noexcept;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //BACKEND_DB_H
|
1095
backend/src/db/model/Inode.cc
Normal file
1095
backend/src/db/model/Inode.cc
Normal file
File diff suppressed because it is too large
Load Diff
275
backend/src/db/model/Inode.h
Normal file
275
backend/src/db/model/Inode.h
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* Inode.h
|
||||||
|
* DO NOT EDIT. This file is generated by drogon_ctl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <drogon/orm/Result.h>
|
||||||
|
#include <drogon/orm/Row.h>
|
||||||
|
#include <drogon/orm/Field.h>
|
||||||
|
#include <drogon/orm/SqlBinder.h>
|
||||||
|
#include <drogon/orm/Mapper.h>
|
||||||
|
#ifdef __cpp_impl_coroutine
|
||||||
|
#include <drogon/orm/CoroMapper.h>
|
||||||
|
#endif
|
||||||
|
#include <trantor/utils/Date.h>
|
||||||
|
#include <trantor/utils/Logger.h>
|
||||||
|
#include <json/json.h>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <tuple>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace drogon
|
||||||
|
{
|
||||||
|
namespace orm
|
||||||
|
{
|
||||||
|
class DbClient;
|
||||||
|
using DbClientPtr = std::shared_ptr<DbClient>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace drogon_model
|
||||||
|
{
|
||||||
|
namespace sqlite3
|
||||||
|
{
|
||||||
|
|
||||||
|
class Inode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Cols
|
||||||
|
{
|
||||||
|
static const std::string _id;
|
||||||
|
static const std::string _is_file;
|
||||||
|
static const std::string _name;
|
||||||
|
static const std::string _parent_id;
|
||||||
|
static const std::string _owner_id;
|
||||||
|
static const std::string _size;
|
||||||
|
};
|
||||||
|
|
||||||
|
const static int primaryKeyNumber;
|
||||||
|
const static std::string tableName;
|
||||||
|
const static bool hasPrimaryKey;
|
||||||
|
const static std::string primaryKeyName;
|
||||||
|
using PrimaryKeyType = uint64_t;
|
||||||
|
const PrimaryKeyType &getPrimaryKey() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief constructor
|
||||||
|
* @param r One row of records in the SQL query result.
|
||||||
|
* @param indexOffset Set the offset to -1 to access all columns by column names,
|
||||||
|
* otherwise access all columns by offsets.
|
||||||
|
* @note If the SQL is not a style of 'select * from table_name ...' (select all
|
||||||
|
* columns by an asterisk), please set the offset to -1.
|
||||||
|
*/
|
||||||
|
explicit Inode(const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief constructor
|
||||||
|
* @param pJson The json object to construct a new instance.
|
||||||
|
*/
|
||||||
|
explicit Inode(const Json::Value &pJson) noexcept(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief constructor
|
||||||
|
* @param pJson The json object to construct a new instance.
|
||||||
|
* @param pMasqueradingVector The aliases of table columns.
|
||||||
|
*/
|
||||||
|
Inode(const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false);
|
||||||
|
|
||||||
|
Inode() = default;
|
||||||
|
|
||||||
|
void updateByJson(const Json::Value &pJson) noexcept(false);
|
||||||
|
void updateByMasqueradedJson(const Json::Value &pJson,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector) noexcept(false);
|
||||||
|
static bool validateJsonForCreation(const Json::Value &pJson, std::string &err);
|
||||||
|
static bool validateMasqueradedJsonForCreation(const Json::Value &,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector,
|
||||||
|
std::string &err);
|
||||||
|
static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err);
|
||||||
|
static bool validateMasqueradedJsonForUpdate(const Json::Value &,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector,
|
||||||
|
std::string &err);
|
||||||
|
static bool validJsonOfField(size_t index,
|
||||||
|
const std::string &fieldName,
|
||||||
|
const Json::Value &pJson,
|
||||||
|
std::string &err,
|
||||||
|
bool isForCreation);
|
||||||
|
|
||||||
|
/** For column id */
|
||||||
|
///Get the value of the column id, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfId() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getId() const noexcept;
|
||||||
|
///Set the value of the column id
|
||||||
|
void setId(const uint64_t &pId) noexcept;
|
||||||
|
|
||||||
|
/** For column is_file */
|
||||||
|
///Get the value of the column is_file, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfIsFile() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getIsFile() const noexcept;
|
||||||
|
///Set the value of the column is_file
|
||||||
|
void setIsFile(const uint64_t &pIsFile) noexcept;
|
||||||
|
|
||||||
|
/** For column name */
|
||||||
|
///Get the value of the column name, returns the default value if the column is null
|
||||||
|
const std::string &getValueOfName() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<std::string> &getName() const noexcept;
|
||||||
|
///Set the value of the column name
|
||||||
|
void setName(const std::string &pName) noexcept;
|
||||||
|
void setName(std::string &&pName) noexcept;
|
||||||
|
void setNameToNull() noexcept;
|
||||||
|
|
||||||
|
/** For column parent_id */
|
||||||
|
///Get the value of the column parent_id, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfParentId() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getParentId() const noexcept;
|
||||||
|
///Set the value of the column parent_id
|
||||||
|
void setParentId(const uint64_t &pParentId) noexcept;
|
||||||
|
void setParentIdToNull() noexcept;
|
||||||
|
|
||||||
|
/** For column owner_id */
|
||||||
|
///Get the value of the column owner_id, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfOwnerId() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getOwnerId() const noexcept;
|
||||||
|
///Set the value of the column owner_id
|
||||||
|
void setOwnerId(const uint64_t &pOwnerId) noexcept;
|
||||||
|
|
||||||
|
/** For column size */
|
||||||
|
///Get the value of the column size, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfSize() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getSize() const noexcept;
|
||||||
|
///Set the value of the column size
|
||||||
|
void setSize(const uint64_t &pSize) noexcept;
|
||||||
|
void setSizeToNull() noexcept;
|
||||||
|
|
||||||
|
|
||||||
|
static size_t getColumnNumber() noexcept { return 6; }
|
||||||
|
static const std::string &getColumnName(size_t index) noexcept(false);
|
||||||
|
|
||||||
|
Json::Value toJson() const;
|
||||||
|
Json::Value toMasqueradedJson(const std::vector<std::string> &pMasqueradingVector) const;
|
||||||
|
/// Relationship interfaces
|
||||||
|
private:
|
||||||
|
friend drogon::orm::Mapper<Inode>;
|
||||||
|
#ifdef __cpp_impl_coroutine
|
||||||
|
friend drogon::orm::CoroMapper<Inode>;
|
||||||
|
#endif
|
||||||
|
static const std::vector<std::string> &insertColumns() noexcept;
|
||||||
|
void outputArgs(drogon::orm::internal::SqlBinder &binder) const;
|
||||||
|
const std::vector<std::string> updateColumns() const;
|
||||||
|
void updateArgs(drogon::orm::internal::SqlBinder &binder) const;
|
||||||
|
///For mysql or sqlite3
|
||||||
|
void updateId(const uint64_t id);
|
||||||
|
std::shared_ptr<uint64_t> id_;
|
||||||
|
std::shared_ptr<uint64_t> isFile_;
|
||||||
|
std::shared_ptr<std::string> name_;
|
||||||
|
std::shared_ptr<uint64_t> parentId_;
|
||||||
|
std::shared_ptr<uint64_t> ownerId_;
|
||||||
|
std::shared_ptr<uint64_t> size_;
|
||||||
|
struct MetaData
|
||||||
|
{
|
||||||
|
const std::string colName_;
|
||||||
|
const std::string colType_;
|
||||||
|
const std::string colDatabaseType_;
|
||||||
|
const ssize_t colLength_;
|
||||||
|
const bool isAutoVal_;
|
||||||
|
const bool isPrimaryKey_;
|
||||||
|
const bool notNull_;
|
||||||
|
};
|
||||||
|
static const std::vector<MetaData> metaData_;
|
||||||
|
bool dirtyFlag_[6]={ false };
|
||||||
|
public:
|
||||||
|
static const std::string &sqlForFindingByPrimaryKey()
|
||||||
|
{
|
||||||
|
static const std::string sql="select * from " + tableName + " where id = ?";
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::string &sqlForDeletingByPrimaryKey()
|
||||||
|
{
|
||||||
|
static const std::string sql="delete from " + tableName + " where id = ?";
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
std::string sqlForInserting(bool &needSelection) const
|
||||||
|
{
|
||||||
|
std::string sql="insert into " + tableName + " (";
|
||||||
|
size_t parametersCount = 0;
|
||||||
|
needSelection = false;
|
||||||
|
if(dirtyFlag_[1])
|
||||||
|
{
|
||||||
|
sql += "is_file,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[2])
|
||||||
|
{
|
||||||
|
sql += "name,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[3])
|
||||||
|
{
|
||||||
|
sql += "parent_id,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[4])
|
||||||
|
{
|
||||||
|
sql += "owner_id,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[5])
|
||||||
|
{
|
||||||
|
sql += "size,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(parametersCount > 0)
|
||||||
|
{
|
||||||
|
sql[sql.length()-1]=')';
|
||||||
|
sql += " values (";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sql += ") values (";
|
||||||
|
|
||||||
|
if(dirtyFlag_[1])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[2])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[3])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[4])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[5])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(parametersCount > 0)
|
||||||
|
{
|
||||||
|
sql.resize(sql.length() - 1);
|
||||||
|
}
|
||||||
|
sql.append(1, ')');
|
||||||
|
LOG_TRACE << sql;
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace sqlite3
|
||||||
|
} // namespace drogon_model
|
631
backend/src/db/model/Tokens.cc
Normal file
631
backend/src/db/model/Tokens.cc
Normal file
@ -0,0 +1,631 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* Tokens.cc
|
||||||
|
* DO NOT EDIT. This file is generated by drogon_ctl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Tokens.h"
|
||||||
|
#include <drogon/utils/Utilities.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace drogon;
|
||||||
|
using namespace drogon::orm;
|
||||||
|
using namespace drogon_model::sqlite3;
|
||||||
|
|
||||||
|
const std::string Tokens::Cols::_id = "id";
|
||||||
|
const std::string Tokens::Cols::_owner_id = "owner_id";
|
||||||
|
const std::string Tokens::Cols::_exp = "exp";
|
||||||
|
const std::string Tokens::primaryKeyName = "id";
|
||||||
|
const bool Tokens::hasPrimaryKey = true;
|
||||||
|
const std::string Tokens::tableName = "tokens";
|
||||||
|
|
||||||
|
const std::vector<typename Tokens::MetaData> Tokens::metaData_={
|
||||||
|
{"id","uint64_t","integer",8,1,1,1},
|
||||||
|
{"owner_id","uint64_t","integer",8,0,0,1},
|
||||||
|
{"exp","uint64_t","integer",8,0,0,1}
|
||||||
|
};
|
||||||
|
const std::string &Tokens::getColumnName(size_t index) noexcept(false)
|
||||||
|
{
|
||||||
|
assert(index < metaData_.size());
|
||||||
|
return metaData_[index].colName_;
|
||||||
|
}
|
||||||
|
Tokens::Tokens(const Row &r, const ssize_t indexOffset) noexcept
|
||||||
|
{
|
||||||
|
if(indexOffset < 0)
|
||||||
|
{
|
||||||
|
if(!r["id"].isNull())
|
||||||
|
{
|
||||||
|
id_=std::make_shared<uint64_t>(r["id"].as<uint64_t>());
|
||||||
|
}
|
||||||
|
if(!r["owner_id"].isNull())
|
||||||
|
{
|
||||||
|
ownerId_=std::make_shared<uint64_t>(r["owner_id"].as<uint64_t>());
|
||||||
|
}
|
||||||
|
if(!r["exp"].isNull())
|
||||||
|
{
|
||||||
|
exp_=std::make_shared<uint64_t>(r["exp"].as<uint64_t>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t offset = (size_t)indexOffset;
|
||||||
|
if(offset + 3 > r.size())
|
||||||
|
{
|
||||||
|
LOG_FATAL << "Invalid SQL result for this model";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t index;
|
||||||
|
index = offset + 0;
|
||||||
|
if(!r[index].isNull())
|
||||||
|
{
|
||||||
|
id_=std::make_shared<uint64_t>(r[index].as<uint64_t>());
|
||||||
|
}
|
||||||
|
index = offset + 1;
|
||||||
|
if(!r[index].isNull())
|
||||||
|
{
|
||||||
|
ownerId_=std::make_shared<uint64_t>(r[index].as<uint64_t>());
|
||||||
|
}
|
||||||
|
index = offset + 2;
|
||||||
|
if(!r[index].isNull())
|
||||||
|
{
|
||||||
|
exp_=std::make_shared<uint64_t>(r[index].as<uint64_t>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Tokens::Tokens(const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false)
|
||||||
|
{
|
||||||
|
if(pMasqueradingVector.size() != 3)
|
||||||
|
{
|
||||||
|
LOG_ERROR << "Bad masquerading vector";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0]))
|
||||||
|
{
|
||||||
|
dirtyFlag_[0] = true;
|
||||||
|
if(!pJson[pMasqueradingVector[0]].isNull())
|
||||||
|
{
|
||||||
|
id_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[0]].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
|
||||||
|
{
|
||||||
|
dirtyFlag_[1] = true;
|
||||||
|
if(!pJson[pMasqueradingVector[1]].isNull())
|
||||||
|
{
|
||||||
|
ownerId_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[1]].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2]))
|
||||||
|
{
|
||||||
|
dirtyFlag_[2] = true;
|
||||||
|
if(!pJson[pMasqueradingVector[2]].isNull())
|
||||||
|
{
|
||||||
|
exp_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[2]].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tokens::Tokens(const Json::Value &pJson) noexcept(false)
|
||||||
|
{
|
||||||
|
if(pJson.isMember("id"))
|
||||||
|
{
|
||||||
|
dirtyFlag_[0]=true;
|
||||||
|
if(!pJson["id"].isNull())
|
||||||
|
{
|
||||||
|
id_=std::make_shared<uint64_t>((uint64_t)pJson["id"].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(pJson.isMember("owner_id"))
|
||||||
|
{
|
||||||
|
dirtyFlag_[1]=true;
|
||||||
|
if(!pJson["owner_id"].isNull())
|
||||||
|
{
|
||||||
|
ownerId_=std::make_shared<uint64_t>((uint64_t)pJson["owner_id"].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(pJson.isMember("exp"))
|
||||||
|
{
|
||||||
|
dirtyFlag_[2]=true;
|
||||||
|
if(!pJson["exp"].isNull())
|
||||||
|
{
|
||||||
|
exp_=std::make_shared<uint64_t>((uint64_t)pJson["exp"].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tokens::updateByMasqueradedJson(const Json::Value &pJson,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector) noexcept(false)
|
||||||
|
{
|
||||||
|
if(pMasqueradingVector.size() != 3)
|
||||||
|
{
|
||||||
|
LOG_ERROR << "Bad masquerading vector";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0]))
|
||||||
|
{
|
||||||
|
if(!pJson[pMasqueradingVector[0]].isNull())
|
||||||
|
{
|
||||||
|
id_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[0]].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
|
||||||
|
{
|
||||||
|
dirtyFlag_[1] = true;
|
||||||
|
if(!pJson[pMasqueradingVector[1]].isNull())
|
||||||
|
{
|
||||||
|
ownerId_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[1]].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2]))
|
||||||
|
{
|
||||||
|
dirtyFlag_[2] = true;
|
||||||
|
if(!pJson[pMasqueradingVector[2]].isNull())
|
||||||
|
{
|
||||||
|
exp_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[2]].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tokens::updateByJson(const Json::Value &pJson) noexcept(false)
|
||||||
|
{
|
||||||
|
if(pJson.isMember("id"))
|
||||||
|
{
|
||||||
|
if(!pJson["id"].isNull())
|
||||||
|
{
|
||||||
|
id_=std::make_shared<uint64_t>((uint64_t)pJson["id"].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(pJson.isMember("owner_id"))
|
||||||
|
{
|
||||||
|
dirtyFlag_[1] = true;
|
||||||
|
if(!pJson["owner_id"].isNull())
|
||||||
|
{
|
||||||
|
ownerId_=std::make_shared<uint64_t>((uint64_t)pJson["owner_id"].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(pJson.isMember("exp"))
|
||||||
|
{
|
||||||
|
dirtyFlag_[2] = true;
|
||||||
|
if(!pJson["exp"].isNull())
|
||||||
|
{
|
||||||
|
exp_=std::make_shared<uint64_t>((uint64_t)pJson["exp"].asUInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t &Tokens::getValueOfId() const noexcept
|
||||||
|
{
|
||||||
|
const static uint64_t defaultValue = uint64_t();
|
||||||
|
if(id_)
|
||||||
|
return *id_;
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
const std::shared_ptr<uint64_t> &Tokens::getId() const noexcept
|
||||||
|
{
|
||||||
|
return id_;
|
||||||
|
}
|
||||||
|
void Tokens::setId(const uint64_t &pId) noexcept
|
||||||
|
{
|
||||||
|
id_ = std::make_shared<uint64_t>(pId);
|
||||||
|
dirtyFlag_[0] = true;
|
||||||
|
}
|
||||||
|
const typename Tokens::PrimaryKeyType & Tokens::getPrimaryKey() const
|
||||||
|
{
|
||||||
|
assert(id_);
|
||||||
|
return *id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t &Tokens::getValueOfOwnerId() const noexcept
|
||||||
|
{
|
||||||
|
const static uint64_t defaultValue = uint64_t();
|
||||||
|
if(ownerId_)
|
||||||
|
return *ownerId_;
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
const std::shared_ptr<uint64_t> &Tokens::getOwnerId() const noexcept
|
||||||
|
{
|
||||||
|
return ownerId_;
|
||||||
|
}
|
||||||
|
void Tokens::setOwnerId(const uint64_t &pOwnerId) noexcept
|
||||||
|
{
|
||||||
|
ownerId_ = std::make_shared<uint64_t>(pOwnerId);
|
||||||
|
dirtyFlag_[1] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t &Tokens::getValueOfExp() const noexcept
|
||||||
|
{
|
||||||
|
const static uint64_t defaultValue = uint64_t();
|
||||||
|
if(exp_)
|
||||||
|
return *exp_;
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
const std::shared_ptr<uint64_t> &Tokens::getExp() const noexcept
|
||||||
|
{
|
||||||
|
return exp_;
|
||||||
|
}
|
||||||
|
void Tokens::setExp(const uint64_t &pExp) noexcept
|
||||||
|
{
|
||||||
|
exp_ = std::make_shared<uint64_t>(pExp);
|
||||||
|
dirtyFlag_[2] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tokens::updateId(const uint64_t id)
|
||||||
|
{
|
||||||
|
id_ = std::make_shared<uint64_t>(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string> &Tokens::insertColumns() noexcept
|
||||||
|
{
|
||||||
|
static const std::vector<std::string> inCols={
|
||||||
|
"owner_id",
|
||||||
|
"exp"
|
||||||
|
};
|
||||||
|
return inCols;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tokens::outputArgs(drogon::orm::internal::SqlBinder &binder) const
|
||||||
|
{
|
||||||
|
if(dirtyFlag_[1])
|
||||||
|
{
|
||||||
|
if(getOwnerId())
|
||||||
|
{
|
||||||
|
binder << getValueOfOwnerId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
binder << nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[2])
|
||||||
|
{
|
||||||
|
if(getExp())
|
||||||
|
{
|
||||||
|
binder << getValueOfExp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
binder << nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string> Tokens::updateColumns() const
|
||||||
|
{
|
||||||
|
std::vector<std::string> ret;
|
||||||
|
if(dirtyFlag_[1])
|
||||||
|
{
|
||||||
|
ret.push_back(getColumnName(1));
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[2])
|
||||||
|
{
|
||||||
|
ret.push_back(getColumnName(2));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tokens::updateArgs(drogon::orm::internal::SqlBinder &binder) const
|
||||||
|
{
|
||||||
|
if(dirtyFlag_[1])
|
||||||
|
{
|
||||||
|
if(getOwnerId())
|
||||||
|
{
|
||||||
|
binder << getValueOfOwnerId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
binder << nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[2])
|
||||||
|
{
|
||||||
|
if(getExp())
|
||||||
|
{
|
||||||
|
binder << getValueOfExp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
binder << nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Json::Value Tokens::toJson() const
|
||||||
|
{
|
||||||
|
Json::Value ret;
|
||||||
|
if(getId())
|
||||||
|
{
|
||||||
|
ret["id"]=(Json::UInt64)getValueOfId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret["id"]=Json::Value();
|
||||||
|
}
|
||||||
|
if(getOwnerId())
|
||||||
|
{
|
||||||
|
ret["owner_id"]=(Json::UInt64)getValueOfOwnerId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret["owner_id"]=Json::Value();
|
||||||
|
}
|
||||||
|
if(getExp())
|
||||||
|
{
|
||||||
|
ret["exp"]=(Json::UInt64)getValueOfExp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret["exp"]=Json::Value();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value Tokens::toMasqueradedJson(
|
||||||
|
const std::vector<std::string> &pMasqueradingVector) const
|
||||||
|
{
|
||||||
|
Json::Value ret;
|
||||||
|
if(pMasqueradingVector.size() == 3)
|
||||||
|
{
|
||||||
|
if(!pMasqueradingVector[0].empty())
|
||||||
|
{
|
||||||
|
if(getId())
|
||||||
|
{
|
||||||
|
ret[pMasqueradingVector[0]]=(Json::UInt64)getValueOfId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret[pMasqueradingVector[0]]=Json::Value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[1].empty())
|
||||||
|
{
|
||||||
|
if(getOwnerId())
|
||||||
|
{
|
||||||
|
ret[pMasqueradingVector[1]]=(Json::UInt64)getValueOfOwnerId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret[pMasqueradingVector[1]]=Json::Value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[2].empty())
|
||||||
|
{
|
||||||
|
if(getExp())
|
||||||
|
{
|
||||||
|
ret[pMasqueradingVector[2]]=(Json::UInt64)getValueOfExp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret[pMasqueradingVector[2]]=Json::Value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
LOG_ERROR << "Masquerade failed";
|
||||||
|
if(getId())
|
||||||
|
{
|
||||||
|
ret["id"]=(Json::UInt64)getValueOfId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret["id"]=Json::Value();
|
||||||
|
}
|
||||||
|
if(getOwnerId())
|
||||||
|
{
|
||||||
|
ret["owner_id"]=(Json::UInt64)getValueOfOwnerId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret["owner_id"]=Json::Value();
|
||||||
|
}
|
||||||
|
if(getExp())
|
||||||
|
{
|
||||||
|
ret["exp"]=(Json::UInt64)getValueOfExp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret["exp"]=Json::Value();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Tokens::validateJsonForCreation(const Json::Value &pJson, std::string &err)
|
||||||
|
{
|
||||||
|
if(pJson.isMember("id"))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(0, "id", pJson["id"], err, true))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(pJson.isMember("owner_id"))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(1, "owner_id", pJson["owner_id"], err, true))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
err="The owner_id column cannot be null";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(pJson.isMember("exp"))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(2, "exp", pJson["exp"], err, true))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
err="The exp column cannot be null";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool Tokens::validateMasqueradedJsonForCreation(const Json::Value &pJson,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector,
|
||||||
|
std::string &err)
|
||||||
|
{
|
||||||
|
if(pMasqueradingVector.size() != 3)
|
||||||
|
{
|
||||||
|
err = "Bad masquerading vector";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if(!pMasqueradingVector[0].empty())
|
||||||
|
{
|
||||||
|
if(pJson.isMember(pMasqueradingVector[0]))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(0, pMasqueradingVector[0], pJson[pMasqueradingVector[0]], err, true))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[1].empty())
|
||||||
|
{
|
||||||
|
if(pJson.isMember(pMasqueradingVector[1]))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(1, pMasqueradingVector[1], pJson[pMasqueradingVector[1]], err, true))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
err="The " + pMasqueradingVector[1] + " column cannot be null";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[2].empty())
|
||||||
|
{
|
||||||
|
if(pJson.isMember(pMasqueradingVector[2]))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(2, pMasqueradingVector[2], pJson[pMasqueradingVector[2]], err, true))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
err="The " + pMasqueradingVector[2] + " column cannot be null";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(const Json::LogicError &e)
|
||||||
|
{
|
||||||
|
err = e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool Tokens::validateJsonForUpdate(const Json::Value &pJson, std::string &err)
|
||||||
|
{
|
||||||
|
if(pJson.isMember("id"))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(0, "id", pJson["id"], err, false))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
err = "The value of primary key must be set in the json object for update";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(pJson.isMember("owner_id"))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(1, "owner_id", pJson["owner_id"], err, false))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(pJson.isMember("exp"))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(2, "exp", pJson["exp"], err, false))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool Tokens::validateMasqueradedJsonForUpdate(const Json::Value &pJson,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector,
|
||||||
|
std::string &err)
|
||||||
|
{
|
||||||
|
if(pMasqueradingVector.size() != 3)
|
||||||
|
{
|
||||||
|
err = "Bad masquerading vector";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0]))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(0, pMasqueradingVector[0], pJson[pMasqueradingVector[0]], err, false))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
err = "The value of primary key must be set in the json object for update";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(1, pMasqueradingVector[1], pJson[pMasqueradingVector[1]], err, false))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2]))
|
||||||
|
{
|
||||||
|
if(!validJsonOfField(2, pMasqueradingVector[2], pJson[pMasqueradingVector[2]], err, false))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(const Json::LogicError &e)
|
||||||
|
{
|
||||||
|
err = e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool Tokens::validJsonOfField(size_t index,
|
||||||
|
const std::string &fieldName,
|
||||||
|
const Json::Value &pJson,
|
||||||
|
std::string &err,
|
||||||
|
bool isForCreation)
|
||||||
|
{
|
||||||
|
switch(index)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if(pJson.isNull())
|
||||||
|
{
|
||||||
|
err="The " + fieldName + " column cannot be null";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(isForCreation)
|
||||||
|
{
|
||||||
|
err="The automatic primary key cannot be set";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!pJson.isUInt64())
|
||||||
|
{
|
||||||
|
err="Type error in the "+fieldName+" field";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if(pJson.isNull())
|
||||||
|
{
|
||||||
|
err="The " + fieldName + " column cannot be null";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!pJson.isUInt64())
|
||||||
|
{
|
||||||
|
err="Type error in the "+fieldName+" field";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if(pJson.isNull())
|
||||||
|
{
|
||||||
|
err="The " + fieldName + " column cannot be null";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!pJson.isUInt64())
|
||||||
|
{
|
||||||
|
err="Type error in the "+fieldName+" field";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
err="Internal error in the server";
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
211
backend/src/db/model/Tokens.h
Normal file
211
backend/src/db/model/Tokens.h
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* Tokens.h
|
||||||
|
* DO NOT EDIT. This file is generated by drogon_ctl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <drogon/orm/Result.h>
|
||||||
|
#include <drogon/orm/Row.h>
|
||||||
|
#include <drogon/orm/Field.h>
|
||||||
|
#include <drogon/orm/SqlBinder.h>
|
||||||
|
#include <drogon/orm/Mapper.h>
|
||||||
|
#ifdef __cpp_impl_coroutine
|
||||||
|
#include <drogon/orm/CoroMapper.h>
|
||||||
|
#endif
|
||||||
|
#include <trantor/utils/Date.h>
|
||||||
|
#include <trantor/utils/Logger.h>
|
||||||
|
#include <json/json.h>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <tuple>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace drogon
|
||||||
|
{
|
||||||
|
namespace orm
|
||||||
|
{
|
||||||
|
class DbClient;
|
||||||
|
using DbClientPtr = std::shared_ptr<DbClient>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace drogon_model
|
||||||
|
{
|
||||||
|
namespace sqlite3
|
||||||
|
{
|
||||||
|
|
||||||
|
class Tokens
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Cols
|
||||||
|
{
|
||||||
|
static const std::string _id;
|
||||||
|
static const std::string _owner_id;
|
||||||
|
static const std::string _exp;
|
||||||
|
};
|
||||||
|
|
||||||
|
const static int primaryKeyNumber;
|
||||||
|
const static std::string tableName;
|
||||||
|
const static bool hasPrimaryKey;
|
||||||
|
const static std::string primaryKeyName;
|
||||||
|
using PrimaryKeyType = uint64_t;
|
||||||
|
const PrimaryKeyType &getPrimaryKey() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief constructor
|
||||||
|
* @param r One row of records in the SQL query result.
|
||||||
|
* @param indexOffset Set the offset to -1 to access all columns by column names,
|
||||||
|
* otherwise access all columns by offsets.
|
||||||
|
* @note If the SQL is not a style of 'select * from table_name ...' (select all
|
||||||
|
* columns by an asterisk), please set the offset to -1.
|
||||||
|
*/
|
||||||
|
explicit Tokens(const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief constructor
|
||||||
|
* @param pJson The json object to construct a new instance.
|
||||||
|
*/
|
||||||
|
explicit Tokens(const Json::Value &pJson) noexcept(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief constructor
|
||||||
|
* @param pJson The json object to construct a new instance.
|
||||||
|
* @param pMasqueradingVector The aliases of table columns.
|
||||||
|
*/
|
||||||
|
Tokens(const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false);
|
||||||
|
|
||||||
|
Tokens() = default;
|
||||||
|
|
||||||
|
void updateByJson(const Json::Value &pJson) noexcept(false);
|
||||||
|
void updateByMasqueradedJson(const Json::Value &pJson,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector) noexcept(false);
|
||||||
|
static bool validateJsonForCreation(const Json::Value &pJson, std::string &err);
|
||||||
|
static bool validateMasqueradedJsonForCreation(const Json::Value &,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector,
|
||||||
|
std::string &err);
|
||||||
|
static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err);
|
||||||
|
static bool validateMasqueradedJsonForUpdate(const Json::Value &,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector,
|
||||||
|
std::string &err);
|
||||||
|
static bool validJsonOfField(size_t index,
|
||||||
|
const std::string &fieldName,
|
||||||
|
const Json::Value &pJson,
|
||||||
|
std::string &err,
|
||||||
|
bool isForCreation);
|
||||||
|
|
||||||
|
/** For column id */
|
||||||
|
///Get the value of the column id, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfId() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getId() const noexcept;
|
||||||
|
///Set the value of the column id
|
||||||
|
void setId(const uint64_t &pId) noexcept;
|
||||||
|
|
||||||
|
/** For column owner_id */
|
||||||
|
///Get the value of the column owner_id, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfOwnerId() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getOwnerId() const noexcept;
|
||||||
|
///Set the value of the column owner_id
|
||||||
|
void setOwnerId(const uint64_t &pOwnerId) noexcept;
|
||||||
|
|
||||||
|
/** For column exp */
|
||||||
|
///Get the value of the column exp, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfExp() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getExp() const noexcept;
|
||||||
|
///Set the value of the column exp
|
||||||
|
void setExp(const uint64_t &pExp) noexcept;
|
||||||
|
|
||||||
|
|
||||||
|
static size_t getColumnNumber() noexcept { return 3; }
|
||||||
|
static const std::string &getColumnName(size_t index) noexcept(false);
|
||||||
|
|
||||||
|
Json::Value toJson() const;
|
||||||
|
Json::Value toMasqueradedJson(const std::vector<std::string> &pMasqueradingVector) const;
|
||||||
|
/// Relationship interfaces
|
||||||
|
private:
|
||||||
|
friend drogon::orm::Mapper<Tokens>;
|
||||||
|
#ifdef __cpp_impl_coroutine
|
||||||
|
friend drogon::orm::CoroMapper<Tokens>;
|
||||||
|
#endif
|
||||||
|
static const std::vector<std::string> &insertColumns() noexcept;
|
||||||
|
void outputArgs(drogon::orm::internal::SqlBinder &binder) const;
|
||||||
|
const std::vector<std::string> updateColumns() const;
|
||||||
|
void updateArgs(drogon::orm::internal::SqlBinder &binder) const;
|
||||||
|
///For mysql or sqlite3
|
||||||
|
void updateId(const uint64_t id);
|
||||||
|
std::shared_ptr<uint64_t> id_;
|
||||||
|
std::shared_ptr<uint64_t> ownerId_;
|
||||||
|
std::shared_ptr<uint64_t> exp_;
|
||||||
|
struct MetaData
|
||||||
|
{
|
||||||
|
const std::string colName_;
|
||||||
|
const std::string colType_;
|
||||||
|
const std::string colDatabaseType_;
|
||||||
|
const ssize_t colLength_;
|
||||||
|
const bool isAutoVal_;
|
||||||
|
const bool isPrimaryKey_;
|
||||||
|
const bool notNull_;
|
||||||
|
};
|
||||||
|
static const std::vector<MetaData> metaData_;
|
||||||
|
bool dirtyFlag_[3]={ false };
|
||||||
|
public:
|
||||||
|
static const std::string &sqlForFindingByPrimaryKey()
|
||||||
|
{
|
||||||
|
static const std::string sql="select * from " + tableName + " where id = ?";
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::string &sqlForDeletingByPrimaryKey()
|
||||||
|
{
|
||||||
|
static const std::string sql="delete from " + tableName + " where id = ?";
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
std::string sqlForInserting(bool &needSelection) const
|
||||||
|
{
|
||||||
|
std::string sql="insert into " + tableName + " (";
|
||||||
|
size_t parametersCount = 0;
|
||||||
|
needSelection = false;
|
||||||
|
if(dirtyFlag_[1])
|
||||||
|
{
|
||||||
|
sql += "owner_id,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[2])
|
||||||
|
{
|
||||||
|
sql += "exp,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(parametersCount > 0)
|
||||||
|
{
|
||||||
|
sql[sql.length()-1]=')';
|
||||||
|
sql += " values (";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sql += ") values (";
|
||||||
|
|
||||||
|
if(dirtyFlag_[1])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[2])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(parametersCount > 0)
|
||||||
|
{
|
||||||
|
sql.resize(sql.length() - 1);
|
||||||
|
}
|
||||||
|
sql.append(1, ')');
|
||||||
|
LOG_TRACE << sql;
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace sqlite3
|
||||||
|
} // namespace drogon_model
|
1762
backend/src/db/model/User.cc
Normal file
1762
backend/src/db/model/User.cc
Normal file
File diff suppressed because it is too large
Load Diff
361
backend/src/db/model/User.h
Normal file
361
backend/src/db/model/User.h
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* User.h
|
||||||
|
* DO NOT EDIT. This file is generated by drogon_ctl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <drogon/orm/Result.h>
|
||||||
|
#include <drogon/orm/Row.h>
|
||||||
|
#include <drogon/orm/Field.h>
|
||||||
|
#include <drogon/orm/SqlBinder.h>
|
||||||
|
#include <drogon/orm/Mapper.h>
|
||||||
|
#ifdef __cpp_impl_coroutine
|
||||||
|
#include <drogon/orm/CoroMapper.h>
|
||||||
|
#endif
|
||||||
|
#include <trantor/utils/Date.h>
|
||||||
|
#include <trantor/utils/Logger.h>
|
||||||
|
#include <json/json.h>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <tuple>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace drogon
|
||||||
|
{
|
||||||
|
namespace orm
|
||||||
|
{
|
||||||
|
class DbClient;
|
||||||
|
using DbClientPtr = std::shared_ptr<DbClient>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace drogon_model
|
||||||
|
{
|
||||||
|
namespace sqlite3
|
||||||
|
{
|
||||||
|
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Cols
|
||||||
|
{
|
||||||
|
static const std::string _id;
|
||||||
|
static const std::string _gitlab;
|
||||||
|
static const std::string _name;
|
||||||
|
static const std::string _password;
|
||||||
|
static const std::string _role;
|
||||||
|
static const std::string _root_id;
|
||||||
|
static const std::string _tfa_type;
|
||||||
|
static const std::string _tfa_secret;
|
||||||
|
static const std::string _gitlab_at;
|
||||||
|
static const std::string _gitlab_rt;
|
||||||
|
};
|
||||||
|
|
||||||
|
const static int primaryKeyNumber;
|
||||||
|
const static std::string tableName;
|
||||||
|
const static bool hasPrimaryKey;
|
||||||
|
const static std::string primaryKeyName;
|
||||||
|
using PrimaryKeyType = uint64_t;
|
||||||
|
const PrimaryKeyType &getPrimaryKey() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief constructor
|
||||||
|
* @param r One row of records in the SQL query result.
|
||||||
|
* @param indexOffset Set the offset to -1 to access all columns by column names,
|
||||||
|
* otherwise access all columns by offsets.
|
||||||
|
* @note If the SQL is not a style of 'select * from table_name ...' (select all
|
||||||
|
* columns by an asterisk), please set the offset to -1.
|
||||||
|
*/
|
||||||
|
explicit User(const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief constructor
|
||||||
|
* @param pJson The json object to construct a new instance.
|
||||||
|
*/
|
||||||
|
explicit User(const Json::Value &pJson) noexcept(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief constructor
|
||||||
|
* @param pJson The json object to construct a new instance.
|
||||||
|
* @param pMasqueradingVector The aliases of table columns.
|
||||||
|
*/
|
||||||
|
User(const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false);
|
||||||
|
|
||||||
|
User() = default;
|
||||||
|
|
||||||
|
void updateByJson(const Json::Value &pJson) noexcept(false);
|
||||||
|
void updateByMasqueradedJson(const Json::Value &pJson,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector) noexcept(false);
|
||||||
|
static bool validateJsonForCreation(const Json::Value &pJson, std::string &err);
|
||||||
|
static bool validateMasqueradedJsonForCreation(const Json::Value &,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector,
|
||||||
|
std::string &err);
|
||||||
|
static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err);
|
||||||
|
static bool validateMasqueradedJsonForUpdate(const Json::Value &,
|
||||||
|
const std::vector<std::string> &pMasqueradingVector,
|
||||||
|
std::string &err);
|
||||||
|
static bool validJsonOfField(size_t index,
|
||||||
|
const std::string &fieldName,
|
||||||
|
const Json::Value &pJson,
|
||||||
|
std::string &err,
|
||||||
|
bool isForCreation);
|
||||||
|
|
||||||
|
/** For column id */
|
||||||
|
///Get the value of the column id, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfId() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getId() const noexcept;
|
||||||
|
///Set the value of the column id
|
||||||
|
void setId(const uint64_t &pId) noexcept;
|
||||||
|
|
||||||
|
/** For column gitlab */
|
||||||
|
///Get the value of the column gitlab, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfGitlab() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getGitlab() const noexcept;
|
||||||
|
///Set the value of the column gitlab
|
||||||
|
void setGitlab(const uint64_t &pGitlab) noexcept;
|
||||||
|
|
||||||
|
/** For column name */
|
||||||
|
///Get the value of the column name, returns the default value if the column is null
|
||||||
|
const std::string &getValueOfName() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<std::string> &getName() const noexcept;
|
||||||
|
///Set the value of the column name
|
||||||
|
void setName(const std::string &pName) noexcept;
|
||||||
|
void setName(std::string &&pName) noexcept;
|
||||||
|
|
||||||
|
/** For column password */
|
||||||
|
///Get the value of the column password, returns the default value if the column is null
|
||||||
|
const std::string &getValueOfPassword() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<std::string> &getPassword() const noexcept;
|
||||||
|
///Set the value of the column password
|
||||||
|
void setPassword(const std::string &pPassword) noexcept;
|
||||||
|
void setPassword(std::string &&pPassword) noexcept;
|
||||||
|
|
||||||
|
/** For column role */
|
||||||
|
///Get the value of the column role, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfRole() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getRole() const noexcept;
|
||||||
|
///Set the value of the column role
|
||||||
|
void setRole(const uint64_t &pRole) noexcept;
|
||||||
|
|
||||||
|
/** For column root_id */
|
||||||
|
///Get the value of the column root_id, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfRootId() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getRootId() const noexcept;
|
||||||
|
///Set the value of the column root_id
|
||||||
|
void setRootId(const uint64_t &pRootId) noexcept;
|
||||||
|
|
||||||
|
/** For column tfa_type */
|
||||||
|
///Get the value of the column tfa_type, returns the default value if the column is null
|
||||||
|
const uint64_t &getValueOfTfaType() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<uint64_t> &getTfaType() const noexcept;
|
||||||
|
///Set the value of the column tfa_type
|
||||||
|
void setTfaType(const uint64_t &pTfaType) noexcept;
|
||||||
|
|
||||||
|
/** For column tfa_secret */
|
||||||
|
///Get the value of the column tfa_secret, returns the default value if the column is null
|
||||||
|
const std::vector<char> &getValueOfTfaSecret() const noexcept;
|
||||||
|
///Return the column value by std::string with binary data
|
||||||
|
std::string getValueOfTfaSecretAsString() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<std::vector<char>> &getTfaSecret() const noexcept;
|
||||||
|
///Set the value of the column tfa_secret
|
||||||
|
void setTfaSecret(const std::vector<char> &pTfaSecret) noexcept;
|
||||||
|
void setTfaSecret(const std::string &pTfaSecret) noexcept;
|
||||||
|
void setTfaSecretToNull() noexcept;
|
||||||
|
|
||||||
|
/** For column gitlab_at */
|
||||||
|
///Get the value of the column gitlab_at, returns the default value if the column is null
|
||||||
|
const std::string &getValueOfGitlabAt() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<std::string> &getGitlabAt() const noexcept;
|
||||||
|
///Set the value of the column gitlab_at
|
||||||
|
void setGitlabAt(const std::string &pGitlabAt) noexcept;
|
||||||
|
void setGitlabAt(std::string &&pGitlabAt) noexcept;
|
||||||
|
void setGitlabAtToNull() noexcept;
|
||||||
|
|
||||||
|
/** For column gitlab_rt */
|
||||||
|
///Get the value of the column gitlab_rt, returns the default value if the column is null
|
||||||
|
const std::string &getValueOfGitlabRt() const noexcept;
|
||||||
|
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
|
||||||
|
const std::shared_ptr<std::string> &getGitlabRt() const noexcept;
|
||||||
|
///Set the value of the column gitlab_rt
|
||||||
|
void setGitlabRt(const std::string &pGitlabRt) noexcept;
|
||||||
|
void setGitlabRt(std::string &&pGitlabRt) noexcept;
|
||||||
|
void setGitlabRtToNull() noexcept;
|
||||||
|
|
||||||
|
|
||||||
|
static size_t getColumnNumber() noexcept { return 10; }
|
||||||
|
static const std::string &getColumnName(size_t index) noexcept(false);
|
||||||
|
|
||||||
|
Json::Value toJson() const;
|
||||||
|
Json::Value toMasqueradedJson(const std::vector<std::string> &pMasqueradingVector) const;
|
||||||
|
/// Relationship interfaces
|
||||||
|
private:
|
||||||
|
friend drogon::orm::Mapper<User>;
|
||||||
|
#ifdef __cpp_impl_coroutine
|
||||||
|
friend drogon::orm::CoroMapper<User>;
|
||||||
|
#endif
|
||||||
|
static const std::vector<std::string> &insertColumns() noexcept;
|
||||||
|
void outputArgs(drogon::orm::internal::SqlBinder &binder) const;
|
||||||
|
const std::vector<std::string> updateColumns() const;
|
||||||
|
void updateArgs(drogon::orm::internal::SqlBinder &binder) const;
|
||||||
|
///For mysql or sqlite3
|
||||||
|
void updateId(const uint64_t id);
|
||||||
|
std::shared_ptr<uint64_t> id_;
|
||||||
|
std::shared_ptr<uint64_t> gitlab_;
|
||||||
|
std::shared_ptr<std::string> name_;
|
||||||
|
std::shared_ptr<std::string> password_;
|
||||||
|
std::shared_ptr<uint64_t> role_;
|
||||||
|
std::shared_ptr<uint64_t> rootId_;
|
||||||
|
std::shared_ptr<uint64_t> tfaType_;
|
||||||
|
std::shared_ptr<std::vector<char>> tfaSecret_;
|
||||||
|
std::shared_ptr<std::string> gitlabAt_;
|
||||||
|
std::shared_ptr<std::string> gitlabRt_;
|
||||||
|
struct MetaData
|
||||||
|
{
|
||||||
|
const std::string colName_;
|
||||||
|
const std::string colType_;
|
||||||
|
const std::string colDatabaseType_;
|
||||||
|
const ssize_t colLength_;
|
||||||
|
const bool isAutoVal_;
|
||||||
|
const bool isPrimaryKey_;
|
||||||
|
const bool notNull_;
|
||||||
|
};
|
||||||
|
static const std::vector<MetaData> metaData_;
|
||||||
|
bool dirtyFlag_[10]={ false };
|
||||||
|
public:
|
||||||
|
static const std::string &sqlForFindingByPrimaryKey()
|
||||||
|
{
|
||||||
|
static const std::string sql="select * from " + tableName + " where id = ?";
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::string &sqlForDeletingByPrimaryKey()
|
||||||
|
{
|
||||||
|
static const std::string sql="delete from " + tableName + " where id = ?";
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
std::string sqlForInserting(bool &needSelection) const
|
||||||
|
{
|
||||||
|
std::string sql="insert into " + tableName + " (";
|
||||||
|
size_t parametersCount = 0;
|
||||||
|
needSelection = false;
|
||||||
|
if(dirtyFlag_[1])
|
||||||
|
{
|
||||||
|
sql += "gitlab,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[2])
|
||||||
|
{
|
||||||
|
sql += "name,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[3])
|
||||||
|
{
|
||||||
|
sql += "password,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[4])
|
||||||
|
{
|
||||||
|
sql += "role,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[5])
|
||||||
|
{
|
||||||
|
sql += "root_id,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[6])
|
||||||
|
{
|
||||||
|
sql += "tfa_type,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[7])
|
||||||
|
{
|
||||||
|
sql += "tfa_secret,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[8])
|
||||||
|
{
|
||||||
|
sql += "gitlab_at,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[9])
|
||||||
|
{
|
||||||
|
sql += "gitlab_rt,";
|
||||||
|
++parametersCount;
|
||||||
|
}
|
||||||
|
if(parametersCount > 0)
|
||||||
|
{
|
||||||
|
sql[sql.length()-1]=')';
|
||||||
|
sql += " values (";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sql += ") values (";
|
||||||
|
|
||||||
|
if(dirtyFlag_[1])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[2])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[3])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[4])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[5])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[6])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[7])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[8])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(dirtyFlag_[9])
|
||||||
|
{
|
||||||
|
sql.append("?,");
|
||||||
|
|
||||||
|
}
|
||||||
|
if(parametersCount > 0)
|
||||||
|
{
|
||||||
|
sql.resize(sql.length() - 1);
|
||||||
|
}
|
||||||
|
sql.append(1, ')');
|
||||||
|
LOG_TRACE << sql;
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace sqlite3
|
||||||
|
} // namespace drogon_model
|
5
backend/src/db/model/model.json
Normal file
5
backend/src/db/model/model.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"rdbms":"sqlite3",
|
||||||
|
"filename":"run/sqlite.db",
|
||||||
|
"tables":[]
|
||||||
|
}
|
56
backend/src/dto/dto.h
Normal file
56
backend/src/dto/dto.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#ifndef BACKEND_DTO_H
|
||||||
|
#define BACKEND_DTO_H
|
||||||
|
|
||||||
|
#include <drogon/HttpResponse.h>
|
||||||
|
#include "db/db.h"
|
||||||
|
|
||||||
|
namespace dto {
|
||||||
|
template<typename T>
|
||||||
|
std::optional<T> json_get(const Json::Value& j, const std::string& key) {
|
||||||
|
return j.isMember(key)
|
||||||
|
? std::make_optional(j[key].as<T>())
|
||||||
|
: std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline db::User get_user(const drogon::HttpRequestPtr& req) {
|
||||||
|
return req->attributes()->get<db::User>("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline db::Token get_token(const drogon::HttpRequestPtr& req) {
|
||||||
|
return req->attributes()->get<db::Token>("token");
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Responses {
|
||||||
|
struct GetUsersEntry {
|
||||||
|
GetUsersEntry(int id, bool gitlab, bool tfa, std::string name, db::UserRole role)
|
||||||
|
: id(id), gitlab(gitlab), tfa(tfa), name(std::move(name)), role(role) {}
|
||||||
|
int id;
|
||||||
|
bool gitlab, tfa;
|
||||||
|
std::string name;
|
||||||
|
db::UserRole role;
|
||||||
|
};
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_error_res(drogon::HttpStatusCode, const std::string &msg);
|
||||||
|
drogon::HttpResponsePtr get_success_res();
|
||||||
|
drogon::HttpResponsePtr get_success_res(Json::Value &);
|
||||||
|
|
||||||
|
inline drogon::HttpResponsePtr get_badreq_res(const std::string &msg) { return get_error_res(drogon::HttpStatusCode::k400BadRequest, msg); }
|
||||||
|
inline drogon::HttpResponsePtr get_unauth_res(const std::string &msg) { return get_error_res(drogon::HttpStatusCode::k401Unauthorized, msg); }
|
||||||
|
inline drogon::HttpResponsePtr get_forbdn_res(const std::string &msg) { return get_error_res(drogon::HttpStatusCode::k403Forbidden, msg); }
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_login_res(const std::string &jwt);
|
||||||
|
drogon::HttpResponsePtr get_tfa_setup_res(const std::string& secret, const std::string& qrcode);
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_user_info_res(const std::string& name, bool gitlab, bool tfa);
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_admin_users_res(const std::vector<GetUsersEntry>& users);
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_root_res(uint64_t root);
|
||||||
|
drogon::HttpResponsePtr get_node_folder_res(uint64_t id, const std::string& name, const std::shared_ptr<uint64_t>& parent, const std::vector<uint64_t>& children);
|
||||||
|
drogon::HttpResponsePtr get_node_file_res(uint64_t id, const std::string& name, const std::shared_ptr<uint64_t>& parent, uint64_t size);
|
||||||
|
drogon::HttpResponsePtr get_path_res(const std::string& path);
|
||||||
|
drogon::HttpResponsePtr get_new_node_res(uint64_t id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //BACKEND_DTO_H
|
98
backend/src/dto/responses.cpp
Normal file
98
backend/src/dto/responses.cpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
#include "dto.h"
|
||||||
|
|
||||||
|
namespace dto::Responses {
|
||||||
|
drogon::HttpResponsePtr get_error_res(drogon::HttpStatusCode code, const std::string& msg) {
|
||||||
|
Json::Value json;
|
||||||
|
json["statusCode"] = static_cast<int>(code);
|
||||||
|
json["message"] = msg;
|
||||||
|
auto res = drogon::HttpResponse::newHttpJsonResponse(json);
|
||||||
|
res->setStatusCode(code);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_success_res() {
|
||||||
|
Json::Value json;
|
||||||
|
return get_success_res(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_success_res(Json::Value& json) {
|
||||||
|
json["statusCode"] = 200;
|
||||||
|
auto res = drogon::HttpResponse::newHttpJsonResponse(json);
|
||||||
|
res->setStatusCode(drogon::HttpStatusCode::k200OK);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_login_res(const std::string &jwt) {
|
||||||
|
Json::Value json;
|
||||||
|
json["jwt"] = jwt;
|
||||||
|
return get_success_res(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_tfa_setup_res(const std::string& secret, const std::string& qrcode) {
|
||||||
|
Json::Value json;
|
||||||
|
json["secret"] = secret;
|
||||||
|
json["qrCode"] = qrcode;
|
||||||
|
return get_success_res(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_user_info_res(const std::string &name, bool gitlab, bool tfa) {
|
||||||
|
Json::Value json;
|
||||||
|
json["name"] = name;
|
||||||
|
json["gitlab"] = gitlab;
|
||||||
|
json["tfaEnabled"] = tfa;
|
||||||
|
return get_success_res(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_admin_users_res(const std::vector<GetUsersEntry>& users) {
|
||||||
|
Json::Value json;
|
||||||
|
for (const GetUsersEntry& user : users) {
|
||||||
|
Json::Value entry;
|
||||||
|
entry["id"] = user.id;
|
||||||
|
entry["gitlab"] = user.gitlab;
|
||||||
|
entry["name"] = user.name;
|
||||||
|
entry["role"] = user.role;
|
||||||
|
entry["tfaEnabled"] = user.tfa;
|
||||||
|
json["users"].append(entry);
|
||||||
|
}
|
||||||
|
return get_success_res(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_root_res(uint64_t root) {
|
||||||
|
Json::Value json;
|
||||||
|
json["rootId"] = root;
|
||||||
|
return get_success_res(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_node_folder_res(uint64_t id, const std::string &name, const std::shared_ptr<uint64_t> &parent, const std::vector<uint64_t> &children) {
|
||||||
|
Json::Value json;
|
||||||
|
json["id"] = id;
|
||||||
|
json["name"] = name;
|
||||||
|
json["isFile"] = false;
|
||||||
|
json["parent"] = (parent != nullptr) ? *parent : Json::Value::nullSingleton();
|
||||||
|
for (uint64_t child : children)
|
||||||
|
json["children"].append(child);
|
||||||
|
return get_success_res(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_node_file_res(uint64_t id, const std::string &name, const std::shared_ptr<uint64_t> &parent, uint64_t size) {
|
||||||
|
Json::Value json;
|
||||||
|
json["id"] = id;
|
||||||
|
json["name"] = name;
|
||||||
|
json["isFile"] = true;
|
||||||
|
json["parent"] = (parent != nullptr) ? *parent : Json::Value::nullSingleton();
|
||||||
|
json["size"] = size;
|
||||||
|
return get_success_res(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_path_res(const std::string& path) {
|
||||||
|
Json::Value json;
|
||||||
|
json["path"] = path;
|
||||||
|
return get_success_res(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
drogon::HttpResponsePtr get_new_node_res(uint64_t id) {
|
||||||
|
Json::Value json;
|
||||||
|
json["id"] = id;
|
||||||
|
return get_success_res(json);
|
||||||
|
}
|
||||||
|
}
|
82
backend/src/filters/filters.cpp
Normal file
82
backend/src/filters/filters.cpp
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#include "filters.h"
|
||||||
|
|
||||||
|
#include <drogon/utils/coroutine.h>
|
||||||
|
#include <jwt-cpp/traits/kazuho-picojson/traits.h>
|
||||||
|
#include <jwt-cpp/jwt.h>
|
||||||
|
#include "db/db.h"
|
||||||
|
#include "dto/dto.h"
|
||||||
|
#include "controllers/controllers.h"
|
||||||
|
|
||||||
|
void cleanup_tokens(db::MapperToken& mapper) {
|
||||||
|
const uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
mapper.deleteBy(
|
||||||
|
db::Criteria(db::Token::Cols::_exp, db::CompareOps::LE, now)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Login::doFilter(const drogon::HttpRequestPtr& req, drogon::FilterCallback&& cb, drogon::FilterChainCallback&& ccb) {
|
||||||
|
std::string token_str;
|
||||||
|
if (req->path() == "/api/fs/download") {
|
||||||
|
token_str = req->getParameter("jwtToken");
|
||||||
|
} else {
|
||||||
|
std::string auth_header = req->getHeader("Authorization");
|
||||||
|
if (auth_header.empty() || (!auth_header.starts_with("Bearer ")))
|
||||||
|
return cb(dto::Responses::get_unauth_res("Unauthorized"));
|
||||||
|
token_str = auth_header.substr(7);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto token = jwt::decode<jwt::traits::kazuho_picojson>(token_str);
|
||||||
|
jwt::verify<jwt::traits::kazuho_picojson>()
|
||||||
|
.allow_algorithm(jwt::algorithm::hs256{jwt_secret})
|
||||||
|
.verify(token);
|
||||||
|
uint64_t token_id = token.get_payload_claim("jti").as_int();
|
||||||
|
uint64_t user_id = token.get_payload_claim("sub").as_int();
|
||||||
|
|
||||||
|
auto db = drogon::app().getDbClient();
|
||||||
|
|
||||||
|
db::MapperUser user_mapper(db);
|
||||||
|
db::MapperToken token_mapper(db);
|
||||||
|
|
||||||
|
cleanup_tokens(token_mapper);
|
||||||
|
|
||||||
|
db::Token db_token = token_mapper.findByPrimaryKey(token_id);
|
||||||
|
db::User db_user = user_mapper.findByPrimaryKey(db_token.getValueOfOwnerId());
|
||||||
|
|
||||||
|
if (db_user.getValueOfId() != user_id) throw std::exception();
|
||||||
|
if (db::User_getEnumRole(db_user) == db::UserRole::DISABLED) throw std::exception();
|
||||||
|
|
||||||
|
if (db_user.getValueOfGitlab() != 0) {
|
||||||
|
auto info = api::auth::get_gitlab_user(db_user.getValueOfGitlabAt());
|
||||||
|
if (!info.has_value()) {
|
||||||
|
auto tokens = api::auth::get_gitlab_tokens(req, db_user.getValueOfGitlabRt(), true);
|
||||||
|
info = api::auth::get_gitlab_user(tokens->at);
|
||||||
|
if (!tokens.has_value() || !info.has_value()) {
|
||||||
|
api::auth::revoke_all(db_user);
|
||||||
|
throw std::exception();
|
||||||
|
}
|
||||||
|
db_user.setGitlabAt(tokens->at);
|
||||||
|
db_user.setGitlabRt(tokens->rt);
|
||||||
|
user_mapper.update(db_user);
|
||||||
|
}
|
||||||
|
if (info->name != db_user.getValueOfName()) {
|
||||||
|
api::auth::revoke_all(db_user);
|
||||||
|
throw std::exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req->attributes()->insert("token", db_token);
|
||||||
|
req->attributes()->insert("user", db_user);
|
||||||
|
ccb();
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
cb(dto::Responses::get_unauth_res("Unauthorized"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Admin::doFilter(const drogon::HttpRequestPtr& req, drogon::FilterCallback&& cb, drogon::FilterChainCallback&& ccb) {
|
||||||
|
db::User user = dto::get_user(req);
|
||||||
|
|
||||||
|
if (db::User_getEnumRole(user) != db::UserRole::ADMIN)
|
||||||
|
cb(dto::Responses::get_forbdn_res("Forbidden"));
|
||||||
|
else
|
||||||
|
ccb();
|
||||||
|
}
|
14
backend/src/filters/filters.h
Normal file
14
backend/src/filters/filters.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef BACKEND_FILTERS_H
|
||||||
|
#define BACKEND_FILTERS_H
|
||||||
|
|
||||||
|
#include <drogon/HttpFilter.h>
|
||||||
|
|
||||||
|
struct Login : public drogon::HttpFilter<Login> {
|
||||||
|
void doFilter(const drogon::HttpRequestPtr&, drogon::FilterCallback&&, drogon::FilterChainCallback&&) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Admin : public drogon::HttpFilter<Admin> {
|
||||||
|
void doFilter(const drogon::HttpRequestPtr&, drogon::FilterCallback&&, drogon::FilterChainCallback&&) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //BACKEND_FILTERS_H
|
112
backend/src/main.cpp
Normal file
112
backend/src/main.cpp
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <drogon/drogon.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include "dto/dto.h"
|
||||||
|
|
||||||
|
void cleanup() {
|
||||||
|
std::cout << "Stopping..." << std::endl;
|
||||||
|
drogon::app().quit();
|
||||||
|
std::cout << "Cleanup up uploads...";
|
||||||
|
std::filesystem::remove_all("uploads");
|
||||||
|
std::cout << " [Done]" << std::endl;
|
||||||
|
std::cout << "Goodbye!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Setting up..." << std::endl;
|
||||||
|
std::cout << "Initializing curl..." << std::flush;
|
||||||
|
curl_global_init(CURL_GLOBAL_ALL);
|
||||||
|
std::cout << " [Done]" << std::endl;
|
||||||
|
if (!std::filesystem::exists("files")) {
|
||||||
|
std::cout << "Creating files..." << std::flush;
|
||||||
|
std::filesystem::create_directory("files");
|
||||||
|
std::cout << " [Done]" << std::endl;
|
||||||
|
}
|
||||||
|
if (!std::filesystem::exists("logs")) {
|
||||||
|
std::cout << "Creating logs..." << std::flush;
|
||||||
|
std::filesystem::create_directory("logs");
|
||||||
|
std::cout << " [Done]" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* loop = drogon::app().getLoop();
|
||||||
|
loop->queueInLoop([]{
|
||||||
|
std::cout << "Starting..." << std::endl;
|
||||||
|
std::cout << "Creating db tables..." << std::flush;
|
||||||
|
auto db = drogon::app().getDbClient();
|
||||||
|
db->execSqlSync("CREATE TABLE IF NOT EXISTS 'tokens' (\n"
|
||||||
|
" 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
|
||||||
|
" 'owner_id' INTEGER NOT NULL,\n"
|
||||||
|
" 'exp' INTEGER NOT NULL\n"
|
||||||
|
")");
|
||||||
|
db->execSqlSync("CREATE TABLE IF NOT EXISTS 'user' (\n"
|
||||||
|
" 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
|
||||||
|
" 'gitlab' INTEGER NOT NULL,\n"
|
||||||
|
" 'name' TEXT NOT NULL,\n"
|
||||||
|
" 'password' TEXT NOT NULL,\n"
|
||||||
|
" 'role' INTEGER NOT NULL,\n"
|
||||||
|
" 'root_id' INTEGER NOT NULL,\n"
|
||||||
|
" 'tfa_type' INTEGER NOT NULL,\n"
|
||||||
|
" 'tfa_secret' BLOB,\n"
|
||||||
|
" 'gitlab_at' TEXT,\n"
|
||||||
|
" 'gitlab_rt' TEXT\n"
|
||||||
|
")");
|
||||||
|
db->execSqlSync("CREATE TABLE IF NOT EXISTS 'inode' (\n"
|
||||||
|
" 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
|
||||||
|
" 'is_file' INTEGER NOT NULL,\n"
|
||||||
|
" 'name' TEXT,\n"
|
||||||
|
" 'parent_id' INTEGER,\n"
|
||||||
|
" 'owner_id' INTEGER NOT NULL,\n"
|
||||||
|
" 'size' INTEGER\n"
|
||||||
|
")");
|
||||||
|
std::cout << " [Done]" << std::endl;
|
||||||
|
std::cout << "Started!" << std::endl;
|
||||||
|
std::cout << "Registered paths: " << std::endl;
|
||||||
|
auto handlers = drogon::app().getHandlersInfo();
|
||||||
|
for (const auto& handler : handlers) {
|
||||||
|
std::cout << " ";
|
||||||
|
if (std::get<1>(handler) == drogon::HttpMethod::Post) std::cout << "POST ";
|
||||||
|
else std::cout << "GET ";
|
||||||
|
std::string func = std::get<2>(handler).substr(16);
|
||||||
|
func.resize(30, ' ');
|
||||||
|
std::cout << '[' << func << "] ";
|
||||||
|
std::cout << std::get<0>(handler) << std::endl;
|
||||||
|
}
|
||||||
|
std::cout << "Listening on:" << std::endl;
|
||||||
|
auto listeners = drogon::app().getListeners();
|
||||||
|
for (const auto& listener : listeners) {
|
||||||
|
std::cout << " " << listener.toIpPort() << std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Json::Value access_logger;
|
||||||
|
access_logger["name"] = "drogon::plugin::AccessLogger";
|
||||||
|
|
||||||
|
Json::Value config;
|
||||||
|
config["plugins"].append(access_logger);
|
||||||
|
|
||||||
|
drogon::app()
|
||||||
|
.setClientMaxBodySize(1024L * 1024L * 1024L * 1024L) // 1 TB
|
||||||
|
|
||||||
|
.loadConfigJson(config)
|
||||||
|
|
||||||
|
.createDbClient("sqlite3", "", 0, "", "", "", 1, "sqlite.db")
|
||||||
|
|
||||||
|
.setCustom404Page(drogon::HttpResponse::newFileResponse("./static/index.html"), false)
|
||||||
|
.setDocumentRoot("./static")
|
||||||
|
.setBrStatic(true)
|
||||||
|
.setStaticFilesCacheTime(0)
|
||||||
|
|
||||||
|
.setLogPath("./logs")
|
||||||
|
.setLogLevel(trantor::Logger::LogLevel::kDebug)
|
||||||
|
|
||||||
|
.setIntSignalHandler(cleanup)
|
||||||
|
.setTermSignalHandler(cleanup)
|
||||||
|
|
||||||
|
.addListener("0.0.0.0", 1234)
|
||||||
|
.setThreadNum(2);
|
||||||
|
std::cout << "Setup done!" << std::endl;
|
||||||
|
|
||||||
|
drogon::app().run();
|
||||||
|
}
|
17
backend/vcpkg.json
Normal file
17
backend/vcpkg.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
||||||
|
"name": "backend",
|
||||||
|
"version-string": "1.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"name": "drogon",
|
||||||
|
"features": ["orm", "sqlite3"]
|
||||||
|
},
|
||||||
|
"jwt-cpp",
|
||||||
|
"botan",
|
||||||
|
"curl",
|
||||||
|
"pngpp",
|
||||||
|
"nayuki-qr-code-generator",
|
||||||
|
"libpng"
|
||||||
|
]
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
export * as Requests from './requests';
|
|
||||||
export * as Responses from './responses';
|
|
||||||
export {
|
|
||||||
UserRole,
|
|
||||||
validateSync,
|
|
||||||
validateAsync,
|
|
||||||
validateAsyncInline
|
|
||||||
} from './utils';
|
|
@ -1,17 +0,0 @@
|
|||||||
import { BaseRequest } from './base';
|
|
||||||
import { IsEnum, IsNumber } from 'class-validator';
|
|
||||||
import { UserRole } from '../utils';
|
|
||||||
|
|
||||||
class AdminRequest extends BaseRequest {
|
|
||||||
@IsNumber()
|
|
||||||
user: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SetUserRole extends AdminRequest {
|
|
||||||
@IsEnum(UserRole)
|
|
||||||
role: UserRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LogoutAll extends AdminRequest {}
|
|
||||||
export class DeleteUser extends AdminRequest {}
|
|
||||||
export class DisableTfa extends AdminRequest {}
|
|
@ -1,50 +0,0 @@
|
|||||||
import { BaseRequest } from './base';
|
|
||||||
import {
|
|
||||||
IsBoolean,
|
|
||||||
IsEmail,
|
|
||||||
IsNotEmpty,
|
|
||||||
IsOptional,
|
|
||||||
IsString
|
|
||||||
} from 'class-validator';
|
|
||||||
|
|
||||||
export class SignUpRequest extends BaseRequest {
|
|
||||||
@IsEmail()
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LoginRequest extends SignUpRequest {
|
|
||||||
@IsOptional()
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
otp?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TfaSetup extends BaseRequest {
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsBoolean()
|
|
||||||
mail: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TfaComplete extends BaseRequest {
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsBoolean()
|
|
||||||
mail: boolean;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
code: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChangePasswordRequest extends BaseRequest {
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
oldPassword: string;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
newPassword: string;
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
import { BaseRequest } from './base';
|
|
||||||
import { IsInt, IsNotEmpty, IsString, Min } from 'class-validator';
|
|
||||||
|
|
||||||
export class CreateFolderRequest extends BaseRequest {
|
|
||||||
@IsInt()
|
|
||||||
@Min(1)
|
|
||||||
parent: number;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DeleteRequest extends BaseRequest {
|
|
||||||
@IsInt()
|
|
||||||
@Min(1)
|
|
||||||
node: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreateFileRequest extends CreateFolderRequest {}
|
|
@ -1,4 +0,0 @@
|
|||||||
export * from './base';
|
|
||||||
export * as Auth from './auth';
|
|
||||||
export * as FS from './fs';
|
|
||||||
export * as Admin from './admin';
|
|
@ -1,61 +0,0 @@
|
|||||||
import { SuccessResponse } from './base';
|
|
||||||
import {
|
|
||||||
IsArray,
|
|
||||||
IsBoolean,
|
|
||||||
IsEnum,
|
|
||||||
IsNotEmpty,
|
|
||||||
IsNumber,
|
|
||||||
IsString,
|
|
||||||
ValidateNested
|
|
||||||
} from 'class-validator';
|
|
||||||
import { UserRole, ValidateConstructor } from '../utils';
|
|
||||||
|
|
||||||
@ValidateConstructor
|
|
||||||
export class GetUsersEntry {
|
|
||||||
constructor(
|
|
||||||
id: number,
|
|
||||||
gitlab: boolean,
|
|
||||||
name: string,
|
|
||||||
role: UserRole,
|
|
||||||
tfaEnabled: boolean
|
|
||||||
) {
|
|
||||||
this.id = id;
|
|
||||||
this.gitlab = gitlab;
|
|
||||||
this.name = name;
|
|
||||||
this.role = role;
|
|
||||||
this.tfaEnabled = tfaEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IsNumber()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@IsBoolean()
|
|
||||||
gitlab: boolean;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@IsEnum(UserRole)
|
|
||||||
role: UserRole;
|
|
||||||
|
|
||||||
@IsBoolean()
|
|
||||||
tfaEnabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ValidateConstructor
|
|
||||||
export class GetUsers extends SuccessResponse {
|
|
||||||
constructor(users: GetUsersEntry[]) {
|
|
||||||
super();
|
|
||||||
this.users = users;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
users: GetUsersEntry[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LogoutAllUser extends SuccessResponse {}
|
|
||||||
export class DeleteUser extends SuccessResponse {}
|
|
||||||
export class SetUserRole extends SuccessResponse {}
|
|
||||||
export class DisableTfa extends SuccessResponse {}
|
|
@ -1,25 +0,0 @@
|
|||||||
import { IsNumber, Max, Min } from 'class-validator';
|
|
||||||
|
|
||||||
export class BaseResponse {
|
|
||||||
constructor(statusCode: number) {
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IsNumber()
|
|
||||||
@Min(100)
|
|
||||||
@Max(599)
|
|
||||||
statusCode: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SuccessResponse extends BaseResponse {
|
|
||||||
constructor() {
|
|
||||||
super(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
declare statusCode: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ErrorResponse extends BaseResponse {
|
|
||||||
declare statusCode: 400 | 401 | 403;
|
|
||||||
message?: string;
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
import { SuccessResponse } from './base';
|
|
||||||
import {
|
|
||||||
IsBoolean,
|
|
||||||
IsInt,
|
|
||||||
IsNotEmpty,
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
Min
|
|
||||||
} from 'class-validator';
|
|
||||||
import { ValidateConstructor } from '../utils';
|
|
||||||
|
|
||||||
@ValidateConstructor
|
|
||||||
export class GetRootResponse extends SuccessResponse {
|
|
||||||
constructor(rootId: number) {
|
|
||||||
super();
|
|
||||||
this.rootId = rootId;
|
|
||||||
}
|
|
||||||
@IsInt()
|
|
||||||
@Min(1)
|
|
||||||
rootId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GetNodeResponse extends SuccessResponse {
|
|
||||||
constructor(
|
|
||||||
id: number,
|
|
||||||
name: string,
|
|
||||||
isFile: boolean,
|
|
||||||
parent: number | null
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.id = id;
|
|
||||||
this.name = name;
|
|
||||||
this.isFile = isFile;
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IsInt()
|
|
||||||
@Min(1)
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@IsBoolean()
|
|
||||||
isFile: boolean;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
|
||||||
@Min(1)
|
|
||||||
parent: number | null;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt({ each: true })
|
|
||||||
@Min(1, { each: true })
|
|
||||||
children?: number[];
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
|
||||||
@Min(0)
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ValidateConstructor
|
|
||||||
export class GetPathResponse extends SuccessResponse {
|
|
||||||
constructor(path: string) {
|
|
||||||
super();
|
|
||||||
this.path = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
path: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ValidateConstructor
|
|
||||||
export class CreateFolderResponse extends SuccessResponse {
|
|
||||||
constructor(id: number) {
|
|
||||||
super();
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IsInt()
|
|
||||||
@Min(1)
|
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UploadFileResponse extends SuccessResponse {}
|
|
||||||
export class DeleteResponse extends SuccessResponse {}
|
|
||||||
export class CreateFileResponse extends CreateFolderResponse {}
|
|
@ -1,5 +0,0 @@
|
|||||||
export * from './base';
|
|
||||||
export * as Auth from './auth';
|
|
||||||
export * as FS from './fs';
|
|
||||||
export * as User from './user';
|
|
||||||
export * as Admin from './admin';
|
|
@ -1,27 +0,0 @@
|
|||||||
import { SuccessResponse } from './base';
|
|
||||||
import { ValidateConstructor } from '../utils';
|
|
||||||
import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
@ValidateConstructor
|
|
||||||
export class UserInfoResponse extends SuccessResponse {
|
|
||||||
constructor(name: string, gitlab: boolean, tfaEnabled: boolean) {
|
|
||||||
super();
|
|
||||||
this.name = name;
|
|
||||||
this.gitlab = gitlab;
|
|
||||||
this.tfaEnabled = tfaEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@IsBoolean()
|
|
||||||
gitlab: boolean;
|
|
||||||
|
|
||||||
@IsBoolean()
|
|
||||||
tfaEnabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DeleteUserResponse extends SuccessResponse {}
|
|
||||||
export class ChangePasswordResponse extends SuccessResponse {}
|
|
||||||
export class LogoutAllResponse extends SuccessResponse {}
|
|
41
dto/utils.ts
41
dto/utils.ts
@ -1,41 +0,0 @@
|
|||||||
import { validate, validateSync as _validateSync } from 'class-validator';
|
|
||||||
|
|
||||||
export enum UserRole {
|
|
||||||
ADMIN = 2,
|
|
||||||
USER = 1,
|
|
||||||
DISABLED = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateSync<T extends object>(data: T): void {
|
|
||||||
const errors = _validateSync(data);
|
|
||||||
if (errors.length > 0) {
|
|
||||||
console.error('Validation failed, errors: ', errors);
|
|
||||||
throw new Error('Validation failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function validateAsync<T extends object>(data: T): Promise<void> {
|
|
||||||
const errors = await validate(data);
|
|
||||||
if (errors.length > 0) {
|
|
||||||
console.error('Validation failed, errors: ', errors);
|
|
||||||
throw new Error('Validation failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function validateAsyncInline<T extends object>(
|
|
||||||
data: T
|
|
||||||
): Promise<T> {
|
|
||||||
await validateAsync(data);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ValidateConstructor<T extends { new (...args: any[]): any }>(
|
|
||||||
constr: T
|
|
||||||
) {
|
|
||||||
return class extends constr {
|
|
||||||
constructor(...args: any[]) {
|
|
||||||
super(...args);
|
|
||||||
validateSync(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,3 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: ['@vue/cli-plugin-babel/preset']
|
presets: ["@vue/cli-plugin-babel/preset"],
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.13.2",
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
"filesize": "^9.0.11",
|
"filesize": "^9.0.11",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
@ -27,8 +29,6 @@
|
|||||||
"@vue/cli-plugin-typescript": "~5.0.0",
|
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||||
"@vue/cli-service": "~5.0.0",
|
"@vue/cli-service": "~5.0.0",
|
||||||
"@vue/eslint-config-typescript": "^9.1.0",
|
"@vue/eslint-config-typescript": "^9.1.0",
|
||||||
"class-transformer": "^0.5.1",
|
|
||||||
"class-validator": "^0.13.2",
|
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
@ -1,62 +1,62 @@
|
|||||||
<script setup async lang="ts">
|
<script setup async lang="ts">
|
||||||
import { provide, ref } from 'vue';
|
import { provide, ref } from "vue";
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from "vue-router";
|
||||||
import { TokenInjectType } from '@/api';
|
import { TokenInjectType } from "@/api";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const jwt = ref<string | null>(localStorage.getItem('token'));
|
const jwt = ref<string | null>(localStorage.getItem("token"));
|
||||||
|
|
||||||
function setToken(token: string) {
|
function setToken(token: string) {
|
||||||
jwt.value = token;
|
jwt.value = token;
|
||||||
localStorage.setItem('token', token);
|
localStorage.setItem("token", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
jwt.value = null;
|
jwt.value = null;
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem("token");
|
||||||
router.push({ name: 'login' });
|
router.push({ name: "login" });
|
||||||
}
|
}
|
||||||
|
|
||||||
provide<TokenInjectType>('jwt', {
|
provide<TokenInjectType>("jwt", {
|
||||||
jwt,
|
jwt,
|
||||||
setToken,
|
setToken,
|
||||||
logout
|
logout,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav>
|
<nav>
|
||||||
<template v-if="jwt != null">
|
<template v-if="jwt != null">
|
||||||
<router-link to="/">Files</router-link>
|
<router-link to="/">Files</router-link>
|
||||||
<span style="margin-left: 2em" />
|
<span style="margin-left: 2em" />
|
||||||
<router-link to="/profile">Profile</router-link>
|
<router-link to="/profile">Profile</router-link>
|
||||||
<span style="margin-left: 2em" />
|
<span style="margin-left: 2em" />
|
||||||
<router-link to="/login" @click="logout()">Logout</router-link>
|
<router-link to="/login" @click="logout()">Logout</router-link>
|
||||||
</template>
|
</template>
|
||||||
</nav>
|
</nav>
|
||||||
<router-view />
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#app {
|
#app {
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
|
|
||||||
&.router-link-exact-active {
|
&.router-link-exact-active {
|
||||||
color: #42b983;
|
color: #42b983;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import App from './App';
|
import App from "./App";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<App></App>
|
<App></App>
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<div>Loading...</div>
|
<div>Loading...</div>
|
||||||
</template>
|
</template>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,54 +1,54 @@
|
|||||||
import { Requests, Responses, UserRole, get_token, post_token } from './base';
|
import { Requests, Responses, UserRole, get_token, post_token } from "./base";
|
||||||
|
|
||||||
export const get_users = (token: string): Promise<Responses.Admin.GetUsers> =>
|
export const get_users = (token: string): Promise<Responses.Admin.GetUsers> =>
|
||||||
get_token('/api/admin/users', token);
|
get_token("/api/admin/users", token);
|
||||||
|
|
||||||
export const set_role = (
|
export const set_role = (
|
||||||
user: number,
|
user: number,
|
||||||
role: UserRole,
|
role: UserRole,
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Admin.SetUserRole | Responses.ErrorResponse> =>
|
): Promise<Responses.Admin.SetUserRole | Responses.ErrorResponse> =>
|
||||||
post_token<Requests.Admin.SetUserRole>(
|
post_token<Requests.Admin.SetUserRole>(
|
||||||
'/api/admin/set_role',
|
"/api/admin/set_role",
|
||||||
{
|
{
|
||||||
user,
|
user,
|
||||||
role
|
role,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
export const logout = (
|
export const logout = (
|
||||||
user: number,
|
user: number,
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Admin.LogoutAllUser | Responses.ErrorResponse> =>
|
): Promise<Responses.Admin.LogoutAllUser | Responses.ErrorResponse> =>
|
||||||
post_token<Requests.Admin.LogoutAll>(
|
post_token<Requests.Admin.LogoutAll>(
|
||||||
'/api/admin/logout',
|
"/api/admin/logout",
|
||||||
{
|
{
|
||||||
user
|
user,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
export const delete_user = (
|
export const delete_user = (
|
||||||
user: number,
|
user: number,
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Admin.DeleteUser | Responses.ErrorResponse> =>
|
): Promise<Responses.Admin.DeleteUser | Responses.ErrorResponse> =>
|
||||||
post_token<Requests.Admin.DeleteUser>(
|
post_token<Requests.Admin.DeleteUser>(
|
||||||
'/api/admin/delete',
|
"/api/admin/delete",
|
||||||
{
|
{
|
||||||
user
|
user,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
export const disable_tfa = (
|
export const disable_tfa = (
|
||||||
user: number,
|
user: number,
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Admin.DisableTfa | Responses.ErrorResponse> =>
|
): Promise<Responses.Admin.DisableTfa | Responses.ErrorResponse> =>
|
||||||
post_token<Requests.Admin.DisableTfa>(
|
post_token<Requests.Admin.DisableTfa>(
|
||||||
'/api/admin/disable_2fa',
|
"/api/admin/disable_2fa",
|
||||||
{
|
{
|
||||||
user
|
user,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
@ -1,93 +1,93 @@
|
|||||||
import { Responses, Requests, post, post_token } from './base';
|
import { Responses, Requests, post, post_token } from "./base";
|
||||||
|
|
||||||
export const auth_login = (
|
export const auth_login = (
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
otp?: string
|
otp?: string
|
||||||
): Promise<
|
): Promise<
|
||||||
| Responses.Auth.LoginResponse
|
| Responses.Auth.LoginResponse
|
||||||
| Responses.Auth.TfaRequiredResponse
|
| Responses.Auth.TfaRequiredResponse
|
||||||
| Responses.ErrorResponse
|
| Responses.ErrorResponse
|
||||||
> =>
|
> =>
|
||||||
post<Requests.Auth.LoginRequest>('/api/auth/login', {
|
post<Requests.Auth.LoginRequest>("/api/auth/login", {
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
otp: otp
|
otp: otp,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const auth_signup = (
|
export const auth_signup = (
|
||||||
username: string,
|
username: string,
|
||||||
password: string
|
password: string
|
||||||
): Promise<Responses.Auth.SignupResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.Auth.SignupResponse | Responses.ErrorResponse> =>
|
||||||
post<Requests.Auth.SignUpRequest>('/api/auth/signup', {
|
post<Requests.Auth.SignUpRequest>("/api/auth/signup", {
|
||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const refresh_token = (
|
export const refresh_token = (
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Auth.RefreshResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.Auth.RefreshResponse | Responses.ErrorResponse> =>
|
||||||
post_token('/api/auth/refresh', {}, token);
|
post_token("/api/auth/refresh", {}, token);
|
||||||
|
|
||||||
export const change_password = (
|
export const change_password = (
|
||||||
oldPw: string,
|
oldPw: string,
|
||||||
newPw: string,
|
newPw: string,
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Auth.ChangePasswordResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.Auth.ChangePasswordResponse | Responses.ErrorResponse> =>
|
||||||
post_token<Requests.Auth.ChangePasswordRequest>(
|
post_token<Requests.Auth.ChangePasswordRequest>(
|
||||||
'/api/auth/change_password',
|
"/api/auth/change_password",
|
||||||
{
|
{
|
||||||
oldPassword: oldPw,
|
oldPassword: oldPw,
|
||||||
newPassword: newPw
|
newPassword: newPw,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
export const logout_all = (
|
export const logout_all = (
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Auth.LogoutAllResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.Auth.LogoutAllResponse | Responses.ErrorResponse> =>
|
||||||
post_token('/api/auth/logout_all', {}, token);
|
post_token("/api/auth/logout_all", {}, token);
|
||||||
|
|
||||||
export function tfa_setup(
|
export function tfa_setup(
|
||||||
mail: false,
|
mail: false,
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Auth.RequestTotpTfaResponse | Responses.ErrorResponse>;
|
): Promise<Responses.Auth.RequestTotpTfaResponse | Responses.ErrorResponse>;
|
||||||
export function tfa_setup(
|
export function tfa_setup(
|
||||||
mail: true,
|
mail: true,
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Auth.RequestEmailTfaResponse | Responses.ErrorResponse>;
|
): Promise<Responses.Auth.RequestEmailTfaResponse | Responses.ErrorResponse>;
|
||||||
export function tfa_setup(
|
export function tfa_setup(
|
||||||
mail: boolean,
|
mail: boolean,
|
||||||
token: string
|
token: string
|
||||||
): Promise<
|
): Promise<
|
||||||
| Responses.Auth.RequestEmailTfaResponse
|
| Responses.Auth.RequestEmailTfaResponse
|
||||||
| Responses.Auth.RequestTotpTfaResponse
|
| Responses.Auth.RequestTotpTfaResponse
|
||||||
| Responses.ErrorResponse
|
| Responses.ErrorResponse
|
||||||
> {
|
> {
|
||||||
return post_token<Requests.Auth.TfaSetup>(
|
return post_token<Requests.Auth.TfaSetup>(
|
||||||
'/api/auth/2fa/setup',
|
"/api/auth/2fa/setup",
|
||||||
{
|
{
|
||||||
mail
|
mail,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tfa_complete = (
|
export const tfa_complete = (
|
||||||
mail: boolean,
|
mail: boolean,
|
||||||
code: string,
|
code: string,
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Auth.TfaCompletedResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.Auth.TfaCompletedResponse | Responses.ErrorResponse> =>
|
||||||
post_token<Requests.Auth.TfaComplete>(
|
post_token<Requests.Auth.TfaComplete>(
|
||||||
'/api/auth/2fa/complete',
|
"/api/auth/2fa/complete",
|
||||||
{
|
{
|
||||||
mail,
|
mail,
|
||||||
code
|
code,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
export const tfa_disable = (
|
export const tfa_disable = (
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Auth.RemoveTfaResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.Auth.RemoveTfaResponse | Responses.ErrorResponse> =>
|
||||||
post_token('/api/auth/2fa/disable', {}, token);
|
post_token("/api/auth/2fa/disable", {}, token);
|
||||||
|
@ -1,62 +1,62 @@
|
|||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
import { Requests, Responses, UserRole } from '../../../dto';
|
import { Requests, Responses, UserRole } from "../dto";
|
||||||
export { Requests, Responses, UserRole };
|
export { Requests, Responses, UserRole };
|
||||||
|
|
||||||
export const post = <T extends Requests.BaseRequest>(url: string, data: T) =>
|
export const post = <T extends Requests.BaseRequest>(url: string, data: T) =>
|
||||||
axios
|
axios
|
||||||
.post(url, data, {
|
.post(url, data, {
|
||||||
headers: { 'Content-type': 'application/json' }
|
headers: { "Content-type": "application/json" },
|
||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch((err) => err.response.data);
|
.catch((err) => err.response.data);
|
||||||
|
|
||||||
export const post_token = <T extends Requests.BaseRequest>(
|
export const post_token = <T extends Requests.BaseRequest>(
|
||||||
url: string,
|
url: string,
|
||||||
data: T,
|
data: T,
|
||||||
token: string
|
token: string
|
||||||
) =>
|
) =>
|
||||||
axios
|
axios
|
||||||
.post(url, data, {
|
.post(url, data, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'Bearer ' + token,
|
Authorization: "Bearer " + token,
|
||||||
'Content-type': 'application/json'
|
"Content-type": "application/json",
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch((err) => err.response.data);
|
.catch((err) => err.response.data);
|
||||||
|
|
||||||
export const post_token_form = (
|
export const post_token_form = (
|
||||||
url: string,
|
url: string,
|
||||||
data: FormData,
|
data: FormData,
|
||||||
token: string,
|
token: string,
|
||||||
onProgress: (progressEvent: ProgressEvent) => void
|
onProgress: (progressEvent: ProgressEvent) => void
|
||||||
) =>
|
) =>
|
||||||
axios
|
axios
|
||||||
.post(url, data, {
|
.post(url, data, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'Bearer ' + token,
|
Authorization: "Bearer " + token,
|
||||||
'Content-type': 'multipart/form-data'
|
"Content-type": "multipart/form-data",
|
||||||
},
|
},
|
||||||
onUploadProgress: onProgress
|
onUploadProgress: onProgress,
|
||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch((err) => err.response.data);
|
.catch((err) => err.response.data);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
export const get = (url: string) =>
|
export const get = (url: string) =>
|
||||||
axios
|
axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch((err) => err.response.data);
|
.catch((err) => err.response.data);
|
||||||
|
|
||||||
export const get_token = (url: string, token: string) =>
|
export const get_token = (url: string, token: string) =>
|
||||||
axios
|
axios
|
||||||
.get(url, {
|
.get(url, {
|
||||||
headers: { Authorization: 'Bearer ' + token }
|
headers: { Authorization: "Bearer " + token },
|
||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch((err) => err.response.data);
|
.catch((err) => err.response.data);
|
||||||
|
|
||||||
export const isErrorResponse = (
|
export const isErrorResponse = (
|
||||||
res: Responses.BaseResponse
|
res: Responses.BaseResponse
|
||||||
): res is Responses.ErrorResponse => res.statusCode != 200;
|
): res is Responses.ErrorResponse => res.statusCode != 200;
|
||||||
|
@ -1,95 +1,84 @@
|
|||||||
import {
|
import {
|
||||||
Responses,
|
Responses,
|
||||||
Requests,
|
Requests,
|
||||||
get_token,
|
get_token,
|
||||||
post_token,
|
post_token,
|
||||||
post_token_form,
|
post_token_form,
|
||||||
isErrorResponse
|
isErrorResponse,
|
||||||
} from './base';
|
} from "./base";
|
||||||
|
|
||||||
export const get_root = (
|
export const get_root = (
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.FS.GetRootResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.FS.GetRootResponse | Responses.ErrorResponse> =>
|
||||||
get_token('/api/fs/root', token);
|
get_token("/api/fs/root", token);
|
||||||
|
|
||||||
export const get_node = (
|
export const get_node = (
|
||||||
token: string,
|
token: string,
|
||||||
node: number
|
node: number
|
||||||
): Promise<Responses.FS.GetNodeResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.FS.GetNodeResponse | Responses.ErrorResponse> =>
|
||||||
get_token(`/api/fs/node/${node}`, token);
|
get_token(`/api/fs/node/${node}`, token);
|
||||||
|
|
||||||
export const get_path = (
|
export const get_path = (
|
||||||
token: string,
|
token: string,
|
||||||
node: number
|
node: number
|
||||||
): Promise<Responses.FS.GetPathResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.FS.GetPathResponse | Responses.ErrorResponse> =>
|
||||||
get_token(`/api/fs/path/${node}`, token);
|
get_token(`/api/fs/path/${node}`, token);
|
||||||
|
|
||||||
export const create_folder = (
|
export const create_folder = (
|
||||||
token: string,
|
token: string,
|
||||||
parent: number,
|
parent: number,
|
||||||
name: string
|
name: string
|
||||||
): Promise<Responses.FS.CreateFolderResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.FS.CreateFolderResponse | Responses.ErrorResponse> =>
|
||||||
post_token<Requests.FS.CreateFolderRequest>(
|
post_token<Requests.FS.CreateFolderRequest>(
|
||||||
'/api/fs/createFolder',
|
"/api/fs/createFolder",
|
||||||
{
|
{
|
||||||
parent: parent,
|
parent: parent,
|
||||||
name: name
|
name: name,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
export const create_file = (
|
export const create_file = (
|
||||||
token: string,
|
token: string,
|
||||||
parent: number,
|
parent: number,
|
||||||
name: string
|
name: string
|
||||||
): Promise<Responses.FS.CreateFileResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.FS.CreateFileResponse | Responses.ErrorResponse> =>
|
||||||
post_token<Requests.FS.CreateFileRequest>(
|
post_token<Requests.FS.CreateFileRequest>(
|
||||||
'/api/fs/createFile',
|
"/api/fs/createFile",
|
||||||
{
|
{
|
||||||
parent: parent,
|
parent: parent,
|
||||||
name: name
|
name: name,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
export const delete_node = (
|
export const delete_node = (
|
||||||
token: string,
|
token: string,
|
||||||
node: number
|
node: number
|
||||||
): Promise<Responses.FS.DeleteResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.FS.DeleteResponse | Responses.ErrorResponse> =>
|
||||||
post_token<Requests.FS.DeleteRequest>(
|
post_token(`/api/fs/delete/${node}`, {}, token);
|
||||||
'/api/fs/delete',
|
|
||||||
{
|
|
||||||
node: node
|
|
||||||
},
|
|
||||||
token
|
|
||||||
);
|
|
||||||
|
|
||||||
export const upload_file = async (
|
export const upload_file = async (
|
||||||
token: string,
|
token: string,
|
||||||
parent: number,
|
parent: number,
|
||||||
file: File,
|
file: File,
|
||||||
onProgress: (progressEvent: ProgressEvent) => void
|
onProgress: (progressEvent: ProgressEvent) => void
|
||||||
): Promise<Responses.FS.UploadFileResponse | Responses.ErrorResponse> => {
|
): Promise<Responses.FS.UploadFileResponse | Responses.ErrorResponse> => {
|
||||||
const node = await create_file(token, parent, file.name);
|
const node = await create_file(token, parent, file.name);
|
||||||
if (isErrorResponse(node)) return node;
|
if (isErrorResponse(node)) return node;
|
||||||
|
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.set('file', file);
|
form.set("file", file);
|
||||||
return post_token_form(
|
return post_token_form(`/api/fs/upload/${node.id}`, form, token, onProgress);
|
||||||
`/api/fs/upload/${node.id}`,
|
|
||||||
form,
|
|
||||||
token,
|
|
||||||
onProgress
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function download_file(token: string, id: number) {
|
export function download_file(token: string, id: number) {
|
||||||
const form = document.createElement('form');
|
const form = document.createElement("form");
|
||||||
form.method = 'post';
|
form.method = "post";
|
||||||
form.target = '_blank';
|
form.target = "_blank";
|
||||||
form.action = '/api/fs/download';
|
form.action = "/api/fs/download";
|
||||||
form.innerHTML = `<input type="hidden" name="jwtToken" value="${token}"><input type="hidden" name="id" value="${id}">`;
|
form.innerHTML = `<input type="hidden" name="jwtToken" value="${token}"><input type="hidden" name="id" value="${id}">`;
|
||||||
document.body.appendChild(form);
|
document.body.appendChild(form);
|
||||||
form.submit();
|
form.submit();
|
||||||
document.body.removeChild(form);
|
document.body.removeChild(form);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export { Requests, Responses, UserRole, isErrorResponse } from './base';
|
export { Requests, Responses, UserRole, isErrorResponse } from "./base";
|
||||||
export * as Auth from './auth';
|
export * as Auth from "./auth";
|
||||||
export * as FS from './fs';
|
export * as FS from "./fs";
|
||||||
export * as User from './user';
|
export * as User from "./user";
|
||||||
export * as Admin from './admin';
|
export * as Admin from "./admin";
|
||||||
export * from './util';
|
export * from "./util";
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { Responses, get_token, post_token } from '@/api/base';
|
import { Responses, get_token, post_token } from "@/api/base";
|
||||||
|
|
||||||
export const get_user_info = (
|
export const get_user_info = (
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.User.UserInfoResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.User.UserInfoResponse | Responses.ErrorResponse> =>
|
||||||
get_token('/api/user/info', token);
|
get_token("/api/user/info", token);
|
||||||
|
|
||||||
export const delete_user = (
|
export const delete_user = (
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.User.DeleteUserResponse | Responses.ErrorResponse> =>
|
): Promise<Responses.User.DeleteUserResponse | Responses.ErrorResponse> =>
|
||||||
post_token('/api/user/delete', {}, token);
|
post_token("/api/user/delete", {}, token);
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
import jwtDecode, { JwtPayload } from 'jwt-decode';
|
import jwtDecode, { JwtPayload } from "jwt-decode";
|
||||||
import { Ref, UnwrapRef } from 'vue';
|
import { Ref, UnwrapRef } from "vue";
|
||||||
import { isErrorResponse } from './base';
|
import { isErrorResponse } from "./base";
|
||||||
import { refresh_token } from './auth';
|
import { refresh_token } from "./auth";
|
||||||
|
|
||||||
export async function check_token(
|
export async function check_token(
|
||||||
token: TokenInjectType
|
token: TokenInjectType
|
||||||
): Promise<string | void> {
|
): Promise<string | void> {
|
||||||
if (!token.jwt.value) return token.logout();
|
if (!token.jwt.value) return token.logout();
|
||||||
const payload = jwtDecode<JwtPayload>(token.jwt.value);
|
const payload = jwtDecode<JwtPayload>(token.jwt.value);
|
||||||
if (!payload) return token.logout();
|
if (!payload) return token.logout();
|
||||||
// Expires in more than 60 Minute
|
// Expires in more than 60 Minute
|
||||||
if (payload.exp && payload.exp > Math.floor(Date.now() / 1000 + 60 * 60))
|
if (payload.exp && payload.exp > Math.floor(Date.now() / 1000 + 60 * 60))
|
||||||
return token.jwt.value;
|
return token.jwt.value;
|
||||||
const new_token = await refresh_token(token.jwt.value);
|
const new_token = await refresh_token(token.jwt.value);
|
||||||
if (isErrorResponse(new_token)) return token.logout();
|
if (isErrorResponse(new_token)) return token.logout();
|
||||||
token.setToken(new_token.jwt);
|
token.setToken(new_token.jwt);
|
||||||
return new_token.jwt;
|
return new_token.jwt;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TokenInjectType = {
|
export type TokenInjectType = {
|
||||||
jwt: Ref<UnwrapRef<string | null>>;
|
jwt: Ref<UnwrapRef<string | null>>;
|
||||||
setToken: (token: string) => void;
|
setToken: (token: string) => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
};
|
};
|
||||||
|
@ -1,40 +1,40 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineEmits, defineProps, inject } from 'vue';
|
import { defineEmits, defineProps, inject } from "vue";
|
||||||
import { check_token, FS, Responses, TokenInjectType } from '@/api';
|
import { check_token, FS, Responses, TokenInjectType } from "@/api";
|
||||||
|
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
node: Responses.FS.GetNodeResponse;
|
node: Responses.FS.GetNodeResponse;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'reloadNode'): void;
|
(e: "reloadNode"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
await FS.delete_node(token, props.node.id);
|
await FS.delete_node(token, props.node.id);
|
||||||
emit('reloadNode');
|
emit("reloadNode");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function download() {
|
async function download() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
FS.download_file(token, props.node.id);
|
FS.download_file(token, props.node.id);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="'/fs/' + props.node.id">{{ node.name }}</router-link>
|
<router-link :to="'/fs/' + props.node.id">{{ node.name }}</router-link>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" @click="download()" v-if="props.node.isFile">Download</a>
|
<a href="#" @click="download()" v-if="props.node.isFile">Download</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" @click="del()" v-if="props.node.name !== '..'">delete</a>
|
<a href="#" @click="del()" v-if="props.node.name !== '..'">delete</a>
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,108 +1,101 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineEmits, defineProps, inject, reactive, ref, watch } from 'vue';
|
import { defineEmits, defineProps, inject, reactive, ref, watch } from "vue";
|
||||||
import { FS, Responses, check_token, TokenInjectType } from '@/api';
|
import { FS, Responses, check_token, TokenInjectType } from "@/api";
|
||||||
import DirEntry from '@/components/FSView/DirEntry.vue';
|
import DirEntry from "@/components/FSView/DirEntry.vue";
|
||||||
import UploadFileDialog from '@/components/UploadDialog/UploadFileDialog.vue';
|
import UploadFileDialog from "@/components/UploadDialog/UploadFileDialog.vue";
|
||||||
import { NModal } from 'naive-ui';
|
import { NModal } from "naive-ui";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
node: Responses.FS.GetNodeResponse;
|
node: Responses.FS.GetNodeResponse;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'reloadNode'): void;
|
(e: "reloadNode"): void;
|
||||||
(e: 'gotoRoot'): void;
|
(e: "gotoRoot"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const fileInput = ref<HTMLInputElement>();
|
const fileInput = ref<HTMLInputElement>();
|
||||||
const uploadDialog = ref();
|
const uploadDialog = ref();
|
||||||
const uploadDialogShow = ref(false);
|
const uploadDialogShow = ref(false);
|
||||||
|
|
||||||
const new_folder_name = ref('');
|
const new_folder_name = ref("");
|
||||||
const files = ref<File[]>([]);
|
const files = ref<File[]>([]);
|
||||||
const nodes = ref<Responses.FS.GetNodeResponse[]>([]);
|
const nodes = ref<Responses.FS.GetNodeResponse[]>([]);
|
||||||
const hasParent = ref(false);
|
const hasParent = ref(false);
|
||||||
const parentNode = reactive<Responses.FS.GetNodeResponse>({
|
const parentNode = reactive<Responses.FS.GetNodeResponse>({
|
||||||
id: 0,
|
id: 0,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
isFile: false,
|
isFile: false,
|
||||||
parent: null,
|
parent: null,
|
||||||
name: '..'
|
name: "..",
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.node,
|
() => props.node,
|
||||||
async (to) => {
|
async (to) => {
|
||||||
parentNode.id = to.parent ?? 0;
|
parentNode.id = to.parent ?? 0;
|
||||||
hasParent.value = to.parent != null;
|
hasParent.value = to.parent != null;
|
||||||
nodes.value = [];
|
nodes.value = [];
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
to.children?.map(async (child) => {
|
to.children?.map(async (child) => {
|
||||||
nodes.value.push(
|
nodes.value.push(
|
||||||
(await FS.get_node(
|
(await FS.get_node(token, child)) as Responses.FS.GetNodeResponse
|
||||||
token,
|
);
|
||||||
child
|
}) ?? []
|
||||||
)) as Responses.FS.GetNodeResponse
|
);
|
||||||
);
|
},
|
||||||
}) ?? []
|
{ immediate: true }
|
||||||
);
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
);
|
||||||
|
|
||||||
async function newFolder() {
|
async function newFolder() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
await FS.create_folder(token, props.node.id, new_folder_name.value);
|
await FS.create_folder(token, props.node.id, new_folder_name.value);
|
||||||
emit('reloadNode');
|
emit("reloadNode");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadFiles() {
|
async function uploadFiles() {
|
||||||
files.value = Array.from(fileInput.value?.files ?? []);
|
files.value = Array.from(fileInput.value?.files ?? []);
|
||||||
if (files.value.length == 0) return;
|
if (files.value.length == 0) return;
|
||||||
uploadDialogShow.value = true;
|
uploadDialogShow.value = true;
|
||||||
}
|
}
|
||||||
async function uploadFilesDialogOpen() {
|
async function uploadFilesDialogOpen() {
|
||||||
await uploadDialog.value?.startUpload(props.node.id);
|
await uploadDialog.value?.startUpload(props.node.id);
|
||||||
uploadDialogShow.value = false;
|
uploadDialogShow.value = false;
|
||||||
if (fileInput.value) fileInput.value.value = '';
|
if (fileInput.value) fileInput.value.value = "";
|
||||||
emit('reloadNode');
|
emit("reloadNode");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input type="text" placeholder="Folder name" v-model="new_folder_name" />
|
||||||
type="text"
|
<a href="#" @click="newFolder()">create folder</a>
|
||||||
placeholder="Folder name"
|
</div>
|
||||||
v-model="new_folder_name"
|
<div>
|
||||||
/>
|
<input type="file" ref="fileInput" multiple />
|
||||||
<a href="#" @click="newFolder()">create folder</a>
|
<a href="#" @click="uploadFiles()">upload files</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<table>
|
||||||
<input type="file" ref="fileInput" multiple />
|
<tr v-if="hasParent">
|
||||||
<a href="#" @click="uploadFiles()">upload files</a>
|
<DirEntry :node="parentNode" @reloadNode="emit('reloadNode')" />
|
||||||
</div>
|
</tr>
|
||||||
<table>
|
<tr v-for="n in nodes" :key="n.id">
|
||||||
<tr v-if="hasParent">
|
<DirEntry :node="n" @reloadNode="emit('reloadNode')" />
|
||||||
<DirEntry :node="parentNode" @reloadNode="emit('reloadNode')" />
|
</tr>
|
||||||
</tr>
|
</table>
|
||||||
<tr v-for="n in nodes" :key="n.id">
|
<n-modal
|
||||||
<DirEntry :node="n" @reloadNode="emit('reloadNode')" />
|
v-model:show="uploadDialogShow"
|
||||||
</tr>
|
:close-on-esc="false"
|
||||||
</table>
|
:mask-closable="false"
|
||||||
<n-modal
|
:on-after-enter="uploadFilesDialogOpen"
|
||||||
v-model:show="uploadDialogShow"
|
>
|
||||||
:close-on-esc="false"
|
<UploadFileDialog ref="uploadDialog" :files="files" />
|
||||||
:mask-closable="false"
|
</n-modal>
|
||||||
:on-after-enter="uploadFilesDialogOpen"
|
|
||||||
>
|
|
||||||
<UploadFileDialog ref="uploadDialog" :files="files" />
|
|
||||||
</n-modal>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,38 +1,38 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, inject } from 'vue';
|
import { defineProps, inject } from "vue";
|
||||||
import { check_token, FS, Responses, TokenInjectType } from '@/api';
|
import { check_token, FS, Responses, TokenInjectType } from "@/api";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
node: Responses.FS.GetNodeResponse;
|
node: Responses.FS.GetNodeResponse;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
await FS.delete_node(token, props.node.id);
|
await FS.delete_node(token, props.node.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function download() {
|
async function download() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
FS.download_file(token, props.node.id);
|
FS.download_file(token, props.node.id);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<router-link :to="'/fs/' + props.node.parent ?? 0">..</router-link>
|
<router-link :to="'/fs/' + props.node.parent ?? 0">..</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href="#" @click="download()" v-if="props.node.isFile">Download</a>
|
<a href="#" @click="download()" v-if="props.node.isFile">Download</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<router-link :to="'/fs/' + props.node.parent ?? 0" @click="del()">
|
<router-link :to="'/fs/' + props.node.parent ?? 0" @click="del()">
|
||||||
delete
|
delete
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,156 +1,140 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="hello">
|
<div class="hello">
|
||||||
<h1>{{ msg }}</h1>
|
<h1>{{ msg }}</h1>
|
||||||
<p>
|
<p>
|
||||||
For a guide and recipes on how to configure / customize this
|
For a guide and recipes on how to configure / customize this project,<br />
|
||||||
project,<br />
|
check out the
|
||||||
check out the
|
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
|
||||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
|
>vue-cli documentation</a
|
||||||
>vue-cli documentation</a
|
>.
|
||||||
>.
|
</p>
|
||||||
</p>
|
<h3>Installed CLI Plugins</h3>
|
||||||
<h3>Installed CLI Plugins</h3>
|
<ul>
|
||||||
<ul>
|
<li>
|
||||||
<li>
|
<a
|
||||||
<a
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
|
||||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
|
target="_blank"
|
||||||
target="_blank"
|
rel="noopener"
|
||||||
rel="noopener"
|
>babel</a
|
||||||
>babel</a
|
>
|
||||||
>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<a
|
||||||
<a
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
|
||||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
|
target="_blank"
|
||||||
target="_blank"
|
rel="noopener"
|
||||||
rel="noopener"
|
>router</a
|
||||||
>router</a
|
>
|
||||||
>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<a
|
||||||
<a
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
|
||||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
|
target="_blank"
|
||||||
target="_blank"
|
rel="noopener"
|
||||||
rel="noopener"
|
>vuex</a
|
||||||
>vuex</a
|
>
|
||||||
>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<a
|
||||||
<a
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
|
||||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
|
target="_blank"
|
||||||
target="_blank"
|
rel="noopener"
|
||||||
rel="noopener"
|
>eslint</a
|
||||||
>eslint</a
|
>
|
||||||
>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<a
|
||||||
<a
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
|
||||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
|
target="_blank"
|
||||||
target="_blank"
|
rel="noopener"
|
||||||
rel="noopener"
|
>typescript</a
|
||||||
>typescript</a
|
>
|
||||||
>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
</ul>
|
<h3>Essential Links</h3>
|
||||||
<h3>Essential Links</h3>
|
<ul>
|
||||||
<ul>
|
<li>
|
||||||
<li>
|
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
|
||||||
<a href="https://vuejs.org" target="_blank" rel="noopener"
|
</li>
|
||||||
>Core Docs</a
|
<li>
|
||||||
>
|
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
|
||||||
</li>
|
>Forum</a
|
||||||
<li>
|
>
|
||||||
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
|
</li>
|
||||||
>Forum</a
|
<li>
|
||||||
>
|
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
|
||||||
</li>
|
>Community Chat</a
|
||||||
<li>
|
>
|
||||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
|
</li>
|
||||||
>Community Chat</a
|
<li>
|
||||||
>
|
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
|
||||||
</li>
|
>Twitter</a
|
||||||
<li>
|
>
|
||||||
<a
|
</li>
|
||||||
href="https://twitter.com/vuejs"
|
<li>
|
||||||
target="_blank"
|
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
|
||||||
rel="noopener"
|
</li>
|
||||||
>Twitter</a
|
</ul>
|
||||||
>
|
<h3>Ecosystem</h3>
|
||||||
</li>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener"
|
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
|
||||||
>News</a
|
>vue-router</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li>
|
||||||
<h3>Ecosystem</h3>
|
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
|
||||||
<ul>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://router.vuejs.org"
|
href="https://github.com/vuejs/vue-devtools#vue-devtools"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
>vue-router</a
|
>vue-devtools</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener"
|
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
|
||||||
>vuex</a
|
>vue-loader</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/vuejs/vue-devtools#vue-devtools"
|
href="https://github.com/vuejs/awesome-vue"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
>vue-devtools</a
|
>awesome-vue</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
</ul>
|
||||||
<a
|
</div>
|
||||||
href="https://vue-loader.vuejs.org"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>vue-loader</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://github.com/vuejs/awesome-vue"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>awesome-vue</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'HelloWorld',
|
name: "HelloWorld",
|
||||||
props: {
|
props: {
|
||||||
msg: String
|
msg: String,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
h3 {
|
h3 {
|
||||||
margin: 40px 0 0;
|
margin: 40px 0 0;
|
||||||
}
|
}
|
||||||
ul {
|
ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #42b983;
|
color: #42b983;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,51 +1,51 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, defineExpose, ref } from 'vue';
|
import { defineProps, defineExpose, ref } from "vue";
|
||||||
import { isErrorResponse, FS } from '@/api';
|
import { isErrorResponse, FS } from "@/api";
|
||||||
import { NProgress } from 'naive-ui';
|
import { NProgress } from "naive-ui";
|
||||||
import filesize from 'filesize';
|
import filesize from "filesize";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
file: File;
|
file: File;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const progress = ref(0);
|
const progress = ref(0);
|
||||||
const percentage = ref(0);
|
const percentage = ref(0);
|
||||||
const err = ref('');
|
const err = ref("");
|
||||||
const status = ref('info');
|
const status = ref("info");
|
||||||
|
|
||||||
async function startUpload(parent: number, token: string) {
|
async function startUpload(parent: number, token: string) {
|
||||||
const resp = await FS.upload_file(token, parent, props.file, (e) => {
|
const resp = await FS.upload_file(token, parent, props.file, (e) => {
|
||||||
progress.value = e.loaded;
|
progress.value = e.loaded;
|
||||||
percentage.value = (e.loaded / e.total) * 100;
|
percentage.value = (e.loaded / e.total) * 100;
|
||||||
});
|
});
|
||||||
percentage.value = 100;
|
percentage.value = 100;
|
||||||
if (isErrorResponse(resp)) {
|
if (isErrorResponse(resp)) {
|
||||||
err.value = resp.message ?? 'Error';
|
err.value = resp.message ?? "Error";
|
||||||
status.value = 'error';
|
status.value = "error";
|
||||||
} else status.value = 'success';
|
} else status.value = "success";
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
startUpload
|
startUpload,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="percentage < 100">
|
<div v-if="percentage < 100">
|
||||||
{{ file.name }} - {{ filesize(progress) }} / {{ filesize(file.size) }} -
|
{{ file.name }} - {{ filesize(progress) }} / {{ filesize(file.size) }} -
|
||||||
{{ Math.floor(percentage * 1000) / 1000 }}%
|
{{ Math.floor(percentage * 1000) / 1000 }}%
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="err !== ''">{{ file.name }} - Error: {{ err }}</div>
|
<div v-else-if="err !== ''">{{ file.name }} - Error: {{ err }}</div>
|
||||||
<div v-else>{{ file.name }} - Completed</div>
|
<div v-else>{{ file.name }} - Completed</div>
|
||||||
<n-progress
|
<n-progress
|
||||||
type="line"
|
type="line"
|
||||||
:percentage="percentage"
|
:percentage="percentage"
|
||||||
:height="20"
|
:height="20"
|
||||||
:status="status"
|
:status="status"
|
||||||
border-radius="10px 0"
|
border-radius="10px 0"
|
||||||
fill-border-radius="10px 0"
|
fill-border-radius="10px 0"
|
||||||
:show-indicator="false"
|
:show-indicator="false"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, defineExpose, ref, inject } from 'vue';
|
import { defineProps, defineExpose, ref, inject } from "vue";
|
||||||
import { check_token, TokenInjectType } from '@/api';
|
import { check_token, TokenInjectType } from "@/api";
|
||||||
import UploadEntry from '@/components/UploadDialog/UploadEntry.vue';
|
import UploadEntry from "@/components/UploadDialog/UploadEntry.vue";
|
||||||
import { NCard } from 'naive-ui';
|
import { NCard } from "naive-ui";
|
||||||
|
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
|
|
||||||
const entries = ref<typeof UploadEntry[]>([]);
|
const entries = ref<typeof UploadEntry[]>([]);
|
||||||
const done = ref(false);
|
const done = ref(false);
|
||||||
@ -12,37 +12,32 @@ let canCloseResolve = null;
|
|||||||
const canClose = new Promise((r) => (canCloseResolve = r));
|
const canClose = new Promise((r) => (canCloseResolve = r));
|
||||||
|
|
||||||
async function startUpload(parent: number) {
|
async function startUpload(parent: number) {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
entries.value.map((entry) => entry.startUpload(parent, token))
|
entries.value.map((entry) => entry.startUpload(parent, token))
|
||||||
);
|
);
|
||||||
done.value = true;
|
done.value = true;
|
||||||
await canClose;
|
await canClose;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
startUpload
|
startUpload,
|
||||||
});
|
});
|
||||||
defineProps<{
|
defineProps<{
|
||||||
files: File[];
|
files: File[];
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-card title="Upload Files">
|
<n-card title="Upload Files">
|
||||||
<div>
|
<div>
|
||||||
<UploadEntry
|
<UploadEntry v-for="f in files" :key="f.name" ref="entries" :file="f" />
|
||||||
v-for="f in files"
|
</div>
|
||||||
:key="f.name"
|
<div>
|
||||||
ref="entries"
|
<button v-if="done" @click="canCloseResolve()">Close</button>
|
||||||
:file="f"
|
</div>
|
||||||
/>
|
</n-card>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button v-if="done" @click="canCloseResolve()">Close</button>
|
|
||||||
</div>
|
|
||||||
</n-card>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
8
frontend/src/dto/index.ts
Normal file
8
frontend/src/dto/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export * as Requests from "./requests";
|
||||||
|
export * as Responses from "./responses";
|
||||||
|
export {
|
||||||
|
UserRole,
|
||||||
|
validateSync,
|
||||||
|
validateAsync,
|
||||||
|
validateAsyncInline,
|
||||||
|
} from "./utils";
|
17
frontend/src/dto/requests/admin.ts
Normal file
17
frontend/src/dto/requests/admin.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { BaseRequest } from "./base";
|
||||||
|
import { IsEnum, IsNumber } from "class-validator";
|
||||||
|
import { UserRole } from "../utils";
|
||||||
|
|
||||||
|
export class AdminRequest extends BaseRequest {
|
||||||
|
@IsNumber()
|
||||||
|
user: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SetUserRole extends AdminRequest {
|
||||||
|
@IsEnum(UserRole)
|
||||||
|
role: UserRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LogoutAll extends AdminRequest {}
|
||||||
|
export class DeleteUser extends AdminRequest {}
|
||||||
|
export class DisableTfa extends AdminRequest {}
|
50
frontend/src/dto/requests/auth.ts
Normal file
50
frontend/src/dto/requests/auth.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { BaseRequest } from "./base";
|
||||||
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsEmail,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
} from "class-validator";
|
||||||
|
|
||||||
|
export class SignUpRequest extends BaseRequest {
|
||||||
|
@IsEmail()
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LoginRequest extends SignUpRequest {
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
otp?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TfaSetup extends BaseRequest {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsBoolean()
|
||||||
|
mail: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TfaComplete extends BaseRequest {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsBoolean()
|
||||||
|
mail: boolean;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ChangePasswordRequest extends BaseRequest {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
oldPassword: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
newPassword: string;
|
||||||
|
}
|
14
frontend/src/dto/requests/fs.ts
Normal file
14
frontend/src/dto/requests/fs.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { BaseRequest } from "./base";
|
||||||
|
import { IsInt, IsNotEmpty, IsString, Min } from "class-validator";
|
||||||
|
|
||||||
|
export class CreateFolderRequest extends BaseRequest {
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
parent: number;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateFileRequest extends CreateFolderRequest {}
|
4
frontend/src/dto/requests/index.ts
Normal file
4
frontend/src/dto/requests/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./base";
|
||||||
|
export * as Auth from "./auth";
|
||||||
|
export * as FS from "./fs";
|
||||||
|
export * as Admin from "./admin";
|
61
frontend/src/dto/responses/admin.ts
Normal file
61
frontend/src/dto/responses/admin.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { SuccessResponse } from "./base";
|
||||||
|
import {
|
||||||
|
IsArray,
|
||||||
|
IsBoolean,
|
||||||
|
IsEnum,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsNumber,
|
||||||
|
IsString,
|
||||||
|
ValidateNested,
|
||||||
|
} from "class-validator";
|
||||||
|
import { UserRole, ValidateConstructor } from "../utils";
|
||||||
|
|
||||||
|
@ValidateConstructor
|
||||||
|
export class GetUsersEntry {
|
||||||
|
constructor(
|
||||||
|
id: number,
|
||||||
|
gitlab: boolean,
|
||||||
|
name: string,
|
||||||
|
role: UserRole,
|
||||||
|
tfaEnabled: boolean
|
||||||
|
) {
|
||||||
|
this.id = id;
|
||||||
|
this.gitlab = gitlab;
|
||||||
|
this.name = name;
|
||||||
|
this.role = role;
|
||||||
|
this.tfaEnabled = tfaEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
gitlab: boolean;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsEnum(UserRole)
|
||||||
|
role: UserRole;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
tfaEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ValidateConstructor
|
||||||
|
export class GetUsers extends SuccessResponse {
|
||||||
|
constructor(users: GetUsersEntry[]) {
|
||||||
|
super();
|
||||||
|
this.users = users;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
users: GetUsersEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LogoutAllUser extends SuccessResponse {}
|
||||||
|
export class DeleteUser extends SuccessResponse {}
|
||||||
|
export class SetUserRole extends SuccessResponse {}
|
||||||
|
export class DisableTfa extends SuccessResponse {}
|
@ -1,33 +1,33 @@
|
|||||||
import { SuccessResponse } from './base';
|
import { SuccessResponse } from "./base";
|
||||||
import { IsBase32, IsJWT, IsNotEmpty } from 'class-validator';
|
import { IsBase32, IsJWT, IsNotEmpty } from "class-validator";
|
||||||
import { ValidateConstructor } from '../utils';
|
import { ValidateConstructor } from "../utils";
|
||||||
|
|
||||||
@ValidateConstructor
|
@ValidateConstructor
|
||||||
export class LoginResponse extends SuccessResponse {
|
export class LoginResponse extends SuccessResponse {
|
||||||
constructor(jwt: string) {
|
constructor(jwt: string) {
|
||||||
super();
|
super();
|
||||||
this.jwt = jwt;
|
this.jwt = jwt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsJWT()
|
@IsJWT()
|
||||||
jwt: string;
|
jwt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ValidateConstructor
|
@ValidateConstructor
|
||||||
export class RequestTotpTfaResponse extends SuccessResponse {
|
export class RequestTotpTfaResponse extends SuccessResponse {
|
||||||
constructor(qrCode: string, secret: string) {
|
constructor(qrCode: string, secret: string) {
|
||||||
super();
|
super();
|
||||||
this.qrCode = qrCode;
|
this.qrCode = qrCode;
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
qrCode: string;
|
qrCode: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsBase32()
|
@IsBase32()
|
||||||
secret: string;
|
secret: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TfaRequiredResponse extends SuccessResponse {}
|
export class TfaRequiredResponse extends SuccessResponse {}
|
25
frontend/src/dto/responses/base.ts
Normal file
25
frontend/src/dto/responses/base.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { IsNumber, Max, Min } from "class-validator";
|
||||||
|
|
||||||
|
export class BaseResponse {
|
||||||
|
constructor(statusCode: number) {
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@Min(100)
|
||||||
|
@Max(599)
|
||||||
|
statusCode: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SuccessResponse extends BaseResponse {
|
||||||
|
constructor() {
|
||||||
|
super(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
declare statusCode: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorResponse extends BaseResponse {
|
||||||
|
declare statusCode: 400 | 401 | 403;
|
||||||
|
message?: string;
|
||||||
|
}
|
89
frontend/src/dto/responses/fs.ts
Normal file
89
frontend/src/dto/responses/fs.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { SuccessResponse } from "./base";
|
||||||
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsInt,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
Min,
|
||||||
|
} from "class-validator";
|
||||||
|
import { ValidateConstructor } from "../utils";
|
||||||
|
|
||||||
|
@ValidateConstructor
|
||||||
|
export class GetRootResponse extends SuccessResponse {
|
||||||
|
constructor(rootId: number) {
|
||||||
|
super();
|
||||||
|
this.rootId = rootId;
|
||||||
|
}
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
rootId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetNodeResponse extends SuccessResponse {
|
||||||
|
constructor(
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
isFile: boolean,
|
||||||
|
parent: number | null
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.isFile = isFile;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
isFile: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
parent: number | null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt({ each: true })
|
||||||
|
@Min(1, { each: true })
|
||||||
|
children?: number[];
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(0)
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ValidateConstructor
|
||||||
|
export class GetPathResponse extends SuccessResponse {
|
||||||
|
constructor(path: string) {
|
||||||
|
super();
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ValidateConstructor
|
||||||
|
export class CreateFolderResponse extends SuccessResponse {
|
||||||
|
constructor(id: number) {
|
||||||
|
super();
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UploadFileResponse extends SuccessResponse {}
|
||||||
|
export class DeleteResponse extends SuccessResponse {}
|
||||||
|
export class CreateFileResponse extends CreateFolderResponse {}
|
5
frontend/src/dto/responses/index.ts
Normal file
5
frontend/src/dto/responses/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./base";
|
||||||
|
export * as Auth from "./auth";
|
||||||
|
export * as FS from "./fs";
|
||||||
|
export * as User from "./user";
|
||||||
|
export * as Admin from "./admin";
|
27
frontend/src/dto/responses/user.ts
Normal file
27
frontend/src/dto/responses/user.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { SuccessResponse } from "./base";
|
||||||
|
import { ValidateConstructor } from "../utils";
|
||||||
|
import { IsBoolean, IsNotEmpty, IsString } from "class-validator";
|
||||||
|
|
||||||
|
@ValidateConstructor
|
||||||
|
export class UserInfoResponse extends SuccessResponse {
|
||||||
|
constructor(name: string, gitlab: boolean, tfaEnabled: boolean) {
|
||||||
|
super();
|
||||||
|
this.name = name;
|
||||||
|
this.gitlab = gitlab;
|
||||||
|
this.tfaEnabled = tfaEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
gitlab: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
tfaEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DeleteUserResponse extends SuccessResponse {}
|
||||||
|
export class ChangePasswordResponse extends SuccessResponse {}
|
||||||
|
export class LogoutAllResponse extends SuccessResponse {}
|
41
frontend/src/dto/utils.ts
Normal file
41
frontend/src/dto/utils.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { validate, validateSync as _validateSync } from "class-validator";
|
||||||
|
|
||||||
|
export enum UserRole {
|
||||||
|
ADMIN = 2,
|
||||||
|
USER = 1,
|
||||||
|
DISABLED = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateSync<T extends object>(data: T): void {
|
||||||
|
const errors = _validateSync(data);
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error("Validation failed, errors: ", errors);
|
||||||
|
throw new Error("Validation failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateAsync<T extends object>(data: T): Promise<void> {
|
||||||
|
const errors = await validate(data);
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error("Validation failed, errors: ", errors);
|
||||||
|
throw new Error("Validation failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateAsyncInline<T extends object>(
|
||||||
|
data: T
|
||||||
|
): Promise<T> {
|
||||||
|
await validateAsync(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ValidateConstructor<T extends { new (...args: any[]): any }>(
|
||||||
|
constr: T
|
||||||
|
) {
|
||||||
|
return class extends constr {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
super(...args);
|
||||||
|
validateSync(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from "vue";
|
||||||
import router from './router';
|
import router from "./router";
|
||||||
import AppAsyncWrapper from './AppAsyncWrapper.vue';
|
import AppAsyncWrapper from "./AppAsyncWrapper.vue";
|
||||||
|
|
||||||
const app = createApp(AppAsyncWrapper);
|
const app = createApp(AppAsyncWrapper);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.config.unwrapInjectedRef = true;
|
app.config.unwrapInjectedRef = true;
|
||||||
app.mount('#app');
|
app.mount("#app");
|
||||||
|
@ -1,63 +1,63 @@
|
|||||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
|
||||||
import LoginView from '@/views/LoginView.vue';
|
import LoginView from "@/views/LoginView.vue";
|
||||||
import SignupView from '@/views/SignupView.vue';
|
import SignupView from "@/views/SignupView.vue";
|
||||||
import HomeView from '@/views/HomeView.vue';
|
import HomeView from "@/views/HomeView.vue";
|
||||||
import AboutView from '@/views/AboutView.vue';
|
import AboutView from "@/views/AboutView.vue";
|
||||||
import FSView from '@/views/FSView.vue';
|
import FSView from "@/views/FSView.vue";
|
||||||
import SetTokenView from '@/views/SetTokenView.vue';
|
import SetTokenView from "@/views/SetTokenView.vue";
|
||||||
import ProfileView from '@/views/ProfileView.vue';
|
import ProfileView from "@/views/ProfileView.vue";
|
||||||
import TFAView from '@/views/TFAView.vue';
|
import TFAView from "@/views/TFAView.vue";
|
||||||
import AdminView from '@/views/AdminView.vue';
|
import AdminView from "@/views/AdminView.vue";
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: "/",
|
||||||
name: 'home',
|
name: "home",
|
||||||
component: HomeView
|
component: HomeView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/profile',
|
path: "/profile",
|
||||||
name: 'profile',
|
name: "profile",
|
||||||
component: ProfileView
|
component: ProfileView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/profile/2fa-enable',
|
path: "/profile/2fa-enable",
|
||||||
name: '2fa',
|
name: "2fa",
|
||||||
component: TFAView
|
component: TFAView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/admin',
|
path: "/admin",
|
||||||
component: AdminView
|
component: AdminView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: "/about",
|
||||||
component: AboutView
|
component: AboutView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: "/login",
|
||||||
name: 'login',
|
name: "login",
|
||||||
component: LoginView
|
component: LoginView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/signup',
|
path: "/signup",
|
||||||
name: 'signup',
|
name: "signup",
|
||||||
component: SignupView
|
component: SignupView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/fs/:node_id',
|
path: "/fs/:node_id",
|
||||||
name: 'fs',
|
name: "fs",
|
||||||
component: FSView
|
component: FSView,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/set_token',
|
path: "/set_token",
|
||||||
component: SetTokenView
|
component: SetTokenView,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(process.env.BASE_URL),
|
history: createWebHistory(process.env.BASE_URL),
|
||||||
routes
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="about">
|
<div class="about">
|
||||||
<h1>This is an about page</h1>
|
<h1>This is an about page</h1>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,109 +1,109 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inject, onBeforeMount, ref } from 'vue';
|
import { inject, onBeforeMount, ref } from "vue";
|
||||||
import {
|
import {
|
||||||
Responses,
|
Responses,
|
||||||
check_token,
|
check_token,
|
||||||
TokenInjectType,
|
TokenInjectType,
|
||||||
Admin,
|
Admin,
|
||||||
isErrorResponse
|
isErrorResponse,
|
||||||
} from '@/api';
|
} from "@/api";
|
||||||
import { onBeforeRouteUpdate } from 'vue-router';
|
import { onBeforeRouteUpdate } from "vue-router";
|
||||||
import router from '@/router';
|
import router from "@/router";
|
||||||
|
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
|
|
||||||
const users = ref<Responses.Admin.GetUsersEntry[]>([]);
|
const users = ref<Responses.Admin.GetUsersEntry[]>([]);
|
||||||
|
|
||||||
onBeforeRouteUpdate(async () => {
|
onBeforeRouteUpdate(async () => {
|
||||||
await updatePanel();
|
await updatePanel();
|
||||||
});
|
});
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
await updatePanel();
|
await updatePanel();
|
||||||
});
|
});
|
||||||
async function updatePanel() {
|
async function updatePanel() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
|
||||||
const res = await Admin.get_users(token);
|
const res = await Admin.get_users(token);
|
||||||
if (isErrorResponse(res)) return router.replace({ path: '/' });
|
if (isErrorResponse(res)) return router.replace({ path: "/" });
|
||||||
users.value = res.users;
|
users.value = res.users;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setRole(user: number, roleStr: string) {
|
async function setRole(user: number, roleStr: string) {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
|
||||||
const res = await Admin.set_role(user, parseInt(roleStr, 10), token);
|
const res = await Admin.set_role(user, parseInt(roleStr, 10), token);
|
||||||
if (isErrorResponse(res)) console.error(res.message);
|
if (isErrorResponse(res)) console.error(res.message);
|
||||||
await updatePanel();
|
await updatePanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function disableTfa(user: number) {
|
async function disableTfa(user: number) {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
|
||||||
const res = await Admin.disable_tfa(user, token);
|
const res = await Admin.disable_tfa(user, token);
|
||||||
if (isErrorResponse(res)) console.error(res.message);
|
if (isErrorResponse(res)) console.error(res.message);
|
||||||
await updatePanel();
|
await updatePanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logoutUser(user: number) {
|
async function logoutUser(user: number) {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
|
||||||
const res = await Admin.logout(user, token);
|
const res = await Admin.logout(user, token);
|
||||||
if (isErrorResponse(res)) console.error(res.message);
|
if (isErrorResponse(res)) console.error(res.message);
|
||||||
await updatePanel();
|
await updatePanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteUser(user: number) {
|
async function deleteUser(user: number) {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
|
||||||
const res = await Admin.delete_user(user, token);
|
const res = await Admin.delete_user(user, token);
|
||||||
if (isErrorResponse(res)) console.error(res.message);
|
if (isErrorResponse(res)) console.error(res.message);
|
||||||
await updatePanel();
|
await updatePanel();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
<th>Tfa Status</th>
|
<th>Tfa Status</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="user in users" :key="user.id">
|
<tr v-for="user in users" :key="user.id">
|
||||||
<td>{{ user.name }}</td>
|
<td>{{ user.name }}</td>
|
||||||
<td>{{ user.gitlab ? 'Gitlab' : 'Password' }}</td>
|
<td>{{ user.gitlab ? "Gitlab" : "Password" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<select @change="setRole(user.id, $event.target.value)">
|
<select @change="setRole(user.id, $event.target.value)">
|
||||||
<option value="0" :selected="user.role === 0 ? true : null">
|
<option value="0" :selected="user.role === 0 ? true : null">
|
||||||
Disabled
|
Disabled
|
||||||
</option>
|
</option>
|
||||||
<option value="1" :selected="user.role === 1 ? true : null">
|
<option value="1" :selected="user.role === 1 ? true : null">
|
||||||
User
|
User
|
||||||
</option>
|
</option>
|
||||||
<option value="2" :selected="user.role === 2 ? true : null">
|
<option value="2" :selected="user.role === 2 ? true : null">
|
||||||
Admin
|
Admin
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="user.gitlab"></td>
|
<td v-if="user.gitlab"></td>
|
||||||
<td v-else>
|
<td v-else>
|
||||||
{{ user.tfaEnabled ? 'Enabled' : 'Disabled' }}
|
{{ user.tfaEnabled ? "Enabled" : "Disabled" }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button v-if="user.tfaEnabled" @click="disableTfa(user.id)">
|
<button v-if="user.tfaEnabled" @click="disableTfa(user.id)">
|
||||||
Disable Tfa
|
Disable Tfa
|
||||||
</button>
|
</button>
|
||||||
<button @click="logoutUser(user.id)">Logout all</button>
|
<button @click="logoutUser(user.id)">Logout all</button>
|
||||||
<button @click="deleteUser(user.id)">Delete</button>
|
<button @click="deleteUser(user.id)">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,70 +1,70 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
|
import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
|
||||||
import { inject, onBeforeMount, ref } from 'vue';
|
import { inject, onBeforeMount, ref } from "vue";
|
||||||
import {
|
import {
|
||||||
check_token,
|
check_token,
|
||||||
FS,
|
FS,
|
||||||
Responses,
|
Responses,
|
||||||
isErrorResponse,
|
isErrorResponse,
|
||||||
TokenInjectType
|
TokenInjectType,
|
||||||
} from '@/api';
|
} from "@/api";
|
||||||
import DirViewer from '@/components/FSView/DirViewer.vue';
|
import DirViewer from "@/components/FSView/DirViewer.vue";
|
||||||
import FileViewer from '@/components/FSView/FileViewer.vue';
|
import FileViewer from "@/components/FSView/FileViewer.vue";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
|
|
||||||
const path = ref('');
|
const path = ref("");
|
||||||
const node = ref<Responses.FS.GetNodeResponse | null>(null);
|
const node = ref<Responses.FS.GetNodeResponse | null>(null);
|
||||||
|
|
||||||
async function fetch_node(node_id: number) {
|
async function fetch_node(node_id: number) {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
let [p, n] = [
|
let [p, n] = [
|
||||||
await FS.get_path(token, node_id),
|
await FS.get_path(token, node_id),
|
||||||
await FS.get_node(token, node_id)
|
await FS.get_node(token, node_id),
|
||||||
];
|
];
|
||||||
if (isErrorResponse(p)) return gotoRoot();
|
if (isErrorResponse(p)) return gotoRoot();
|
||||||
if (isErrorResponse(n)) return gotoRoot();
|
if (isErrorResponse(n)) return gotoRoot();
|
||||||
[path.value, node.value] = [p.path, n];
|
[path.value, node.value] = [p.path, n];
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeRouteUpdate(async (to) => {
|
onBeforeRouteUpdate(async (to) => {
|
||||||
await fetch_node(Number(to.params.node_id));
|
await fetch_node(Number(to.params.node_id));
|
||||||
});
|
});
|
||||||
|
|
||||||
async function reloadNode() {
|
async function reloadNode() {
|
||||||
await fetch_node(Number(route.params.node_id));
|
await fetch_node(Number(route.params.node_id));
|
||||||
}
|
}
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
await reloadNode();
|
await reloadNode();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function gotoRoot() {
|
async function gotoRoot() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
const rootRes = await FS.get_root(token);
|
const rootRes = await FS.get_root(token);
|
||||||
if (isErrorResponse(rootRes)) return jwt.logout();
|
if (isErrorResponse(rootRes)) return jwt.logout();
|
||||||
const root = rootRes.rootId;
|
const root = rootRes.rootId;
|
||||||
await router.replace({
|
await router.replace({
|
||||||
name: 'fs',
|
name: "fs",
|
||||||
params: { node_id: root }
|
params: { node_id: root },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="node">
|
<div v-if="node">
|
||||||
<div>Path: {{ path }}</div>
|
<div>Path: {{ path }}</div>
|
||||||
<DirViewer
|
<DirViewer
|
||||||
v-if="!node.isFile"
|
v-if="!node.isFile"
|
||||||
:node="node"
|
:node="node"
|
||||||
@reloadNode="reloadNode"
|
@reloadNode="reloadNode"
|
||||||
@gotoRoot="gotoRoot"
|
@gotoRoot="gotoRoot"
|
||||||
/>
|
/>
|
||||||
<FileViewer v-else :node="node" />
|
<FileViewer v-else :node="node" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
<template><p></p></template>
|
<template><p></p></template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeRouteUpdate, useRouter } from 'vue-router';
|
import { onBeforeRouteUpdate, useRouter } from "vue-router";
|
||||||
import { inject, onBeforeMount } from 'vue';
|
import { inject, onBeforeMount } from "vue";
|
||||||
import { FS, check_token, isErrorResponse, TokenInjectType } from '@/api';
|
import { FS, check_token, isErrorResponse, TokenInjectType } from "@/api";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
|
|
||||||
async function start_redirect() {
|
async function start_redirect() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
const root = await FS.get_root(token);
|
const root = await FS.get_root(token);
|
||||||
if (isErrorResponse(root)) return jwt.logout();
|
if (isErrorResponse(root)) return jwt.logout();
|
||||||
await router.replace({
|
await router.replace({
|
||||||
name: 'fs',
|
name: "fs",
|
||||||
params: { node_id: root.rootId }
|
params: { node_id: root.rootId },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeRouteUpdate(async () => {
|
onBeforeRouteUpdate(async () => {
|
||||||
await start_redirect();
|
await start_redirect();
|
||||||
});
|
});
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
await start_redirect();
|
await start_redirect();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,61 +1,61 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, inject } from 'vue';
|
import { ref, inject } from "vue";
|
||||||
import { Auth, FS, isErrorResponse, TokenInjectType } from '@/api';
|
import { Auth, FS, isErrorResponse, TokenInjectType } from "@/api";
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const username = ref('');
|
const username = ref("");
|
||||||
const password = ref('');
|
const password = ref("");
|
||||||
const otp = ref('');
|
const otp = ref("");
|
||||||
|
|
||||||
const error = ref('');
|
const error = ref("");
|
||||||
|
|
||||||
const requestOtp = ref(false);
|
const requestOtp = ref(false);
|
||||||
|
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
error.value = '';
|
error.value = "";
|
||||||
if (username.value === '' || password.value === '') {
|
if (username.value === "" || password.value === "") {
|
||||||
error.value = 'Email and/or Password missing';
|
error.value = "Email and/or Password missing";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await (requestOtp.value
|
const res = await (requestOtp.value
|
||||||
? Auth.auth_login(username.value, password.value, otp.value)
|
? Auth.auth_login(username.value, password.value, otp.value)
|
||||||
: Auth.auth_login(username.value, password.value));
|
: Auth.auth_login(username.value, password.value));
|
||||||
if (isErrorResponse(res)) error.value = 'Login failed: ' + res.message;
|
if (isErrorResponse(res)) error.value = "Login failed: " + res.message;
|
||||||
else if ('jwt' in res) {
|
else if ("jwt" in res) {
|
||||||
const root = await FS.get_root(res.jwt);
|
const root = await FS.get_root(res.jwt);
|
||||||
if (isErrorResponse(root)) {
|
if (isErrorResponse(root)) {
|
||||||
error.value = 'Get root failed: ' + root.message;
|
error.value = "Get root failed: " + root.message;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
jwt.setToken(res.jwt);
|
jwt.setToken(res.jwt);
|
||||||
await router.push({
|
await router.push({
|
||||||
name: 'fs',
|
name: "fs",
|
||||||
params: { node_id: root.rootId }
|
params: { node_id: root.rootId },
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
error.value = '';
|
error.value = "";
|
||||||
requestOtp.value = true;
|
requestOtp.value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="error !== ''" v-text="error"></div>
|
<div v-if="error !== ''" v-text="error"></div>
|
||||||
<template v-if="!requestOtp">
|
<template v-if="!requestOtp">
|
||||||
<input type="email" placeholder="Email" v-model="username" />
|
<input type="email" placeholder="Email" v-model="username" />
|
||||||
<input type="password" placeholder="Password" v-model="password" />
|
<input type="password" placeholder="Password" v-model="password" />
|
||||||
<a href="/api/auth/gitlab">Login with gitlab</a>
|
<a href="/api/auth/gitlab">Login with gitlab</a>
|
||||||
<router-link to="signup">Signup instead?</router-link>
|
<router-link to="signup">Signup instead?</router-link>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div>Please input your 2 factor authentication code</div>
|
<div>Please input your 2 factor authentication code</div>
|
||||||
<input type="text" placeholder="Code" v-model="otp" />
|
<input type="text" placeholder="Code" v-model="otp" />
|
||||||
</template>
|
</template>
|
||||||
<button @click="login()">Login</button>
|
<button @click="login()">Login</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,124 +1,112 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, inject, onBeforeMount } from 'vue';
|
import { ref, inject, onBeforeMount } from "vue";
|
||||||
import {
|
import {
|
||||||
Auth,
|
Auth,
|
||||||
User,
|
User,
|
||||||
check_token,
|
check_token,
|
||||||
isErrorResponse,
|
isErrorResponse,
|
||||||
TokenInjectType,
|
TokenInjectType,
|
||||||
Responses
|
Responses,
|
||||||
} from '@/api';
|
} from "@/api";
|
||||||
import { onBeforeRouteUpdate } from 'vue-router';
|
import { onBeforeRouteUpdate } from "vue-router";
|
||||||
|
|
||||||
const error = ref('');
|
const error = ref("");
|
||||||
const oldPw = ref('');
|
const oldPw = ref("");
|
||||||
const newPw = ref('');
|
const newPw = ref("");
|
||||||
const newPw2 = ref('');
|
const newPw2 = ref("");
|
||||||
const user = ref<Responses.User.UserInfoResponse | null>(null);
|
const user = ref<Responses.User.UserInfoResponse | null>(null);
|
||||||
|
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
|
|
||||||
onBeforeRouteUpdate(async () => {
|
onBeforeRouteUpdate(async () => {
|
||||||
await updateProfile();
|
await updateProfile();
|
||||||
});
|
});
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
await updateProfile();
|
await updateProfile();
|
||||||
});
|
});
|
||||||
async function updateProfile() {
|
async function updateProfile() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
|
||||||
const res = await User.get_user_info(token);
|
const res = await User.get_user_info(token);
|
||||||
if (isErrorResponse(res)) return jwt.logout();
|
if (isErrorResponse(res)) return jwt.logout();
|
||||||
user.value = res;
|
user.value = res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteUser() {
|
async function deleteUser() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
await User.delete_user(token);
|
await User.delete_user(token);
|
||||||
jwt.logout();
|
jwt.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logoutAll() {
|
async function logoutAll() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
await Auth.logout_all(token);
|
await Auth.logout_all(token);
|
||||||
jwt.logout();
|
jwt.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changePw() {
|
async function changePw() {
|
||||||
if (oldPw.value === '' || newPw.value === '' || newPw2.value === '') {
|
if (oldPw.value === "" || newPw.value === "" || newPw2.value === "") {
|
||||||
error.value = 'Password missing';
|
error.value = "Password missing";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (newPw.value !== newPw2.value) {
|
if (newPw.value !== newPw2.value) {
|
||||||
error.value = "Passwords don't match";
|
error.value = "Passwords don't match";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
const res = await Auth.change_password(oldPw.value, newPw.value, token);
|
const res = await Auth.change_password(oldPw.value, newPw.value, token);
|
||||||
if (isErrorResponse(res))
|
if (isErrorResponse(res))
|
||||||
error.value = 'Password change failed: ' + res.message;
|
error.value = "Password change failed: " + res.message;
|
||||||
else jwt.logout();
|
else jwt.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tfaDisable() {
|
async function tfaDisable() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
await Auth.tfa_disable(token);
|
await Auth.tfa_disable(token);
|
||||||
jwt.logout();
|
jwt.logout();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="user">
|
<template v-if="user">
|
||||||
<div v-if="error !== ''" v-text="error"></div>
|
<div v-if="error !== ''" v-text="error"></div>
|
||||||
<div>User: {{ user.name }}</div>
|
<div>User: {{ user.name }}</div>
|
||||||
<div>Signed in with {{ user.gitlab ? 'gitlab' : 'password' }}</div>
|
<div>Signed in with {{ user.gitlab ? "gitlab" : "password" }}</div>
|
||||||
<template v-if="!user.gitlab">
|
<template v-if="!user.gitlab">
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input type="password" placeholder="Old password" v-model="oldPw" />
|
||||||
type="password"
|
<input type="password" placeholder="New password" v-model="newPw" />
|
||||||
placeholder="Old password"
|
<input
|
||||||
v-model="oldPw"
|
type="password"
|
||||||
/>
|
placeholder="Repeat new password"
|
||||||
<input
|
v-model="newPw2"
|
||||||
type="password"
|
/>
|
||||||
placeholder="New password"
|
<button @click="changePw">Change</button>
|
||||||
v-model="newPw"
|
</div>
|
||||||
/>
|
<div>
|
||||||
<input
|
<div>
|
||||||
type="password"
|
2 Factor authentication:
|
||||||
placeholder="Repeat new password"
|
{{ user.tfaEnabled ? "Enabled" : "Disabled" }}
|
||||||
v-model="newPw2"
|
</div>
|
||||||
/>
|
<div>
|
||||||
<button @click="changePw">Change</button>
|
<a href="#" v-if="user.tfaEnabled" @click="tfaDisable"> Disable </a>
|
||||||
</div>
|
<router-link to="/profile/2fa-enable" v-else> Enable </router-link>
|
||||||
<div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
2 Factor authentication:
|
</template>
|
||||||
{{ user.tfaEnabled ? 'Enabled' : 'Disabled' }}
|
<div>
|
||||||
</div>
|
<a href="#" @click="logoutAll">Logout everywhere</a>
|
||||||
<div>
|
<a href="#" @click="deleteUser">Delete Account</a>
|
||||||
<a href="#" v-if="user.tfaEnabled" @click="tfaDisable">
|
</div>
|
||||||
Disable
|
</template>
|
||||||
</a>
|
<template v-else>
|
||||||
<router-link to="/profile/2fa-enable" v-else>
|
<div>Loading...</div>
|
||||||
Enable
|
</template>
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div>
|
|
||||||
<a href="#" @click="logoutAll">Logout everywhere</a>
|
|
||||||
<a href="#" @click="deleteUser">Delete Account</a>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div>Loading...</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inject } from 'vue';
|
import { inject } from "vue";
|
||||||
import { TokenInjectType } from '@/api';
|
import { TokenInjectType } from "@/api";
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
|
|
||||||
if ('token' in route.query) jwt.setToken(route.query['token'] as string);
|
if ("token" in route.query) jwt.setToken(route.query["token"] as string);
|
||||||
router.replace({ path: '/' });
|
router.replace({ path: "/" });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<router-link to="/">Click here to go home</router-link>
|
<router-link to="/">Click here to go home</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,35 +1,35 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from "vue";
|
||||||
import { Auth, isErrorResponse } from '@/api';
|
import { Auth, isErrorResponse } from "@/api";
|
||||||
|
|
||||||
const username = ref('');
|
const username = ref("");
|
||||||
const password = ref('');
|
const password = ref("");
|
||||||
const password2 = ref('');
|
const password2 = ref("");
|
||||||
const error = ref('');
|
const error = ref("");
|
||||||
|
|
||||||
async function signup() {
|
async function signup() {
|
||||||
if (username.value === '' || password.value === '') {
|
if (username.value === "" || password.value === "") {
|
||||||
error.value = 'Email and/or Password missing';
|
error.value = "Email and/or Password missing";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (password.value !== password2.value) {
|
if (password.value !== password2.value) {
|
||||||
error.value = "Passwords don't match";
|
error.value = "Passwords don't match";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await Auth.auth_signup(username.value, password.value);
|
const res = await Auth.auth_signup(username.value, password.value);
|
||||||
error.value = isErrorResponse(res)
|
error.value = isErrorResponse(res)
|
||||||
? 'Signup failed: ' + res.message
|
? "Signup failed: " + res.message
|
||||||
: 'Signup successful, please wait till an admin unlocks your account.';
|
: "Signup successful, please wait till an admin unlocks your account.";
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="error !== ''" v-text="error"></div>
|
<div v-if="error !== ''" v-text="error"></div>
|
||||||
<input type="email" placeholder="Email" v-model="username" />
|
<input type="email" placeholder="Email" v-model="username" />
|
||||||
<input type="password" placeholder="Password" v-model="password" />
|
<input type="password" placeholder="Password" v-model="password" />
|
||||||
<input type="password" placeholder="Repeat password" v-model="password2" />
|
<input type="password" placeholder="Repeat password" v-model="password2" />
|
||||||
<button @click="signup()">Signup</button>
|
<button @click="signup()">Signup</button>
|
||||||
<router-link to="login">Login instead?</router-link>
|
<router-link to="login">Login instead?</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,89 +1,89 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, inject } from 'vue';
|
import { ref, inject } from "vue";
|
||||||
import { Auth, check_token, isErrorResponse, TokenInjectType } from '@/api';
|
import { Auth, check_token, isErrorResponse, TokenInjectType } from "@/api";
|
||||||
|
|
||||||
enum state {
|
enum state {
|
||||||
SELECT,
|
SELECT,
|
||||||
MAIL,
|
MAIL,
|
||||||
TOTP
|
TOTP,
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentState = ref<state>(state.SELECT);
|
const currentState = ref<state>(state.SELECT);
|
||||||
|
|
||||||
const error = ref('');
|
const error = ref("");
|
||||||
const qrImage = ref('');
|
const qrImage = ref("");
|
||||||
const secret = ref('');
|
const secret = ref("");
|
||||||
const code = ref('');
|
const code = ref("");
|
||||||
|
|
||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
|
||||||
|
|
||||||
async function selectMail() {
|
async function selectMail() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
error.value = 'Working...';
|
error.value = "Working...";
|
||||||
const res = await Auth.tfa_setup(true, token);
|
const res = await Auth.tfa_setup(true, token);
|
||||||
if (isErrorResponse(res))
|
if (isErrorResponse(res))
|
||||||
error.value = 'Failed to select 2fa type: ' + res.message;
|
error.value = "Failed to select 2fa type: " + res.message;
|
||||||
else {
|
else {
|
||||||
error.value = '';
|
error.value = "";
|
||||||
currentState.value = state.MAIL;
|
currentState.value = state.MAIL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function selectTotp() {
|
async function selectTotp() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
error.value = 'Working...';
|
error.value = "Working...";
|
||||||
const res = await Auth.tfa_setup(false, token);
|
const res = await Auth.tfa_setup(false, token);
|
||||||
if (isErrorResponse(res))
|
if (isErrorResponse(res))
|
||||||
error.value = 'Failed to select 2fa type: ' + res.message;
|
error.value = "Failed to select 2fa type: " + res.message;
|
||||||
else {
|
else {
|
||||||
qrImage.value = res.qrCode;
|
qrImage.value = res.qrCode;
|
||||||
secret.value = res.secret;
|
secret.value = res.secret;
|
||||||
error.value = '';
|
error.value = "";
|
||||||
currentState.value = state.TOTP;
|
currentState.value = state.TOTP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
const token = await check_token(jwt);
|
const token = await check_token(jwt);
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
error.value = 'Working...';
|
error.value = "Working...";
|
||||||
const res = await Auth.tfa_complete(
|
const res = await Auth.tfa_complete(
|
||||||
currentState.value === state.MAIL,
|
currentState.value === state.MAIL,
|
||||||
code.value,
|
code.value,
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
if (isErrorResponse(res))
|
if (isErrorResponse(res))
|
||||||
error.value = 'Failed to submit code: ' + res.message;
|
error.value = "Failed to submit code: " + res.message;
|
||||||
else jwt.logout();
|
else jwt.logout();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="error !== ''" v-text="error"></div>
|
<div v-if="error !== ''" v-text="error"></div>
|
||||||
<template v-if="currentState === state.SELECT">
|
<template v-if="currentState === state.SELECT">
|
||||||
<div>Select 2 Factor authentication type:</div>
|
<div>Select 2 Factor authentication type:</div>
|
||||||
<div>
|
<div>
|
||||||
<button @click="selectMail">Mail</button>
|
<button @click="selectMail">Mail</button>
|
||||||
<button @click="selectTotp">Google Authenticator</button>
|
<button @click="selectTotp">Google Authenticator</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="currentState === state.MAIL">
|
<template v-else-if="currentState === state.MAIL">
|
||||||
<div>Please enter the code you got by mail</div>
|
<div>Please enter the code you got by mail</div>
|
||||||
<input type="text" placeholder="Code" v-model="code" />
|
<input type="text" placeholder="Code" v-model="code" />
|
||||||
<button @click="submit()">Submit</button>
|
<button @click="submit()">Submit</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<img :src="qrImage" alt="QrCode" />
|
<img :src="qrImage" alt="QrCode" />
|
||||||
<details>
|
<details>
|
||||||
<summary>Show manual input code</summary>
|
<summary>Show manual input code</summary>
|
||||||
{{ secret }}
|
{{ secret }}
|
||||||
</details>
|
</details>
|
||||||
<div>Please enter the current code</div>
|
<div>Please enter the current code</div>
|
||||||
<input type="text" placeholder="Code" v-model="code" />
|
<input type="text" placeholder="Code" v-model="code" />
|
||||||
<button @click="submit()">Submit</button>
|
<button @click="submit()">Submit</button>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
const { defineConfig } = require('@vue/cli-service');
|
const { defineConfig } = require("@vue/cli-service");
|
||||||
module.exports = defineConfig({
|
module.exports = defineConfig({
|
||||||
transpileDependencies: true,
|
transpileDependencies: true,
|
||||||
configureWebpack: {
|
configureWebpack: {
|
||||||
resolve: {
|
resolve: {
|
||||||
fallback: {
|
fallback: {
|
||||||
crypto: false,
|
crypto: false,
|
||||||
stream: require.resolve('stream-browserify')
|
stream: require.resolve("stream-browserify"),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/nest-cli",
|
|
||||||
"collection": "@nestjs/schematics",
|
|
||||||
"monorepo": true,
|
|
||||||
"sourceRoot": "src",
|
|
||||||
"compilerOptions": {
|
|
||||||
"tsConfigPath": "tsconfig.json"
|
|
||||||
}
|
|
||||||
}
|
|
122
package.json
122
package.json
@ -1,122 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "fileserver",
|
|
||||||
"private": true,
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Crackhead fileserver",
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
|
||||||
"prebuild": "rimraf dist",
|
|
||||||
"build": "nest build",
|
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
||||||
"start": "nest start",
|
|
||||||
"start:dev": "nest start --watch",
|
|
||||||
"lint": "eslint \"src/**/*.ts\"",
|
|
||||||
"lint-fix": "eslint \"src/**/*.ts\" --fix",
|
|
||||||
"test": "jest",
|
|
||||||
"test:watch": "jest --watch",
|
|
||||||
"test:cov": "jest --coverage",
|
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
|
||||||
"genapi": "ts-node tools/apigen.ts",
|
|
||||||
"updateDto": "cd dto && yarn build && cd .. && yarn add ./dto && cd frontend && yarn add ../dto",
|
|
||||||
"lint-fix-all": "yarn lint-fix && cd dto && yarn lint-fix && cd ../frontend && yarn lint --fix"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fastify/multipart": "^7.1.0",
|
|
||||||
"@fastify/static": "^6.5.0",
|
|
||||||
"@nestjs/common": "^9.0.8",
|
|
||||||
"@nestjs/core": "^9.0.8",
|
|
||||||
"@nestjs/jwt": "^9.0.0",
|
|
||||||
"@nestjs/passport": "^9.0.0",
|
|
||||||
"@nestjs/platform-fastify": "^9.0.8",
|
|
||||||
"@nestjs/serve-static": "^3.0.0",
|
|
||||||
"@nestjs/typeorm": "^9.0.0",
|
|
||||||
"argon2": "^0.28.7",
|
|
||||||
"axios": "^0.27.2",
|
|
||||||
"class-transformer": "^0.5.1",
|
|
||||||
"class-validator": "^0.13.2",
|
|
||||||
"jsonwebtoken": "^8.5.1",
|
|
||||||
"nodemailer": "^6.7.8",
|
|
||||||
"notp": "^2.0.3",
|
|
||||||
"passport": "^0.6.0",
|
|
||||||
"passport-jwt": "^4.0.0",
|
|
||||||
"passport-local": "^1.0.0",
|
|
||||||
"qrcode": "^1.5.1",
|
|
||||||
"reflect-metadata": "^0.1.13",
|
|
||||||
"rxjs": "^7.5.6",
|
|
||||||
"sqlite3": "^5.0.11",
|
|
||||||
"thirty-two": "^1.0.2",
|
|
||||||
"typeorm": "^0.3.7"
|
|
||||||
},
|
|
||||||
"runtimeDependencies": [
|
|
||||||
"@fastify/multipart",
|
|
||||||
"@fastify/static",
|
|
||||||
"@nestjs/common",
|
|
||||||
"@nestjs/core",
|
|
||||||
"@nestjs/platform-fastify",
|
|
||||||
"@nestjs/serve-static",
|
|
||||||
"argon2",
|
|
||||||
"class-transformer",
|
|
||||||
"class-validator",
|
|
||||||
"reflect-metadata",
|
|
||||||
"rxjs",
|
|
||||||
"sqlite3",
|
|
||||||
"typeorm"
|
|
||||||
],
|
|
||||||
"devDependencies": {
|
|
||||||
"@nestjs/cli": "^9.0.0",
|
|
||||||
"@nestjs/schematics": "^9.0.1",
|
|
||||||
"@nestjs/testing": "^9.0.8",
|
|
||||||
"@types/express": "^4.17.13",
|
|
||||||
"@types/jest": "^28.1.6",
|
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
|
||||||
"@types/node": "^18.6.5",
|
|
||||||
"@types/nodemailer": "^6.4.5",
|
|
||||||
"@types/notp": "^2.0.2",
|
|
||||||
"@types/passport-jwt": "^3.0.6",
|
|
||||||
"@types/passport-local": "^1.0.34",
|
|
||||||
"@types/qrcode": "^1.5.0",
|
|
||||||
"@types/supertest": "^2.0.12",
|
|
||||||
"@types/webpack": "^5.28.0",
|
|
||||||
"@types/webpack-node-externals": "^2.5.3",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
|
||||||
"@typescript-eslint/parser": "^5.33.0",
|
|
||||||
"@typescript-eslint/typescript-estree": "^5.33.0",
|
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
|
||||||
"eslint": "^8.21.0",
|
|
||||||
"eslint-config-prettier": "^8.5.0",
|
|
||||||
"eslint-plugin-no-relative-import-paths": "^1.4.0",
|
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
|
||||||
"jest": "^28.1.3",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"source-map-support": "^0.5.21",
|
|
||||||
"supertest": "^6.2.4",
|
|
||||||
"ts-jest": "^28.0.7",
|
|
||||||
"ts-loader": "^9.3.1",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"tsconfig-paths": "^4.1.0",
|
|
||||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
|
||||||
"typescript": "^4.7.4",
|
|
||||||
"webpack": "^5.74.0",
|
|
||||||
"webpack-cli": "^4.10.0",
|
|
||||||
"webpack-node-externals": "^3.0.0"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"moduleFileExtensions": [
|
|
||||||
"js",
|
|
||||||
"json",
|
|
||||||
"ts"
|
|
||||||
],
|
|
||||||
"rootDir": "src",
|
|
||||||
"testRegex": ".*\\.spec\\.ts$",
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
|
||||||
},
|
|
||||||
"collectCoverageFrom": [
|
|
||||||
"**/*.(t|j)s"
|
|
||||||
],
|
|
||||||
"coverageDirectory": "../coverage",
|
|
||||||
"testEnvironment": "node"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
### Create account
|
|
||||||
POST http://127.0.0.1:8080/api/auth/signup
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{"username": "root@mattv.de", "password": "123"}
|
|
||||||
|
|
||||||
### Wrong authenctication
|
|
||||||
POST http://127.0.0.1:8080/api/auth/login
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{"username": "root@mattv.de", "password": "this is not correct"}
|
|
||||||
|
|
||||||
### Correct authentication
|
|
||||||
POST http://127.0.0.1:8080/api/auth/login
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{"username": "root@mattv.de", "password": "123"}
|
|
||||||
|
|
||||||
> {% client.global.set("auth_token", response.body.jwt); %}
|
|
||||||
|
|
||||||
### Check if authenticated with admin perms
|
|
||||||
GET http://127.0.0.1:8080/test/hello2
|
|
||||||
Authorization: Bearer {{auth_token}}
|
|
||||||
|
|
||||||
|
|
||||||
### Refresh token
|
|
||||||
POST http://127.0.0.1:8080/api/auth/refresh
|
|
||||||
Authorization: Bearer {{auth_token}}
|
|
@ -1,59 +0,0 @@
|
|||||||
import { Controller, Get, Module } from '@nestjs/common';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { INode, JWTToken, User } from './entities';
|
|
||||||
import FileSystemModule from './modules/filesystem';
|
|
||||||
import { JWTAuthGuard, Role, RoleGuard } from './authguards';
|
|
||||||
import AuthModule from './modules/auth';
|
|
||||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { cwd } from 'process';
|
|
||||||
import { UserRole } from '../dto/';
|
|
||||||
|
|
||||||
declare const PROD: boolean | undefined;
|
|
||||||
|
|
||||||
@Controller('test')
|
|
||||||
class TestController {
|
|
||||||
@Role(UserRole.USER)
|
|
||||||
@Get('hello')
|
|
||||||
getHello(): string {
|
|
||||||
return 'UwU';
|
|
||||||
}
|
|
||||||
|
|
||||||
@Role(UserRole.ADMIN)
|
|
||||||
@Get('hello2')
|
|
||||||
getHelloAdmin(): string {
|
|
||||||
return 'UwU Admin';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
TypeOrmModule.forRoot({
|
|
||||||
type: 'sqlite',
|
|
||||||
database: 'sqlite.db',
|
|
||||||
synchronize: true,
|
|
||||||
entities: [User, INode, JWTToken]
|
|
||||||
}),
|
|
||||||
ServeStaticModule.forRoot({
|
|
||||||
rootPath:
|
|
||||||
typeof PROD !== 'undefined' && PROD
|
|
||||||
? join(cwd(), 'frontend')
|
|
||||||
: join(__dirname, '..', '..', 'frontend', 'dist'),
|
|
||||||
exclude: ['/api*']
|
|
||||||
}),
|
|
||||||
FileSystemModule,
|
|
||||||
AuthModule
|
|
||||||
],
|
|
||||||
controllers: [TestController],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: 'APP_GUARD',
|
|
||||||
useClass: JWTAuthGuard
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: 'APP_GUARD',
|
|
||||||
useClass: RoleGuard
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class AppModule {}
|
|
@ -1,47 +0,0 @@
|
|||||||
import {
|
|
||||||
CanActivate,
|
|
||||||
ExecutionContext,
|
|
||||||
Injectable,
|
|
||||||
SetMetadata
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
|
||||||
import { Reflector } from '@nestjs/core';
|
|
||||||
import { User } from './entities';
|
|
||||||
import { UserRole } from '../dto';
|
|
||||||
|
|
||||||
const IS_PUBLIC_KEY = 'isPublic';
|
|
||||||
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class JWTAuthGuard extends AuthGuard('jwt') {
|
|
||||||
constructor(private reflector: Reflector) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
canActivate(context: ExecutionContext) {
|
|
||||||
const isPublic = this.reflector.getAllAndOverride<boolean>(
|
|
||||||
IS_PUBLIC_KEY,
|
|
||||||
[context.getHandler(), context.getClass()]
|
|
||||||
);
|
|
||||||
if (isPublic) return true;
|
|
||||||
return super.canActivate(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ROLE_KEY = 'role';
|
|
||||||
export const Role = (role: UserRole) => SetMetadata(ROLE_KEY, role);
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class RoleGuard implements CanActivate {
|
|
||||||
constructor(private reflector: Reflector) {}
|
|
||||||
|
|
||||||
canActivate(context: ExecutionContext) {
|
|
||||||
const requiredRole = this.reflector.getAllAndOverride<UserRole>(
|
|
||||||
ROLE_KEY,
|
|
||||||
[context.getHandler(), context.getClass()]
|
|
||||||
);
|
|
||||||
if (!requiredRole) return true;
|
|
||||||
const user: User = context.switchToHttp().getRequest().user;
|
|
||||||
return user.role >= requiredRole;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
import {
|
|
||||||
BadRequestException,
|
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Post,
|
|
||||||
Request,
|
|
||||||
ValidationPipe
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { AuthService } from 'services/auth';
|
|
||||||
import { Requests, Responses, UserRole } from '../../dto';
|
|
||||||
import { Role } from 'authguards';
|
|
||||||
import { tfaTypes } from 'entities';
|
|
||||||
|
|
||||||
@Controller('api/admin')
|
|
||||||
export default class AdminController {
|
|
||||||
constructor(private authService: AuthService) {}
|
|
||||||
|
|
||||||
@Role(UserRole.ADMIN)
|
|
||||||
@Get('users')
|
|
||||||
async getUsers(): Promise<Responses.Admin.GetUsers> {
|
|
||||||
const users = await this.authService.getUsers();
|
|
||||||
const entries = users.map(
|
|
||||||
(user) =>
|
|
||||||
new Responses.Admin.GetUsersEntry(
|
|
||||||
user.id,
|
|
||||||
user.isGitlabUser,
|
|
||||||
user.name,
|
|
||||||
user.role,
|
|
||||||
this.authService.requiresTfa(user)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return new Responses.Admin.GetUsers(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Role(UserRole.ADMIN)
|
|
||||||
@Post('set_role')
|
|
||||||
async setRole(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.Admin.SetUserRole
|
|
||||||
): Promise<Responses.Admin.SetUserRole> {
|
|
||||||
const user = await this.authService.getUser(data.user);
|
|
||||||
if (!user) throw new BadRequestException('Invalid user');
|
|
||||||
await this.authService.setUserRole(user, data.role);
|
|
||||||
return new Responses.Admin.SetUserRole();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Role(UserRole.ADMIN)
|
|
||||||
@Post('logout')
|
|
||||||
async logout(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.Admin.LogoutAll
|
|
||||||
): Promise<Responses.Admin.LogoutAllUser> {
|
|
||||||
const user = await this.authService.getUser(data.user);
|
|
||||||
if (!user) throw new BadRequestException('Invalid user');
|
|
||||||
await this.authService.revokeAll(user);
|
|
||||||
return new Responses.Admin.LogoutAllUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Role(UserRole.ADMIN)
|
|
||||||
@Post('delete')
|
|
||||||
async delete(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.Admin.DeleteUser
|
|
||||||
): Promise<Responses.Admin.DeleteUser> {
|
|
||||||
const user = await this.authService.getUser(data.user);
|
|
||||||
if (!user) throw new BadRequestException('Invalid user');
|
|
||||||
await this.authService.deleteUser(user);
|
|
||||||
return new Responses.Admin.DeleteUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Role(UserRole.ADMIN)
|
|
||||||
@Post('disable_2fa')
|
|
||||||
async disableTfa(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.Admin.DisableTfa
|
|
||||||
): Promise<Responses.Admin.DisableTfa> {
|
|
||||||
const user = await this.authService.getUser(data.user);
|
|
||||||
if (!user) throw new BadRequestException('Invalid user');
|
|
||||||
await this.authService.setTfaType(user, tfaTypes.NONE);
|
|
||||||
return new Responses.Admin.DisableTfa();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
import {
|
|
||||||
BadRequestException,
|
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
HttpCode,
|
|
||||||
Post,
|
|
||||||
Query,
|
|
||||||
Redirect,
|
|
||||||
Request,
|
|
||||||
UnauthorizedException,
|
|
||||||
UseGuards,
|
|
||||||
ValidationPipe
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { AuthService } from 'services/auth';
|
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
|
||||||
import { Public } from 'authguards';
|
|
||||||
import { Requests, Responses } from '../../dto';
|
|
||||||
import { tfaTypes } from 'entities';
|
|
||||||
import { toDataURL } from 'qrcode';
|
|
||||||
import * as base32 from 'thirty-two';
|
|
||||||
|
|
||||||
@Controller('api/auth')
|
|
||||||
export default class AuthController {
|
|
||||||
constructor(private authService: AuthService) {}
|
|
||||||
|
|
||||||
@Public()
|
|
||||||
@UseGuards(AuthGuard('local'))
|
|
||||||
@Post('login')
|
|
||||||
@HttpCode(200)
|
|
||||||
async login(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.Auth.LoginRequest
|
|
||||||
): Promise<
|
|
||||||
Responses.Auth.LoginResponse | Responses.Auth.TfaRequiredResponse
|
|
||||||
> {
|
|
||||||
if (this.authService.requiresTfa(req.user)) {
|
|
||||||
if (!data.otp) {
|
|
||||||
if (req.user.tfaType == tfaTypes.EMAIL)
|
|
||||||
await this.authService.sendTfaMail(req.user);
|
|
||||||
return new Responses.Auth.TfaRequiredResponse();
|
|
||||||
}
|
|
||||||
if (!(await this.authService.verifyTfa(req.user, data.otp)))
|
|
||||||
throw new UnauthorizedException('Incorrect 2fa');
|
|
||||||
}
|
|
||||||
return new Responses.Auth.LoginResponse(
|
|
||||||
await this.authService.login(req, req.user)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('2fa/disable')
|
|
||||||
async tfaDisable(
|
|
||||||
@Request() req
|
|
||||||
): Promise<Responses.Auth.RemoveTfaResponse> {
|
|
||||||
await this.authService.setTfaType(req.user, tfaTypes.NONE);
|
|
||||||
await this.authService.revokeAll(req.user);
|
|
||||||
return new Responses.Auth.RemoveTfaResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('2fa/complete')
|
|
||||||
async tfaMail(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.Auth.TfaComplete
|
|
||||||
): Promise<Responses.Auth.TfaCompletedResponse> {
|
|
||||||
const type = data.mail ? tfaTypes.EMAIL : tfaTypes.TOTP;
|
|
||||||
if (!(await this.authService.verifyTfa(req.user, data.code, type))) {
|
|
||||||
throw new UnauthorizedException('Incorrect 2fa');
|
|
||||||
}
|
|
||||||
await this.authService.setTfaType(req.user, type);
|
|
||||||
await this.authService.revokeAll(req.user);
|
|
||||||
return new Responses.Auth.TfaCompletedResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('2fa/setup')
|
|
||||||
async setupTotp(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.Auth.TfaSetup
|
|
||||||
): Promise<
|
|
||||||
| Responses.Auth.RequestTotpTfaResponse
|
|
||||||
| Responses.Auth.RequestEmailTfaResponse
|
|
||||||
> {
|
|
||||||
const secret = await this.authService.setupTfa(req.user);
|
|
||||||
if (data.mail) return new Responses.Auth.RequestEmailTfaResponse();
|
|
||||||
return new Responses.Auth.RequestTotpTfaResponse(
|
|
||||||
await toDataURL(
|
|
||||||
`otpauth://totp/MFileserver:${req.user.name}?secret=${base32
|
|
||||||
.encode(secret)
|
|
||||||
.toString()}&issuer=MFileserver`
|
|
||||||
),
|
|
||||||
base32.encode(secret).toString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Public()
|
|
||||||
@Post('signup')
|
|
||||||
async signup(
|
|
||||||
@Body(new ValidationPipe()) data: Requests.Auth.SignUpRequest
|
|
||||||
): Promise<Responses.Auth.SignupResponse> {
|
|
||||||
if ((await this.authService.findUser(data.username, false)) != null)
|
|
||||||
throw new BadRequestException('Username already taken');
|
|
||||||
await this.authService.signup(data.username, data.password);
|
|
||||||
return new Responses.Auth.SignupResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('refresh')
|
|
||||||
async refresh(@Request() req): Promise<Responses.Auth.RefreshResponse> {
|
|
||||||
const token = await this.authService.login(req, req.user);
|
|
||||||
await this.authService.revoke(req.token);
|
|
||||||
return await new Responses.Auth.RefreshResponse(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Public()
|
|
||||||
@Redirect()
|
|
||||||
@Get('gitlab')
|
|
||||||
async gitlab(@Request() req) {
|
|
||||||
return {
|
|
||||||
url: this.authService.getGitlabAuthUrl(req)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Public()
|
|
||||||
@Redirect()
|
|
||||||
@Get('gitlab_callback')
|
|
||||||
async gitlabCallback(@Request() req, @Query('code') code) {
|
|
||||||
const user = await this.authService.getGitlabUserFromCode(req, code);
|
|
||||||
const token = await this.authService.login(req, user);
|
|
||||||
return {
|
|
||||||
url: `/set_token?token=${token}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('change_password')
|
|
||||||
async changePassword(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.Auth.ChangePasswordRequest
|
|
||||||
): Promise<Responses.Auth.ChangePasswordResponse> {
|
|
||||||
await this.authService.changePassword(
|
|
||||||
req.user,
|
|
||||||
data.oldPassword,
|
|
||||||
data.newPassword
|
|
||||||
);
|
|
||||||
return new Responses.Auth.ChangePasswordResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('logout_all')
|
|
||||||
async logoutAll(@Request() req): Promise<Responses.Auth.LogoutAllResponse> {
|
|
||||||
await this.authService.revokeAll(req.user);
|
|
||||||
return new Responses.Auth.LogoutAllResponse();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,121 +0,0 @@
|
|||||||
import {
|
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Param,
|
|
||||||
ParseIntPipe,
|
|
||||||
Post,
|
|
||||||
Request,
|
|
||||||
StreamableFile,
|
|
||||||
ValidationPipe
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { Responses, Requests, validateAsyncInline, UserRole } from '../../dto';
|
|
||||||
import FileSystemService from 'services/filesystem';
|
|
||||||
import { Role } from 'authguards';
|
|
||||||
|
|
||||||
@Controller('api/fs')
|
|
||||||
export default class FileSystemController {
|
|
||||||
constructor(private fsService: FileSystemService) {}
|
|
||||||
|
|
||||||
@Get('root')
|
|
||||||
@Role(UserRole.USER)
|
|
||||||
async getRoot(@Request() req): Promise<Responses.FS.GetRootResponse> {
|
|
||||||
return new Responses.FS.GetRootResponse(req.user.rootId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('node/:node')
|
|
||||||
@Role(UserRole.USER)
|
|
||||||
async getNode(
|
|
||||||
@Request() req,
|
|
||||||
@Param('node', ParseIntPipe) nodeId
|
|
||||||
): Promise<Responses.FS.GetNodeResponse> {
|
|
||||||
const node = await this.fsService.getNodeAndValidate(nodeId, req.user);
|
|
||||||
const data = new Responses.FS.GetNodeResponse(
|
|
||||||
nodeId,
|
|
||||||
node.name,
|
|
||||||
node.isFile,
|
|
||||||
node.parentId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (data.isFile) {
|
|
||||||
data.size = node.size;
|
|
||||||
} else {
|
|
||||||
data.children = (await node.children).map((child) => child.id);
|
|
||||||
}
|
|
||||||
return validateAsyncInline(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('path/:node')
|
|
||||||
@Role(UserRole.USER)
|
|
||||||
async getPath(
|
|
||||||
@Request() req,
|
|
||||||
@Param('node', ParseIntPipe) nodeId
|
|
||||||
): Promise<Responses.FS.GetPathResponse> {
|
|
||||||
return new Responses.FS.GetPathResponse(
|
|
||||||
await this.fsService.generatePath(
|
|
||||||
await this.fsService.getNodeAndValidate(nodeId, req.user)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('createFolder')
|
|
||||||
@Role(UserRole.USER)
|
|
||||||
async createFolder(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.FS.CreateFolderRequest
|
|
||||||
): Promise<Responses.FS.CreateFolderResponse> {
|
|
||||||
const newChild = await this.fsService.create(
|
|
||||||
await this.fsService.getNodeAndValidate(data.parent, req.user),
|
|
||||||
data.name,
|
|
||||||
req.user,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
return new Responses.FS.CreateFolderResponse(newChild.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('createFile')
|
|
||||||
@Role(UserRole.USER)
|
|
||||||
async createFile(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.FS.CreateFileRequest
|
|
||||||
): Promise<Responses.FS.CreateFileResponse> {
|
|
||||||
const newChild = await this.fsService.create(
|
|
||||||
await this.fsService.getNodeAndValidate(data.parent, req.user),
|
|
||||||
data.name,
|
|
||||||
req.user,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
return new Responses.FS.CreateFileResponse(newChild.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('delete')
|
|
||||||
@Role(UserRole.USER)
|
|
||||||
async delete(
|
|
||||||
@Request() req,
|
|
||||||
@Body(new ValidationPipe()) data: Requests.FS.DeleteRequest
|
|
||||||
): Promise<Responses.FS.DeleteResponse> {
|
|
||||||
await this.fsService.delete(
|
|
||||||
await this.fsService.getNodeAndValidate(data.node, req.user)
|
|
||||||
);
|
|
||||||
return new Responses.FS.DeleteResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('upload/:node')
|
|
||||||
@Role(UserRole.USER)
|
|
||||||
async upload(
|
|
||||||
@Request() req,
|
|
||||||
@Param('node', ParseIntPipe) nodeId
|
|
||||||
): Promise<Responses.FS.UploadFileResponse> {
|
|
||||||
await this.fsService.uploadFile(await req.file(), nodeId, req.user);
|
|
||||||
return new Responses.FS.UploadFileResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('download')
|
|
||||||
@Role(UserRole.USER)
|
|
||||||
async download(
|
|
||||||
@Request() req,
|
|
||||||
@Body('id', ParseIntPipe) id
|
|
||||||
): Promise<StreamableFile> {
|
|
||||||
return this.fsService.downloadFile(id, req.user);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
import { Controller, Get, Post, Request } from '@nestjs/common';
|
|
||||||
import { AuthService } from 'services/auth';
|
|
||||||
import { Responses } from '../../dto';
|
|
||||||
|
|
||||||
@Controller('api/user')
|
|
||||||
export default class UserController {
|
|
||||||
constructor(private authService: AuthService) {}
|
|
||||||
|
|
||||||
@Get('info')
|
|
||||||
async getUserInfo(
|
|
||||||
@Request() req
|
|
||||||
): Promise<Responses.User.UserInfoResponse> {
|
|
||||||
return new Responses.User.UserInfoResponse(
|
|
||||||
req.user.name,
|
|
||||||
req.user.isGitlabUser,
|
|
||||||
this.authService.requiresTfa(req.user)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('delete')
|
|
||||||
async deleteUser(
|
|
||||||
@Request() req
|
|
||||||
): Promise<Responses.User.DeleteUserResponse> {
|
|
||||||
await this.authService.deleteUser(req.user);
|
|
||||||
return new Responses.User.DeleteUserResponse();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
Column,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
ManyToOne,
|
|
||||||
OneToMany,
|
|
||||||
OneToOne
|
|
||||||
} from 'typeorm';
|
|
||||||
import { UserRole } from '../dto';
|
|
||||||
|
|
||||||
export enum tfaTypes {
|
|
||||||
NONE = 0,
|
|
||||||
EMAIL = 1,
|
|
||||||
TOTP = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export class INode {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
@Column()
|
|
||||||
isFile: boolean;
|
|
||||||
@Column()
|
|
||||||
name: string;
|
|
||||||
@Column({ nullable: true })
|
|
||||||
size: number;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
parentId: number;
|
|
||||||
@ManyToOne(() => INode, (node) => node.children)
|
|
||||||
parent: Promise<INode>;
|
|
||||||
@OneToMany(() => INode, (node) => node.parent)
|
|
||||||
children: Promise<INode[]>;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
ownerId: number;
|
|
||||||
@ManyToOne(() => User)
|
|
||||||
owner: Promise<User>;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export class User {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
@Column({ default: false })
|
|
||||||
isGitlabUser: boolean;
|
|
||||||
@Column()
|
|
||||||
name: string;
|
|
||||||
@Column()
|
|
||||||
password: string;
|
|
||||||
@Column({
|
|
||||||
type: 'int',
|
|
||||||
default: UserRole.DISABLED,
|
|
||||||
transformer: {
|
|
||||||
from: (db: number): UserRole => db,
|
|
||||||
to: (role: UserRole): number => role
|
|
||||||
}
|
|
||||||
})
|
|
||||||
role: UserRole;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
rootId: number;
|
|
||||||
@OneToOne(() => INode)
|
|
||||||
root: Promise<INode>;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
type: 'int',
|
|
||||||
default: tfaTypes.NONE,
|
|
||||||
transformer: {
|
|
||||||
from: (db: number): tfaTypes => db,
|
|
||||||
to: (type: tfaTypes): number => type
|
|
||||||
}
|
|
||||||
})
|
|
||||||
tfaType: tfaTypes;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
tfaSecret: string;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
gitlabAT: string;
|
|
||||||
@Column({ nullable: true })
|
|
||||||
gitlabRT: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export class JWTToken {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
ownerId: number;
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
exp: number;
|
|
||||||
}
|
|
20
src/main.ts
20
src/main.ts
@ -1,20 +0,0 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
|
||||||
import { AppModule } from './app.module';
|
|
||||||
import {
|
|
||||||
FastifyAdapter,
|
|
||||||
NestFastifyApplication
|
|
||||||
} from '@nestjs/platform-fastify';
|
|
||||||
import fastifyMultipart from '@fastify/multipart';
|
|
||||||
import { existsSync, mkdirSync } from 'fs';
|
|
||||||
|
|
||||||
async function bootstrap() {
|
|
||||||
if (!existsSync('files')) mkdirSync('files');
|
|
||||||
|
|
||||||
const app = await NestFactory.create<NestFastifyApplication>(
|
|
||||||
AppModule,
|
|
||||||
new FastifyAdapter({ logger: true })
|
|
||||||
);
|
|
||||||
await app.register(fastifyMultipart);
|
|
||||||
await app.listen(8080, '0.0.0.0');
|
|
||||||
}
|
|
||||||
bootstrap();
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user