From e1164eb05b2e5a7f940aeefeb16febad60d42694 Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Fri, 14 Apr 2023 15:13:53 +0200 Subject: [PATCH] Add JRPC API, reworked Login and User pages --- .drone.yml | 9 +- .vscode/launch.json | 16 - .vscode/tasks.json | 22 - Backend/example.config.ini | 31 +- Backend/locales/de.json | 7 +- Backend/package.json | 149 +-- Backend/src/api/admin/permission.ts | 222 ++--- Backend/src/api/client/permissions.ts | 196 ++-- Backend/src/api/index.ts | 104 +- Backend/src/api/internal/oauth.ts | 80 +- Backend/src/api/jrpc/index.ts | 83 +- Backend/src/api/jrpc/security_service.ts | 71 -- .../account.ts} | 101 +- Backend/src/api/jrpc/services/login.ts | 265 +++++ Backend/src/api/jrpc/services/security.ts | 35 + Backend/src/api/jrpc/services/twofactor.ts | 194 ++++ Backend/src/api/middlewares/user.ts | 176 ++-- Backend/src/api/oauth/auth.ts | 498 ++++----- Backend/src/api/user/account.ts | 19 - Backend/src/api/user/contact.ts | 19 - Backend/src/api/user/index.ts | 171 +--- Backend/src/api/user/login.ts | 134 --- Backend/src/api/user/oauth/permissions.ts | 76 +- Backend/src/api/user/token.ts | 45 - .../src/api/user/twofactor/backup/index.ts | 100 -- Backend/src/api/user/twofactor/helper.ts | 16 - Backend/src/api/user/twofactor/index.ts | 56 -- Backend/src/api/user/twofactor/otc/index.ts | 135 --- .../src/api/user/twofactor/yubikey/index.ts | 206 ---- Backend/src/config.ts | 156 +-- Backend/src/database.ts | 26 +- Backend/src/express.d.ts | 39 +- Backend/src/helper/jwt.ts | 120 +-- Backend/src/helper/login.ts | 29 + Backend/src/keys.ts | 138 +-- Backend/src/models/client.ts | 80 +- Backend/src/models/client_code.ts | 58 +- Backend/src/models/grants.ts | 50 +- Backend/src/models/login_token.ts | 152 +-- Backend/src/models/permissions.ts | 74 +- Backend/src/models/refresh_token.ts | 64 +- Backend/src/models/regcodes.ts | 114 +-- Backend/src/models/twofactor.ts | 140 +-- Backend/src/models/user.ts | 268 ++--- Backend/src/testdata.ts | 317 +++--- Backend/src/views/{views.ts => index.ts} | 242 ++--- Backend/src/web.ts | 297 +++--- Backend/tsconfig.json | 34 +- Frontend/package.json | 10 +- .../src/components/HoveringContentBox.svelte | 50 +- Frontend/src/components/MainNavbar.svelte | 33 + Frontend/src/components/theme/theme.css | 6 + Frontend/src/helper/api.ts | 36 +- Frontend/src/pages/home/App.svelte | 11 - Frontend/src/pages/login/App.svelte | 136 +-- Frontend/src/pages/login/Credentials.svelte | 84 -- Frontend/src/pages/login/Error.svelte | 16 + Frontend/src/pages/login/Password.svelte | 30 + .../login/{Redirect.svelte => Success.svelte} | 0 .../codeInput.svelte => TF/CodeInput.svelte} | 16 +- Frontend/src/pages/login/TF/TOTP.svelte | 21 + Frontend/src/pages/login/TF/WebAuthn.svelte | 28 + Frontend/src/pages/login/TwoFactor.svelte | 114 +++ Frontend/src/pages/login/Twofactor.svelte | 104 -- Frontend/src/pages/login/Username.svelte | 29 + Frontend/src/pages/login/api.ts | 182 ---- Frontend/src/pages/login/state.ts | 183 ++++ .../src/pages/login/twofactors/otc.svelte | 50 - .../src/pages/login/twofactors/push.svelte | 389 -------- .../src/pages/login/twofactors/sms.svelte | 49 - .../src/pages/login/twofactors/toList.svelte | 17 - .../src/pages/login/twofactors/u2f.svelte | 69 -- Frontend/src/pages/popup/main.ts | 16 + Frontend/src/pages/user/App.svelte | 19 +- .../src/pages/user/Pages/PersonalInfo.svelte | 197 ---- Frontend/src/pages/user/Sidebar.svelte | 24 +- .../src/pages/user/pages/AddTwoFactor.svelte | 36 + .../src/pages/user/pages/PersonalInfo.svelte | 203 ++++ .../user/{Pages => pages}/Security.svelte | 104 +- .../pages/TwoFactorRegistration/TOTP.svelte | 102 ++ .../TwoFactorRegistration/WebAuthn.svelte | 102 ++ Frontend/src/pages/user_old/App.svelte | 207 ---- .../src/pages/user_old/NavigationBar.svelte | 54 - .../src/pages/user_old/Pages/Account.svelte | 192 ---- Frontend/src/pages/user_old/Pages/Box.svelte | 36 - .../src/pages/user_old/Pages/BoxItem.svelte | 94 -- .../src/pages/user_old/Pages/NextIcon.svelte | 13 - .../src/pages/user_old/Pages/Security.svelte | 188 ---- Frontend/src/pages/user_old/main.ts | 6 - FrontendLegacy/package.json | 2 +- InternalAPI/account.jrpc | 16 + InternalAPI/api.jrpc | 82 +- InternalAPI/login.jrpc | 21 + InternalAPI/security.jrpc | 14 + InternalAPI/twofactor.jrpc | 38 + InternalAPI/types.jrpc | 31 + _API/package.json | 2 +- package.json | 2 +- yarn.lock | 943 +++++++++++++----- 99 files changed, 4570 insertions(+), 5471 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/tasks.json delete mode 100644 Backend/src/api/jrpc/security_service.ts rename Backend/src/api/jrpc/{account_service.ts => services/account.ts} (70%) create mode 100644 Backend/src/api/jrpc/services/login.ts create mode 100644 Backend/src/api/jrpc/services/security.ts create mode 100644 Backend/src/api/jrpc/services/twofactor.ts delete mode 100644 Backend/src/api/user/account.ts delete mode 100644 Backend/src/api/user/contact.ts delete mode 100644 Backend/src/api/user/login.ts delete mode 100644 Backend/src/api/user/token.ts delete mode 100644 Backend/src/api/user/twofactor/backup/index.ts delete mode 100644 Backend/src/api/user/twofactor/helper.ts delete mode 100644 Backend/src/api/user/twofactor/index.ts delete mode 100644 Backend/src/api/user/twofactor/otc/index.ts delete mode 100644 Backend/src/api/user/twofactor/yubikey/index.ts create mode 100644 Backend/src/helper/login.ts rename Backend/src/views/{views.ts => index.ts} (97%) create mode 100644 Frontend/src/components/MainNavbar.svelte delete mode 100644 Frontend/src/pages/login/Credentials.svelte create mode 100644 Frontend/src/pages/login/Error.svelte create mode 100644 Frontend/src/pages/login/Password.svelte rename Frontend/src/pages/login/{Redirect.svelte => Success.svelte} (100%) rename Frontend/src/pages/login/{twofactors/codeInput.svelte => TF/CodeInput.svelte} (60%) create mode 100644 Frontend/src/pages/login/TF/TOTP.svelte create mode 100644 Frontend/src/pages/login/TF/WebAuthn.svelte create mode 100644 Frontend/src/pages/login/TwoFactor.svelte delete mode 100644 Frontend/src/pages/login/Twofactor.svelte create mode 100644 Frontend/src/pages/login/Username.svelte delete mode 100644 Frontend/src/pages/login/api.ts create mode 100644 Frontend/src/pages/login/state.ts delete mode 100644 Frontend/src/pages/login/twofactors/otc.svelte delete mode 100644 Frontend/src/pages/login/twofactors/push.svelte delete mode 100644 Frontend/src/pages/login/twofactors/sms.svelte delete mode 100644 Frontend/src/pages/login/twofactors/toList.svelte delete mode 100644 Frontend/src/pages/login/twofactors/u2f.svelte delete mode 100644 Frontend/src/pages/user/Pages/PersonalInfo.svelte create mode 100644 Frontend/src/pages/user/pages/AddTwoFactor.svelte create mode 100644 Frontend/src/pages/user/pages/PersonalInfo.svelte rename Frontend/src/pages/user/{Pages => pages}/Security.svelte (56%) create mode 100644 Frontend/src/pages/user/pages/TwoFactorRegistration/TOTP.svelte create mode 100644 Frontend/src/pages/user/pages/TwoFactorRegistration/WebAuthn.svelte delete mode 100644 Frontend/src/pages/user_old/App.svelte delete mode 100644 Frontend/src/pages/user_old/NavigationBar.svelte delete mode 100644 Frontend/src/pages/user_old/Pages/Account.svelte delete mode 100644 Frontend/src/pages/user_old/Pages/Box.svelte delete mode 100644 Frontend/src/pages/user_old/Pages/BoxItem.svelte delete mode 100644 Frontend/src/pages/user_old/Pages/NextIcon.svelte delete mode 100644 Frontend/src/pages/user_old/Pages/Security.svelte delete mode 100644 Frontend/src/pages/user_old/main.ts create mode 100644 InternalAPI/account.jrpc create mode 100644 InternalAPI/login.jrpc create mode 100644 InternalAPI/security.jrpc create mode 100644 InternalAPI/twofactor.jrpc create mode 100644 InternalAPI/types.jrpc diff --git a/.drone.yml b/.drone.yml index c4938bf..21415c1 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,14 +3,7 @@ type: docker name: default steps: - - name: Build with node - image: node:12 - commands: - - npm config set registry https://npm.hibas123.de - - npm install - - npm run install - - npm run build - - name: Publish to docker + - name: Build docker image: plugins/docker settings: username: diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index d5c9108..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "pwa-node", - "request": "launch", - "name": "Launch Program", - "program": "${workspaceFolder}/lib/index.js", - "outFiles": ["${workspaceFolder}/**/*.js"], - "preLaunchTask": "build" - } - ] -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 1033e39..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - // Unter https://go.microsoft.com/fwlink/?LinkId=733558 - // finden Sie Informationen zum Format von "tasks.json" - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "build-ts", - "group": "build", - "problemMatcher": ["$tsc"], - "presentation": { - "echo": true, - "reveal": "never", - "focus": false, - "panel": "shared", - "showReuseMessage": true, - "clear": false - }, - "label": "build" - } - ] -} diff --git a/Backend/example.config.ini b/Backend/example.config.ini index 574cd4b..78cdd0e 100644 --- a/Backend/example.config.ini +++ b/Backend/example.config.ini @@ -1,15 +1,16 @@ -[database] -host=localhost -database=openauth - -[core] -name = OpenAuthService - -[web] -port = 3000 - -[mail] -server = mail.example.com -username = test -password = test -port = 595 \ No newline at end of file +[database] +host=localhost +database=openauth + +[core] +name = OpenAuthService +secret = dev + +[web] +port = 3000 + +[mail] +server = mail.example.com +username = test +password = test +port = 595 diff --git a/Backend/locales/de.json b/Backend/locales/de.json index 9d37add..12b495e 100644 --- a/Backend/locales/de.json +++ b/Backend/locales/de.json @@ -39,5 +39,10 @@ "No login token": "No login token", "You are not logged in or your login is expired (Login token invalid)": "You are not logged in or your login is expired (Login token invalid)", "You are not logged in or your login is expired (No special token)": "You are not logged in or your login is expired (No special token)", - "Special token invalid": "Special token invalid" + "Special token invalid": "Special token invalid", + "You are not logged in or your login is expired (No login token)": "You are not logged in or your login is expired (No login token)", + "": "", + "You are not logged in or your login is expired ()": "You are not logged in or your login is expired ()", + "Session not validated!": "Session not validated!", + "Not logged in": "Not logged in" } \ No newline at end of file diff --git a/Backend/package.json b/Backend/package.json index d9f5bff..88f5349 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -1,72 +1,77 @@ -{ - "name": "@hibas123/openauth-backend", - "main": "lib/index.js", - "author": "Fabian Stamm ", - "license": "MIT", - "scripts": { - "build": "run-s build-ts build-doc", - "build-doc": "apidoc -i src/ -p apidoc/", - "build-ts": "tsc", - "start": "node lib/index.js", - "dev": "nodemon -e ts --exec ts-node src/index.ts", - "format": "prettier --write \"src/**\"" - }, - "pipelines": { - "install": [ - "cd views && npm install", - "git submodule init", - "git submodule update", - "cd views_repo && npm install" - ] - }, - "devDependencies": { - "@types/body-parser": "^1.19.2", - "@types/compression": "^1.7.2", - "@types/cookie-parser": "^1.4.3", - "@types/dotenv": "^8.2.0", - "@types/express": "^4.17.17", - "@types/i18n": "^0.13.6", - "@types/ini": "^1.3.31", - "@types/jsonwebtoken": "^9.0.1", - "@types/mongodb": "^3.6.20", - "@types/node": "^18.15.11", - "@types/node-rsa": "^1.1.1", - "@types/qrcode": "^1.5.0", - "@types/speakeasy": "^2.0.7", - "@types/uuid": "^9.0.1", - "apidoc": "^0.54.0", - "concurrently": "^8.0.1", - "nodemon": "^2.0.22", - "prettier": "^2.8.7", - "ts-node": "^10.9.1", - "typescript": "^5.0.3" - }, - "dependencies": { - "@hibas123/config": "^1.1.2", - "@hibas123/nodelogging": "^3.1.3", - "@hibas123/nodeloggingserver_client": "^1.1.2", - "@hibas123/openauth-internalapi": "workspace:^", - "@hibas123/openauth-views-v1": "workspace:^", - "@hibas123/safe_mongo": "^1.7.1", - "body-parser": "^1.20.2", - "compression": "^1.7.4", - "cookie-parser": "^1.4.6", - "cors": "^2.8.5", - "dotenv": "^16.0.3", - "express": "^4.18.2", - "handlebars": "^4.7.7", - "i18n": "^0.15.1", - "ini": "^4.0.0", - "jsonwebtoken": "^9.0.0", - "moment": "^2.29.4", - "mongodb": "^3.7.3", - "node-rsa": "^1.1.1", - "npm-run-all": "^4.1.5", - "qrcode": "^1.5.1", - "reflect-metadata": "^0.1.13", - "speakeasy": "^2.0.0", - "u2f": "^0.1.3", - "uuid": "^9.0.0" - }, - "packageManager": "yarn@3.5.0" -} +{ + "name": "@hibas123/openauth-backend", + "main": "lib/index.js", + "author": "Fabian Stamm ", + "license": "MIT", + "scripts": { + "build": "run-s build-ts build-doc", + "build-doc": "apidoc -i src/ -p apidoc/", + "build-ts": "tsc", + "start": "node lib/index.js", + "dev": "nodemon -e ts --exec ts-node src/index.ts", + "format": "prettier --write \"src/**\"" + }, + "pipelines": { + "install": [ + "cd views && npm install", + "git submodule init", + "git submodule update", + "cd views_repo && npm install" + ] + }, + "devDependencies": { + "@types/body-parser": "^1.19.2", + "@types/compression": "^1.7.2", + "@types/cookie-parser": "^1.4.3", + "@types/dotenv": "^8.2.0", + "@types/express": "^4.17.17", + "@types/express-session": "^1.17.7", + "@types/i18n": "^0.13.6", + "@types/ini": "^1.3.31", + "@types/jsonwebtoken": "^9.0.1", + "@types/mongodb": "^4.0.7", + "@types/node": "^18.15.11", + "@types/node-rsa": "^1.1.1", + "@types/qrcode": "^1.5.0", + "@types/speakeasy": "^2.0.7", + "@types/uuid": "^9.0.1", + "apidoc": "^0.54.0", + "concurrently": "^8.0.1", + "nodemon": "^2.0.22", + "prettier": "^2.8.7", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "dependencies": { + "@hibas123/config": "^1.1.2", + "@hibas123/nodelogging": "^3.1.3", + "@hibas123/nodeloggingserver_client": "^1.1.2", + "@hibas123/openauth-internalapi": "workspace:^", + "@hibas123/openauth-views-v1": "workspace:^", + "@hibas123/safe_mongo": "^2.0.1", + "@simplewebauthn/server": "^7.2.0", + "body-parser": "^1.20.2", + "compression": "^1.7.4", + "connect-mongo": "^5.0.0", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "express-session": "^1.17.3", + "handlebars": "^4.7.7", + "i18n": "^0.15.1", + "ini": "^4.0.0", + "joi": "^17.9.1", + "jsonwebtoken": "^9.0.0", + "moment": "^2.29.4", + "mongodb": "^5.2.0", + "node-rsa": "^1.1.1", + "npm-run-all": "^4.1.5", + "qrcode": "^1.5.1", + "reflect-metadata": "^0.1.13", + "speakeasy": "^2.0.0", + "u2f": "^0.1.3", + "uuid": "^9.0.0" + }, + "packageManager": "yarn@3.5.0" +} diff --git a/Backend/src/api/admin/permission.ts b/Backend/src/api/admin/permission.ts index e98320c..0595e1e 100644 --- a/Backend/src/api/admin/permission.ts +++ b/Backend/src/api/admin/permission.ts @@ -1,111 +1,111 @@ -import { Request, Router } from "express"; -import { GetUserMiddleware } from "../middlewares/user"; -import RequestError, { HttpStatusCode } from "../../helper/request_error"; -import promiseMiddleware from "../../helper/promiseMiddleware"; -import Permission from "../../models/permissions"; -import verify, { Types } from "../middlewares/verify"; -import Client from "../../models/client"; -import { ObjectID } from "bson"; - -const PermissionRoute: Router = Router(); -PermissionRoute.route("/") - /** - * @api {get} /admin/permission - * @apiName AdminGetPermissions - * - * @apiParam client Optionally filter by client _id - * - * @apiGroup admin_permission - * @apiPermission admin - * - * @apiSuccess {Object[]} permissions - * @apiSuccess {String} permissions._id The ID - * @apiSuccess {String} permissions.name Permission name - * @apiSuccess {String} permissions.description A description, that makes it clear to the user, what this Permission allows to do - * @apiSuccess {String} permissions.client The ID of the owning client - */ - .get( - promiseMiddleware(async (req, res) => { - let query = {}; - if (req.query.client) { - query = { client: new ObjectID(req.query.client as string) }; - } - let permissions = await Permission.find(query); - res.json(permissions); - }) - ) - /** - * @api {post} /admin/permission - * @apiName AdminAddPermission - * - * @apiParam client The ID of the owning client - * @apiParam name Permission name - * @apiParam description A description, that makes it clear to the user, what this Permission allows to do - * - * @apiGroup admin_permission - * @apiPermission admin - * - * @apiSuccess {Object[]} permissions - * @apiSuccess {String} permissions._id The ID - * @apiSuccess {String} permissions.name Permission name - * @apiSuccess {String} permissions.description A description, that makes it clear to the user, what this Permission allows to do - * @apiSuccess {String} permissions.client The ID of the owning client - * @apiSuccess {String} permissions.grant_type The type of the permission. "user" | "client" granted - */ - .post( - verify( - { - client: { - type: Types.STRING, - }, - name: { - type: Types.STRING, - }, - description: { - type: Types.STRING, - }, - type: { - type: Types.ENUM, - values: ["user", "client"], - }, - }, - true - ), - promiseMiddleware(async (req, res) => { - let client = await Client.findById(req.body.client); - if (!client) { - throw new RequestError( - "Client not found", - HttpStatusCode.BAD_REQUEST - ); - } - let permission = Permission.new({ - description: req.body.description, - name: req.body.name, - client: client._id, - grant_type: req.body.type, - }); - await Permission.save(permission); - res.json(permission); - }) - ) - /** - * @api {delete} /admin/permission - * @apiName AdminDeletePermission - * - * @apiParam id The permission ID - * - * @apiGroup admin_permission - * @apiPermission admin - * - * @apiSuccess {Boolean} success - */ - .delete( - promiseMiddleware(async (req, res) => { - let { id } = req.query as { [key: string]: string }; - await Permission.delete(id); - res.json({ success: true }); - }) - ); - -export default PermissionRoute; +import { Request, Router } from "express"; +import { GetUserMiddleware } from "../middlewares/user"; +import RequestError, { HttpStatusCode } from "../../helper/request_error"; +import promiseMiddleware from "../../helper/promiseMiddleware"; +import Permission from "../../models/permissions"; +import verify, { Types } from "../middlewares/verify"; +import Client from "../../models/client"; +import { ObjectId } from "bson"; + +const PermissionRoute: Router = Router(); +PermissionRoute.route("/") + /** + * @api {get} /admin/permission + * @apiName AdminGetPermissions + * + * @apiParam client Optionally filter by client _id + * + * @apiGroup admin_permission + * @apiPermission admin + * + * @apiSuccess {Object[]} permissions + * @apiSuccess {String} permissions._id The ID + * @apiSuccess {String} permissions.name Permission name + * @apiSuccess {String} permissions.description A description, that makes it clear to the user, what this Permission allows to do + * @apiSuccess {String} permissions.client The ID of the owning client + */ + .get( + promiseMiddleware(async (req, res) => { + let query = {}; + if (req.query.client) { + query = { client: new ObjectId(req.query.client as string) }; + } + let permissions = await Permission.find(query); + res.json(permissions); + }) + ) + /** + * @api {post} /admin/permission + * @apiName AdminAddPermission + * + * @apiParam client The ID of the owning client + * @apiParam name Permission name + * @apiParam description A description, that makes it clear to the user, what this Permission allows to do + * + * @apiGroup admin_permission + * @apiPermission admin + * + * @apiSuccess {Object[]} permissions + * @apiSuccess {String} permissions._id The ID + * @apiSuccess {String} permissions.name Permission name + * @apiSuccess {String} permissions.description A description, that makes it clear to the user, what this Permission allows to do + * @apiSuccess {String} permissions.client The ID of the owning client + * @apiSuccess {String} permissions.grant_type The type of the permission. "user" | "client" granted + */ + .post( + verify( + { + client: { + type: Types.STRING, + }, + name: { + type: Types.STRING, + }, + description: { + type: Types.STRING, + }, + type: { + type: Types.ENUM, + values: ["user", "client"], + }, + }, + true + ), + promiseMiddleware(async (req, res) => { + let client = await Client.findById(req.body.client); + if (!client) { + throw new RequestError( + "Client not found", + HttpStatusCode.BAD_REQUEST + ); + } + let permission = Permission.new({ + description: req.body.description, + name: req.body.name, + client: client._id, + grant_type: req.body.type, + }); + await Permission.save(permission); + res.json(permission); + }) + ) + /** + * @api {delete} /admin/permission + * @apiName AdminDeletePermission + * + * @apiParam id The permission ID + * + * @apiGroup admin_permission + * @apiPermission admin + * + * @apiSuccess {Boolean} success + */ + .delete( + promiseMiddleware(async (req, res) => { + let { id } = req.query as { [key: string]: string }; + await Permission.delete(id); + res.json({ success: true }); + }) + ); + +export default PermissionRoute; diff --git a/Backend/src/api/client/permissions.ts b/Backend/src/api/client/permissions.ts index 7a84282..a52dc26 100644 --- a/Backend/src/api/client/permissions.ts +++ b/Backend/src/api/client/permissions.ts @@ -1,98 +1,98 @@ -import { Request, Response } from "express"; -import Stacker from "../middlewares/stacker"; -import { - ClientAuthMiddleware, - GetClientAuthMiddleware, -} from "../middlewares/client"; -import Permission from "../../models/permissions"; -import User from "../../models/user"; - -import RequestError, { HttpStatusCode } from "../../helper/request_error"; -import Grant from "../../models/grants"; -import { ObjectID } from "mongodb"; - -export const GetPermissions = Stacker( - GetClientAuthMiddleware(true), - async (req: Request, res: Response) => { - const { user, permission } = req.query as { [key: string]: string }; - - let permissions: { id: string; name: string; description: string }[]; - let users: string[]; - - if (user) { - const grant = await Grant.findOne({ - client: req.client._id, - user: new ObjectID(user), - }); - - permissions = await Promise.all( - grant.permissions.map((perm) => Permission.findById(perm)) - ).then((res) => - res - .filter((e) => e.grant_type === "client") - .map((e) => { - return { - id: e._id.toHexString(), - name: e.name, - description: e.description, - }; - }) - ); - } - - if (permission) { - const grants = await Grant.find({ - client: req.client._id, - permissions: new ObjectID(permission), - }); - - users = grants.map((grant) => grant.user.toHexString()); - } - - res.json({ permissions, users }); - } -); - -export const PostPermissions = Stacker( - GetClientAuthMiddleware(true), - async (req: Request, res: Response) => { - const { permission, uid } = req.body; - - const user = await User.findOne({ uid }); - if (!user) { - throw new RequestError("User not found!", HttpStatusCode.BAD_REQUEST); - } - - const permissionDoc = await Permission.findById(permission); - if (!permissionDoc || !permissionDoc.client.equals(req.client._id)) { - throw new RequestError( - "Permission not found!", - HttpStatusCode.BAD_REQUEST - ); - } - - let grant = await Grant.findOne({ - client: req.client._id, - user: req.user._id, - }); - - if (!grant) { - grant = Grant.new({ - client: req.client._id, - user: req.user._id, - permissions: [], - }); - } - - //TODO: Fix clients getting user data without consent, when a grant is created and no additional permissions are requested, since for now, it is only checked for grant existance to make client access user data - - if (grant.permissions.indexOf(permission) < 0) - grant.permissions.push(permission); - - await Grant.save(grant); - - res.json({ - success: true, - }); - } -); +import { Request, Response } from "express"; +import Stacker from "../middlewares/stacker"; +import { + ClientAuthMiddleware, + GetClientAuthMiddleware, +} from "../middlewares/client"; +import Permission from "../../models/permissions"; +import User from "../../models/user"; + +import RequestError, { HttpStatusCode } from "../../helper/request_error"; +import Grant from "../../models/grants"; +import { ObjectId } from "mongodb"; + +export const GetPermissions = Stacker( + GetClientAuthMiddleware(true), + async (req: Request, res: Response) => { + const { user, permission } = req.query as { [key: string]: string }; + + let permissions: { id: string; name: string; description: string }[]; + let users: string[]; + + if (user) { + const grant = await Grant.findOne({ + client: req.client._id, + user: new ObjectId(user), + }); + + permissions = await Promise.all( + grant.permissions.map((perm) => Permission.findById(perm)) + ).then((res) => + res + .filter((e) => e.grant_type === "client") + .map((e) => { + return { + id: e._id.toHexString(), + name: e.name, + description: e.description, + }; + }) + ); + } + + if (permission) { + const grants = await Grant.find({ + client: req.client._id, + permissions: new ObjectId(permission), + }); + + users = grants.map((grant) => grant.user.toHexString()); + } + + res.json({ permissions, users }); + } +); + +export const PostPermissions = Stacker( + GetClientAuthMiddleware(true), + async (req: Request, res: Response) => { + const { permission, uid } = req.body; + + const user = await User.findOne({ uid }); + if (!user) { + throw new RequestError("User not found!", HttpStatusCode.BAD_REQUEST); + } + + const permissionDoc = await Permission.findById(permission); + if (!permissionDoc || !permissionDoc.client.equals(req.client._id)) { + throw new RequestError( + "Permission not found!", + HttpStatusCode.BAD_REQUEST + ); + } + + let grant = await Grant.findOne({ + client: req.client._id, + user: req.user._id, + }); + + if (!grant) { + grant = Grant.new({ + client: req.client._id, + user: req.user._id, + permissions: [], + }); + } + + //TODO: Fix clients getting user data without consent, when a grant is created and no additional permissions are requested, since for now, it is only checked for grant existance to make client access user data + + if (grant.permissions.indexOf(permission) < 0) + grant.permissions.push(permission); + + await Grant.save(grant); + + res.json({ + success: true, + }); + } +); diff --git a/Backend/src/api/index.ts b/Backend/src/api/index.ts index 09db7a4..09c6015 100644 --- a/Backend/src/api/index.ts +++ b/Backend/src/api/index.ts @@ -1,54 +1,50 @@ -import * as express from "express"; -import AdminRoute from "./admin"; -import UserRoute from "./user"; -import InternalRoute from "./internal"; -import Login from "./user/login"; -import ClientRouter from "./client"; -import * as cors from "cors"; -import OAuthRoute from "./oauth"; -import config from "../config"; -import JRPCEndpoint from "./jrpc"; - -const ApiRouter: express.IRouter = express.Router(); -ApiRouter.use("/admin", AdminRoute); -ApiRouter.use(cors()); -ApiRouter.use("/user", UserRoute); -ApiRouter.use("/internal", InternalRoute); -ApiRouter.use("/oauth", OAuthRoute); - -ApiRouter.use("/client", ClientRouter); - -/** - * @api {post} /jrpc - * @apiName InternalJRPCEndpoint - * - * @apiGroup user - * @apiPermission none - * - * @apiErrorExample {Object} Error-Response: - { - error: [ - { - message: "Some Error", - field: "username" - } - ], - status: 400 - } - */ -ApiRouter.post("/jrpc", JRPCEndpoint); - -// Legacy reasons (deprecated) -ApiRouter.use("/", ClientRouter); - -// Legacy reasons (deprecated) -ApiRouter.post("/login", Login); - -ApiRouter.get("/config.json", (req, res) => { - return res.json({ - name: config.core.name, - url: config.core.url, - }); -}); - -export default ApiRouter; +import * as express from "express"; +import AdminRoute from "./admin"; +import UserRoute from "./user"; +import InternalRoute from "./internal"; +import ClientRouter from "./client"; +import cors from "cors"; +import OAuthRoute from "./oauth"; +import config from "../config"; +import JRPCEndpoint from "./jrpc"; + +const ApiRouter: express.IRouter = express.Router(); +ApiRouter.use("/admin", AdminRoute); +ApiRouter.use(cors()); +ApiRouter.use("/user", UserRoute); +ApiRouter.use("/internal", InternalRoute); +ApiRouter.use("/oauth", OAuthRoute); + +ApiRouter.use("/client", ClientRouter); + +/** + * @api {post} /jrpc + * @apiName InternalJRPCEndpoint + * + * @apiGroup user + * @apiPermission none + * + * @apiErrorExample {Object} Error-Response: + { + error: [ + { + message: "Some Error", + field: "username" + } + ], + status: 400 + } + */ +ApiRouter.post("/jrpc", JRPCEndpoint); + +// Legacy reasons (deprecated) +ApiRouter.use("/", ClientRouter); + +ApiRouter.get("/config.json", (req, res) => { + return res.json({ + name: config.core.name, + url: config.core.url, + }); +}); + +export default ApiRouter; diff --git a/Backend/src/api/internal/oauth.ts b/Backend/src/api/internal/oauth.ts index 0647f42..1ebc5c4 100644 --- a/Backend/src/api/internal/oauth.ts +++ b/Backend/src/api/internal/oauth.ts @@ -1,41 +1,39 @@ -import { Request, Response, NextFunction } from "express"; -import Stacker from "../middlewares/stacker"; -import { GetClientAuthMiddleware } from "../middlewares/client"; -import { UserMiddleware } from "../middlewares/user"; -import RequestError, { HttpStatusCode } from "../../helper/request_error"; -import ClientCode from "../../models/client_code"; -import moment = require("moment"); -import { randomBytes } from "crypto"; -export const OAuthInternalApp = Stacker( - GetClientAuthMiddleware(false, true), - UserMiddleware, - async (req: Request, res: Response) => { - let { redirect_uri, state } = req.query as { [key: string]: string }; - if (!redirect_uri) { - throw new RequestError( - "No redirect url set!", - HttpStatusCode.BAD_REQUEST - ); - } - - let sep = redirect_uri.indexOf("?") < 0 ? "?" : "&"; - - let code = ClientCode.new({ - user: req.user._id, - client: req.client._id, - validTill: moment().add(30, "minutes").toDate(), - code: randomBytes(16).toString("hex"), - permissions: [], - }); - await ClientCode.save(code); - - res.redirect( - redirect_uri + - sep + - "code=" + - code.code + - (state ? "&state=" + state : "") - ); - res.end(); - } -); +import { Request, Response, NextFunction } from "express"; +import Stacker from "../middlewares/stacker"; +import { GetClientAuthMiddleware } from "../middlewares/client"; +import { UserMiddleware } from "../middlewares/user"; +import RequestError, { HttpStatusCode } from "../../helper/request_error"; +import ClientCode from "../../models/client_code"; +import moment = require("moment"); +import { randomBytes } from "crypto"; +export const OAuthInternalApp = Stacker( + GetClientAuthMiddleware(false, true), + UserMiddleware, + async (req: Request, res: Response) => { + let { redirect_uri, state } = req.query as { [key: string]: string }; + if (!redirect_uri) { + throw new RequestError( + "No redirect url set!", + HttpStatusCode.BAD_REQUEST + ); + } + + let redurl = new URL(redirect_uri); + + let code = ClientCode.new({ + user: req.user._id, + client: req.client._id, + validTill: moment().add(30, "minutes").toDate(), + code: randomBytes(16).toString("hex"), + permissions: [], + }); + await ClientCode.save(code); + + redurl.searchParams.set("code", code.code); + if (state) + redurl.searchParams.set("state", state); + + res.redirect(redurl.href); + res.end(); + } +); diff --git a/Backend/src/api/jrpc/index.ts b/Backend/src/api/jrpc/index.ts index 8e286cc..711df8f 100644 --- a/Backend/src/api/jrpc/index.ts +++ b/Backend/src/api/jrpc/index.ts @@ -1,45 +1,38 @@ -import { Request, Response } from "express"; -import Stacker from "../middlewares/stacker"; -import { GetUserMiddleware } from "../middlewares/user"; -import { IUser } from "../../models/user"; -import { Server } from "@hibas123/openauth-internalapi"; -import AccountService from "./account_service"; -import SecurityService from "./security_service"; -import { ILoginToken } from "../../models/login_token"; - -export interface SessionContext { - user: IUser, - request: Request, - isAdmin: boolean, - special: boolean, - token: { - login: ILoginToken, - special?: ILoginToken, - } -} - -const provider = new Server.ServiceProvider(); -provider.addService(new AccountService()); -provider.addService(new SecurityService()); - -const JRPCEndpoint = Stacker( - GetUserMiddleware(true, true), - async (req: Request, res: Response) => { - const session = provider.getSession((data) => { - res.json(data); - }, { - user: req.user, - request: req, - isAdmin: req.isAdmin, - special: req.special, - token: { - login: req.token.login, - special: req.token.special, - } - }); - - session.onMessage(req.body); - } -); - -export default JRPCEndpoint; +import { Format } from "@hibas123/logging"; +import Logging from "@hibas123/nodelogging"; +import { Server, } from "@hibas123/openauth-internalapi"; +import { RequestObject, ResponseObject } from "@hibas123/openauth-internalapi/lib/service_base"; +import { Request, Response } from "express"; +import Stacker from "../middlewares/stacker"; +import AccountService from "./services/account"; +import LoginService from "./services/login"; +import SecurityService from "./services/security"; +import TFAService from "./services/twofactor"; + +export type SessionContext = Request; + +const provider = new Server.ServiceProvider(); +provider.addService(new AccountService()); +provider.addService(new SecurityService()); +provider.addService(new TFAService()); +provider.addService(new LoginService()); + +const JRPCEndpoint = Stacker( + async (req: Request, res: Response) => { + let jrpcreq = req.body as RequestObject; + let startTime = process.hrtime.bigint(); + const session = provider.getSession((data: ResponseObject) => { + let time = process.hrtime.bigint() - startTime; + let state = data.error ? Format.red(`err(${data.error.message})`) : Format.green("OK"); + + Logging.getChild("JRPC").log(jrpcreq.method, state, "-", (Number(time / 10000n) / 100) + "ms"); + + res.json(data); + }, req); + + + session.onMessage(jrpcreq); + } +); + +export default JRPCEndpoint; diff --git a/Backend/src/api/jrpc/security_service.ts b/Backend/src/api/jrpc/security_service.ts deleted file mode 100644 index 3360ba8..0000000 --- a/Backend/src/api/jrpc/security_service.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Server, Token, TwoFactor, UserRegisterInfo } from "@hibas123/openauth-internalapi"; -import type { SessionContext } from "./index"; -import LoginToken, { CheckToken } from "../../models/login_token"; -import TwoFactorModel from "../../models/twofactor"; -import moment = require("moment"); - -export default class SecurityService extends Server.SecurityService { - async GetTokens(ctx: SessionContext): Promise { - if (!ctx.user) throw new Error("Not logged in"); - - let raw_token = await LoginToken.find({ - user: ctx.user._id, - valid: true, - }); - let token = await Promise.all( - raw_token - .map>(async (token) => { - await CheckToken(token); - return { - id: token._id.toString(), - special: token.special, - ip: token.ip, - browser: token.browser, - isthis: token._id.equals( - token.special ? ctx.token.special._id : ctx.token.login._id - ), - }; - }) - .filter((t) => t !== undefined) - ); - - return token - } - async RevokeToken(id: string, ctx: SessionContext): Promise { - if (!ctx.user) throw new Error("Not logged in"); - - let token = await LoginToken.findById(id); - if (!token || !token.user.equals(ctx.user._id)) - throw new Error("Invalid ID"); - token.valid = false; - await LoginToken.save(token); - } - - async GetTwofactorOptions(ctx: SessionContext): Promise { - if (!ctx.user) throw new Error("Not logged in"); - - - let twofactor = await TwoFactorModel.find({ user: ctx.user._id, valid: true }); - let expired = twofactor.filter((e) => - e.expires ? moment().isAfter(moment(e.expires)) : false - ); - await Promise.all( - expired.map((e) => { - e.valid = false; - return TwoFactorModel.save(e); - }) - ); - - twofactor = twofactor.filter((e) => e.valid); - let tfa = twofactor.map((e) => { - return { - id: e._id.toString(), - name: e.name, - tfatype: e.type as number, - expires: e.expires?.valueOf() - }; - }); - - return tfa; - } -} diff --git a/Backend/src/api/jrpc/account_service.ts b/Backend/src/api/jrpc/services/account.ts similarity index 70% rename from Backend/src/api/jrpc/account_service.ts rename to Backend/src/api/jrpc/services/account.ts index f109456..4c0d22e 100644 --- a/Backend/src/api/jrpc/account_service.ts +++ b/Backend/src/api/jrpc/services/account.ts @@ -1,49 +1,52 @@ -import { Account, ContactInfo, Gender, Server, UserRegisterInfo } from "@hibas123/openauth-internalapi"; -import type { SessionContext } from "./index"; -import Mail from "../../models/mail"; -import User from "../../models/user"; - -export default class AccountService extends Server.AccountService { - - Register(regcode: string, info: UserRegisterInfo, ctx: SessionContext): Promise { - throw new Error("Method not implemented."); - } - - async GetProfile(ctx: SessionContext): Promise { - if (!ctx.user) throw new Error("Not logged in"); - - - return { - id: ctx.user.uid, - username: ctx.user.username, - name: ctx.user.name, - birthday: ctx.user.birthday.valueOf(), - gender: ctx.user.gender as number as Gender, - } - } - - async UpdateProfile(info: Account, ctx: SessionContext): Promise { - if (!ctx.user) throw new Error("Not logged in"); - - ctx.user.name = info.name; - ctx.user.birthday = new Date(info.birthday); - ctx.user.gender = info.gender as number; - - await User.save(ctx.user); - } - - async GetContactInfos(ctx: SessionContext): Promise { - if (!ctx.user) throw new Error("Not logged in"); - - let mails = await Promise.all( - ctx.user.mails.map((mail) => Mail.findById(mail)) - ); - - let contact = { - mail: mails.filter((e) => !!e), - phone: ctx.user.phones, - }; - - return contact; - } -} +import { Profile, ContactInfo, Gender, Server, UserRegisterInfo } from "@hibas123/openauth-internalapi"; +import type { SessionContext } from "../index"; +import Mail from "../../../models/mail"; +import User from "../../../models/user"; +import { RequireLogin } from "../../../helper/login"; + +export default class AccountService extends Server.AccountService { + Register(regcode: string, info: UserRegisterInfo, ctx: SessionContext): Promise { + throw new Error("Method not implemented."); + } + + @RequireLogin() + async GetProfile(ctx: SessionContext): Promise { + if (!ctx.user) throw new Error("Not logged in"); + + + return { + id: ctx.user.uid, + username: ctx.user.username, + name: ctx.user.name, + birthday: ctx.user.birthday.valueOf(), + gender: ctx.user.gender as number as Gender, + } + } + + @RequireLogin() + async UpdateProfile(info: Profile, ctx: SessionContext): Promise { + if (!ctx.user) throw new Error("Not logged in"); + + ctx.user.name = info.name; + ctx.user.birthday = new Date(info.birthday); + ctx.user.gender = info.gender as number; + + await User.save(ctx.user); + } + + @RequireLogin() + async GetContactInfos(ctx: SessionContext): Promise { + if (!ctx.user) throw new Error("Not logged in"); + + let mails = await Promise.all( + ctx.user.mails.map((mail) => Mail.findById(mail)) + ); + + let contact = { + mail: mails.filter((e) => !!e), + phone: ctx.user.phones, + }; + + return contact; + } +} diff --git a/Backend/src/api/jrpc/services/login.ts b/Backend/src/api/jrpc/services/login.ts new file mode 100644 index 0000000..cca691b --- /dev/null +++ b/Backend/src/api/jrpc/services/login.ts @@ -0,0 +1,265 @@ +import { Server, LoginState, TFAOption, TFAType } from "@hibas123/openauth-internalapi"; +import type { SessionContext } from "../index"; +import Logging from "@hibas123/nodelogging"; +import User, { IUser } from "../../../models/user"; +import moment from "moment"; +import crypto from "node:crypto"; +import TwoFactor, { ITwoFactor, IWebAuthn } from "../../../models/twofactor"; +import speakeasy from "speakeasy"; +import { generateAuthenticationOptions, verifyAuthenticationResponse } from "@simplewebauthn/server"; +import config from "../../../config"; + +//FIXME: There are a lot of uneccessary database requests happening here. Since this is not a "hot" path, it should not matter to much, but it should be fixed nontheless. + +export default class LoginService extends Server.LoginService { + private async getUser(username: string): Promise { + let user = await User.findOne({ username: username.toLowerCase() }); + if (!user) { + throw new Error("User not found"); + } + return user; + } + + private async getLoginState(ctx: SessionContext): Promise { + if (ctx.user && ctx.session.validated) { + return { + success: true + } + } else if (ctx.session.login_state) { + //TODO: Check login_state expiration or so + if (ctx.session.login_state.username) { + let user = await this.getUser(ctx.session.login_state.username); + if (!ctx.session.login_state.password_correct) { + let passwordSalt = user.salt; + return { + success: false, + username: ctx.session.login_state.username, + password: false, + passwordSalt: passwordSalt, + } + } else { + let tfa = await this.getTwoFactors(await this.getUser(ctx.session.login_state.username)) + if (tfa.length <= 0) { + ctx.session.user_id = user._id.toString(); + ctx.session.login_state = undefined; + Logging.warn("This should have been set somewhere else!"); + return { + success: true, + } + } else { + return { + success: false, + username: ctx.session.login_state.username, + password: true, + requireTwoFactor: tfa, + } + } + } + } + } else { + return { + success: false, + username: undefined, + password: false, + } + } + } + + private async getTwoFactors(user: IUser): Promise { + let twofactors = await TwoFactor.find({ + user: user._id, + valid: true + }) + + return twofactors.map(tf => { + return { + id: tf._id.toString(), + name: tf.name, + tfatype: tf.type as number, + } + }) + + } + + private async enableSession(ctx: SessionContext) { + let user = await this.getUser(ctx.session.login_state.username); + ctx.user = user; + ctx.session.user_id = user._id.toString(); + ctx.session.login_state = undefined; + ctx.session.validated = true; + } + + GetState(ctx: SessionContext): Promise { + return this.getLoginState(ctx); + } + + async Start(username: string, ctx: SessionContext): Promise { + let user = await this.getUser(username); + + ctx.session.login_state = { + username: username, + password_correct: false, + } + + return this.getLoginState(ctx); + } + + async UsePassword(password_hash: string, date: number, ctx: SessionContext): Promise { + if (!ctx.session.login_state) { + throw new Error("No login state. Call Start() first."); + } + + let user = await this.getUser(ctx.session.login_state.username); + + if (date <= 0) { + if (user.password !== password_hash) { + throw new Error("Password incorrect"); + } + } else { + if ( + !moment(date).isBetween( + moment().subtract(1, "minute"), + moment().add(1, "minute") + ) + ) { + throw new Error("Date incorrect. Please check your devices time!"); + } else { + let upw = crypto + .createHash("sha512") + .update(user.password + date.toString()) + .digest("hex"); + + if (upw !== password_hash) { + throw new Error("Password incorrect"); + } + } + } + + ctx.session.login_state.password_correct = true; + + let tfas = await this.getTwoFactors(user); + if (tfas.length <= 0) { + await this.enableSession(ctx); + } + + return this.getLoginState(ctx); + } + + + private async getAndCheckTFA(id: string, shouldType: TFAType, ctx: SessionContext): Promise { + if (!ctx.session.login_state) { + throw new Error("No login state. Call Start() first."); + } + + let user = await this.getUser(ctx.session.login_state.username); + + let tfa = await TwoFactor.findById(id); + if (!tfa || tfa.user.toString() != user._id.toString()) { + throw new Error("Two factor not found"); + } + + if (tfa.type != shouldType as number) { + throw new Error("Two factor is not the correct type!"); + } + + if (!tfa.valid) { + throw new Error("Two factor is not valid"); + } + + if (tfa.expires && moment().isAfter(moment(tfa.expires))) { + throw new Error("Two factor is expired"); + } + + return tfa as T; + } + + async UseTOTP(id: string, code: string, ctx: SessionContext): Promise { + let tfa = await this.getAndCheckTFA(id, TFAType.TOTP, ctx); + + let valid = speakeasy.totp.verify({ + secret: tfa.data, + encoding: "base32", + token: code, + }); + + if (!valid) { + throw new Error("Code incorrect"); + } + + await this.enableSession(ctx); + + return this.getLoginState(ctx); + } + async UseBackupCode(id: string, code: string, ctx: SessionContext): Promise { + let tfa = await this.getAndCheckTFA(id, TFAType.BACKUP_CODE, ctx); + + if (tfa.data.indexOf(code) < 0) { + throw new Error("Code incorrect"); + } + + tfa.data = tfa.data.filter(c => c != code); + + await TwoFactor.save(tfa); + //TODO: handle the case where the last backup code is used + + await this.enableSession(ctx); + + return this.getLoginState(ctx); + } + + async GetWebAuthnChallenge(id: string, ctx: SessionContext): Promise { + let tfa = await this.getAndCheckTFA(id, TFAType.WEBAUTHN, ctx); + + const rpID = new URL(config.core.url).hostname; + + let options = generateAuthenticationOptions({ + timeout: 60000, + userVerification: "discouraged", + rpID, + allowCredentials: [{ + id: tfa.data.device.credentialID.buffer, + type: "public-key", + transports: tfa.data.device.transports + }] + }) + + ctx.session.login_state.webauthn_challenge = options.challenge; + + Logging.debug("Challenge", options, tfa, tfa.data.device.credentialID); + + return JSON.stringify(options); + } + + async UseWebAuthn(id: string, response: string, ctx: SessionContext): Promise { + let tfa = await this.getAndCheckTFA(id, TFAType.WEBAUTHN, ctx); + + if (!ctx.session.login_state.webauthn_challenge) { + throw new Error("No webauthn challenge"); + } + + let rpID = new URL(config.core.url).hostname; + + let verification = await verifyAuthenticationResponse({ + response: JSON.parse(response), + authenticator: { + counter: tfa.data.device.counter, + credentialID: tfa.data.device.credentialID.buffer, + credentialPublicKey: tfa.data.device.credentialPublicKey.buffer, + transports: tfa.data.device.transports + }, + expectedChallenge: ctx.session.login_state.webauthn_challenge, + expectedOrigin: config.core.url, + expectedRPID: rpID, + requireUserVerification: false + }) + + if (verification.verified) { + tfa.data.device.counter = verification.authenticationInfo.newCounter; + await TwoFactor.save(tfa); + } + + await this.enableSession(ctx); + + return this.getLoginState(ctx); + } +} diff --git a/Backend/src/api/jrpc/services/security.ts b/Backend/src/api/jrpc/services/security.ts new file mode 100644 index 0000000..3bc7768 --- /dev/null +++ b/Backend/src/api/jrpc/services/security.ts @@ -0,0 +1,35 @@ +import { Server, Session } from "@hibas123/openauth-internalapi"; +import type { SessionContext } from "../index"; +import Logging from "@hibas123/nodelogging"; +import { RequireLogin } from "../../../helper/login"; +import crypto from "node:crypto"; +import User from "../../../models/user"; + +export default class SecurityService extends Server.SecurityService { + @RequireLogin() + async GetSessions(ctx: SessionContext): Promise { + return [] + throw new Error("Method not implemented."); + } + @RequireLogin() + async RevokeSession(id: string, ctx: SessionContext): Promise { + throw new Error("Method not implemented."); + } + + @RequireLogin() + async ChangePassword(old_pw: string, new_pw: string, ctx: SessionContext): Promise { + let old_pw_hash = crypto.createHash("sha512").update(ctx.user.salt + old_pw).digest("hex"); + + if (old_pw_hash != ctx.user.password) { + throw new Error("Wrong password"); + } + + let salt = crypto.randomBytes(32).toString("base64"); + let password_hash = crypto.createHash("sha512").update(salt + new_pw).digest("hex"); + + ctx.user.salt = salt; + ctx.user.password = password_hash; + + await User.save(ctx.user); + } +} diff --git a/Backend/src/api/jrpc/services/twofactor.ts b/Backend/src/api/jrpc/services/twofactor.ts new file mode 100644 index 0000000..20ac123 --- /dev/null +++ b/Backend/src/api/jrpc/services/twofactor.ts @@ -0,0 +1,194 @@ +import { TFANewTOTP, Server, TFAOption, UserRegisterInfo, TFAWebAuthRegister } from "@hibas123/openauth-internalapi"; +import type { SessionContext } from "../index"; +import TwoFactorModel, { ITOTP, IWebAuthn, TFATypes } from "../../../models/twofactor"; +import moment = require("moment"); +import * as speakeasy from "speakeasy"; +import * as qrcode from "qrcode"; +import config from "../../../config"; +import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server'; +import type { RegistrationResponseJSON } from '@simplewebauthn/typescript-types'; +import Logging from "@hibas123/nodelogging"; +import { Binary } from "mongodb"; +import { RequireLogin } from "../../../helper/login"; + + +export default class TFAService extends Server.TFAService { + @RequireLogin() + AddBackupCodes(name: string, ctx: SessionContext): Promise { + throw new Error("Method not implemented."); + } + + @RequireLogin() + RemoveBackupCodes(id: string, ctx: SessionContext): Promise { + throw new Error("Method not implemented."); + } + + @RequireLogin() + async GetOptions(ctx: SessionContext): Promise { + let twofactor = await TwoFactorModel.find({ user: ctx.user._id, valid: true }); + let expired = twofactor.filter((e) => + e.expires ? moment().isAfter(moment(e.expires)) : false + ); + await Promise.all( + expired.map((e) => { + e.valid = false; + return TwoFactorModel.save(e); + }) + ); + + twofactor = twofactor.filter((e) => e.valid); + let tfa = twofactor.map((e) => { + return { + id: e._id.toString(), + name: e.name, + tfatype: e.type as number, + expires: e.expires?.valueOf() + }; + }); + + return tfa; + } + + @RequireLogin() + async Delete(id: string, ctx: SessionContext): Promise { + let twofactor = await TwoFactorModel.findById(id); + if (!twofactor || !twofactor.user.equals(ctx.user._id)) + throw new Error("Invalid ID"); + + twofactor.valid = false; + await TwoFactorModel.save(twofactor); + } + + @RequireLogin() + async AddTOTP(name: string, ctx: SessionContext): Promise { + //Generating new + let secret = speakeasy.generateSecret({ + name: config.core.name, + issuer: config.core.name, + otpauth_url: true + }); + + let twofactor = TwoFactorModel.new({ + name: name, + user: ctx.user._id, + type: TFATypes.TOTP, + valid: false, + data: secret.base32, + }); + + let dataurl = await qrcode.toDataURL(secret.otpauth_url); + await TwoFactorModel.save(twofactor); + + return { + id: twofactor._id.toString(), + qr: dataurl, + secret: secret.base32 + } + } + + @RequireLogin() + async VerifyTOTP(id: string, code: string, ctx: SessionContext): Promise { + let twofactor = await TwoFactorModel.findById(id); + if (!twofactor || !twofactor.user.equals(ctx.user._id)) + throw new Error("Invalid ID"); + + let verified = speakeasy.totp.verify({ + secret: twofactor.data, + encoding: "base32", + token: code, + }); + + if (!verified) throw new Error("Invalid code"); + + twofactor.valid = true; + twofactor.expires = undefined; + + await TwoFactorModel.save(twofactor); + } + + @RequireLogin() + async AddWebauthn(name: string, ctx: SessionContext): Promise { + // TODO: Get already registered options + + const rpID = new URL(config.core.url).hostname; + const options = generateRegistrationOptions({ + rpName: config.core.name, + rpID, + userID: ctx.user.uid, + userName: ctx.user.username, + attestationType: 'direct', + userDisplayName: ctx.user.name, + excludeCredentials: [], + authenticatorSelection: { + userVerification: "required", + requireResidentKey: false, + residentKey: "discouraged", + authenticatorAttachment: "cross-platform" + } + }) + + + const twofactor = TwoFactorModel.new({ + name, + type: TFATypes.WEBAUTHN, + user: ctx.user._id, + valid: false, + data: { + challenge: options.challenge + } + }); + + await TwoFactorModel.save(twofactor); + + Logging.debug(twofactor); + + return { + id: twofactor._id.toString(), + challenge: JSON.stringify(options) + }; + } + + @RequireLogin() + async VerifyWebAuthn(id: string, registration: string, ctx: SessionContext): Promise { + let twofactor = await TwoFactorModel.findById(id) as IWebAuthn; + if (!twofactor || !twofactor.user.equals(ctx.user._id)) + throw new Error("Invalid ID"); + + const rpID = new URL(config.core.url).hostname; + + const response = JSON.parse(registration) as RegistrationResponseJSON; + + let verification = await verifyRegistrationResponse({ + response, + expectedChallenge: twofactor.data.challenge, + expectedOrigin: config.core.url, + expectedRPID: rpID, + requireUserVerification: true, + }); + + if (verification.verified) { + const { credentialPublicKey, credentialID, counter } = verification.registrationInfo; + + //TODO: Check if already registered! + // TwoFactorModel.find({ + // data: { + // } + // }) + + twofactor.data = { + device: { + credentialPublicKey: new Binary(credentialPublicKey), + credentialID: new Binary(credentialID), + counter: verification.registrationInfo.counter, + transports: response.response.transports as any[] + } + } + + twofactor.valid = true; + + await TwoFactorModel.save(twofactor); + } else { + throw new Error("Invalid response"); + } + } +} diff --git a/Backend/src/api/middlewares/user.ts b/Backend/src/api/middlewares/user.ts index c073083..d81507e 100644 --- a/Backend/src/api/middlewares/user.ts +++ b/Backend/src/api/middlewares/user.ts @@ -1,106 +1,70 @@ -import { NextFunction, Request, Response } from "express"; -import LoginToken, { CheckToken } from "../../models/login_token"; -import Logging from "@hibas123/nodelogging"; -import RequestError, { HttpStatusCode } from "../../helper/request_error"; -import User from "../../models/user"; -import promiseMiddleware from "../../helper/promiseMiddleware"; - -class Invalid extends Error {} - -/** - * Returns customized Middleware function, that could also be called directly - * by code and will return true or false depending on the token. In the false - * case it will also send error and redirect if json is not set - * @param json Default false. Checks if requests wants an json or html for returning errors - * @param special_required Default false. If true, a special token is required - * @param redirect_uri Default current uri. Sets the uri to redirect, if json is not set and user not logged in - * @param validated Default true. If false, the token must not be validated - */ -export function GetUserMiddleware( - json = false, - special_required: boolean = false, - redirect_uri?: string, - validated = true -) { - return promiseMiddleware(async function ( - req: Request, - res: Response, - next?: NextFunction - ) { - const invalid = (message: string) => { - throw new Invalid(req.__(message)); - }; - try { - let { login, special } = req.query as { [key: string]: string }; - if (!login) { - login = req.cookies.login; - special = req.cookies.special; - } - if (!login) invalid("No login token"); - if (!special && special_required) invalid("No special token"); - - let token = await LoginToken.findOne({ token: login, valid: true }); - if (!(await CheckToken(token, validated))) - invalid("Login token invalid"); - - let user = await User.findById(token.user); - if (!user) { - token.valid = false; - await LoginToken.save(token); - invalid("Login token invalid"); - } - - let special_token; - if (special) { - Logging.debug("Special found"); - special_token = await LoginToken.findOne({ - token: special, - special: true, - valid: true, - user: token.user, - }); - if (!(await CheckToken(special_token, validated))) - invalid("Special token invalid"); - req.special = true; - } - - req.user = user; - req.isAdmin = user.admin; - req.token = { - login: token, - special: special_token, - }; - - if (next) next(); - return true; - } catch (e) { - if (e instanceof Invalid) { - if (req.method === "GET" && !json) { - res.status(HttpStatusCode.UNAUTHORIZED); - res.redirect( - "/login?base64=true&state=" + - Buffer.from( - redirect_uri ? redirect_uri : req.originalUrl - ).toString("base64") - ); - } else { - throw new RequestError( - req.__( - "You are not logged in or your login is expired" + - ` (${e.message})` - ), - HttpStatusCode.UNAUTHORIZED, - undefined, - { auth: true } - ); - } - } else { - if (next) next(e); - else throw e; - } - return false; - } - }); -} - -export const UserMiddleware = GetUserMiddleware(); +import { NextFunction, Request, Response } from "express"; +import Logging from "@hibas123/nodelogging"; +import RequestError, { HttpStatusCode } from "../../helper/request_error"; +import promiseMiddleware from "../../helper/promiseMiddleware"; +import { requireLoginState } from "../../helper/login"; + +class Invalid extends Error { } + +/** + * Returns customized Middleware function, that could also be called directly + * by code and will return true or false depending on the token. In the false + * case it will also send error and redirect if json is not set + * @param json Default false. Checks if requests wants an json or html for returning errors + * @param special_required Default false. If true, a special token is required + * @param redirect_uri Default current uri. Sets the uri to redirect, if json is not set and user not logged in + * @param validated Default true. If false, the token must not be validated + */ +export function GetUserMiddleware( + json = false, + special_required: boolean = false, + redirect_uri?: string, + validated = true +) { + return promiseMiddleware(async function ( + req: Request, + res: Response, + next?: NextFunction + ) { + const invalid = (message: string) => { + throw new Invalid(req.__(message)); + }; + try { + if (!requireLoginState(req, validated, special_required)) { + invalid("Not logged in"); + } + + if (next) next(); + return true; + } catch (e) { + Logging.getChild("UserMiddleware").warn(e); + if (e instanceof Invalid) { + if (req.method === "GET" && !json) { + res.status(HttpStatusCode.UNAUTHORIZED); + res.redirect( + "/login?base64=true&state=" + + Buffer.from( + redirect_uri ? redirect_uri : req.originalUrl + ).toString("base64") + ); + } else { + throw new RequestError( + req.__( + "You are not logged in or your login is expired" + + ` (${e.message})` + ), + HttpStatusCode.UNAUTHORIZED, + undefined, + { auth: true } + ); + } + } else { + if (next) next(e); + else throw e; + } + return false; + } + }); +} + +export const UserMiddleware = GetUserMiddleware(); diff --git a/Backend/src/api/oauth/auth.ts b/Backend/src/api/oauth/auth.ts index e413784..9881207 100644 --- a/Backend/src/api/oauth/auth.ts +++ b/Backend/src/api/oauth/auth.ts @@ -1,249 +1,249 @@ -import Stacker from "../middlewares/stacker"; -import { GetUserMiddleware } from "../middlewares/user"; -import { Request, Response } from "express"; -import Client from "../../models/client"; -import Logging from "@hibas123/nodelogging"; -import Permission, { IPermission } from "../../models/permissions"; -import ClientCode from "../../models/client_code"; -import moment = require("moment"); -import { randomBytes } from "crypto"; -// import { ObjectID } from "bson"; -import Grant, { IGrant } from "../../models/grants"; -import GetAuthPage from "../../views/authorize"; -import { ObjectID } from "mongodb"; - -// const AuthRoute = Stacker(GetUserMiddleware(true), async (req: Request, res: Response) => { -// let { response_type, client_id, redirect_uri, scope, state, nored } = req.query; -// const sendError = (type) => { -// if (redirect_uri === "$local") -// redirect_uri = "/code"; -// res.redirect(redirect_uri += `?error=${type}&state=${state}`); -// } -// /** -// * error -// REQUIRED. A single ASCII [USASCII] error code from the -// following: -// invalid_request -// The request is missing a required parameter, includes an -// invalid parameter value, includes a parameter more than -// once, or is otherwise malformed. -// unauthorized_client -// The client is not authorized to request an authorization -// code using this method. -// access_denied -// The resource owner or authorization server denied the -// request. -// */ -// try { - -// if (response_type !== "code") { -// return sendError("unsupported_response_type"); -// } else { -// let client = await Client.findOne({ client_id: client_id }) -// if (!client) { -// return sendError("unauthorized_client") -// } - -// if (redirect_uri && client.redirect_url !== redirect_uri) { -// Logging.log(redirect_uri, client.redirect_url); -// return res.send("Invalid redirect_uri. Please check the integrity of the site requesting and contact the administrator of the page, you want to authorize!"); -// } - -// let permissions: IPermission[] = []; -// if (scope) { -// let perms = (scope).split(";").filter(e => e !== "read_user").map(p => new ObjectID(p)); -// permissions = await Permission.find({ _id: { $in: perms } }) - -// if (permissions.length != perms.length) { -// return sendError("invalid_scope"); -// } -// } - -// let code = ClientCode.new({ -// user: req.user._id, -// client: client._id, -// permissions: permissions.map(p => p._id), -// validTill: moment().add(30, "minutes").toDate(), -// code: randomBytes(16).toString("hex") -// }); -// await ClientCode.save(code); - -// let redir = client.redirect_url === "$local" ? "/code" : client.redirect_url; - -// let ruri = redir + `?code=${code.code}&state=${state}`; -// if (nored === "true") { -// res.json({ -// redirect_uri: ruri -// }) -// } else { -// res.redirect(ruri); -// } -// } -// } catch (err) { -// Logging.error(err); -// sendError("server_error") -// } -// }) - -const GetAuthRoute = (view = false) => - Stacker(GetUserMiddleware(false), async (req: Request, res: Response) => { - let { - response_type, - client_id, - redirect_uri, - scope = "", - state, - nored, - } = req.query as { [key: string]: string }; - const sendError = (type) => { - if (redirect_uri === "$local") redirect_uri = "/code"; - res.redirect( - (redirect_uri += `?error=${type}${state ? "&state=" + state : ""}`) - ); - }; - - const scopes = scope.split(";").filter((e: string) => e !== ""); - - Logging.debug("Scopes:", scope); - - try { - if (response_type !== "code") { - return sendError("unsupported_response_type"); - } else { - let client = await Client.findOne({ client_id: client_id }); - if (!client) { - return sendError("unauthorized_client"); - } - - if (redirect_uri && client.redirect_url !== redirect_uri) { - Logging.log(redirect_uri, client.redirect_url); - return res.send( - "Invalid redirect_uri. Please check the integrity of the site requesting and contact the administrator of the page, you want to authorize!" - ); - } - - let permissions: IPermission[] = []; - let proms: PromiseLike[] = []; - if (scopes) { - for (let perm of scopes.filter((e) => e !== "read_user")) { - let oid = undefined; - try { - oid = new ObjectID(perm); - } catch (err) { - Logging.error(err); - continue; - } - proms.push( - Permission.findById(oid).then((p) => { - if (!p) return Promise.reject(new Error()); - permissions.push(p); - }) - ); - } - } - - let err = undefined; - await Promise.all(proms).catch((e) => { - err = e; - }); - - if (err) { - Logging.error(err); - return sendError("invalid_scope"); - } - - let grant: IGrant | undefined = await Grant.findOne({ - client: client._id, - user: req.user._id, - }); - - Logging.debug("Grant", grant, permissions); - - let missing_permissions: IPermission[] = []; - - if (grant) { - missing_permissions = grant.permissions - .map((perm) => permissions.find((p) => p._id.equals(perm))) - .filter((e) => !!e); - } else { - missing_permissions = permissions; - } - - let client_granted_perm = missing_permissions.filter( - (e) => e.grant_type == "client" - ); - if (client_granted_perm.length > 0) { - return sendError("no_permission"); - } - - if (!grant && missing_permissions.length > 0) { - await new Promise((yes, no) => - GetUserMiddleware(false, true)( - req, - res, - (err?: Error | string) => (err ? no(err) : yes()) - ) - ); // Maybe unresolved when redirect is happening - - if (view) { - res.send( - GetAuthPage( - req.__, - client.name, - permissions.map((perm) => { - return { - name: perm.name, - description: perm.description, - logo: client.logo, - }; - }) - ) - ); - return; - } else { - if ((req.body.allow = "true")) { - if (!grant) - grant = Grant.new({ - client: client._id, - user: req.user._id, - permissions: [], - }); - - grant.permissions.push( - ...missing_permissions.map((e) => e._id) - ); - await Grant.save(grant); - } else { - return sendError("access_denied"); - } - } - } - - let code = ClientCode.new({ - user: req.user._id, - client: client._id, - permissions: permissions.map((p) => p._id), - validTill: moment().add(30, "minutes").toDate(), - code: randomBytes(16).toString("hex"), - }); - await ClientCode.save(code); - - let redir = - client.redirect_url === "$local" ? "/code" : client.redirect_url; - let ruri = - redir + `?code=${code.code}${state ? "&state=" + state : ""}`; - if (nored === "true") { - res.json({ - redirect_uri: ruri, - }); - } else { - res.redirect(ruri); - } - } - } catch (err) { - Logging.error(err); - sendError("server_error"); - } - }); - -export default GetAuthRoute; +import Stacker from "../middlewares/stacker"; +import { GetUserMiddleware } from "../middlewares/user"; +import { Request, Response } from "express"; +import Client from "../../models/client"; +import Logging from "@hibas123/nodelogging"; +import Permission, { IPermission } from "../../models/permissions"; +import ClientCode from "../../models/client_code"; +import moment = require("moment"); +import { randomBytes } from "crypto"; +// import { ObjectId } from "bson"; +import Grant, { IGrant } from "../../models/grants"; +import GetAuthPage from "../../views/authorize"; +import { ObjectId } from "mongodb"; + +// const AuthRoute = Stacker(GetUserMiddleware(true), async (req: Request, res: Response) => { +// let { response_type, client_id, redirect_uri, scope, state, nored } = req.query; +// const sendError = (type) => { +// if (redirect_uri === "$local") +// redirect_uri = "/code"; +// res.redirect(redirect_uri += `?error=${type}&state=${state}`); +// } +// /** +// * error +// REQUIRED. A single ASCII [USASCII] error code from the +// following: +// invalid_request +// The request is missing a required parameter, includes an +// invalid parameter value, includes a parameter more than +// once, or is otherwise malformed. +// unauthorized_client +// The client is not authorized to request an authorization +// code using this method. +// access_denied +// The resource owner or authorization server denied the +// request. +// */ +// try { + +// if (response_type !== "code") { +// return sendError("unsupported_response_type"); +// } else { +// let client = await Client.findOne({ client_id: client_id }) +// if (!client) { +// return sendError("unauthorized_client") +// } + +// if (redirect_uri && client.redirect_url !== redirect_uri) { +// Logging.log(redirect_uri, client.redirect_url); +// return res.send("Invalid redirect_uri. Please check the integrity of the site requesting and contact the administrator of the page, you want to authorize!"); +// } + +// let permissions: IPermission[] = []; +// if (scope) { +// let perms = (scope).split(";").filter(e => e !== "read_user").map(p => new ObjectId(p)); +// permissions = await Permission.find({ _id: { $in: perms } }) + +// if (permissions.length != perms.length) { +// return sendError("invalid_scope"); +// } +// } + +// let code = ClientCode.new({ +// user: req.user._id, +// client: client._id, +// permissions: permissions.map(p => p._id), +// validTill: moment().add(30, "minutes").toDate(), +// code: randomBytes(16).toString("hex") +// }); +// await ClientCode.save(code); + +// let redir = client.redirect_url === "$local" ? "/code" : client.redirect_url; + +// let ruri = redir + `?code=${code.code}&state=${state}`; +// if (nored === "true") { +// res.json({ +// redirect_uri: ruri +// }) +// } else { +// res.redirect(ruri); +// } +// } +// } catch (err) { +// Logging.error(err); +// sendError("server_error") +// } +// }) + +const GetAuthRoute = (view = false) => + Stacker(GetUserMiddleware(false), async (req: Request, res: Response) => { + let { + response_type, + client_id, + redirect_uri, + scope = "", + state, + nored, + } = req.query as { [key: string]: string }; + const sendError = (type) => { + if (redirect_uri === "$local") redirect_uri = "/code"; + res.redirect( + (redirect_uri += `?error=${type}${state ? "&state=" + state : ""}`) + ); + }; + + const scopes = scope.split(";").filter((e: string) => e !== ""); + + Logging.debug("Scopes:", scope); + + try { + if (response_type !== "code") { + return sendError("unsupported_response_type"); + } else { + let client = await Client.findOne({ client_id: client_id }); + if (!client) { + return sendError("unauthorized_client"); + } + + if (redirect_uri && client.redirect_url !== redirect_uri) { + Logging.log(redirect_uri, client.redirect_url); + return res.send( + "Invalid redirect_uri. Please check the integrity of the site requesting and contact the administrator of the page, you want to authorize!" + ); + } + + let permissions: IPermission[] = []; + let proms: PromiseLike[] = []; + if (scopes) { + for (let perm of scopes.filter((e) => e !== "read_user")) { + let oid = undefined; + try { + oid = new ObjectId(perm); + } catch (err) { + Logging.error(err); + continue; + } + proms.push( + Permission.findById(oid).then((p) => { + if (!p) return Promise.reject(new Error()); + permissions.push(p); + }) + ); + } + } + + let err = undefined; + await Promise.all(proms).catch((e) => { + err = e; + }); + + if (err) { + Logging.error(err); + return sendError("invalid_scope"); + } + + let grant: IGrant | undefined = await Grant.findOne({ + client: client._id, + user: req.user._id, + }); + + Logging.debug("Grant", grant, permissions); + + let missing_permissions: IPermission[] = []; + + if (grant) { + missing_permissions = grant.permissions + .map((perm) => permissions.find((p) => p._id.equals(perm))) + .filter((e) => !!e); + } else { + missing_permissions = permissions; + } + + let client_granted_perm = missing_permissions.filter( + (e) => e.grant_type == "client" + ); + if (client_granted_perm.length > 0) { + return sendError("no_permission"); + } + + if (!grant && missing_permissions.length > 0) { + await new Promise((yes, no) => + GetUserMiddleware(false, true)( + req, + res, + (err?: Error | string) => (err ? no(err) : yes()) + ) + ); // Maybe unresolved when redirect is happening + + if (view) { + res.send( + GetAuthPage( + req.__, + client.name, + permissions.map((perm) => { + return { + name: perm.name, + description: perm.description, + logo: client.logo, + }; + }) + ) + ); + return; + } else { + if ((req.body.allow = "true")) { + if (!grant) + grant = Grant.new({ + client: client._id, + user: req.user._id, + permissions: [], + }); + + grant.permissions.push( + ...missing_permissions.map((e) => e._id) + ); + await Grant.save(grant); + } else { + return sendError("access_denied"); + } + } + } + + let code = ClientCode.new({ + user: req.user._id, + client: client._id, + permissions: permissions.map((p) => p._id), + validTill: moment().add(30, "minutes").toDate(), + code: randomBytes(16).toString("hex"), + }); + await ClientCode.save(code); + + let redir = + client.redirect_url === "$local" ? "/code" : client.redirect_url; + let ruri = + redir + `?code=${code.code}${state ? "&state=" + state : ""}`; + if (nored === "true") { + res.json({ + redirect_uri: ruri, + }); + } else { + res.redirect(ruri); + } + } + } catch (err) { + Logging.error(err); + sendError("server_error"); + } + }); + +export default GetAuthRoute; diff --git a/Backend/src/api/user/account.ts b/Backend/src/api/user/account.ts deleted file mode 100644 index 58c734b..0000000 --- a/Backend/src/api/user/account.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Request, Response } from "express"; -import Stacker from "../middlewares/stacker"; -import { GetUserMiddleware } from "../middlewares/user"; -import LoginToken, { CheckToken } from "../../models/login_token"; -import RequestError, { HttpStatusCode } from "../../helper/request_error"; - -export const GetAccount = Stacker( - GetUserMiddleware(true, true), - async (req: Request, res: Response) => { - let user = { - id: req.user.uid, - name: req.user.name, - username: req.user.username, - birthday: req.user.birthday, - gender: req.user.gender, - }; - res.json({ user }); - } -); diff --git a/Backend/src/api/user/contact.ts b/Backend/src/api/user/contact.ts deleted file mode 100644 index ad67a17..0000000 --- a/Backend/src/api/user/contact.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Request, Response } from "express"; -import Stacker from "../middlewares/stacker"; -import { GetUserMiddleware } from "../middlewares/user"; -import Mail from "../../models/mail"; - -export const GetContactInfos = Stacker( - GetUserMiddleware(true, true), - async (req: Request, res: Response) => { - let mails = await Promise.all( - req.user.mails.map((mail) => Mail.findById(mail)) - ); - - let contact = { - mails: mails.filter((e) => !!e), - phones: req.user.phones, - }; - res.json({ contact }); - } -); diff --git a/Backend/src/api/user/index.ts b/Backend/src/api/user/index.ts index e0463e2..c181a10 100644 --- a/Backend/src/api/user/index.ts +++ b/Backend/src/api/user/index.ts @@ -1,132 +1,39 @@ -import { Router } from "express"; -import { GetAccount } from "./account"; -import { GetContactInfos } from "./contact"; -import Login from "./login"; -import Register from "./register"; -import { DeleteToken, GetToken } from "./token"; -import TwoFactorRoute from "./twofactor"; -import OAuthRoute from "./oauth"; - -const UserRoute: Router = Router(); - -/** - * @api {post} /user/register - * @apiName UserRegister - * - * @apiGroup user - * @apiPermission none - * - * @apiParam {String} mail EMail linked to this Account - * @apiParam {String} username The new Username - * @apiParam {String} password Password hashed and salted like specification - * @apiParam {String} salt The Salt used for password hashing - * @apiParam {String} regcode The regcode, that should be used - * @apiParam {String} gender Gender can be: "male", "female", "other", "none" - * @apiParam {String} name The real name of the User - * - * @apiSuccess {Boolean} success - * - * @apiErrorExample {Object} Error-Response: - { - error: [ - { - message: "Some Error", - field: "username" - } - ], - status: 400 - } - */ -UserRoute.post("/register", Register); - -/** - * @api {post} /user/login?type=:type - * @apiName UserLogin - * - * @apiParam {String} type Type could be either "username" or "password" - * - * @apiGroup user - * @apiPermission none - * - * @apiParam {String} username Username (either username or uid required) - * @apiParam {String} uid (either username or uid required) - * @apiParam {String} password Password hashed and salted like specification (only on type password) - * @apiParam {Number} time in milliseconds used to hash password. This is used to make passwords "expire" - * - * @apiSuccess {String} uid On type = "username" - * @apiSuccess {String} salt On type = "username" - * - * @apiSuccess {String} login On type = "password". Login Token - * @apiSuccess {String} special On type = "password". Special Token - * @apiSuccess {Object[]} tfa Will be set when TwoFactorAuthentication is required - * @apiSuccess {String} tfa.id The ID of the TFA Method - * @apiSuccess {String} tfa.name The name of the TFA Method - * @apiSuccess {String} tfa.type The type of the TFA Method - */ -UserRoute.post("/login", Login); -UserRoute.use("/twofactor", TwoFactorRoute); - -/** - * @api {get} /user/token - * @apiName UserGetToken - * - * @apiGroup user - * @apiPermission user - * - * @apiSuccess {Object[]} token - * @apiSuccess {String} token.id The Token ID - * @apiSuccess {String} token.special Identifies Special Token - * @apiSuccess {String} token.ip IP the token was optained from - * @apiSuccess {String} token.browser The Browser the token was optained from (User Agent) - * @apiSuccess {Boolean} token.isthis Shows if it is token used by this session - */ -UserRoute.get("/token", GetToken); - -/** - * @api {delete} /user/token/:id - * @apiParam {String} id The id of the token to be deleted - * - * @apiName UserDeleteToken - * - * - * @apiGroup user - * @apiPermission user - * - * @apiSuccess {Boolean} success - */ -UserRoute.delete("/token/:id", DeleteToken); - -/** - * @api {delete} /user/account - * @apiName UserGetAccount - * - * @apiGroup user - * @apiPermission user - * - * @apiSuccess {Boolean} success - * @apiSuccess {Object[]} user - * @apiSuccess {String} user.id User ID - * @apiSuccess {String} user.name Full name of the user - * @apiSuccess {String} user.username Username of user - * @apiSuccess {Date} user.birthday Birthday - * @apiSuccess {Number} user.gender Gender of user (none = 0, male = 1, female = 2, other = 3) - */ -UserRoute.get("/account", GetAccount); - -/** - * @api {delete} /user/account - * @apiName UserGetAccount - * - * @apiGroup user - * @apiPermission user - * - * @apiSuccess {Boolean} success - * @apiSuccess {Object} contact - * @apiSuccess {Object[]} user.mail EMail addresses - * @apiSuccess {Object[]} user.phone Phone numbers - */ -UserRoute.get("/contact", GetContactInfos); - -UserRoute.use("/oauth", OAuthRoute); - -export default UserRoute; +import { Router } from "express"; +import Register from "./register"; +import OAuthRoute from "./oauth"; + +const UserRoute: Router = Router(); + +/** + * @api {post} /user/register + * @apiName UserRegister + * + * @apiGroup user + * @apiPermission none + * + * @apiParam {String} mail EMail linked to this Account + * @apiParam {String} username The new Username + * @apiParam {String} password Password hashed and salted like specification + * @apiParam {String} salt The Salt used for password hashing + * @apiParam {String} regcode The regcode, that should be used + * @apiParam {String} gender Gender can be: "male", "female", "other", "none" + * @apiParam {String} name The real name of the User + * + * @apiSuccess {Boolean} success + * + * @apiErrorExample {Object} Error-Response: + { + error: [ + { + message: "Some Error", + field: "username" + } + ], + status: 400 + } + */ +UserRoute.post("/register", Register); + +UserRoute.use("/oauth", OAuthRoute); + +export default UserRoute; diff --git a/Backend/src/api/user/login.ts b/Backend/src/api/user/login.ts deleted file mode 100644 index c424e22..0000000 --- a/Backend/src/api/user/login.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { Request, Response } from "express"; -import User, { IUser } from "../../models/user"; -import { randomBytes } from "crypto"; -import moment = require("moment"); -import LoginToken from "../../models/login_token"; -import promiseMiddleware from "../../helper/promiseMiddleware"; -import TwoFactor, { TFATypes, TFANames } from "../../models/twofactor"; -import * as crypto from "crypto"; -import Logging from "@hibas123/nodelogging"; - -const Login = promiseMiddleware(async (req: Request, res: Response) => { - let type = req.query.type as string; - if (type === "username") { - let { username, uid } = req.query as { [key: string]: string }; - let user = await User.findOne( - username ? { username: username.toLowerCase() } : { uid: uid } - ); - if (!user) { - res.json({ error: req.__("User not found") }); - } else { - res.json({ salt: user.salt, uid: user.uid }); - } - return; - } else if (type === "password") { - const sendToken = async (user: IUser, tfa?: any[]) => { - let ip = - req.headers["x-forwarded-for"] || req.connection.remoteAddress; - let client = { - ip: Array.isArray(ip) ? ip[0] : ip, - browser: req.headers["user-agent"], - }; - - let token_str = randomBytes(16).toString("hex"); - let tfa_exp = moment().add(5, "minutes").toDate(); - let token_exp = moment().add(6, "months").toDate(); - let token = LoginToken.new({ - token: token_str, - valid: true, - validTill: tfa ? tfa_exp : token_exp, - user: user._id, - validated: tfa ? false : true, - ...client, - }); - await LoginToken.save(token); - - let special_str = randomBytes(24).toString("hex"); - let special_exp = moment().add(30, "minutes").toDate(); - let special = LoginToken.new({ - token: special_str, - valid: true, - validTill: tfa ? tfa_exp : special_exp, - special: true, - user: user._id, - validated: tfa ? false : true, - ...client, - }); - await LoginToken.save(special); - - res.json({ - login: { token: token_str, expires: token.validTill.toUTCString() }, - special: { - token: special_str, - expires: special.validTill.toUTCString(), - }, - tfa, - }); - }; - - let { username, password, uid, date } = req.body; - - let user = await User.findOne( - username ? { username: username.toLowerCase() } : { uid: uid } - ); - if (!user) { - res.json({ error: req.__("User not found") }); - } else { - let upw = user.password; - if (date) { - if ( - !moment(date).isBetween( - moment().subtract(1, "minute"), - moment().add(1, "minute") - ) - ) { - res.json({ - error: req.__( - "Invalid timestamp. Please check your devices time!" - ), - }); - return; - } else { - upw = crypto - .createHash("sha512") - .update(upw + date.toString()) - .digest("hex"); - } - } - if (upw !== password) { - res.json({ error: req.__("Password or username wrong") }); - } else { - let twofactor = await TwoFactor.find({ - user: user._id, - valid: true, - }); - let expired = twofactor.filter((e) => - e.expires ? moment().isAfter(moment(e.expires)) : false - ); - await Promise.all( - expired.map((e) => { - e.valid = false; - return TwoFactor.save(e); - }) - ); - twofactor = twofactor.filter((e) => e.valid); - if (twofactor && twofactor.length > 0) { - let tfa = twofactor.map((e) => { - return { - id: e._id, - name: e.name || TFANames.get(e.type), - type: e.type, - }; - }); - await sendToken(user, tfa); - } else { - await sendToken(user); - } - } - } - } else { - res.json({ error: req.__("Invalid type!") }); - } -}); - -export default Login; diff --git a/Backend/src/api/user/oauth/permissions.ts b/Backend/src/api/user/oauth/permissions.ts index 395c4df..dc9ed1a 100644 --- a/Backend/src/api/user/oauth/permissions.ts +++ b/Backend/src/api/user/oauth/permissions.ts @@ -1,38 +1,38 @@ -import { Request, Response } from "express"; -import Stacker from "../../middlewares/stacker"; -import { GetUserMiddleware } from "../../middlewares/user"; -import { URL } from "url"; -import Client from "../../../models/client"; -import RequestError, { HttpStatusCode } from "../../../helper/request_error"; -import { randomBytes } from "crypto"; -import moment = require("moment"); -import RefreshToken from "../../../models/refresh_token"; -import { refreshTokenValidTime } from "../../../config"; -import { getClientWithOrigin } from "./_helper"; -import Permission from "../../../models/permissions"; - -export const GetPermissionsForAuthRequest = Stacker( - GetUserMiddleware(true, false), - async (req: Request, res: Response) => { - const { client_id, origin, permissions } = req.query as { - [key: string]: string; - }; - - const client = await getClientWithOrigin(client_id, origin); - - const perm = permissions.split(",").filter((e) => !!e); - - const resolved = await Promise.all( - perm.map((p) => Permission.findById(p)) - ); - - if (resolved.some((e) => e.grant_type !== "user")) { - throw new RequestError( - "Invalid Permission requested", - HttpStatusCode.BAD_REQUEST - ); - } - - res.json({ permissions: resolved }); - } -); +import { Request, Response } from "express"; +import Stacker from "../../middlewares/stacker"; +import { GetUserMiddleware } from "../../middlewares/user"; +import { URL } from "url"; +import Client from "../../../models/client"; +import RequestError, { HttpStatusCode } from "../../../helper/request_error"; +import { randomBytes } from "crypto"; +import moment = require("moment"); +import RefreshToken from "../../../models/refresh_token"; +import { refreshTokenValidTime } from "../../../config"; +import { getClientWithOrigin } from "./_helper"; +import Permission from "../../../models/permissions"; + +export const GetPermissionsForAuthRequest = Stacker( + GetUserMiddleware(true, false), + async (req: Request, res: Response) => { + const { client_id, origin, permissions } = req.query as { + [key: string]: string; + }; + + const client = await getClientWithOrigin(client_id, origin); + + const perm = permissions.split(",").filter((e) => !!e); + + const resolved = await Promise.all( + perm.map((p) => Permission.findById(p)) + ); + + if (resolved.some((e) => e.grant_type !== "user")) { + throw new RequestError( + "Invalid Permission requested", + HttpStatusCode.BAD_REQUEST + ); + } + + res.json({ permissions: resolved }); + } +); diff --git a/Backend/src/api/user/token.ts b/Backend/src/api/user/token.ts deleted file mode 100644 index 271039c..0000000 --- a/Backend/src/api/user/token.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Request, Response } from "express"; -import Stacker from "../middlewares/stacker"; -import { GetUserMiddleware } from "../middlewares/user"; -import LoginToken, { CheckToken } from "../../models/login_token"; -import RequestError, { HttpStatusCode } from "../../helper/request_error"; - -export const GetToken = Stacker( - GetUserMiddleware(true, true), - async (req: Request, res: Response) => { - let raw_token = await LoginToken.find({ - user: req.user._id, - valid: true, - }); - let token = await Promise.all( - raw_token - .map(async (token) => { - await CheckToken(token); - return { - id: token._id, - special: token.special, - ip: token.ip, - browser: token.browser, - isthis: token._id.equals( - token.special ? req.token.special._id : req.token.login._id - ), - }; - }) - .filter((t) => t !== undefined) - ); - res.json({ token }); - } -); - -export const DeleteToken = Stacker( - GetUserMiddleware(true, true), - async (req: Request, res: Response) => { - let { id } = req.params; - let token = await LoginToken.findById(id); - if (!token || !token.user.equals(req.user._id)) - throw new RequestError("Invalid ID", HttpStatusCode.BAD_REQUEST); - token.valid = false; - await LoginToken.save(token); - res.json({ success: true }); - } -); diff --git a/Backend/src/api/user/twofactor/backup/index.ts b/Backend/src/api/user/twofactor/backup/index.ts deleted file mode 100644 index 88a420b..0000000 --- a/Backend/src/api/user/twofactor/backup/index.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Router } from "express"; -import Stacker from "../../../middlewares/stacker"; -import { GetUserMiddleware } from "../../../middlewares/user"; -import TwoFactor, { - TFATypes as TwoFATypes, - IBackupCode, -} from "../../../../models/twofactor"; -import RequestError, { HttpStatusCode } from "../../../../helper/request_error"; -import moment = require("moment"); -import { upgradeToken } from "../helper"; -import * as crypto from "crypto"; -import Logging from "@hibas123/nodelogging"; - -const BackupCodeRoute = Router(); - -// TODO: Further checks if this is good enough randomness -function generateCode(length: number) { - let bytes = crypto.randomBytes(length); - let nrs = ""; - bytes.forEach((b, idx) => { - let nr = Math.floor((b / 255) * 9.9999); - if (nr > 9) nr = 9; - nrs += String(nr); - }); - return nrs; -} - -BackupCodeRoute.post( - "/", - Stacker(GetUserMiddleware(true, true), async (req, res) => { - //Generating new - let codes = Array(10).map(() => generateCode(8)); - console.log(codes); - let twofactor = TwoFactor.new({ - user: req.user._id, - type: TwoFATypes.TOTP, - valid: true, - data: codes, - name: "", - }); - await TwoFactor.save(twofactor); - res.json({ - codes, - id: twofactor._id, - }); - }) -); - -BackupCodeRoute.put( - "/", - Stacker( - GetUserMiddleware(true, false, undefined, false), - async (req, res) => { - let { login, special } = req.token; - let { id, code }: { id: string; code: string } = req.body; - - let twofactor: IBackupCode = await TwoFactor.findById(id); - - if ( - !twofactor || - !twofactor.valid || - !twofactor.user.equals(req.user._id) || - twofactor.type !== TwoFATypes.TOTP - ) { - throw new RequestError( - "Invalid Method!", - HttpStatusCode.BAD_REQUEST - ); - } - - if (twofactor.expires && moment().isAfter(twofactor.expires)) { - twofactor.valid = false; - await TwoFactor.save(twofactor); - throw new RequestError( - "Invalid Method!", - HttpStatusCode.BAD_REQUEST - ); - } - code = code.replace(/\s/g, ""); - let valid = twofactor.data.find((c) => c === code); - - if (valid) { - twofactor.data = twofactor.data.filter((c) => c !== code); - await TwoFactor.save(twofactor); - let [login_exp, special_exp] = await Promise.all([ - upgradeToken(login), - upgradeToken(special), - ]); - res.json({ success: true, login_exp, special_exp }); - } else { - throw new RequestError( - "Invalid or already used code!", - HttpStatusCode.BAD_REQUEST - ); - } - } - ) -); - -export default BackupCodeRoute; diff --git a/Backend/src/api/user/twofactor/helper.ts b/Backend/src/api/user/twofactor/helper.ts deleted file mode 100644 index d561090..0000000 --- a/Backend/src/api/user/twofactor/helper.ts +++ /dev/null @@ -1,16 +0,0 @@ -import LoginToken, { ILoginToken } from "../../../models/login_token"; -import moment = require("moment"); - -export async function upgradeToken(token: ILoginToken) { - token.data = undefined; - token.valid = true; - token.validated = true; - //TODO durations from config - let expires = (token.special - ? moment().add(30, "minute") - : moment().add(6, "months") - ).toDate(); - token.validTill = expires; - await LoginToken.save(token); - return expires; -} diff --git a/Backend/src/api/user/twofactor/index.ts b/Backend/src/api/user/twofactor/index.ts deleted file mode 100644 index 757fcb4..0000000 --- a/Backend/src/api/user/twofactor/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Router } from "express"; -import YubiKeyRoute from "./yubikey"; -import { GetUserMiddleware } from "../../middlewares/user"; -import Stacker from "../../middlewares/stacker"; -import TwoFactor from "../../../models/twofactor"; -import * as moment from "moment"; -import RequestError, { HttpStatusCode } from "../../../helper/request_error"; -import OTCRoute from "./otc"; -import BackupCodeRoute from "./backup"; - -const TwoFactorRouter = Router(); - -TwoFactorRouter.get( - "/", - Stacker(GetUserMiddleware(true, true), async (req, res) => { - let twofactor = await TwoFactor.find({ user: req.user._id, valid: true }); - let expired = twofactor.filter((e) => - e.expires ? moment().isAfter(moment(e.expires)) : false - ); - await Promise.all( - expired.map((e) => { - e.valid = false; - return TwoFactor.save(e); - }) - ); - twofactor = twofactor.filter((e) => e.valid); - let tfa = twofactor.map((e) => { - return { - id: e._id, - name: e.name, - type: e.type, - }; - }); - res.json({ methods: tfa }); - }) -); - -TwoFactorRouter.delete( - "/:id", - Stacker(GetUserMiddleware(true, true), async (req, res) => { - let { id } = req.params; - let tfa = await TwoFactor.findById(id); - if (!tfa || !tfa.user.equals(req.user._id)) { - throw new RequestError("Invalid id", HttpStatusCode.BAD_REQUEST); - } - tfa.valid = false; - await TwoFactor.save(tfa); - res.json({ success: true }); - }) -); - -TwoFactorRouter.use("/yubikey", YubiKeyRoute); -TwoFactorRouter.use("/otc", OTCRoute); -TwoFactorRouter.use("/backup", BackupCodeRoute); - -export default TwoFactorRouter; diff --git a/Backend/src/api/user/twofactor/otc/index.ts b/Backend/src/api/user/twofactor/otc/index.ts deleted file mode 100644 index 8eeb900..0000000 --- a/Backend/src/api/user/twofactor/otc/index.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Router } from "express"; -import Stacker from "../../../middlewares/stacker"; -import { GetUserMiddleware } from "../../../middlewares/user"; -import TwoFactor, { - TFATypes as TwoFATypes, - IOTC, -} from "../../../../models/twofactor"; -import RequestError, { HttpStatusCode } from "../../../../helper/request_error"; -import moment = require("moment"); -import { upgradeToken } from "../helper"; -import Logging from "@hibas123/nodelogging"; - -import * as speakeasy from "speakeasy"; -import * as qrcode from "qrcode"; -import config from "../../../../config"; - -const OTCRoute = Router(); - -OTCRoute.post( - "/", - Stacker(GetUserMiddleware(true, true), async (req, res) => { - const { type } = req.query; - if (type === "create") { - //Generating new - let secret = speakeasy.generateSecret({ - name: config.core.name, - issuer: config.core.name, - }); - let twofactor = TwoFactor.new({ - user: req.user._id, - type: TwoFATypes.TOTP, - valid: false, - data: secret.base32, - }); - let dataurl = await qrcode.toDataURL(secret.otpauth_url); - await TwoFactor.save(twofactor); - res.json({ - image: dataurl, - id: twofactor._id, - }); - } else if (type === "validate") { - // Checking code and marking as valid - const { code, id } = req.body; - Logging.debug(req.body, id); - let twofactor: IOTC = await TwoFactor.findById(id); - const err = () => { - throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST); - }; - if ( - !twofactor || - !twofactor.user.equals(req.user._id) || - twofactor.type !== TwoFATypes.TOTP || - !twofactor.data || - twofactor.valid - ) { - Logging.debug("Not found or wrong user", twofactor); - err(); - } - - if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) { - await TwoFactor.delete(twofactor); - Logging.debug("Expired!", twofactor); - err(); - } - - let valid = speakeasy.totp.verify({ - secret: twofactor.data, - encoding: "base32", - token: code, - }); - - if (valid) { - twofactor.expires = undefined; - twofactor.valid = true; - await TwoFactor.save(twofactor); - res.json({ success: true }); - } else { - throw new RequestError("Invalid Code!", HttpStatusCode.BAD_REQUEST); - } - } else { - throw new RequestError("Invalid type", HttpStatusCode.BAD_REQUEST); - } - }) -); - -OTCRoute.put( - "/", - Stacker( - GetUserMiddleware(true, false, undefined, false), - async (req, res) => { - let { login, special } = req.token; - let { id, code } = req.body; - let twofactor: IOTC = await TwoFactor.findById(id); - - if ( - !twofactor || - !twofactor.valid || - !twofactor.user.equals(req.user._id) || - twofactor.type !== TwoFATypes.TOTP - ) { - throw new RequestError( - "Invalid Method!", - HttpStatusCode.BAD_REQUEST - ); - } - - if (twofactor.expires && moment().isAfter(twofactor.expires)) { - twofactor.valid = false; - await TwoFactor.save(twofactor); - throw new RequestError( - "Invalid Method!", - HttpStatusCode.BAD_REQUEST - ); - } - - let valid = speakeasy.totp.verify({ - secret: twofactor.data, - encoding: "base32", - token: code, - }); - - if (valid) { - let [login_exp, special_exp] = await Promise.all([ - upgradeToken(login), - upgradeToken(special), - ]); - res.json({ success: true, login_exp, special_exp }); - } else { - throw new RequestError("Invalid Code", HttpStatusCode.BAD_REQUEST); - } - } - ) -); - -export default OTCRoute; diff --git a/Backend/src/api/user/twofactor/yubikey/index.ts b/Backend/src/api/user/twofactor/yubikey/index.ts deleted file mode 100644 index 472f3a4..0000000 --- a/Backend/src/api/user/twofactor/yubikey/index.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { Router, Request } from "express"; -import Stacker from "../../../middlewares/stacker"; -import { UserMiddleware, GetUserMiddleware } from "../../../middlewares/user"; -import * as u2f from "u2f"; -import config from "../../../../config"; -import TwoFactor, { - TFATypes as TwoFATypes, - IYubiKey, -} from "../../../../models/twofactor"; -import RequestError, { HttpStatusCode } from "../../../../helper/request_error"; -import moment = require("moment"); -import LoginToken from "../../../../models/login_token"; -import { upgradeToken } from "../helper"; -import Logging from "@hibas123/nodelogging"; - -const U2FRoute = Router(); - -/** - * Registerinf a new YubiKey - */ -U2FRoute.post( - "/", - Stacker(GetUserMiddleware(true, true), async (req, res) => { - const { type } = req.query; - if (type === "challenge") { - const registrationRequest = u2f.request(config.core.url); - - let twofactor = TwoFactor.new({ - user: req.user._id, - type: TwoFATypes.WEBAUTHN, - valid: false, - data: { - registration: registrationRequest, - }, - }); - await TwoFactor.save(twofactor); - res.json({ - request: registrationRequest, - id: twofactor._id, - appid: config.core.url, - }); - } else { - const { response, id } = req.body; - Logging.debug(req.body, id); - let twofactor: IYubiKey = await TwoFactor.findById(id); - const err = () => { - throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST); - }; - if ( - !twofactor || - !twofactor.user.equals(req.user._id) || - twofactor.type !== TwoFATypes.WEBAUTHN || - !twofactor.data.registration || - twofactor.valid - ) { - Logging.debug("Not found or wrong user", twofactor); - err(); - } - - if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) { - await TwoFactor.delete(twofactor); - Logging.debug("Expired!", twofactor); - err(); - } - - const result = u2f.checkRegistration( - twofactor.data.registration, - response - ); - - if (result.successful) { - twofactor.data = { - keyHandle: result.keyHandle, - publicKey: result.publicKey, - }; - twofactor.expires = undefined; - twofactor.valid = true; - await TwoFactor.save(twofactor); - res.json({ success: true }); - } else { - throw new RequestError( - result.errorMessage, - HttpStatusCode.BAD_REQUEST - ); - } - } - }) -); - -U2FRoute.get( - "/", - Stacker( - GetUserMiddleware(true, false, undefined, false), - async (req, res) => { - let { login, special } = req.token; - let twofactor: IYubiKey = await TwoFactor.findOne({ - user: req.user._id, - type: TwoFATypes.WEBAUTHN, - valid: true, - }); - - if (!twofactor) { - throw new RequestError( - "Invalid Method!", - HttpStatusCode.BAD_REQUEST - ); - } - - if (twofactor.expires) { - if (moment().isAfter(twofactor.expires)) { - twofactor.valid = false; - await TwoFactor.save(twofactor); - throw new RequestError( - "Invalid Method!", - HttpStatusCode.BAD_REQUEST - ); - } - } - - let request = u2f.request(config.core.url, twofactor.data.keyHandle); - login.data = { - type: "ykr", - request, - }; - let r; - if (special) { - special.data = login.data; - r = LoginToken.save(special); - } - - await Promise.all([r, LoginToken.save(login)]); - res.json({ request }); - } - ) -); - -U2FRoute.put( - "/", - Stacker( - GetUserMiddleware(true, false, undefined, false), - async (req, res) => { - let { login, special } = req.token; - let twofactor: IYubiKey = await TwoFactor.findOne({ - user: req.user._id, - type: TwoFATypes.WEBAUTHN, - valid: true, - }); - - let { response } = req.body; - if ( - !twofactor || - !login.data || - login.data.type !== "ykr" || - (special && (!special.data || special.data.type !== "ykr")) - ) { - throw new RequestError( - "Invalid Method!", - HttpStatusCode.BAD_REQUEST - ); - } - - if (twofactor.expires && moment().isAfter(twofactor.expires)) { - twofactor.valid = false; - await TwoFactor.save(twofactor); - throw new RequestError( - "Invalid Method!", - HttpStatusCode.BAD_REQUEST - ); - } - - let login_exp; - let special_exp; - let result = u2f.checkSignature( - login.data.request, - response, - twofactor.data.publicKey - ); - if (result.successful) { - if (special) { - let result = u2f.checkSignature( - special.data.request, - response, - twofactor.data.publicKey - ); - if (result.successful) { - special_exp = await upgradeToken(special); - } else { - throw new RequestError( - result.errorMessage, - HttpStatusCode.BAD_REQUEST - ); - } - } - login_exp = await upgradeToken(login); - } else { - throw new RequestError( - result.errorMessage, - HttpStatusCode.BAD_REQUEST - ); - } - res.json({ success: true, login_exp, special_exp }); - } - ) -); - -export default U2FRoute; diff --git a/Backend/src/config.ts b/Backend/src/config.ts index 221e3e3..576020c 100644 --- a/Backend/src/config.ts +++ b/Backend/src/config.ts @@ -1,75 +1,81 @@ -import { parse } from "@hibas123/config"; -import Logging from "@hibas123/nodelogging"; -import * as dotenv from "dotenv"; -import moment = require("moment"); - -export const refreshTokenValidTime = moment.duration(6, "month"); - -dotenv.config(); - -export interface DatabaseConfig { - host: string; - database: string; -} - -export interface WebConfig { - port: string; - secure: "true" | "false" | undefined; -} - -export interface CoreConfig { - name: string; - url: string; - dev: boolean; -} - -export interface Config { - core: CoreConfig; - database: DatabaseConfig; - web: WebConfig; -} - -const config = (parse( - { - core: { - dev: { - default: false, - type: Boolean, - }, - name: { - type: String, - default: "Open Auth", - }, - url: String, - }, - database: { - database: { - type: String, - default: "openauth", - }, - host: { - type: String, - default: "localhost", - }, - }, - web: { - port: { - type: Number, - default: 3004, - }, - secure: { - type: Boolean, - default: false, - }, - }, - }, - "config.ini" -) as any) as Config; - -if (process.env.DEV === "true") config.core.dev = true; -if (config.core.dev) - Logging.warning( - "DEV mode active. This can cause major performance issues, data loss and vulnerabilities! " - ); - -export default config; +import { parse } from "@hibas123/config"; +import Logging from "@hibas123/nodelogging"; +import * as dotenv from "dotenv"; +import moment = require("moment"); + +export const refreshTokenValidTime = moment.duration(6, "month"); + +dotenv.config(); + +export interface DatabaseConfig { + host: string; + database: string; +} + +export interface WebConfig { + port: string; + secure: "true" | "false" | undefined; +} + +export interface CoreConfig { + name: string; + url: string; + dev: boolean; + secret: string; +} + +export interface Config { + core: CoreConfig; + database: DatabaseConfig; + web: WebConfig; +} + +const config = (parse( + { + core: { + dev: { + default: false, + type: Boolean, + }, + name: { + type: String, + default: "Open Auth", + }, + url: String, + secret: { + type: String, + optional: false, + description: "Cookie secret" + } + }, + database: { + database: { + type: String, + default: "openauth", + }, + host: { + type: String, + default: "localhost", + }, + }, + web: { + port: { + type: Number, + default: 3004, + }, + secure: { + type: Boolean, + default: false, + }, + }, + }, + "config.ini" +) as any) as Config; + +if (process.env.DEV === "true") config.core.dev = true; +if (config.core.dev) + Logging.warning( + "DEV mode active. This can cause major performance issues, data loss and vulnerabilities! " + ); + +export default config; diff --git a/Backend/src/database.ts b/Backend/src/database.ts index e9a9a77..64d84d9 100644 --- a/Backend/src/database.ts +++ b/Backend/src/database.ts @@ -1,13 +1,13 @@ -import SafeMongo from "@hibas123/safe_mongo"; -import Config from "./config"; -let dbname = "openauth"; -let host = "localhost"; -if (Config.database) { - if (Config.database.database) dbname = Config.database.database; - if (Config.database.host) host = Config.database.host; -} -if (Config.core.dev) dbname += "_dev"; -const DB = new SafeMongo("mongodb://" + host, dbname, { - useUnifiedTopology: true, -}); -export default DB; +import SafeMongo from "@hibas123/safe_mongo"; +import Config from "./config"; +let dbname = "openauth"; +let host = "localhost"; +if (Config.database) { + if (Config.database.database) dbname = Config.database.database; + if (Config.database.host) host = Config.database.host; +} +if (Config.core.dev) dbname += "_dev"; +const DB = new SafeMongo("mongodb://" + host, dbname, { + +}); +export default DB; diff --git a/Backend/src/express.d.ts b/Backend/src/express.d.ts index b004258..d7c3ecb 100644 --- a/Backend/src/express.d.ts +++ b/Backend/src/express.d.ts @@ -1,16 +1,23 @@ -import { IUser } from "./models/user"; -import { IClient } from "./models/client"; -import { ILoginToken } from "./models/login_token"; - -declare module "express" { - interface Request { - user: IUser; - client: IClient; - isAdmin: boolean; - special: boolean; - token: { - login: ILoginToken; - special?: ILoginToken; - }; - } -} +import { IUser } from "./models/user"; +import { IClient } from "./models/client"; + +declare module "express" { + interface Request { + user: IUser; + client: IClient; + isAdmin: boolean; + special: boolean; + } +} + +declare module 'express-session' { + interface SessionData { + user_id: string; + validated: boolean; + login_state: { + username: string; + password_correct: boolean; + webauthn_challenge?: any; + }; + } +} diff --git a/Backend/src/helper/jwt.ts b/Backend/src/helper/jwt.ts index 7d141aa..2e5b119 100644 --- a/Backend/src/helper/jwt.ts +++ b/Backend/src/helper/jwt.ts @@ -1,60 +1,60 @@ -import { IUser, Gender } from "../models/user"; -import { ObjectID } from "bson"; -import { createJWT } from "../keys"; -import { IClient } from "../models/client"; -import config from "../config"; -import * as moment from "moment"; - -export interface OAuthJWT { - user: string; - username: string; - permissions: string[]; - application: string; -} - -const issuer = config.core.url; - -export const IDTokenJWTExp = moment.duration(30, "m").asSeconds(); -export function getIDToken(user: IUser, client_id: string, nonce: string) { - return createJWT( - { - user: user.uid, - name: user.name, - nickname: user.username, - username: user.username, - preferred_username: user.username, - gender: Gender[user.gender], - nonce, - }, - { - expiresIn: IDTokenJWTExp, - issuer, - algorithm: "RS256", - subject: user.uid, - audience: client_id, - } - ); -} - -export const AccessTokenJWTExp = moment.duration(6, "h"); -export function getAccessTokenJWT(token: { - user: IUser; - permissions: ObjectID[]; - client: IClient; -}) { - return createJWT( - { - user: token.user.uid, - username: token.user.username, - permissions: token.permissions.map((p) => p.toHexString()), - application: token.client.client_id, - }, - { - expiresIn: AccessTokenJWTExp.asSeconds(), - issuer, - algorithm: "RS256", - subject: token.user.uid, - audience: token.client.client_id, - } - ); -} +import { IUser, Gender } from "../models/user"; +import { ObjectId } from "bson"; +import { createJWT } from "../keys"; +import { IClient } from "../models/client"; +import config from "../config"; +import moment = require("moment"); + +export interface OAuthJWT { + user: string; + username: string; + permissions: string[]; + application: string; +} + +const issuer = config.core.url; + +export const IDTokenJWTExp = moment.duration(30, "m").asSeconds(); +export function getIDToken(user: IUser, client_id: string, nonce: string) { + return createJWT( + { + user: user.uid, + name: user.name, + nickname: user.username, + username: user.username, + preferred_username: user.username, + gender: Gender[user.gender], + nonce, + }, + { + expiresIn: IDTokenJWTExp, + issuer, + algorithm: "RS256", + subject: user.uid, + audience: client_id, + } + ); +} + +export const AccessTokenJWTExp = moment.duration(6, "h"); +export function getAccessTokenJWT(token: { + user: IUser; + permissions: ObjectId[]; + client: IClient; +}) { + return createJWT( + { + user: token.user.uid, + username: token.user.username, + permissions: token.permissions.map((p) => p.toHexString()), + application: token.client.client_id, + }, + { + expiresIn: AccessTokenJWTExp.asSeconds(), + issuer, + algorithm: "RS256", + subject: token.user.uid, + audience: token.client.client_id, + } + ); +} diff --git a/Backend/src/helper/login.ts b/Backend/src/helper/login.ts new file mode 100644 index 0000000..2f7a78c --- /dev/null +++ b/Backend/src/helper/login.ts @@ -0,0 +1,29 @@ +import { SessionContext } from "../api/jrpc"; + +export function requireLoginState(ctx: SessionContext, validated: boolean = true, special: boolean = false): boolean { + if (!ctx.user) return false; + if (validated && !ctx.session.validated) return false; + + if (special) { + //TODO: Implement something... + } + + return true; +} + +export function RequireLogin(validated = true, special = false) { + return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { + let original = descriptor.value; + + descriptor.value = function (...args: any[]) { + let ctx = args[args.length - 1] as SessionContext; + if (!ctx) throw new Error("Invalid request"); + + if (!requireLoginState(ctx, validated, special)) { + throw new Error("Not logged in"); + } + + return original.apply(this, args); + } + } +} diff --git a/Backend/src/keys.ts b/Backend/src/keys.ts index 2d607b4..a58b37b 100644 --- a/Backend/src/keys.ts +++ b/Backend/src/keys.ts @@ -1,69 +1,69 @@ -import Logging from "@hibas123/nodelogging"; -import * as fs from "fs"; - -let private_key: string; -let rsa: RSA; -export function sign(message: Buffer): Buffer { - return rsa.sign(message, "buffer"); -} - -export function verify(message: Buffer, signature: Buffer): boolean { - return rsa.verify(message, signature); -} - -export let public_key: string; - -import * as jwt from "jsonwebtoken"; -import config from "./config"; - -export function createJWT(payload: any, options: jwt.SignOptions) { - return new Promise((resolve, reject) => { - return jwt.sign(payload, private_key, options, (err, token) => { - if (err) reject(err); - else resolve(token); - }); - }); -} - -export async function validateJWT(data: string) { - return new Promise((resolve, reject) => { - jwt.verify(data, public_key, (err, valid) => { - if (err) reject(err); - else resolve(valid); - }); - }); -} - -let create = false; -if (fs.existsSync("./keys")) { - if (fs.existsSync("./keys/private.pem")) { - if (fs.existsSync("./keys/public.pem")) { - Logging.log("Using existing private and public key"); - private_key = fs.readFileSync("./keys/private.pem").toString("utf8"); - public_key = fs.readFileSync("./keys/public.pem").toString("utf8"); - - if (!private_key || !public_key) { - create = true; - } - } else create = true; - } else create = true; -} else create = true; - -import * as RSA from "node-rsa"; - -if (create === true) { - Logging.log("Started RSA Key gen"); - let rsa = new RSA({ b: 4096 }); - private_key = rsa.exportKey("private"); - public_key = rsa.exportKey("public"); - - if (!fs.existsSync("./keys")) { - fs.mkdirSync("./keys"); - } - fs.writeFileSync("./keys/private.pem", private_key); - fs.writeFileSync("./keys/public.pem", public_key); - Logging.log("Key pair generated"); -} - -rsa = new RSA(private_key, "private"); -rsa.importKey(public_key, "public"); +import Logging from "@hibas123/nodelogging"; +import * as fs from "fs"; + +let private_key: string; +let rsa: RSA; +export function sign(message: Buffer): Buffer { + return rsa.sign(message, "buffer"); +} + +export function verify(message: Buffer, signature: Buffer): boolean { + return rsa.verify(message, signature); +} + +export let public_key: string; + +import * as jwt from "jsonwebtoken"; +import config from "./config"; + +export function createJWT(payload: any, options: jwt.SignOptions) { + return new Promise((resolve, reject) => { + return jwt.sign(payload, private_key, options, (err, token) => { + if (err) reject(err); + else resolve(token); + }); + }); +} + +export async function validateJWT(data: string) { + return new Promise((resolve, reject) => { + jwt.verify(data, public_key, (err, valid) => { + if (err) reject(err); + else resolve(valid); + }); + }); +} + +let create = false; +if (fs.existsSync("./keys")) { + if (fs.existsSync("./keys/private.pem")) { + if (fs.existsSync("./keys/public.pem")) { + Logging.log("Using existing private and public key"); + private_key = fs.readFileSync("./keys/private.pem").toString("utf8"); + public_key = fs.readFileSync("./keys/public.pem").toString("utf8"); + + if (!private_key || !public_key) { + create = true; + } + } else create = true; + } else create = true; +} else create = true; + +import RSA from "node-rsa"; + +if (create === true) { + Logging.log("Started RSA Key gen"); + let rsa = new RSA({ b: 4096 }); + private_key = rsa.exportKey("private"); + public_key = rsa.exportKey("public"); + + if (!fs.existsSync("./keys")) { + fs.mkdirSync("./keys"); + } + fs.writeFileSync("./keys/private.pem", private_key); + fs.writeFileSync("./keys/public.pem", public_key); + Logging.log("Key pair generated"); +} + +rsa = new RSA(private_key, "private"); +rsa.importKey(public_key, "public"); diff --git a/Backend/src/models/client.ts b/Backend/src/models/client.ts index 659632f..69cbc4c 100644 --- a/Backend/src/models/client.ts +++ b/Backend/src/models/client.ts @@ -1,40 +1,40 @@ -import DB from "../database"; -import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; -import { ObjectID } from "mongodb"; -import { v4 } from "uuid"; - -export interface IClient extends ModelDataBase { - maintainer: ObjectID; - internal: boolean; - name: string; - redirect_url: string; - website: string; - logo?: string; - client_id: string; - client_secret: string; - featured?: boolean; - description?: string; -} - -const Client = DB.addModel({ - name: "client", - versions: [ - { - migration: () => {}, - schema: { - maintainer: { type: ObjectID }, - internal: { type: Boolean, default: false }, - name: { type: String }, - redirect_url: { type: String }, - website: { type: String }, - logo: { type: String, optional: true }, - client_id: { type: String, default: () => v4() }, - client_secret: { type: String }, - featured: { type: Boolean, optional: true }, - description: { type: String, optional: true }, - }, - }, - ], -}); - -export default Client; +import DB from "../database"; +import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; +import { ObjectId } from "mongodb"; +import { v4 } from "uuid"; + +export interface IClient extends ModelDataBase { + maintainer: ObjectId; + internal: boolean; + name: string; + redirect_url: string; + website: string; + logo?: string; + client_id: string; + client_secret: string; + featured?: boolean; + description?: string; +} + +const Client = DB.addModel({ + name: "client", + versions: [ + { + migration: () => { }, + schema: { + maintainer: { type: ObjectId }, + internal: { type: Boolean, default: false }, + name: { type: String }, + redirect_url: { type: String }, + website: { type: String }, + logo: { type: String, optional: true }, + client_id: { type: String, default: () => v4() }, + client_secret: { type: String }, + featured: { type: Boolean, optional: true }, + description: { type: String, optional: true }, + }, + }, + ], +}); + +export default Client; diff --git a/Backend/src/models/client_code.ts b/Backend/src/models/client_code.ts index b3208c5..0dce2a2 100644 --- a/Backend/src/models/client_code.ts +++ b/Backend/src/models/client_code.ts @@ -1,29 +1,29 @@ -import DB from "../database"; -import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; -import { ObjectID } from "mongodb"; -import { v4 } from "uuid"; - -export interface IClientCode extends ModelDataBase { - user: ObjectID; - code: string; - client: ObjectID; - permissions: ObjectID[]; - validTill: Date; -} - -const ClientCode = DB.addModel({ - name: "client_code", - versions: [ - { - migration: () => {}, - schema: { - user: { type: ObjectID }, - code: { type: String }, - client: { type: ObjectID }, - permissions: { type: Array }, - validTill: { type: Date }, - }, - }, - ], -}); -export default ClientCode; +import DB from "../database"; +import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; +import { ObjectId } from "mongodb"; +import { v4 } from "uuid"; + +export interface IClientCode extends ModelDataBase { + user: ObjectId; + code: string; + client: ObjectId; + permissions: ObjectId[]; + validTill: Date; +} + +const ClientCode = DB.addModel({ + name: "client_code", + versions: [ + { + migration: () => { }, + schema: { + user: { type: ObjectId }, + code: { type: String }, + client: { type: ObjectId }, + permissions: { type: Array }, + validTill: { type: Date }, + }, + }, + ], +}); +export default ClientCode; diff --git a/Backend/src/models/grants.ts b/Backend/src/models/grants.ts index 0c18bae..9d972f5 100644 --- a/Backend/src/models/grants.ts +++ b/Backend/src/models/grants.ts @@ -1,25 +1,25 @@ -import DB from "../database"; -import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; -import { ObjectID } from "mongodb"; - -export interface IGrant extends ModelDataBase { - user: ObjectID; - client: ObjectID; - permissions: ObjectID[]; -} - -const Grant = DB.addModel({ - name: "grant", - versions: [ - { - migration: () => {}, - schema: { - user: { type: ObjectID }, - client: { type: ObjectID }, - permissions: { type: ObjectID, array: true }, - }, - }, - ], -}); - -export default Grant; +import DB from "../database"; +import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; +import { ObjectId } from "mongodb"; + +export interface IGrant extends ModelDataBase { + user: ObjectId; + client: ObjectId; + permissions: ObjectId[]; +} + +const Grant = DB.addModel({ + name: "grant", + versions: [ + { + migration: () => { }, + schema: { + user: { type: ObjectId }, + client: { type: ObjectId }, + permissions: { type: ObjectId, array: true }, + }, + }, + ], +}); + +export default Grant; diff --git a/Backend/src/models/login_token.ts b/Backend/src/models/login_token.ts index 84ee734..1322d92 100644 --- a/Backend/src/models/login_token.ts +++ b/Backend/src/models/login_token.ts @@ -1,76 +1,76 @@ -import DB from "../database"; -import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; -import { ObjectID } from "mongodb"; -import moment = require("moment"); - -export interface ILoginToken extends ModelDataBase { - token: string; - special: boolean; - user: ObjectID; - validTill: Date; - valid: boolean; - validated: boolean; - data: any; - ip: string; - browser: string; -} -const LoginToken = DB.addModel({ - name: "login_token", - versions: [ - { - migration: () => {}, - schema: { - token: { type: String }, - special: { type: Boolean, default: () => false }, - user: { type: ObjectID }, - validTill: { type: Date }, - valid: { type: Boolean }, - }, - }, - { - migration: (doc: ILoginToken) => { - doc.validated = true; - }, - schema: { - token: { type: String }, - special: { type: Boolean, default: () => false }, - user: { type: ObjectID }, - validTill: { type: Date }, - valid: { type: Boolean }, - validated: { type: Boolean, default: false }, - }, - }, - { - migration: (doc: ILoginToken) => { - doc.validated = true; - }, - schema: { - token: { type: String }, - special: { type: Boolean, default: () => false }, - user: { type: ObjectID }, - validTill: { type: Date }, - valid: { type: Boolean }, - validated: { type: Boolean, default: false }, - data: { type: "any", optional: true }, - ip: { type: String, optional: true }, - browser: { type: String, optional: true }, - }, - }, - ], -}); - -export async function CheckToken( - token: ILoginToken, - validated: boolean = true -): Promise { - if (!token || !token.valid) return false; - if (validated && !token.validated) return false; - if (moment().isAfter(token.validTill)) { - token.valid = false; - await LoginToken.save(token); - return false; - } - return true; -} - -export default LoginToken; +import DB from "../database"; +import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; +import { ObjectId } from "mongodb"; +import moment = require("moment"); + +export interface ILoginToken extends ModelDataBase { + token: string; + special: boolean; + user: ObjectId; + validTill: Date; + valid: boolean; + validated: boolean; + data: any; + ip: string; + browser: string; +} +const LoginToken = DB.addModel({ + name: "login_token", + versions: [ + { + migration: () => { }, + schema: { + token: { type: String }, + special: { type: Boolean, default: () => false }, + user: { type: ObjectId }, + validTill: { type: Date }, + valid: { type: Boolean }, + }, + }, + { + migration: (doc: ILoginToken) => { + doc.validated = true; + }, + schema: { + token: { type: String }, + special: { type: Boolean, default: () => false }, + user: { type: ObjectId }, + validTill: { type: Date }, + valid: { type: Boolean }, + validated: { type: Boolean, default: false }, + }, + }, + { + migration: (doc: ILoginToken) => { + doc.validated = true; + }, + schema: { + token: { type: String }, + special: { type: Boolean, default: () => false }, + user: { type: ObjectId }, + validTill: { type: Date }, + valid: { type: Boolean }, + validated: { type: Boolean, default: false }, + data: { type: "any", optional: true }, + ip: { type: String, optional: true }, + browser: { type: String, optional: true }, + }, + }, + ], +}); + +export async function CheckToken( + token: ILoginToken, + validated: boolean = true +): Promise { + if (!token || !token.valid) return false; + if (validated && !token.validated) return false; + if (moment().isAfter(token.validTill)) { + token.valid = false; + await LoginToken.save(token); + return false; + } + return true; +} + +export default LoginToken; diff --git a/Backend/src/models/permissions.ts b/Backend/src/models/permissions.ts index 04eaadc..3138164 100644 --- a/Backend/src/models/permissions.ts +++ b/Backend/src/models/permissions.ts @@ -1,37 +1,37 @@ -import DB from "../database"; -import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; -import { ObjectID } from "mongodb"; - -export interface IPermission extends ModelDataBase { - name: string; - description: string; - client: ObjectID; - grant_type: "user" | "client"; -} - -const Permission = DB.addModel({ - name: "permission", - versions: [ - { - migration: () => {}, - schema: { - name: { type: String }, - description: { type: String }, - client: { type: ObjectID }, - }, - }, - { - migration: (old) => { - old.grant_type = "user"; - }, - schema: { - name: { type: String }, - description: { type: String }, - client: { type: ObjectID }, - grant_type: { type: String, default: "user" }, - }, - }, - ], -}); - -export default Permission; +import DB from "../database"; +import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; +import { ObjectId } from "mongodb"; + +export interface IPermission extends ModelDataBase { + name: string; + description: string; + client: ObjectId; + grant_type: "user" | "client"; +} + +const Permission = DB.addModel({ + name: "permission", + versions: [ + { + migration: () => { }, + schema: { + name: { type: String }, + description: { type: String }, + client: { type: ObjectId }, + }, + }, + { + migration: (old) => { + old.grant_type = "user"; + }, + schema: { + name: { type: String }, + description: { type: String }, + client: { type: ObjectId }, + grant_type: { type: String, default: "user" }, + }, + }, + ], +}); + +export default Permission; diff --git a/Backend/src/models/refresh_token.ts b/Backend/src/models/refresh_token.ts index d7c0b63..39d7c45 100644 --- a/Backend/src/models/refresh_token.ts +++ b/Backend/src/models/refresh_token.ts @@ -1,32 +1,32 @@ -import DB from "../database"; -import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; -import { ObjectID } from "mongodb"; -import { v4 } from "uuid"; - -export interface IRefreshToken extends ModelDataBase { - token: string; - user: ObjectID; - client: ObjectID; - permissions: ObjectID[]; - validTill: Date; - valid: boolean; -} - -const RefreshToken = DB.addModel({ - name: "refresh_token", - versions: [ - { - migration: () => {}, - schema: { - token: { type: String }, - user: { type: ObjectID }, - client: { type: ObjectID }, - permissions: { type: Array }, - validTill: { type: Date }, - valid: { type: Boolean }, - }, - }, - ], -}); - -export default RefreshToken; +import DB from "../database"; +import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; +import { ObjectId } from "mongodb"; +import { v4 } from "uuid"; + +export interface IRefreshToken extends ModelDataBase { + token: string; + user: ObjectId; + client: ObjectId; + permissions: ObjectId[]; + validTill: Date; + valid: boolean; +} + +const RefreshToken = DB.addModel({ + name: "refresh_token", + versions: [ + { + migration: () => { }, + schema: { + token: { type: String }, + user: { type: ObjectId }, + client: { type: ObjectId }, + permissions: { type: Array }, + validTill: { type: Date }, + valid: { type: Boolean }, + }, + }, + ], +}); + +export default RefreshToken; diff --git a/Backend/src/models/regcodes.ts b/Backend/src/models/regcodes.ts index 39365aa..5d1f9c7 100644 --- a/Backend/src/models/regcodes.ts +++ b/Backend/src/models/regcodes.ts @@ -1,57 +1,57 @@ -import DB from "../database"; -import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; -import { ObjectID } from "mongodb"; -import { v4 } from "uuid"; - -export interface IRegCode extends ModelDataBase { - token: string; - valid: boolean; - validTill: Date; -} - -const RegCode = DB.addModel({ - name: "reg_code", - versions: [ - { - migration: () => {}, - schema: { - token: { type: String }, - valid: { type: Boolean }, - validTill: { type: Date }, - }, - }, - ], -}); - -export default RegCode; - -// import { Model, Table, Column, ForeignKey, BelongsTo, Unique, CreatedAt, UpdatedAt, DeletedAt, HasMany, BelongsToMany, Default, DataType } from "sequelize-typescript" -// import User from "./user"; -// import Permission from "./permissions"; -// import RefreshPermission from "./refresh_permission"; - -// @Table -// export default class RegCode extends Model { -// @Unique -// @Default(DataType.UUIDV4) -// @Column(DataType.UUID) -// token: string - -// @Column -// validTill: Date - -// @Column -// valid: boolean - -// @Column -// @CreatedAt -// creationDate: Date; - -// @Column -// @UpdatedAt -// updatedOn: Date; - -// @Column -// @DeletedAt -// deletionDate: Date; -// } +import DB from "../database"; +import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; +import { ObjectId } from "mongodb"; +import { v4 } from "uuid"; + +export interface IRegCode extends ModelDataBase { + token: string; + valid: boolean; + validTill: Date; +} + +const RegCode = DB.addModel({ + name: "reg_code", + versions: [ + { + migration: () => { }, + schema: { + token: { type: String }, + valid: { type: Boolean }, + validTill: { type: Date }, + }, + }, + ], +}); + +export default RegCode; + +// import { Model, Table, Column, ForeignKey, BelongsTo, Unique, CreatedAt, UpdatedAt, DeletedAt, HasMany, BelongsToMany, Default, DataType } from "sequelize-typescript" +// import User from "./user"; +// import Permission from "./permissions"; +// import RefreshPermission from "./refresh_permission"; + +// @Table +// export default class RegCode extends Model { +// @Unique +// @Default(DataType.UUIDV4) +// @Column(DataType.UUID) +// token: string + +// @Column +// validTill: Date + +// @Column +// valid: boolean + +// @Column +// @CreatedAt +// creationDate: Date; + +// @Column +// @UpdatedAt +// updatedOn: Date; + +// @Column +// @DeletedAt +// deletionDate: Date; +// } diff --git a/Backend/src/models/twofactor.ts b/Backend/src/models/twofactor.ts index 2e04164..d05a207 100644 --- a/Backend/src/models/twofactor.ts +++ b/Backend/src/models/twofactor.ts @@ -1,69 +1,71 @@ -import DB from "../database"; -import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; -import { ObjectID } from "bson"; - -export enum TFATypes { - TOTP, - BACKUP_CODE, - WEBAUTHN, - APP_ALLOW, -} - -export const TFANames = new Map(); -TFANames.set(TFATypes.TOTP, "Authenticator"); -TFANames.set(TFATypes.BACKUP_CODE, "Backup Codes"); -TFANames.set(TFATypes.WEBAUTHN, "Security Key (WebAuthn)"); -TFANames.set(TFATypes.APP_ALLOW, "App Push"); - -export interface ITwoFactor extends ModelDataBase { - user: ObjectID; - valid: boolean; - expires?: Date; - name?: string; - type: TFATypes; - data: any; -} - -export interface IOTC extends ITwoFactor { - data: string; -} - -export interface IYubiKey extends ITwoFactor { - data: { - registration?: any; - publicKey: string; - keyHandle: string; - }; -} - -export interface IU2F extends ITwoFactor { - data: { - challenge?: string; - publicKey: string; - keyHandle: string; - registration?: string; - }; -} - -export interface IBackupCode extends ITwoFactor { - data: string[]; -} - -const TwoFactor = DB.addModel({ - name: "twofactor", - versions: [ - { - migration: (e) => { }, - schema: { - user: { type: ObjectID }, - valid: { type: Boolean }, - expires: { type: Date, optional: true }, - name: { type: String, optional: true }, - type: { type: Number }, - data: { type: "any" }, - }, - }, - ], -}); - -export default TwoFactor; +import { TFAType } from "@hibas123/openauth-internalapi"; +import DB from "../database"; +import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; +import { ObjectId } from "bson"; +import { Binary } from "mongodb"; + +export { TFAType as TFATypes }; + + +export const TFANames = new Map(); +TFANames.set(TFAType.TOTP, "Authenticator"); +TFANames.set(TFAType.BACKUP_CODE, "Backup Codes"); +TFANames.set(TFAType.WEBAUTHN, "Security Key (WebAuthn)"); +TFANames.set(TFAType.APP_ALLOW, "App Push"); + +export interface ITwoFactor extends ModelDataBase { + user: ObjectId; + valid: boolean; + expires?: Date; + name?: string; + type: TFAType; + data: any; +} + +export interface ITOTP extends ITwoFactor { + data: string; +} + +export interface IWebAuthn extends ITwoFactor { + data: { + challenge?: any; + device?: { + credentialID: Binary; + credentialPublicKey: Binary; + counter: number; + transports: AuthenticatorTransport[] + } + }; +} + +export interface IU2F extends ITwoFactor { + data: { + challenge?: string; + publicKey: string; + keyHandle: string; + registration?: string; + }; +} + +export interface IBackupCode extends ITwoFactor { + data: string[]; +} + +const TwoFactor = DB.addModel({ + name: "twofactor", + versions: [ + { + migration: (e) => { }, + schema: { + user: { type: ObjectId }, + valid: { type: Boolean }, + expires: { type: Date, optional: true }, + name: { type: String, optional: true }, + type: { type: Number }, + data: { type: "any" }, + }, + }, + ], +}); + +export default TwoFactor; diff --git a/Backend/src/models/user.ts b/Backend/src/models/user.ts index 2342e2b..8b62c19 100644 --- a/Backend/src/models/user.ts +++ b/Backend/src/models/user.ts @@ -1,134 +1,134 @@ -import DB from "../database"; -import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; -import { ObjectID } from "mongodb"; -import { v4 } from "uuid"; -import { randomString } from "../helper/random"; - -export enum Gender { - none, - male, - female, - other, -} - -export interface IUser extends ModelDataBase { - uid: string; - username: string; - - name: string; - birthday?: Date; - gender: Gender; - admin: boolean; - password: string; - salt: string; - mails: ObjectID[]; - phones: { phone: string; verified: boolean; primary: boolean }[]; - encryption_key: string; -} - -const User = DB.addModel({ - name: "user", - versions: [ - { - migration: () => {}, - schema: { - uid: { type: String, default: () => v4() }, - username: { type: String }, - name: { type: String }, - birthday: { type: Date, optional: true }, - gender: { type: Number }, - admin: { type: Boolean }, - password: { type: String }, - salt: { type: String }, - mails: { type: Array, default: () => [] }, - phones: { - array: true, - model: true, - type: { - phone: { type: String }, - verified: { type: Boolean }, - primary: { type: Boolean }, - }, - }, - twofactor: { - array: true, - model: true, - type: { - token: { type: String }, - valid: { type: Boolean }, - type: { type: Number }, - }, - }, - }, - }, - { - migration: (e: IUser) => { - e.encryption_key = randomString(64); - }, - schema: { - uid: { type: String, default: () => v4() }, - username: { type: String }, - name: { type: String }, - birthday: { type: Date, optional: true }, - gender: { type: Number }, - admin: { type: Boolean }, - password: { type: String }, - salt: { type: String }, - mails: { type: Array, default: () => [] }, - phones: { - array: true, - model: true, - type: { - phone: { type: String }, - verified: { type: Boolean }, - primary: { type: Boolean }, - }, - }, - twofactor: { - array: true, - model: true, - type: { - token: { type: String }, - valid: { type: Boolean }, - type: { type: Number }, - }, - }, - encryption_key: { - type: String, - default: () => randomString(64), - }, - }, - }, - { - migration: (e: any) => { - delete e.twofactor; - }, - schema: { - uid: { type: String, default: () => v4() }, - username: { type: String }, - name: { type: String }, - birthday: { type: Date, optional: true }, - gender: { type: Number }, - admin: { type: Boolean }, - password: { type: String }, - salt: { type: String }, - mails: { type: Array, default: () => [] }, - phones: { - array: true, - model: true, - type: { - phone: { type: String }, - verified: { type: Boolean }, - primary: { type: Boolean }, - }, - }, - encryption_key: { - type: String, - default: () => randomString(64), - }, - }, - }, - ], -}); - -export default User; +import DB from "../database"; +import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; +import { ObjectId } from "mongodb"; +import { v4 } from "uuid"; +import { randomString } from "../helper/random"; + +export enum Gender { + none, + male, + female, + other, +} + +export interface IUser extends ModelDataBase { + uid: string; + username: string; + + name: string; + birthday?: Date; + gender: Gender; + admin: boolean; + password: string; + salt: string; + mails: ObjectId[]; + phones: { phone: string; verified: boolean; primary: boolean }[]; + encryption_key: string; +} + +const User = DB.addModel({ + name: "user", + versions: [ + { + migration: () => { }, + schema: { + uid: { type: String, default: () => v4() }, + username: { type: String }, + name: { type: String }, + birthday: { type: Date, optional: true }, + gender: { type: Number }, + admin: { type: Boolean }, + password: { type: String }, + salt: { type: String }, + mails: { type: Array, default: () => [] }, + phones: { + array: true, + model: true, + type: { + phone: { type: String }, + verified: { type: Boolean }, + primary: { type: Boolean }, + }, + }, + twofactor: { + array: true, + model: true, + type: { + token: { type: String }, + valid: { type: Boolean }, + type: { type: Number }, + }, + }, + }, + }, + { + migration: (e: IUser) => { + e.encryption_key = randomString(64); + }, + schema: { + uid: { type: String, default: () => v4() }, + username: { type: String }, + name: { type: String }, + birthday: { type: Date, optional: true }, + gender: { type: Number }, + admin: { type: Boolean }, + password: { type: String }, + salt: { type: String }, + mails: { type: Array, default: () => [] }, + phones: { + array: true, + model: true, + type: { + phone: { type: String }, + verified: { type: Boolean }, + primary: { type: Boolean }, + }, + }, + twofactor: { + array: true, + model: true, + type: { + token: { type: String }, + valid: { type: Boolean }, + type: { type: Number }, + }, + }, + encryption_key: { + type: String, + default: () => randomString(64), + }, + }, + }, + { + migration: (e: any) => { + delete e.twofactor; + }, + schema: { + uid: { type: String, default: () => v4() }, + username: { type: String }, + name: { type: String }, + birthday: { type: Date, optional: true }, + gender: { type: Number }, + admin: { type: Boolean }, + password: { type: String }, + salt: { type: String }, + mails: { type: Array, default: () => [] }, + phones: { + array: true, + model: true, + type: { + phone: { type: String }, + verified: { type: Boolean }, + primary: { type: Boolean }, + }, + }, + encryption_key: { + type: String, + default: () => randomString(64), + }, + }, + }, + ], +}); + +export default User; diff --git a/Backend/src/testdata.ts b/Backend/src/testdata.ts index 2e8e364..f09f16e 100644 --- a/Backend/src/testdata.ts +++ b/Backend/src/testdata.ts @@ -1,147 +1,170 @@ -import User, { Gender } from "./models/user"; -import Client from "./models/client"; -import Logging from "@hibas123/nodelogging"; -import RegCode from "./models/regcodes"; -import * as moment from "moment"; -import Permission from "./models/permissions"; -import { ObjectID } from "bson"; -import DB from "./database"; -import TwoFactor from "./models/twofactor"; - -import * as speakeasy from "speakeasy"; -import LoginToken from "./models/login_token"; -import Mail from "./models/mail"; - -export default async function TestData() { - Logging.warn("Running in dev mode! Database will be cleared!"); - await DB.db.dropDatabase(); - - let mail = await Mail.findOne({ mail: "test@test.de" }); - if (!mail) { - mail = Mail.new({ - mail: "test@test.de", - primary: true, - verified: true, - }); - - await Mail.save(mail); - } - - let u = await User.findOne({ username: "test" }); - if (!u) { - Logging.log("Adding test user"); - u = User.new({ - username: "test", - birthday: new Date(), - gender: Gender.male, - name: "Test Test", - password: - "125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc", - salt: "test", - admin: true, - phones: [ - { phone: "+4915962855955", primary: true, verified: true }, - { phone: "+4915962855932", primary: false, verified: false }, - ], - mails: [mail._id], - }); - await User.save(u); - } - - let c = await Client.findOne({ client_id: "test001" }); - if (!c) { - Logging.log("Adding test client"); - c = Client.new({ - client_id: "test001", - client_secret: "test001", - internal: true, - maintainer: u._id, - name: "Test Client", - website: "http://example.com", - redirect_url: "http://example.com", - featured: true, - description: - "This client is just for testing purposes. It does not have any functionality.", - }); - await Client.save(c); - } - - let perm = await Permission.findById("507f1f77bcf86cd799439011"); - if (!perm) { - Logging.log("Adding test permission"); - perm = Permission.new({ - _id: new ObjectID("507f1f77bcf86cd799439011"), - name: "TestPerm", - description: "Permission just for testing purposes", - client: c._id, - }); - - await (await (Permission as any)._collection).insertOne(perm); - - // Permission.save(perm); - } - - let r = await RegCode.findOne({ token: "test" }); - if (!r) { - Logging.log("Adding test reg_code"); - r = RegCode.new({ - token: "test", - valid: true, - validTill: moment().add("1", "year").toDate(), - }); - await RegCode.save(r); - } - - let t = await TwoFactor.findOne({ user: u._id, type: 0 }); - if (!t) { - t = TwoFactor.new({ - user: u._id, - name: "Test OTP", - type: 0, - valid: true, - data: "IIRW2P2UJRDDO2LDIRYW4LSREZLWMOKDNBJES2LLHRREK3R6KZJQ", - expires: null, - }); - await TwoFactor.save(t); - } - - let login_token = await LoginToken.findOne({ token: "test01" }); - if (login_token) await LoginToken.delete(login_token); - - login_token = LoginToken.new({ - browser: "DEMO", - ip: "10.0.0.1", - special: false, - token: "test01", - valid: true, - validTill: moment().add("10", "years").toDate(), - user: u._id, - validated: true, - }); - await LoginToken.save(login_token); - - let special_token = await LoginToken.findOne({ token: "test02" }); - if (special_token) await LoginToken.delete(special_token); - - special_token = LoginToken.new({ - browser: "DEMO", - ip: "10.0.0.1", - special: true, - token: "test02", - valid: true, - validTill: moment().add("10", "years").toDate(), - user: u._id, - validated: true, - }); - await LoginToken.save(special_token); - - // setInterval(() => { - // let code = speakeasy.totp({ - // secret: t.data, - // encoding: "base32" - // }) - // Logging.debug("OTC Code is:", code); - // }, 1000) - - console.log("Finished adding test data") -} +import User, { Gender } from "./models/user"; +import Client from "./models/client"; +import Logging from "@hibas123/nodelogging"; +import RegCode from "./models/regcodes"; +import moment from "moment"; +import Permission from "./models/permissions"; +import { ObjectId } from "mongodb"; +import DB from "./database"; +import TwoFactor from "./models/twofactor"; + +import LoginToken from "./models/login_token"; +import Mail from "./models/mail"; + +export default async function TestData() { + Logging.warn("Running in dev mode! Database will be cleared!"); + // await DB.db.dropDatabase(); + + let mail = await Mail.findOne({ mail: "test@test.de" }); + if (!mail) { + mail = Mail.new({ + mail: "test@test.de", + primary: true, + verified: true, + }); + + await Mail.save(mail); + } + + let u = await User.findOne({ username: "test" }); + if (!u) { + Logging.log("Adding test user"); + u = User.new({ + username: "test", + birthday: new Date(), + gender: Gender.male, + name: "Test Test", + password: + "125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc", + salt: "test", + admin: true, + phones: [ + { phone: "+4915962855955", primary: true, verified: true }, + { phone: "+4915962855932", primary: false, verified: false }, + ], + mails: [mail._id], + }); + await User.save(u); + } + + let c = await Client.findOne({ client_id: "test001" }); + if (!c) { + Logging.log("Adding test client"); + c = Client.new({ + client_id: "test001", + client_secret: "test001", + internal: true, + maintainer: u._id, + name: "Test Client", + website: "http://example.com", + redirect_url: "http://example.com", + featured: true, + description: + "This client is just for testing purposes. It does not have any functionality.", + }); + await Client.save(c); + } + + let perm = await Permission.findById("507f1f77bcf86cd799439011"); + if (!perm) { + Logging.log("Adding test permission"); + perm = Permission.new({ + _id: new ObjectId("507f1f77bcf86cd799439011"), + name: "TestPerm", + description: "Permission just for testing purposes", + client: c._id, + }); + + await (await (Permission as any)._collection).insertOne(perm); + + // Permission.save(perm); + } + + let r = await RegCode.findOne({ token: "test" }); + if (!r) { + Logging.log("Adding test reg_code"); + r = RegCode.new({ + token: "test", + valid: true, + validTill: moment().add("1", "year").toDate(), + }); + await RegCode.save(r); + } + + let t = await TwoFactor.findOne({ user: u._id, type: 0 }); + if (!t) { + Logging.log("Adding test TOTP") + t = TwoFactor.new({ + user: u._id, + name: "Test OTP", + type: 0, + valid: true, + data: "IIRW2P2UJRDDO2LDIRYW4LSREZLWMOKDNBJES2LLHRREK3R6KZJQ", + expires: null, + }); + await TwoFactor.save(t); + } + + // let tw = await TwoFactor.findOne({ user: u._id, type: 2 }); + // if (!tw) { + // Logging.log("Adding test WebAuthn") + // tw = TwoFactor.new({ + // user: u._id, + // name: "WebAuthn", + // type: 2, + // valid: true, + // data: { + // device: { + // credentialPublicKey: Buffer.from("pQECAyYgASFYINiHCRopJIn1GoTXq7SpDTJR1nzocqOWhjvpYaKLzzhSIlggvuHhjABe8NxbOIGA11vrd5deUT5R30anpE7W7xzPcsk=", "base64"), + // credentialID: Buffer.from("i/BJiffx0bxjQ9Ptyvc9ORELXALxrvD6pad1Xc/2nDI=", "base64"), + // counter: 1, + // transports: [ + // "usb" + // ] + // } + // } + // }); + // await TwoFactor.save(tw); + // } + + let login_token = await LoginToken.findOne({ token: "test01" }); + if (login_token) await LoginToken.delete(login_token); + + login_token = LoginToken.new({ + browser: "DEMO", + ip: "10.0.0.1", + special: false, + token: "test01", + valid: true, + validTill: moment().add("10", "years").toDate(), + user: u._id, + validated: true, + }); + await LoginToken.save(login_token); + + let special_token = await LoginToken.findOne({ token: "test02" }); + if (special_token) await LoginToken.delete(special_token); + + special_token = LoginToken.new({ + browser: "DEMO", + ip: "10.0.0.1", + special: true, + token: "test02", + valid: true, + validTill: moment().add("10", "years").toDate(), + user: u._id, + validated: true, + }); + await LoginToken.save(special_token); + + // setInterval(() => { + // let code = speakeasy.totp({ + // secret: t.data, + // encoding: "base32" + // }) + // Logging.debug("OTC Code is:", code); + // }, 1000) + + Logging.log("Finished adding test data"); +} + diff --git a/Backend/src/views/views.ts b/Backend/src/views/index.ts similarity index 97% rename from Backend/src/views/views.ts rename to Backend/src/views/index.ts index 9f56831..27bff86 100644 --- a/Backend/src/views/views.ts +++ b/Backend/src/views/index.ts @@ -1,121 +1,121 @@ -import { - IRouter, - Request, - RequestHandler, - Router, - static as ServeStatic, -} from "express"; -import * as Handlebars from "handlebars"; -import * as moment from "moment"; -import { GetUserMiddleware, UserMiddleware } from "../api/middlewares/user"; -import GetAuthRoute from "../api/oauth/auth"; -import config from "../config"; -import { HttpStatusCode } from "../helper/request_error"; -import GetAdminPage from "./admin"; -import GetRegistrationPage from "./register"; -import * as path from "path"; - -const viewsv2_location = path.join(path.dirname(require.resolve("@hibas123/openauth-views-v2")), "build"); - - -Handlebars.registerHelper("appname", () => config.core.name); - -const cacheTime = !config.core.dev - ? moment.duration(1, "month").asSeconds() - : 1000; - -const addCache: RequestHandler = (req, res, next) => { - res.setHeader("cache-control", "public, max-age=" + cacheTime); - next(); -}; - - -const ViewRouter: IRouter = Router(); -ViewRouter.get("/", UserMiddleware, (req, res) => { - res.send("This is the main page"); -}); - -ViewRouter.get("/register", (req, res) => { - res.setHeader("Cache-Control", "public, max-age=" + cacheTime); - res.send(GetRegistrationPage(req.__)); -}); - -ViewRouter.use( - "/login", - addCache, - ServeStatic(path.join(viewsv2_location, "login"), { cacheControl: false }) -); - -ViewRouter.use( - "/user", - addCache, - ServeStatic(path.join(viewsv2_location, "user"), { cacheControl: false, }) -); - -ViewRouter.use( - "/static", - addCache, - ServeStatic(path.join(viewsv2_location, "../static"), { cacheControl: false, }) -); - -ViewRouter.get("/code", (req, res) => { - res.setHeader("Cache-Control", "no-cache"); - if (req.query.error) res.send("Some error occured: " + req.query.error); - else res.send(`Your code is: ${req.query.code}`); -}); - -ViewRouter.get( - "/admin", - GetUserMiddleware(false, true), - (req: Request, res, next) => { - if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN); - else next(); - }, - (req, res) => { - res.send(GetAdminPage(req.__)); - } -); - -ViewRouter.get("/auth", GetAuthRoute(true)); - -ViewRouter.use( - "/popup", - GetUserMiddleware(false, false), - addCache, - ServeStatic(path.join(viewsv2_location, "popup"), { cacheControl: false }) -); - -// ViewRouter.get("/popup", UserMiddleware, (req, res) => { -// res.send(GetPopupPage(req.__)); -// }); - -// if (config.core.dev) { -// const logo = -// "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAlwSFlzAAASdAAAEnQB3mYfeAAAAAZiS0dEAP8A/wD/oL2nkwAAAFR0RVh0Y29tbWVudABGaWxlIHNvdXJjZTogaHR0cHM6Ly9jb21tb25zLndpa2ltZWRpYS5vcmcvd2lraS9GaWxlOkdvb2dsZS1mYXZpY29uLTIwMTUucG5nLE0iZQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0wOS0wMlQxNzo1ODowOCswMDowMNkAVU4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTUtMDktMDJUMTc6NTg6MDgrMDA6MDCoXe3yAAAAR3RFWHRzb2Z0d2FyZQBJbWFnZU1hZ2ljayA2LjcuNy0xMCAyMDE0LTAzLTA2IFExNiBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9yZ2+foqIAAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAYdEVYdFRodW1iOjpJbWFnZTo6aGVpZ2h0ADQwMUEAWp0AAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgAMzk0OUtQUwAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxNDQxMjE2Njg4mYj7RAAAABN0RVh0VGh1bWI6OlNpemUAMTcuN0tCQhK/wrgAAAAzdEVYdFRodW1iOjpVUkkAZmlsZTovLy90bXAvbG9jYWxjb3B5XzFmZGViZjk0YmZkZC0xLnBuZ8EhOmkAAAkUSURBVGhDzZkLcBXVGcf/u/f9yM07gDq06Rh5BZTAtKMVwQGqaFEZmNLWgoBO1RRFausjbR1qKR0rHUDFtwVtqWPHgc4UmBq1NASUiFQGwiskMqZMSW6Tm+Q+knvvvvp9u0uamNxXctO5P9i5u+ec3T3/c77zfd/ZCBqBMUANhaB2+Ok3CMgSYLVC9ORBLC2F6CswW2WPrAmJHa5D9EAtpGMNkC+2AvE4IIrGcRlV1Q/BRqLGXwn7zNlwzF0A54JbqVIw2oyQUQmRms4g8voLiB14H5qiQHS5AJsNsFghCIk7pr+S2kOSoMWi+r2OG+fBc99a2K+tMltlxoiESKdPIvirGvo9ATHPB9gdEAaOfIboXYjHoAaDsF59DXy/+A3s180ya9MjIyGaLKP70QdoBmohFhSRidDoJxn5jKGuaLSe1K4uMrn5KNj6GgSLxaxMTtpCYh/VoXv9/fRgK0AmlMx0Rg0LivbpA1e4fQfss683KxKTlj2EXtiMrgdWQHB76HCPrQiGni+46D30vsDq7yD00hazIjEphfTUPILIGy+S2xyX9jRnDV53Ah0SecAUJDWtnifXIVq7j9ZD4cjWApuIecroT0jzOdwt9T9+eH/8M3hX/dAsTUxCIcFtz6D3zVdgKSzOSITGsYJiiEZeCGTjPKoamwq/hutoVgXycrDbE86wIaIdeY9vgOfuNWZpcoYVEj10AN3V9xjmlO4IUqe1cAiC0wX79XNg/+Zc2KZUQiwuheBwQCPz0Do7IDWfI8dxEPFD/4AW7IFA7lugqH8ZXYSfRPx8IzzLV5qlqRkihIOT/+uTIOQXpBUb+HbukFhUDO9Dj8H17SVmTWqif38Pod9thNrepr+PUf1t8G34LdxLv6dfp8sQIYEHV0I+8U/da6SCRauBThLwE3gpKo+UMGUH4eeeJRUKfL/eCvedy8ya9BkkJHa0EV2rbqc8qCylSWlmelH89l9hLb/aLB05sSOHoHxxAe7lK8ySzBgkJL7/K+jZ5oMa9kJ0Ui6UQIu+HkhE6YdHIZKvzwX6F4EapE6JrSj6ZSMc0wNQQjb2nkNgr6R2B1C6rz5nRDD9QrTmGmh0pUWs8N3bBN/3m2kRkxiFpsUUxJOnBjpQsJkCJC3uXKLftKT95Ovt5Nc5klLPBZcCpdNJplYJNSqSC+X4EIO1YhKKfv9nviWn0GdE7dhrrAddBCNA66PNjy+Ooqc/hb0iSDHCSibVjfxNW802uYXec82/e4CRDUCmqBy1wFd9Bu7bz8M27RuwjL/CrMwtdNOS6ydSh/9FE0L7i2Eh6xNliJN3QJywyixLn4ZmGR80ynDY0ssS0oUzHq8TqF5IGzsWIr1H64Pznn7T+hK0jLSYDNuiPhJEd2bIm/VxbN4bo5dmWQiNb1zW8NmmPDIojeIF/TcWyfBo1EBwUj40AhGM1SLA5RDgzvLhoYPF6AajSQHjbckiOTs29yTzInfgLnPXuno1mhE5bBYngVXby4zzHIPFRKIsJM00nUK6eZJb0OqGQtMiwuI1RjwZrFVqN85zDN6DWmgyRMHGO0AuSaKGvJkWOWde5Bb68iVvaJiWvuNMLISbIU71csgsyQx+GX9YlBUtoyPZ2DJ6PXW/wGXGc8H7NSrUffDwUGMbie299I5ZkBkOirMFlCjnu4W0j5I8SpOop8nEsFk5rTTMov5NgCScuR9q66u0dx4a2S1QybIUPNNdiX3iPBy883mzZuyZWRNCoSexP5Jo1kq8Anav95gzUkZbyy9NCA+EFTJNhoalnbfg1eh0XAg0oqWn1Wgwxnx4ilIiHukEIhiZ+nztRONLjC5ELF5onPW7WA02QcJ5uRCV/mU4K+ejUKBM2OHDw3UbzTZjy866ONx28yIBMQm44RrjC4wuhBEn3EE6FCpQYaUE8Y3IVNzceRsJUuEWKEXhdSJacTrQgj81Udo/hjRdUnH8C0Vfl4ng9cN51vxKo1H/xkqLnIZQNw2gdGpl13zUx8ahWIwNmVpu3t7bgYPLdqHcd5VZml0WboogTvbPOVoiWMRXS0T8odr42tM/I4JnKlq812Fm22J8Gi9BsWWoCIa/rpS6irDgL6vRFukwS7PH2p19CMWSi2DCUeCem/7nnPqFMN3T96MtGiRTkvQYmQiLSJstmxc3vPtdfNJ+wiwdPeveiuJIs6JntclQKOXl9bOgMoGQWYUTsKR8Hnpl2nekwEpiCmnxL9v/MJ468pxZOjJawmdx6/ZjaDjnRL4ruQgmSN1bv2iwJ+hfIwOZ/MdFcFuc+singm8PS72wW2z40Yy7sXrKEjpP4W5MzgY+x5bjO/G31noUuDV4Ox6Eq2M5NAv1lJzMcHDEd1AQrH1y8KeoYYUc85/CXXurMc5doq+JdFBo3xmRe3VhVWXTMOeKWZhRMgnjXCVwWR2IqRK6oj16HOLnf9T2Gf4d9sNjc8PJwuk1mqUHtvBseC6yi6duiYP/LsLP9gc17HnEjYoJgwd5WCHMK43vYNPRl/WFnclfqFR6nESdjisS/crUHY36yGHVeI1VsOizZxNtlLVy1B74bGol9kFQ3PC0PgtrrJyuI7pIJhDWcO9cG9be4jAKBpBQCLOh4XnsPLMHJc7CjMQMZODj030GpYw0O2G429bBEVhM570IRVVUlYt4ec3wH9eTCmGe/mQ7Xj/1LsoynJnRolFgZlOzB2+C8vkTqBivYdfaxN8MUgphdpzejacattHMFOne6v8Fd61TuoQ5xXfgrdueMEuHJy0hzMmOJvzg/Z8iJsfhpQU61rPD66sz2oWHZqzAY7PuM0sTk7aQyzx+eDPeplwr354HB3mbbAtSKN/riYd1U35t/kZUFleYNcnJWAhzMdyGmo+34MDFBuTZPOQ+HRRzBsXWjOAu8AyE4hHk2d14tGoNVk6+y6xNjxEJuYy/txPbT+7CnpYPaBRDFERdlLFaaR1ZKWXgvcTQ2eLX8T+Ftgzc+ZgSpw2SpMeeNVOXYnH5zWbLzBiVkIGc6jyP2tbD+LjtOM51XdDNQ6POshgjjrAI45pn8CrveFSVTsGcK2fjWxNv1M10NGRNyHAEKJKzuXCA5FliJ5HvyNP3NdkF+C8dn/ikO2g+hwAAAABJRU5ErkJggg=="; -// ViewRouter.get("/devauth", (req, res) => { -// res.send( -// GetAuthPage(req.__, "Test 05265", [ -// { -// name: "Access Profile", -// description: -// "It allows the application to know who you are. Required for all applications. And a lot of more Text, because why not? This will not stop, till it is multiple lines long and maybe kill the layout, so keep reading as long as you like, but I promise it will get boring after some time. So this should be enougth.", -// logo: logo, -// }, -// { -// name: "Test 1", -// description: -// "This is not an real permission. This is used just to verify the layout", -// logo: logo, -// }, -// { -// name: "Test 2", -// description: -// "This is not an real permission. This is used just to verify the layout", -// logo: logo, -// }, -// ]) -// ); -// }); -// } - -export default ViewRouter; +import { + IRouter, + Request, + RequestHandler, + Router, + static as ServeStatic, +} from "express"; +import * as Handlebars from "handlebars"; +import moment = require("moment"); +import { GetUserMiddleware, UserMiddleware } from "../api/middlewares/user"; +import GetAuthRoute from "../api/oauth/auth"; +import config from "../config"; +import { HttpStatusCode } from "../helper/request_error"; +import GetAdminPage from "./admin"; +import GetRegistrationPage from "./register"; +import * as path from "path"; + +const viewsv2_location = path.join(path.dirname(require.resolve("@hibas123/openauth-views-v2")), "build"); + + +Handlebars.registerHelper("appname", () => config.core.name); + +const cacheTime = !config.core.dev + ? moment.duration(1, "month").asSeconds() + : 1000; + +const addCache: RequestHandler = (req, res, next) => { + res.setHeader("cache-control", "public, max-age=" + cacheTime); + next(); +}; + + +const ViewRouter: IRouter = Router(); +ViewRouter.get("/", UserMiddleware, (req, res) => { + res.send("This is the main page"); +}); + +ViewRouter.get("/register", (req, res) => { + res.setHeader("Cache-Control", "public, max-age=" + cacheTime); + res.send(GetRegistrationPage(req.__)); +}); + +ViewRouter.use( + "/login", + addCache, + ServeStatic(path.join(viewsv2_location, "login"), { cacheControl: false }) +); + +ViewRouter.use( + "/user", + addCache, + ServeStatic(path.join(viewsv2_location, "user"), { cacheControl: false, }) +); + +ViewRouter.use( + "/static", + addCache, + ServeStatic(path.join(viewsv2_location, "../static"), { cacheControl: false, }) +); + +ViewRouter.get("/code", (req, res) => { + res.setHeader("Cache-Control", "no-cache"); + if (req.query.error) res.send("Some error occured: " + req.query.error); + else res.send(`Your code is: ${req.query.code}`); +}); + +ViewRouter.get( + "/admin", + GetUserMiddleware(false, true), + (req: Request, res, next) => { + if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN); + else next(); + }, + (req, res) => { + res.send(GetAdminPage(req.__)); + } +); + +ViewRouter.get("/auth", GetAuthRoute(true)); + +ViewRouter.use( + "/popup", + GetUserMiddleware(false, false), + addCache, + ServeStatic(path.join(viewsv2_location, "popup"), { cacheControl: false }) +); + +// ViewRouter.get("/popup", UserMiddleware, (req, res) => { +// res.send(GetPopupPage(req.__)); +// }); + +// if (config.core.dev) { +// const logo = +// "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAlwSFlzAAASdAAAEnQB3mYfeAAAAAZiS0dEAP8A/wD/oL2nkwAAAFR0RVh0Y29tbWVudABGaWxlIHNvdXJjZTogaHR0cHM6Ly9jb21tb25zLndpa2ltZWRpYS5vcmcvd2lraS9GaWxlOkdvb2dsZS1mYXZpY29uLTIwMTUucG5nLE0iZQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0wOS0wMlQxNzo1ODowOCswMDowMNkAVU4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTUtMDktMDJUMTc6NTg6MDgrMDA6MDCoXe3yAAAAR3RFWHRzb2Z0d2FyZQBJbWFnZU1hZ2ljayA2LjcuNy0xMCAyMDE0LTAzLTA2IFExNiBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9yZ2+foqIAAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAYdEVYdFRodW1iOjpJbWFnZTo6aGVpZ2h0ADQwMUEAWp0AAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgAMzk0OUtQUwAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxNDQxMjE2Njg4mYj7RAAAABN0RVh0VGh1bWI6OlNpemUAMTcuN0tCQhK/wrgAAAAzdEVYdFRodW1iOjpVUkkAZmlsZTovLy90bXAvbG9jYWxjb3B5XzFmZGViZjk0YmZkZC0xLnBuZ8EhOmkAAAkUSURBVGhDzZkLcBXVGcf/u/f9yM07gDq06Rh5BZTAtKMVwQGqaFEZmNLWgoBO1RRFausjbR1qKR0rHUDFtwVtqWPHgc4UmBq1NASUiFQGwiskMqZMSW6Tm+Q+knvvvvp9u0uamNxXctO5P9i5u+ec3T3/c77zfd/ZCBqBMUANhaB2+Ok3CMgSYLVC9ORBLC2F6CswW2WPrAmJHa5D9EAtpGMNkC+2AvE4IIrGcRlV1Q/BRqLGXwn7zNlwzF0A54JbqVIw2oyQUQmRms4g8voLiB14H5qiQHS5AJsNsFghCIk7pr+S2kOSoMWi+r2OG+fBc99a2K+tMltlxoiESKdPIvirGvo9ATHPB9gdEAaOfIboXYjHoAaDsF59DXy/+A3s180ya9MjIyGaLKP70QdoBmohFhSRidDoJxn5jKGuaLSe1K4uMrn5KNj6GgSLxaxMTtpCYh/VoXv9/fRgK0AmlMx0Rg0LivbpA1e4fQfss683KxKTlj2EXtiMrgdWQHB76HCPrQiGni+46D30vsDq7yD00hazIjEphfTUPILIGy+S2xyX9jRnDV53Ah0SecAUJDWtnifXIVq7j9ZD4cjWApuIecroT0jzOdwt9T9+eH/8M3hX/dAsTUxCIcFtz6D3zVdgKSzOSITGsYJiiEZeCGTjPKoamwq/hutoVgXycrDbE86wIaIdeY9vgOfuNWZpcoYVEj10AN3V9xjmlO4IUqe1cAiC0wX79XNg/+Zc2KZUQiwuheBwQCPz0Do7IDWfI8dxEPFD/4AW7IFA7lugqH8ZXYSfRPx8IzzLV5qlqRkihIOT/+uTIOQXpBUb+HbukFhUDO9Dj8H17SVmTWqif38Pod9thNrepr+PUf1t8G34LdxLv6dfp8sQIYEHV0I+8U/da6SCRauBThLwE3gpKo+UMGUH4eeeJRUKfL/eCvedy8ya9BkkJHa0EV2rbqc8qCylSWlmelH89l9hLb/aLB05sSOHoHxxAe7lK8ySzBgkJL7/K+jZ5oMa9kJ0Ui6UQIu+HkhE6YdHIZKvzwX6F4EapE6JrSj6ZSMc0wNQQjb2nkNgr6R2B1C6rz5nRDD9QrTmGmh0pUWs8N3bBN/3m2kRkxiFpsUUxJOnBjpQsJkCJC3uXKLftKT95Ovt5Nc5klLPBZcCpdNJplYJNSqSC+X4EIO1YhKKfv9nviWn0GdE7dhrrAddBCNA66PNjy+Ooqc/hb0iSDHCSibVjfxNW802uYXec82/e4CRDUCmqBy1wFd9Bu7bz8M27RuwjL/CrMwtdNOS6ydSh/9FE0L7i2Eh6xNliJN3QJywyixLn4ZmGR80ynDY0ssS0oUzHq8TqF5IGzsWIr1H64Pznn7T+hK0jLSYDNuiPhJEd2bIm/VxbN4bo5dmWQiNb1zW8NmmPDIojeIF/TcWyfBo1EBwUj40AhGM1SLA5RDgzvLhoYPF6AajSQHjbckiOTs29yTzInfgLnPXuno1mhE5bBYngVXby4zzHIPFRKIsJM00nUK6eZJb0OqGQtMiwuI1RjwZrFVqN85zDN6DWmgyRMHGO0AuSaKGvJkWOWde5Bb68iVvaJiWvuNMLISbIU71csgsyQx+GX9YlBUtoyPZ2DJ6PXW/wGXGc8H7NSrUffDwUGMbie299I5ZkBkOirMFlCjnu4W0j5I8SpOop8nEsFk5rTTMov5NgCScuR9q66u0dx4a2S1QybIUPNNdiX3iPBy883mzZuyZWRNCoSexP5Jo1kq8Anav95gzUkZbyy9NCA+EFTJNhoalnbfg1eh0XAg0oqWn1Wgwxnx4ilIiHukEIhiZ+nztRONLjC5ELF5onPW7WA02QcJ5uRCV/mU4K+ejUKBM2OHDw3UbzTZjy866ONx28yIBMQm44RrjC4wuhBEn3EE6FCpQYaUE8Y3IVNzceRsJUuEWKEXhdSJacTrQgj81Udo/hjRdUnH8C0Vfl4ng9cN51vxKo1H/xkqLnIZQNw2gdGpl13zUx8ahWIwNmVpu3t7bgYPLdqHcd5VZml0WboogTvbPOVoiWMRXS0T8odr42tM/I4JnKlq812Fm22J8Gi9BsWWoCIa/rpS6irDgL6vRFukwS7PH2p19CMWSi2DCUeCem/7nnPqFMN3T96MtGiRTkvQYmQiLSJstmxc3vPtdfNJ+wiwdPeveiuJIs6JntclQKOXl9bOgMoGQWYUTsKR8Hnpl2nekwEpiCmnxL9v/MJ468pxZOjJawmdx6/ZjaDjnRL4ruQgmSN1bv2iwJ+hfIwOZ/MdFcFuc+singm8PS72wW2z40Yy7sXrKEjpP4W5MzgY+x5bjO/G31noUuDV4Ox6Eq2M5NAv1lJzMcHDEd1AQrH1y8KeoYYUc85/CXXurMc5doq+JdFBo3xmRe3VhVWXTMOeKWZhRMgnjXCVwWR2IqRK6oj16HOLnf9T2Gf4d9sNjc8PJwuk1mqUHtvBseC6yi6duiYP/LsLP9gc17HnEjYoJgwd5WCHMK43vYNPRl/WFnclfqFR6nESdjisS/crUHY36yGHVeI1VsOizZxNtlLVy1B74bGol9kFQ3PC0PgtrrJyuI7pIJhDWcO9cG9be4jAKBpBQCLOh4XnsPLMHJc7CjMQMZODj030GpYw0O2G429bBEVhM570IRVVUlYt4ec3wH9eTCmGe/mQ7Xj/1LsoynJnRolFgZlOzB2+C8vkTqBivYdfaxN8MUgphdpzejacattHMFOne6v8Fd61TuoQ5xXfgrdueMEuHJy0hzMmOJvzg/Z8iJsfhpQU61rPD66sz2oWHZqzAY7PuM0sTk7aQyzx+eDPeplwr354HB3mbbAtSKN/riYd1U35t/kZUFleYNcnJWAhzMdyGmo+34MDFBuTZPOQ+HRRzBsXWjOAu8AyE4hHk2d14tGoNVk6+y6xNjxEJuYy/txPbT+7CnpYPaBRDFERdlLFaaR1ZKWXgvcTQ2eLX8T+Ftgzc+ZgSpw2SpMeeNVOXYnH5zWbLzBiVkIGc6jyP2tbD+LjtOM51XdDNQ6POshgjjrAI45pn8CrveFSVTsGcK2fjWxNv1M10NGRNyHAEKJKzuXCA5FliJ5HvyNP3NdkF+C8dn/ikO2g+hwAAAABJRU5ErkJggg=="; +// ViewRouter.get("/devauth", (req, res) => { +// res.send( +// GetAuthPage(req.__, "Test 05265", [ +// { +// name: "Access Profile", +// description: +// "It allows the application to know who you are. Required for all applications. And a lot of more Text, because why not? This will not stop, till it is multiple lines long and maybe kill the layout, so keep reading as long as you like, but I promise it will get boring after some time. So this should be enougth.", +// logo: logo, +// }, +// { +// name: "Test 1", +// description: +// "This is not an real permission. This is used just to verify the layout", +// logo: logo, +// }, +// { +// name: "Test 2", +// description: +// "This is not an real permission. This is used just to verify the layout", +// logo: logo, +// }, +// ]) +// ); +// }); +// } + +export default ViewRouter; diff --git a/Backend/src/web.ts b/Backend/src/web.ts index 2b636f7..3cb2003 100644 --- a/Backend/src/web.ts +++ b/Backend/src/web.ts @@ -1,122 +1,175 @@ -import { WebConfig } from "./config"; -import * as express from "express"; -import { Express } from "express"; - -import Logging from "@hibas123/nodelogging"; - -import * as bodyparser from "body-parser"; -import * as cookieparser from "cookie-parser"; - -import * as i18n from "i18n"; -import * as compression from "compression"; -import ApiRouter from "./api"; -import ViewRouter from "./views/views"; -import RequestError, { HttpStatusCode } from "./helper/request_error"; - -export default class Web { - server: Express; - private port: number; - - constructor(config: WebConfig) { - this.server = express(); - this.port = Number(config.port); - this.registerMiddleware(); - this.registerEndpoints(); - this.registerErrorHandler(); - } - - listen() { - this.server.listen(this.port, () => { - Logging.log(`Server listening on port ${this.port}`); - }); - } - - private registerMiddleware() { - this.server.use(cookieparser()); - this.server.use( - bodyparser.json(), - bodyparser.urlencoded({ extended: true }) - ); - this.server.use(i18n.init); - - //Logging Middleware - this.server.use((req, res, next) => { - let start = process.hrtime(); - let finished = false; - let to = false; - let listener = () => { - if (finished) return; - finished = true; - let td = process.hrtime(start); - let time = !to ? (td[0] * 1e3 + td[1] / 1e6).toFixed(2) : "--.--"; - let resColor = ""; - if (res.statusCode >= 200 && res.statusCode < 300) - resColor = "\x1b[32m"; - //Green - else if (res.statusCode === 304 || res.statusCode === 302) - resColor = "\x1b[33m"; - else if (res.statusCode >= 400 && res.statusCode < 500) - resColor = "\x1b[36m"; - //Cyan - else if (res.statusCode >= 500 && res.statusCode < 600) - resColor = "\x1b[31m"; //Red - let m = req.method; - while (m.length < 4) m += " "; - Logging.log( - `${m} ${req.originalUrl} ${(req as any).language || "" - } ${resColor}${res.statusCode}\x1b[0m - ${time}ms` - ); - res.removeListener("finish", listener); - }; - res.on("finish", listener); - setTimeout(() => { - to = true; - listener(); - }, 2000); - next(); - }); - - this.server.use( - compression({ - filter: (req, res) => { - if (req.headers["x-no-compression"]) { - return false; - } - return compression.filter(req, res); - }, - }) - ); - } - - private registerEndpoints() { - this.server.use("/api", ApiRouter); - this.server.use("/", ViewRouter); - } - - private registerErrorHandler() { - this.server.use((error, req: express.Request, res, next) => { - if (!(error instanceof RequestError)) { - error = new RequestError( - error.message, - error.status || HttpStatusCode.INTERNAL_SERVER_ERROR, - error.nolog || false - ); - } - - if (error.status === 500 && !(error).nolog) { - Logging.error(error); - } else { - Logging.log("Responded with Error", error.status, error.message); - } - - if (req.accepts(["json"])) { - res.json_status = error.status || 500; - res.json({ - error: error.message, - status: error.status || 500, - additional: error.additional, - }); - } else res.status(error.status || 500).send(error.message); - }); - } -} +import config, { WebConfig } from "./config"; +import express from "express"; +import { Express } from "express"; + +import Logging from "@hibas123/nodelogging"; +import { Format } from "@hibas123/logging"; + +import bodyparser from "body-parser"; +import cookieparser from "cookie-parser"; +import session from "express-session"; +import MongoStore from "connect-mongo"; + +import i18n from "i18n"; +import compression from "compression"; +import ApiRouter from "./api"; +import ViewRouter from "./views"; +import RequestError, { HttpStatusCode } from "./helper/request_error"; +import DB from "./database"; +import promiseMiddleware from "./helper/promiseMiddleware"; +import User from "./models/user"; +import LoginToken, { CheckToken } from "./models/login_token"; + +export default class Web { + server: Express; + private port: number; + + constructor(config: WebConfig) { + this.server = express(); + this.port = Number(config.port); + this.registerMiddleware(); + this.registerUserSession(); + this.registerEndpoints(); + this.registerErrorHandler(); + } + + listen() { + this.server.listen(this.port, () => { + Logging.log(`Server listening on port ${this.port}`); + }); + } + + private registerMiddleware() { + this.server.use(session({ + secret: config.core.secret, + resave: false, + saveUninitialized: false, + store: MongoStore.create({ + client: DB.getClient(), + dbName: DB.db.databaseName, + collectionName: "sessions", + autoRemove: "native", + touchAfter: 60 * 60 * 24, + }), + cookie: { + maxAge: 1000 * 60 * 60 * 24 * 30 * 6, + secure: !config.core.dev, + sameSite: "strict", + } + })) + this.server.use(cookieparser()); + this.server.use( + bodyparser.json(), + bodyparser.urlencoded({ extended: true }) + ); + this.server.use(i18n.init); + + //Logging Middleware + this.server.use((req, res, next) => { + let start = process.hrtime(); + let finished = false; + let to = false; + let listener = () => { + if (finished) return; + finished = true; + let td = process.hrtime(start); + let time = !to ? (td[0] * 1e3 + td[1] / 1e6).toFixed(2) : "--.--"; + let resFormat: (arg: any) => any = (arg) => arg; + if (res.statusCode >= 200 && res.statusCode < 300) + resFormat = Format.green; + //Green + else if (res.statusCode === 304 || res.statusCode === 302) + resFormat = Format.yellow; //"\x1b[33m"; + else if (res.statusCode >= 400 && res.statusCode < 500) + resFormat = Format.red; // "\x1b[36m"; + //Cyan + else if (res.statusCode >= 500 && res.statusCode < 600) + resFormat = Format.cyan //"\x1b[31m"; //Red + + let m = req.method; + while (m.length < 4) m += " "; + Logging.getChild("HTTP").log( + `${m} ${req.originalUrl} ${(req as any).language || "" + }`, resFormat(res.statusCode), `- ${time}ms` + ); + res.removeListener("finish", listener); + }; + res.on("finish", listener); + setTimeout(() => { + to = true; + listener(); + }, 2000); + next(); + }); + + this.server.use( + compression({ + filter: (req, res) => { + if (req.headers["x-no-compression"]) { + return false; + } + return compression.filter(req, res); + }, + }) + ); + } + + private registerEndpoints() { + this.server.use("/api", ApiRouter); + this.server.use("/", ViewRouter); + } + + private registerErrorHandler() { + this.server.use((error, req: express.Request, res, next) => { + if (!(error instanceof RequestError)) { + error = new RequestError( + error.message, + error.status || HttpStatusCode.INTERNAL_SERVER_ERROR, + error.nolog || false + ); + } + + if (error.status === 500 && !(error).nolog) { + Logging.error(error); + } else { + Logging.log("Responded with Error", error.status, error.message); + } + + if (req.accepts(["json"])) { + res.json_status = error.status || 500; + res.json({ + error: error.message, + status: error.status || 500, + additional: error.additional, + }); + } else res.status(error.status || 500).send(error.message); + }); + } + + private registerUserSession() { + this.server.use(promiseMiddleware(async (req, res, next) => { + // if (!req.session.user_id) { + // if (req.cookies && req.cookies.login) { + // let token = await LoginToken.findOne({ token: req.cookies.login, valid: true }); + // if (await CheckToken(token, true)) { + // req.session.user_id = token.user.toString(); + // } + // } + + // if (req.cookies && req.cookies.special) { + // let token = await LoginToken.findOne({ token: req.cookies.special, valid: true }); + // if (await CheckToken(token, true)) { + // req.session.user_id = token.user.toString(); + // } + // } + // } + + if (req.session.user_id) { + req.user = await User.findById(req.session.user_id); + req.isAdmin = req.user.admin; + } + + return next(); + })); + } +} diff --git a/Backend/tsconfig.json b/Backend/tsconfig.json index a04dceb..c63cc7f 100644 --- a/Backend/tsconfig.json +++ b/Backend/tsconfig.json @@ -1,17 +1,17 @@ -{ - "compilerOptions": { - /* Basic Options */ - "target": "es2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, - "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, - "declaration": true /* Generates corresponding '.d.ts' file. */, - "sourceMap": true /* Generates corresponding '.map' file. */, - "outDir": "./lib" /* Redirect output structure to the directory. */, - "strict": false /* Enable all strict type-checking options. */, - "preserveWatchOutput": true, - "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, - "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ - }, - "exclude": ["node_modules/"], - "files": ["src/express.d.ts"], - "include": ["./src"] -} +{ + "compilerOptions": { + "target": "ESNext", + "module": "commonjs", + "declaration": true, + "sourceMap": true, + "outDir": "./lib", + "strict": false, + "preserveWatchOutput": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true + }, + "exclude": ["node_modules/"], + "files": ["src/express.d.ts"], + "include": ["./src"] +} diff --git a/Frontend/package.json b/Frontend/package.json index 8f9cdc8..bfdb70f 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -11,9 +11,9 @@ "autoprefixer": "^10.4.14", "classnames": "^2.3.2", "cssnano": "^6.0.0", - "esbuild": "^0.17.15", - "flowbite": "^1.6.4", - "flowbite-svelte": "^0.34.7", + "esbuild": "^0.17.16", + "flowbite": "^1.6.5", + "flowbite-svelte": "^0.34.9", "postcss": "^8.4.21", "postcss-import": "^15.1.0", "postcss-url": "^10.1.3", @@ -27,7 +27,7 @@ "svelte": "^3.58.0", "svelte-preprocess": "^5.0.3", "tailwindcss": "^3.3.1", - "typescript": "^5.0.3" + "typescript": "^5.0.4" }, "scripts": { "prepublishOnly": "npm run build", @@ -39,7 +39,9 @@ "@hibas123/theme": "^2.0.6", "@hibas123/utils": "^2.2.18", "@rollup/plugin-commonjs": "^24.0.1", + "@simplewebauthn/browser": "^7.2.0", "cleave.js": "^1.6.0", + "joi": "^17.9.1", "what-the-pack": "^2.0.3" } } diff --git a/Frontend/src/components/HoveringContentBox.svelte b/Frontend/src/components/HoveringContentBox.svelte index e40a728..2d18889 100644 --- a/Frontend/src/components/HoveringContentBox.svelte +++ b/Frontend/src/components/HoveringContentBox.svelte @@ -4,8 +4,33 @@ export let title: string; export let loading = false; export let hide = false; + + $: console.log({ loading }); +
+
+ +
+

{title}

+
+ {#if loading} +
+
+
+
+
+ {/if} + +
+ {#if !(loading && hide)} + + {/if} +
+ +
+
+ - -
-
- -
-

{title}

-
- {#if loading} -
-
-
-
-
- {/if} - -
- {#if !(loading && hide)} - - {/if} -
- -
-
diff --git a/Frontend/src/components/MainNavbar.svelte b/Frontend/src/components/MainNavbar.svelte new file mode 100644 index 0000000..8886f10 --- /dev/null +++ b/Frontend/src/components/MainNavbar.svelte @@ -0,0 +1,33 @@ + + + + {#if sidebarOpenVisible} + (sidebarOpen = !sidebarOpen)} /> + {/if} + + + OpenAuth + + + + + Home + User + + + diff --git a/Frontend/src/components/theme/theme.css b/Frontend/src/components/theme/theme.css index 2b8032f..f12c27a 100644 --- a/Frontend/src/components/theme/theme.css +++ b/Frontend/src/components/theme/theme.css @@ -28,6 +28,7 @@ body { .group { position: relative; + margin-top: 2rem; margin-bottom: 24px; min-height: 45px; } @@ -212,6 +213,11 @@ body { transition: width 0.2s ease-out, padding-top 0.2s ease-out; } +.btn-wide { + width: 100%; + margin: 0; +} + .loader_box { width: 64px; height: 64px; diff --git a/Frontend/src/helper/api.ts b/Frontend/src/helper/api.ts index 9419778..0163083 100644 --- a/Frontend/src/helper/api.ts +++ b/Frontend/src/helper/api.ts @@ -2,22 +2,38 @@ import { Client } from "@hibas123/openauth-internalapi"; import request, { RequestError } from "./request"; const provider = new Client.ServiceProvider((data) => { - request("/api/jrpc", {}, "POST", data, true, true).then(result => { - provider.onPacket(result); + fetch("/api/jrpc", { + method: "POST", + credentials: "same-origin", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }).then(res => { + if (res.ok) return res.json(); + else throw new Error(res.statusText); + }).then(res => { + provider.onPacket(res); }).catch(err => { - if (err instanceof RequestError) { - let data = err.response; - if (data.error && Array.isArray(data.error)) { - data.error = data.error[0]; - } - provider.onPacket(data); - } - }); + provider.onPacket({ + jsonrpc: "2.0", + method: data.method, + id: data.id, + error: { + code: -32603, + message: err.message, + }, + }) + }) }); const InternalAPI = { Account: new Client.AccountService(provider), Security: new Client.SecurityService(provider), + TwoFactor: new Client.TFAService(provider), + Login: new Client.LoginService(provider), } export default InternalAPI; + +(window as any).InternalAPI = InternalAPI; diff --git a/Frontend/src/pages/home/App.svelte b/Frontend/src/pages/home/App.svelte index e753907..ed88bd2 100644 --- a/Frontend/src/pages/home/App.svelte +++ b/Frontend/src/pages/home/App.svelte @@ -15,17 +15,6 @@

Applications using OpenAuth

- -
- - +
- {#if state === states.redirect} - - {:else if state === states.credentials} - (loading = s)} /> - {:else if state === states.twofactor} - (loading = s)} /> + {#if $state.success} + + {:else if !$state.username} + loginState.setUsername(evt.detail)} /> + {:else if !$state.password} + loginState.setPassword(evt.detail)} + /> + {:else if $state.requireTwoFactor.length > 0} + {/if}
-
-

Powered by {appname}

-
diff --git a/Frontend/src/pages/login/Credentials.svelte b/Frontend/src/pages/login/Credentials.svelte deleted file mode 100644 index d6613c1..0000000 --- a/Frontend/src/pages/login/Credentials.svelte +++ /dev/null @@ -1,84 +0,0 @@ - - - - -{#if state === states.username} -

Enter your Username or your E-Mail Address

-
- - - - -
{error}
-
-{:else} -

Enter password for {username}

-
- - - - -
{error}
-
-{/if} - - diff --git a/Frontend/src/pages/login/Error.svelte b/Frontend/src/pages/login/Error.svelte new file mode 100644 index 0000000..db476b3 --- /dev/null +++ b/Frontend/src/pages/login/Error.svelte @@ -0,0 +1,16 @@ + + +{#if $state.error} +
{$state.error}
+{/if} + + diff --git a/Frontend/src/pages/login/Password.svelte b/Frontend/src/pages/login/Password.svelte new file mode 100644 index 0000000..db1d089 --- /dev/null +++ b/Frontend/src/pages/login/Password.svelte @@ -0,0 +1,30 @@ + + +

Enter the password for {username}

+
+ + + + + + +
+ + diff --git a/Frontend/src/pages/login/Redirect.svelte b/Frontend/src/pages/login/Success.svelte similarity index 100% rename from Frontend/src/pages/login/Redirect.svelte rename to Frontend/src/pages/login/Success.svelte diff --git a/Frontend/src/pages/login/twofactors/codeInput.svelte b/Frontend/src/pages/login/TF/CodeInput.svelte similarity index 60% rename from Frontend/src/pages/login/twofactors/codeInput.svelte rename to Frontend/src/pages/login/TF/CodeInput.svelte index 7b1bec2..b0221bd 100644 --- a/Frontend/src/pages/login/twofactors/codeInput.svelte +++ b/Frontend/src/pages/login/TF/CodeInput.svelte @@ -1,8 +1,8 @@ - -
- + - -
{error}
+ + +
diff --git a/Frontend/src/pages/login/TF/TOTP.svelte b/Frontend/src/pages/login/TF/TOTP.svelte new file mode 100644 index 0000000..804d3de --- /dev/null +++ b/Frontend/src/pages/login/TF/TOTP.svelte @@ -0,0 +1,21 @@ + + +

TOTP {name}

+ + +
+ +
diff --git a/Frontend/src/pages/login/TF/WebAuthn.svelte b/Frontend/src/pages/login/TF/WebAuthn.svelte new file mode 100644 index 0000000..7efacb4 --- /dev/null +++ b/Frontend/src/pages/login/TF/WebAuthn.svelte @@ -0,0 +1,28 @@ + + + diff --git a/Frontend/src/pages/login/TwoFactor.svelte b/Frontend/src/pages/login/TwoFactor.svelte new file mode 100644 index 0000000..321907f --- /dev/null +++ b/Frontend/src/pages/login/TwoFactor.svelte @@ -0,0 +1,114 @@ + + +{#if !selected} +

Choose your 2FA method

+
    + {#each $state.requireTwoFactor ?? [] as method} + +
  • (selected = method)}> +
    + +
    + +
    {method.name}
    +
  • + {/each} + + +
+{:else} + {#if selected.tfatype == TFAType.TOTP} + + {:else if selected.tfatype == TFAType.BACKUP_CODE} + backup + {:else if selected.tfatype == TFAType.WEBAUTHN} + + {:else if selected.tfatype == TFAType.APP_ALLOW} + appallow + {:else} +

Unknown 2FA type

+ {/if} + +

+ { + evt.preventDefault(); + loginState.setError(undefined); + selected = undefined; + }} + > + Choose another Method + +

+{/if} + + diff --git a/Frontend/src/pages/login/Twofactor.svelte b/Frontend/src/pages/login/Twofactor.svelte deleted file mode 100644 index f329c25..0000000 --- a/Frontend/src/pages/login/Twofactor.svelte +++ /dev/null @@ -1,104 +0,0 @@ - - - - -
- {#if !twofactor} -

Select your Authentication method:

-
    - {#each twofactors as tf} -
  • (twofactor = tf)}> -
    - -
    - -
    {tf.name}
    -
  • - {/each} -
- {:else if twofactor.type === TFATypes.OTC} - - {:else if twofactor.type === TFATypes.BACKUP_CODE} - - {:else if twofactor.type === TFATypes.U2F} - - {:else if twofactor.type === TFATypes.APP_ALLOW} - - {:else} -
Invalid TwoFactor Method!
- {/if} - -
diff --git a/Frontend/src/pages/login/Username.svelte b/Frontend/src/pages/login/Username.svelte new file mode 100644 index 0000000..002a75a --- /dev/null +++ b/Frontend/src/pages/login/Username.svelte @@ -0,0 +1,29 @@ + + +

Enter your Username or your E-Mail Address

+
+ + + + + + +
+ + diff --git a/Frontend/src/pages/login/api.ts b/Frontend/src/pages/login/api.ts deleted file mode 100644 index 0d13cb6..0000000 --- a/Frontend/src/pages/login/api.ts +++ /dev/null @@ -1,182 +0,0 @@ -import request from "../../helper/request"; -import sha from "../../helper/sha512"; -import { setCookie, getCookie } from "../../helper/cookie"; - -export interface TwoFactor { - id: string; - name: string; - type: TFATypes; -} - -export enum TFATypes { - OTC, - BACKUP_CODE, - U2F, - APP_ALLOW, -} - -// const Api = { -// // twofactor: [{ -// // id: "1", -// // name: "Backup Codes", -// // type: TFATypes.BACKUP_CODE -// // }, { -// // id: "2", -// // name: "YubiKey", -// // type: TFATypes.U2F -// // }, { -// // id: "3", -// // name: "Authenticator", -// // type: TFATypes.OTC -// // }] as TwoFactor[], - -// } - -export interface IToken { - token: string; - expires: string; -} - -function makeid(length) { - var result = ""; - var characters = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - var charactersLength = characters.length; - for (var i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; -} - -export default class Api { - static salt: string; - static login: IToken; - static special: IToken; - static username: string; - - static twofactor: any[]; - - static getUsername() { - return this.username || getCookie("username"); - } - - static async setUsername( - username: string - ): Promise<{ error: string | undefined }> { - return request( - "/api/user/login", - { - type: "username", - username, - }, - "POST" - ) - .then((res) => { - this.salt = res.salt; - this.username = username; - return { - error: undefined, - }; - }) - .catch((err) => { - let error = err.message; - return { error }; - }); - } - - static async setPassword( - password: string - ): Promise<{ error: string | undefined; twofactor?: any }> { - const date = new Date().valueOf(); - let pw = sha(sha(this.salt + password) + date.toString()); - return request( - "/api/user/login", - { - type: "password", - }, - "POST", - { - username: this.username, - password: pw, - date, - } - ) - .then(({ login, special, tfa }) => { - this.login = login; - this.special = special; - - if (tfa && Array.isArray(tfa) && tfa.length > 0) - this.twofactor = tfa; - else this.twofactor = undefined; - - return { - error: undefined, - }; - }) - .catch((err) => { - let error = err.message; - return { error }; - }); - } - - static gettok() { - return { - login: this.login.token, - special: this.special.token, - }; - } - - static async sendBackup(id: string, code: string) { - return request("/api/user/twofactor/backup", this.gettok(), "PUT", { - code, - id, - }) - .then(({ login_exp, special_exp }) => { - this.login.expires = login_exp; - this.special.expires = special_exp; - return {}; - }) - .catch((err) => ({ error: err.message })); - } - - static async sendOTC(id: string, code: string) { - return request("/api/user/twofactor/otc", this.gettok(), "PUT", { - code, - id, - }) - .then(({ login_exp, special_exp }) => { - this.login.expires = login_exp; - this.special.expires = special_exp; - return {}; - }) - .catch((error) => ({ error: error.message })); - } - - static finish() { - let d = new Date(); - d.setTime(d.getTime() + 30 * 24 * 60 * 60 * 1000); //Keep the username 30 days - setCookie("username", this.username, d.toUTCString()); - - setCookie( - "login", - this.login.token, - new Date(this.login.expires).toUTCString() - ); - setCookie( - "special", - this.special.token, - new Date(this.special.expires).toUTCString() - ); - - let url = new URL(window.location.href); - let state = url.searchParams.get("state"); - let red = "/"; - - if (state) { - let base64 = url.searchParams.get("base64"); - if (base64) red = atob(state); - else red = state; - } - setTimeout(() => (window.location.href = red), 200); - } -} diff --git a/Frontend/src/pages/login/state.ts b/Frontend/src/pages/login/state.ts new file mode 100644 index 0000000..bfe5319 --- /dev/null +++ b/Frontend/src/pages/login/state.ts @@ -0,0 +1,183 @@ +import type { LoginState } from "@hibas123/openauth-internalapi"; +import { derived, get, writable } from "svelte/store"; +import InternalAPI from "../../helper/api"; +import sha from "../../helper/sha512"; + +interface LocalLoginState extends LoginState { + loading: boolean; + error?: string; + username?: string; +} + +class LoginStore { + state = writable({ + username: undefined, + password: false, + passwordSalt: undefined, + requireTwoFactor: [], + success: false, + loading: true, + error: undefined + }) + + isFinished = derived(this.state, $state => $state.success); + + constructor() { + this.state.subscribe((state) => { + if (state.success) { + setTimeout(() => { + this.finish(); + }, 2000); + } + }) + this.getState(); + } + + setLoading(loading: boolean) { + this.state.update(current => ({ + ...current, + loading, + error: loading ? undefined : current.error, + })); + } + + setError(error: string) { + this.state.update(current => ({ + ...current, + error, + })); + } + + + async getState() { + try { + this.setLoading(true); + + let state = await InternalAPI.Login.GetState(); + this.state.update(current => ({ + ...current, + ...state, + })); + } catch (err) { + this.setError(err.message); + } finally { + this.setLoading(false); + } + } + + async setUsername(username: string) { + try { + this.setLoading(true); + + let state = await InternalAPI.Login.Start(username); + this.state.update(current => ({ + ...current, + ...state, + username + })); + } catch (err) { + this.setError(err.message); + } finally { + this.setLoading(false); + } + + } + + async setPassword(password: string) { + try { + this.setLoading(true); + + const date = new Date().valueOf(); + let salt = get(this.state).passwordSalt + let pw = sha(sha(salt + password) + date.toString()); + + let state = await InternalAPI.Login.UsePassword(pw, date); + this.state.update(current => ({ + ...current, + ...state, + })); + } catch (err) { + this.setError(err.message); + } finally { + this.setLoading(false); + } + } + + async useTOTP(id: string, code: string) { + try { + this.setLoading(true); + + let state = await InternalAPI.Login.UseTOTP(id, code); + this.state.update(current => ({ + ...current, + ...state, + })); + } catch (err) { + this.setError(err.message); + } finally { + this.setLoading(false); + } + } + + async useBackupCode(id: string, code: string) { + try { + this.setLoading(true); + + let state = await InternalAPI.Login.UseBackupCode(id, code); + this.state.update(current => ({ + ...current, + ...state, + })); + } catch (err) { + this.setError(err.message); + } finally { + this.setLoading(false); + } + } + + async getWebAuthnChallenge(id: string) { + try { + this.setLoading(true); + + let challenge = await InternalAPI.Login.GetWebAuthnChallenge(id); + return challenge; + } catch (err) { + this.setError(err.message); + } finally { + this.setLoading(false); + } + } + + async useWebAuthn(id: string, response: any) { + try { + this.setLoading(true); + + let state = await InternalAPI.Login.UseWebAuthn(id, JSON.stringify(response)); + this.state.update(current => ({ + ...current, + ...state, + })); + } catch (err) { + this.setError(err.message); + } finally { + this.setLoading(false); + } + } + + async finish() { + let url = new URL(window.location.href); + let state = url.searchParams.get("state"); + let red = "/"; + + if (state) { + let base64 = url.searchParams.get("base64"); + if (base64) red = atob(state); + else red = state; + } + setTimeout(() => (window.location.href = red), 200); + } +} + +const loginState = new LoginStore(); + +export default loginState; diff --git a/Frontend/src/pages/login/twofactors/otc.svelte b/Frontend/src/pages/login/twofactors/otc.svelte deleted file mode 100644 index dadf109..0000000 --- a/Frontend/src/pages/login/twofactors/otc.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - - -

{title}

- - - -
- - -
diff --git a/Frontend/src/pages/login/twofactors/push.svelte b/Frontend/src/pages/login/twofactors/push.svelte deleted file mode 100644 index 1bd7b4e..0000000 --- a/Frontend/src/pages/login/twofactors/push.svelte +++ /dev/null @@ -1,389 +0,0 @@ - - - - -

SMS

- -

A code was sent to your Device {device}

- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
{error}
- diff --git a/Frontend/src/pages/login/twofactors/sms.svelte b/Frontend/src/pages/login/twofactors/sms.svelte deleted file mode 100644 index 8c86d60..0000000 --- a/Frontend/src/pages/login/twofactors/sms.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - - - -

SMS

-{#if state === states.approve} -

Send SMS to {number}

- -{:else} -

A code was sent to you. Please enter

- - -
- (state = states.approve)}> - Not received? - -{/if} -
{error}
- - diff --git a/Frontend/src/pages/login/twofactors/toList.svelte b/Frontend/src/pages/login/twofactors/toList.svelte deleted file mode 100644 index 11905d9..0000000 --- a/Frontend/src/pages/login/twofactors/toList.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - - -

- evt.preventDefault() || finish(false)}> - Choose another Method - -

diff --git a/Frontend/src/pages/login/twofactors/u2f.svelte b/Frontend/src/pages/login/twofactors/u2f.svelte deleted file mode 100644 index 58d26ac..0000000 --- a/Frontend/src/pages/login/twofactors/u2f.svelte +++ /dev/null @@ -1,69 +0,0 @@ - - - - -

U2F Security Key

-

This Method is currently not supported. Please choose another one!

- diff --git a/Frontend/src/pages/popup/main.ts b/Frontend/src/pages/popup/main.ts index 579b373..5066efb 100644 --- a/Frontend/src/pages/popup/main.ts +++ b/Frontend/src/pages/popup/main.ts @@ -84,9 +84,25 @@ async function onMessage(msg: MessageEvent) { const url = new URL(msg.origin); setAppName(url.hostname); + if (!msg.data.client_id) { + alert("The site requesting the login is not valid"); + window.close(); + return; + } + try { if (!msg.data.type || msg.data.type === "jwt") { console.log("JWT Request"); + + await request( + "/api/user/oauth/permissions", + { + client_id: msg.data.client_id, + origin: url.hostname, + permissions: permissions.join(","), + } + ); // Will fail if client does not exist + await new Promise((yes) => { console.log("Await user acceptance"); setLoading(false); diff --git a/Frontend/src/pages/user/App.svelte b/Frontend/src/pages/user/App.svelte index 9e69188..ee00cd0 100644 --- a/Frontend/src/pages/user/App.svelte +++ b/Frontend/src/pages/user/App.svelte @@ -1,13 +1,29 @@
+
+ +
- +
{#if $CurrentPage == "personal-info"} @@ -21,5 +37,6 @@ diff --git a/Frontend/src/pages/user/Pages/PersonalInfo.svelte b/Frontend/src/pages/user/Pages/PersonalInfo.svelte deleted file mode 100644 index 70b774a..0000000 --- a/Frontend/src/pages/user/Pages/PersonalInfo.svelte +++ /dev/null @@ -1,197 +0,0 @@ - - - - - General Account Details -
-
- - -
-
- - -
-
-
- {/each} - - Phones -
- - {#each contactInfo.phone as phone} -
- - - {#if phone.verified} - Well done! Phone is verified. - {:else} - Oh no! Phone needs verification. - {/if} -
- {/each} - - - -
-
diff --git a/Frontend/src/pages/user/Sidebar.svelte b/Frontend/src/pages/user/Sidebar.svelte index ec0db48..c5aefad 100644 --- a/Frontend/src/pages/user/Sidebar.svelte +++ b/Frontend/src/pages/user/Sidebar.svelte @@ -1,4 +1,4 @@ - - + + import { Listgroup, ListgroupItem, Modal, Radio } from "flowbite-svelte"; + import Totp from "./TwoFactorRegistration/TOTP.svelte"; + import WebAuthn from "./TwoFactorRegistration/WebAuthn.svelte"; + + export let open: boolean; + + let selectedType = undefined; + $: { + if (!open) { + selectedType = undefined; + } + } + + + + {#if !selectedType} +

+ Select type +

+ + (selectedType = "totp")}>TOTP + (selectedType = "webauthn")}>WebAuthn + + {:else if selectedType == "totp"} + + {:else if selectedType == "webauthn"} + + {/if} +
diff --git a/Frontend/src/pages/user/pages/PersonalInfo.svelte b/Frontend/src/pages/user/pages/PersonalInfo.svelte new file mode 100644 index 0000000..126a8e3 --- /dev/null +++ b/Frontend/src/pages/user/pages/PersonalInfo.svelte @@ -0,0 +1,203 @@ + + + +
+ + General Account Details +
+
+ + +
+
+ + +
+
+
+ {/each} + + Phones +
+ + {#each contactInfo.phone as phone} +
+ + + {#if phone.verified} + Well done! Phone is verified. + {:else} + Oh no! Phone needs verification. + {/if} +
+ {/each} + + + +
+
+
diff --git a/Frontend/src/pages/user/Pages/Security.svelte b/Frontend/src/pages/user/pages/Security.svelte similarity index 56% rename from Frontend/src/pages/user/Pages/Security.svelte rename to Frontend/src/pages/user/pages/Security.svelte index 05e1a5d..942d444 100644 --- a/Frontend/src/pages/user/Pages/Security.svelte +++ b/Frontend/src/pages/user/pages/Security.svelte @@ -1,12 +1,5 @@ @@ -107,19 +154,31 @@ Change Password
+ {#if change_password_success} + Password changed successfully. + {/if} + + {#if change_password_error} + {change_password_error} + {/if} +
- +
- +
- +
- + @@ -130,12 +189,21 @@ {#each twofactors as tfa} {tfa.name ?? typeToName[tfa.tfatype]} +
+ +
{/each} - +
+ + +{:else if stage == "done"} + +

Success

+

Your WebAuthn device has been registered.

+
+{/if} diff --git a/Frontend/src/pages/user_old/App.svelte b/Frontend/src/pages/user_old/App.svelte deleted file mode 100644 index 835f487..0000000 --- a/Frontend/src/pages/user_old/App.svelte +++ /dev/null @@ -1,207 +0,0 @@ - - -
-
-
- {#if sidebar_button} - - {/if} -

{page.title}

-
- -
- -
- -
- -{#if loading} -
-
-
-
-
-{/if} - - diff --git a/Frontend/src/pages/user_old/NavigationBar.svelte b/Frontend/src/pages/user_old/NavigationBar.svelte deleted file mode 100644 index 5ce2727..0000000 --- a/Frontend/src/pages/user_old/NavigationBar.svelte +++ /dev/null @@ -1,54 +0,0 @@ - - -{#each pages as page} -
open(page.id)} - > -
- {page.title} -
-

{page.title}

-
-{/each} - - diff --git a/Frontend/src/pages/user_old/Pages/Account.svelte b/Frontend/src/pages/user_old/Pages/Account.svelte deleted file mode 100644 index 0e9b26e..0000000 --- a/Frontend/src/pages/user_old/Pages/Account.svelte +++ /dev/null @@ -1,192 +0,0 @@ - - - - - -

Profile

- {#if account_error} -

{account_error}

- {/if} - -
-
- - - - -
- -
-
- -
-
- -
- -
-
- - -
- - -

Contact

- {#if contact_error} -

{contact_error}

- {/if} - - -
diff --git a/Frontend/src/pages/user_old/Pages/Box.svelte b/Frontend/src/pages/user_old/Pages/Box.svelte deleted file mode 100644 index a656dd5..0000000 --- a/Frontend/src/pages/user_old/Pages/Box.svelte +++ /dev/null @@ -1,36 +0,0 @@ - - -
- -
\ No newline at end of file diff --git a/Frontend/src/pages/user_old/Pages/BoxItem.svelte b/Frontend/src/pages/user_old/Pages/BoxItem.svelte deleted file mode 100644 index 6ea24fd..0000000 --- a/Frontend/src/pages/user_old/Pages/BoxItem.svelte +++ /dev/null @@ -1,94 +0,0 @@ - - - - -
-
(open = !open)}> -
-
{name}
-
- {#if Array.isArray(value)} - {#each value as v, i} - {v} - {#if i < value.length - 1} -
- {/if} - {/each} - {:else}{value}{/if} -
-
- {#if !noOpen} - - {/if} -
- {#if open && !noOpen} -
- -
- {/if} -
diff --git a/Frontend/src/pages/user_old/Pages/NextIcon.svelte b/Frontend/src/pages/user_old/Pages/NextIcon.svelte deleted file mode 100644 index 0ba1da6..0000000 --- a/Frontend/src/pages/user_old/Pages/NextIcon.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/Frontend/src/pages/user_old/Pages/Security.svelte b/Frontend/src/pages/user_old/Pages/Security.svelte deleted file mode 100644 index 85f89e1..0000000 --- a/Frontend/src/pages/user_old/Pages/Security.svelte +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - -

Two Factor

- - {#each twofactor as t} - - - - {/each} - -
- - -

Anmeldungen

- - {#each token as t} - - - - {:else}No Tokens{/each} - - -
diff --git a/Frontend/src/pages/user_old/main.ts b/Frontend/src/pages/user_old/main.ts deleted file mode 100644 index e6d3925..0000000 --- a/Frontend/src/pages/user_old/main.ts +++ /dev/null @@ -1,6 +0,0 @@ -import "../../components/theme"; -import App from "./App.svelte"; - -new App({ - target: document.body, -}); diff --git a/FrontendLegacy/package.json b/FrontendLegacy/package.json index 3c7c004..04743aa 100644 --- a/FrontendLegacy/package.json +++ b/FrontendLegacy/package.json @@ -23,6 +23,6 @@ "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-typescript2": "^0.34.1", "sass": "^1.61.0", - "typescript": "^5.0.3" + "typescript": "^5.0.4" } } diff --git a/InternalAPI/account.jrpc b/InternalAPI/account.jrpc new file mode 100644 index 0000000..d4c3e18 --- /dev/null +++ b/InternalAPI/account.jrpc @@ -0,0 +1,16 @@ +type UserRegisterInfo { + username: string; + name: string; + gender: string; + mail: string; + password: string; + salt: string; +} + +service AccountService { + Register(regcode: string, info: UserRegisterInfo): void; + GetProfile(): Profile; + UpdateProfile(info: Profile): void; + GetContactInfos(): ContactInfo; +} + diff --git a/InternalAPI/api.jrpc b/InternalAPI/api.jrpc index aee2aa4..24a7807 100644 --- a/InternalAPI/api.jrpc +++ b/InternalAPI/api.jrpc @@ -1,77 +1,5 @@ -type UserRegisterInfo { - username: string; - name: string; - gender: string; - mail: string; - password: string; - salt: string; -} - -type Token { - id: string; - special: boolean; - ip: string; - browser: string; - isthis: boolean; -} - -enum Gender { - None = 0, - Male = 1, - Female = 2, - Other = 3 -} - -type Account { - id: string; - name: string; - username: string; - birthday: int; - gender: Gender; -} - -type Mail { - mail: string; - verified: boolean; - primary: boolean; -} - -type Phone { - phone: string; - verified: boolean; - primary: boolean; -} - -type ContactInfo { - mail: Mail[]; - phone: Phone[]; -} - -enum TFAType { - TOTP = 0, - BACKUP_CODE = 1, - WEBAUTHN = 2, - APP_ALLOW = 3 -} - - -type TwoFactor { - id: string; - name?: string; - expires?: int; - tfatype: TFAType; -} - -service AccountService { - Register(regcode: string, info: UserRegisterInfo): void; - GetProfile(): Account; - UpdateProfile(info: Account): void; - GetContactInfos(): ContactInfo; -} - -service SecurityService { - GetTokens(): Token[]; - RevokeToken(id: string): void; - - GetTwofactorOptions(): TwoFactor[]; -} +import "./types"; +import "./twofactor"; +import "./login"; +import "./account"; +import "./security"; diff --git a/InternalAPI/login.jrpc b/InternalAPI/login.jrpc new file mode 100644 index 0000000..50e38e4 --- /dev/null +++ b/InternalAPI/login.jrpc @@ -0,0 +1,21 @@ +import "./twofactor"; + +type LoginState { + success: boolean; + username?: string; + password?: boolean; + passwordSalt?: string; + requireTwoFactor?: TFAOption[]; +} + + +service LoginService { + GetState(): LoginState; + Start(username: string): LoginState; + UsePassword(password_hash: string, date: int): LoginState; + + UseTOTP(id: string, code: string): LoginState; + UseBackupCode(id: string, code:string): LoginState; + GetWebAuthnChallenge(id: string): string; + UseWebAuthn(id: string, response: string): LoginState; +} diff --git a/InternalAPI/security.jrpc b/InternalAPI/security.jrpc new file mode 100644 index 0000000..7e9bc7a --- /dev/null +++ b/InternalAPI/security.jrpc @@ -0,0 +1,14 @@ +type Session { + id: string; + special: boolean; + ip: string; + browser: string; + isthis: boolean; +} + +service SecurityService { + GetSessions(): Session[]; + RevokeSession(id: string): void; + + ChangePassword(old: string, new_pw: string): void; +} diff --git a/InternalAPI/twofactor.jrpc b/InternalAPI/twofactor.jrpc new file mode 100644 index 0000000..1260033 --- /dev/null +++ b/InternalAPI/twofactor.jrpc @@ -0,0 +1,38 @@ +enum TFAType { + TOTP = 0, + BACKUP_CODE = 1, + WEBAUTHN = 2, + APP_ALLOW = 3 +} + +type TFAOption { + id: string; + name?: string; + expires?: int; + tfatype: TFAType; +} + +type TFANewTOTP { + id: string; + secret: string; + qr: string; +} + +type TFAWebAuthRegister { + id: string; + challenge: string; +} + +service TFAService { + GetOptions(): TFAOption[]; + Delete(id: string): void; + + AddTOTP(name: string): TFANewTOTP; + VerifyTOTP(id: string, code: string): void; + + AddWebauthn(name: string): TFAWebAuthRegister; + VerifyWebAuthn(id: string, registration_response: string): void; + + AddBackupCodes(name:string): string[]; + RemoveBackupCodes(id: string): void; +} diff --git a/InternalAPI/types.jrpc b/InternalAPI/types.jrpc new file mode 100644 index 0000000..c54d2f9 --- /dev/null +++ b/InternalAPI/types.jrpc @@ -0,0 +1,31 @@ +enum Gender { + None = 0, + Male = 1, + Female = 2, + Other = 3 +} + +type Profile { + id: string; + name: string; + username: string; + birthday: int; + gender: Gender; +} + +type Mail { + mail: string; + verified: boolean; + primary: boolean; +} + +type Phone { + phone: string; + verified: boolean; + primary: boolean; +} + +type ContactInfo { + mail: Mail[]; + phone: Phone[]; +} diff --git a/_API/package.json b/_API/package.json index 04d6301..409dfd2 100644 --- a/_API/package.json +++ b/_API/package.json @@ -14,6 +14,6 @@ "author": "Fabian Stamm ", "license": "ISC", "devDependencies": { - "typescript": "^5.0.2" + "typescript": "^5.0.4" } } diff --git a/package.json b/package.json index eb356ac..229fba4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "author": "Fabian Stamm ", "private": true, "scripts": { - "build": "yarn run build-views-1 && yarn run build-views-2 && yarn run build-backend", + "build": "yarn build-api && yarn run build-views-1 && yarn run build-views-2 && yarn run build-backend", "build-api": "jrpc compile ./InternalAPI/api.jrpc -o=ts-node:_API/src && yarn workspace @hibas123/openauth-internalapi run build", "build-backend": "yarn workspace @hibas123/openauth-backend run build", "build-views-1": "yarn workspace @hibas123/openauth-views-v1 run build", diff --git a/yarn.lock b/yarn.lock index 3791b6b..fa03cfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,48 @@ __metadata: version: 6 cacheKey: 8 +"@cbor-extract/cbor-extract-darwin-arm64@npm:2.1.1": + version: 2.1.1 + resolution: "@cbor-extract/cbor-extract-darwin-arm64@npm:2.1.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@cbor-extract/cbor-extract-darwin-x64@npm:2.1.1": + version: 2.1.1 + resolution: "@cbor-extract/cbor-extract-darwin-x64@npm:2.1.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@cbor-extract/cbor-extract-linux-arm64@npm:2.1.1": + version: 2.1.1 + resolution: "@cbor-extract/cbor-extract-linux-arm64@npm:2.1.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@cbor-extract/cbor-extract-linux-arm@npm:2.1.1": + version: 2.1.1 + resolution: "@cbor-extract/cbor-extract-linux-arm@npm:2.1.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@cbor-extract/cbor-extract-linux-x64@npm:2.1.1": + version: 2.1.1 + resolution: "@cbor-extract/cbor-extract-linux-x64@npm:2.1.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@cbor-extract/cbor-extract-win32-x64@npm:2.1.1": + version: 2.1.1 + resolution: "@cbor-extract/cbor-extract-win32-x64@npm:2.1.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -46,9 +88,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/android-arm64@npm:0.17.15" +"@esbuild/android-arm64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/android-arm64@npm:0.17.16" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -60,9 +102,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/android-arm@npm:0.17.15" +"@esbuild/android-arm@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/android-arm@npm:0.17.16" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -74,9 +116,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/android-x64@npm:0.17.15" +"@esbuild/android-x64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/android-x64@npm:0.17.16" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -88,9 +130,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/darwin-arm64@npm:0.17.15" +"@esbuild/darwin-arm64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/darwin-arm64@npm:0.17.16" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -102,9 +144,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/darwin-x64@npm:0.17.15" +"@esbuild/darwin-x64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/darwin-x64@npm:0.17.16" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -116,9 +158,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/freebsd-arm64@npm:0.17.15" +"@esbuild/freebsd-arm64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/freebsd-arm64@npm:0.17.16" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -130,9 +172,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/freebsd-x64@npm:0.17.15" +"@esbuild/freebsd-x64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/freebsd-x64@npm:0.17.16" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -144,9 +186,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-arm64@npm:0.17.15" +"@esbuild/linux-arm64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/linux-arm64@npm:0.17.16" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -158,9 +200,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-arm@npm:0.17.15" +"@esbuild/linux-arm@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/linux-arm@npm:0.17.16" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -172,9 +214,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-ia32@npm:0.17.15" +"@esbuild/linux-ia32@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/linux-ia32@npm:0.17.16" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -186,9 +228,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-loong64@npm:0.17.15" +"@esbuild/linux-loong64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/linux-loong64@npm:0.17.16" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -200,9 +242,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-mips64el@npm:0.17.15" +"@esbuild/linux-mips64el@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/linux-mips64el@npm:0.17.16" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -214,9 +256,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-ppc64@npm:0.17.15" +"@esbuild/linux-ppc64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/linux-ppc64@npm:0.17.16" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -228,9 +270,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-riscv64@npm:0.17.15" +"@esbuild/linux-riscv64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/linux-riscv64@npm:0.17.16" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -242,9 +284,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-s390x@npm:0.17.15" +"@esbuild/linux-s390x@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/linux-s390x@npm:0.17.16" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -256,9 +298,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-x64@npm:0.17.15" +"@esbuild/linux-x64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/linux-x64@npm:0.17.16" conditions: os=linux & cpu=x64 languageName: node linkType: hard @@ -270,9 +312,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/netbsd-x64@npm:0.17.15" +"@esbuild/netbsd-x64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/netbsd-x64@npm:0.17.16" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard @@ -284,9 +326,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/openbsd-x64@npm:0.17.15" +"@esbuild/openbsd-x64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/openbsd-x64@npm:0.17.16" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard @@ -298,9 +340,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/sunos-x64@npm:0.17.15" +"@esbuild/sunos-x64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/sunos-x64@npm:0.17.16" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -312,9 +354,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/win32-arm64@npm:0.17.15" +"@esbuild/win32-arm64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/win32-arm64@npm:0.17.16" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -326,9 +368,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/win32-ia32@npm:0.17.15" +"@esbuild/win32-ia32@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/win32-ia32@npm:0.17.16" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -340,9 +382,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/win32-x64@npm:0.17.15" +"@esbuild/win32-x64@npm:0.17.16": + version: 0.17.16 + resolution: "@esbuild/win32-x64@npm:0.17.16" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -354,6 +396,29 @@ __metadata: languageName: node linkType: hard +"@hapi/hoek@npm:^9.0.0": + version: 9.3.0 + resolution: "@hapi/hoek@npm:9.3.0" + checksum: 4771c7a776242c3c022b168046af4e324d116a9d2e1d60631ee64f474c6e38d1bb07092d898bf95c7bc5d334c5582798a1456321b2e53ca817d4e7c88bc25b43 + languageName: node + linkType: hard + +"@hapi/topo@npm:^5.0.0": + version: 5.1.0 + resolution: "@hapi/topo@npm:5.1.0" + dependencies: + "@hapi/hoek": ^9.0.0 + checksum: 604dfd5dde76d5c334bd03f9001fce69c7ce529883acf92da96f4fe7e51221bf5e5110e964caca287a6a616ba027c071748ab636ff178ad750547fba611d6014 + languageName: node + linkType: hard + +"@hexagon/base64@npm:^1.1.25": + version: 1.1.26 + resolution: "@hexagon/base64@npm:1.1.26" + checksum: 596ccc3973a2f9481b25dbbc840fe3209b90fa57649f781126396c64c4d31228f8b1953ebc21d5723805cca138b86249f08d29b5bad2b2987bd15c5f97d08faf + languageName: node + linkType: hard + "@hibas123/config@npm:^1.1.2": version: 1.1.2 resolution: "@hibas123/config@npm:1.1.2" @@ -374,15 +439,6 @@ __metadata: languageName: node linkType: hard -"@hibas123/logging@npm:^2.4.1": - version: 2.6.1 - resolution: "@hibas123/logging@npm:2.6.1" - dependencies: - "@hibas123/utils": ^2.2.10 - checksum: f56b6ccf4c061568d72e72b0f225f4722b3eb42b4d40fd51a934c7f88d5492c281a4f0a93b9edf3e5d5000d98a6b36bc8cefd103df6aac45fc606847aee6d290 - languageName: node - linkType: hard - "@hibas123/logging@npm:^3.1.2": version: 3.1.2 resolution: "@hibas123/logging@npm:3.1.2" @@ -420,16 +476,18 @@ __metadata: "@hibas123/nodeloggingserver_client": ^1.1.2 "@hibas123/openauth-internalapi": "workspace:^" "@hibas123/openauth-views-v1": "workspace:^" - "@hibas123/safe_mongo": ^1.7.1 + "@hibas123/safe_mongo": ^2.0.1 + "@simplewebauthn/server": ^7.2.0 "@types/body-parser": ^1.19.2 "@types/compression": ^1.7.2 "@types/cookie-parser": ^1.4.3 "@types/dotenv": ^8.2.0 "@types/express": ^4.17.17 + "@types/express-session": ^1.17.7 "@types/i18n": ^0.13.6 "@types/ini": ^1.3.31 "@types/jsonwebtoken": ^9.0.1 - "@types/mongodb": ^3.6.20 + "@types/mongodb": ^4.0.7 "@types/node": ^18.15.11 "@types/node-rsa": ^1.1.1 "@types/qrcode": ^1.5.0 @@ -439,16 +497,19 @@ __metadata: body-parser: ^1.20.2 compression: ^1.7.4 concurrently: ^8.0.1 + connect-mongo: ^5.0.0 cookie-parser: ^1.4.6 cors: ^2.8.5 dotenv: ^16.0.3 express: ^4.18.2 + express-session: ^1.17.3 handlebars: ^4.7.7 i18n: ^0.15.1 ini: ^4.0.0 + joi: ^17.9.1 jsonwebtoken: ^9.0.0 moment: ^2.29.4 - mongodb: ^3.7.3 + mongodb: ^5.2.0 node-rsa: ^1.1.1 nodemon: ^2.0.22 npm-run-all: ^4.1.5 @@ -457,7 +518,7 @@ __metadata: reflect-metadata: ^0.1.13 speakeasy: ^2.0.0 ts-node: ^10.9.1 - typescript: ^5.0.3 + typescript: ^5.0.4 u2f: ^0.1.3 uuid: ^9.0.0 languageName: unknown @@ -467,7 +528,7 @@ __metadata: version: 0.0.0-use.local resolution: "@hibas123/openauth-internalapi@workspace:_API" dependencies: - typescript: ^5.0.2 + typescript: ^5.0.4 languageName: unknown linkType: soft @@ -488,7 +549,7 @@ __metadata: rollup-plugin-node-resolve: ^5.2.0 rollup-plugin-typescript2: ^0.34.1 sass: ^1.61.0 - typescript: ^5.0.3 + typescript: ^5.0.4 languageName: unknown linkType: soft @@ -504,15 +565,17 @@ __metadata: "@rollup/plugin-html": ^1.0.2 "@rollup/plugin-image": ^3.0.2 "@rollup/plugin-node-resolve": ^15.0.2 + "@simplewebauthn/browser": ^7.2.0 "@tsconfig/svelte": ^4.0.1 "@types/cleave.js": ^1.4.7 autoprefixer: ^10.4.14 classnames: ^2.3.2 cleave.js: ^1.6.0 cssnano: ^6.0.0 - esbuild: ^0.17.15 - flowbite: ^1.6.4 - flowbite-svelte: ^0.34.7 + esbuild: ^0.17.16 + flowbite: ^1.6.5 + flowbite-svelte: ^0.34.9 + joi: ^17.9.1 postcss: ^8.4.21 postcss-import: ^15.1.0 postcss-url: ^10.1.3 @@ -526,7 +589,7 @@ __metadata: svelte: ^3.58.0 svelte-preprocess: ^5.0.3 tailwindcss: ^3.3.1 - typescript: ^5.0.3 + typescript: ^5.0.4 what-the-pack: ^2.0.3 languageName: unknown linkType: soft @@ -539,13 +602,13 @@ __metadata: languageName: unknown linkType: soft -"@hibas123/safe_mongo@npm:^1.7.1": - version: 1.7.1 - resolution: "@hibas123/safe_mongo@npm:1.7.1" +"@hibas123/safe_mongo@npm:^2.0.1": + version: 2.0.1 + resolution: "@hibas123/safe_mongo@npm:2.0.1" dependencies: - "@hibas123/logging": ^2.4.1 - mongodb: ^3.5.6 - checksum: a179e2038b38fc4795cf586fd1db666b5cc0596e47f7ca3edab162d20697aa1e4ea202ef526f3c02cd838166f5f5f1f5888b8c6cf124cad8ffd4cebc2165bd35 + "@hibas123/logging": ^3.1.2 + mongodb: ^5.2.0 + checksum: 6d02d6803312a61de23a741eb7b72b92e7443f76ffecce7faa4887109f6478ed5ad2920bd92371db23e1108fb6dfd83a0adcedf1448708c03ba51f564ba20afd languageName: node linkType: hard @@ -556,7 +619,7 @@ __metadata: languageName: node linkType: hard -"@hibas123/utils@npm:^2.2.10, @hibas123/utils@npm:^2.2.18": +"@hibas123/utils@npm:^2.2.18": version: 2.2.18 resolution: "@hibas123/utils@npm:2.2.18" checksum: 6461171581183780fd1e7516e21cd26e967ce9f5396fa7abb9b47f607f70d0d9353b15c7c420baccba40ffbc37fbdc323cb25fda98b6c7995dcd784763a364b7 @@ -894,6 +957,65 @@ __metadata: languageName: node linkType: hard +"@peculiar/asn1-android@npm:^2.3.3": + version: 2.3.6 + resolution: "@peculiar/asn1-android@npm:2.3.6" + dependencies: + "@peculiar/asn1-schema": ^2.3.6 + asn1js: ^3.0.5 + tslib: ^2.4.0 + checksum: 66615ada47238e91a42eaefe21501d9b49a8eb769c23a508d1f2aca345a2e03aa2860666d3bac7cf47eb09d10f3800c564d63a2a91c7dce13f90b01a4208f625 + languageName: node + linkType: hard + +"@peculiar/asn1-ecc@npm:^2.3.4": + version: 2.3.6 + resolution: "@peculiar/asn1-ecc@npm:2.3.6" + dependencies: + "@peculiar/asn1-schema": ^2.3.6 + "@peculiar/asn1-x509": ^2.3.6 + asn1js: ^3.0.5 + tslib: ^2.4.0 + checksum: 4b9a383dd443fbb9699d79550e03d1185781885768d8c7b780e26a959344286a53539824fa4a3103e9e8393a7d062fe6820bf79abafb340dc18ee5ce81b1d470 + languageName: node + linkType: hard + +"@peculiar/asn1-rsa@npm:^2.3.4": + version: 2.3.6 + resolution: "@peculiar/asn1-rsa@npm:2.3.6" + dependencies: + "@peculiar/asn1-schema": ^2.3.6 + "@peculiar/asn1-x509": ^2.3.6 + asn1js: ^3.0.5 + tslib: ^2.4.0 + checksum: 120dda00af6e1b1e5568826ac8211d60d36b3cbe91b086cae6b5ba132f1670ba129284068110305b237550e402c0beeda45fd713d640f97ad11d8cf6c925b31a + languageName: node + linkType: hard + +"@peculiar/asn1-schema@npm:^2.3.3, @peculiar/asn1-schema@npm:^2.3.6": + version: 2.3.6 + resolution: "@peculiar/asn1-schema@npm:2.3.6" + dependencies: + asn1js: ^3.0.5 + pvtsutils: ^1.3.2 + tslib: ^2.4.0 + checksum: fc09387c6e3dea07fca21b54ea8c71ce3ec0f8c92377237e51aef729f0c2df92781aa7a18a546a6fe809519faeaa222df576ec21a35c6095037a78677204a55b + languageName: node + linkType: hard + +"@peculiar/asn1-x509@npm:^2.3.4, @peculiar/asn1-x509@npm:^2.3.6": + version: 2.3.6 + resolution: "@peculiar/asn1-x509@npm:2.3.6" + dependencies: + "@peculiar/asn1-schema": ^2.3.6 + asn1js: ^3.0.5 + ipaddr.js: ^2.0.1 + pvtsutils: ^1.3.2 + tslib: ^2.4.0 + checksum: 6e946bd44091fb88f617c3bbf54ed1113ed2b249675dd36004513444f409160f6d446bdb82d3cb6041b4d15c68fa4cf40ad452891a5f85dda2af89ee5b0590d2 + languageName: node + linkType: hard + "@popperjs/core@npm:^2.11.6, @popperjs/core@npm:^2.11.7, @popperjs/core@npm:^2.9.3": version: 2.11.7 resolution: "@popperjs/core@npm:2.11.7" @@ -992,6 +1114,76 @@ __metadata: languageName: node linkType: hard +"@sideway/address@npm:^4.1.3": + version: 4.1.4 + resolution: "@sideway/address@npm:4.1.4" + dependencies: + "@hapi/hoek": ^9.0.0 + checksum: b9fca2a93ac2c975ba12e0a6d97853832fb1f4fb02393015e012b47fa916a75ca95102d77214b2a29a2784740df2407951af8c5dde054824c65577fd293c4cdb + languageName: node + linkType: hard + +"@sideway/formula@npm:^3.0.1": + version: 3.0.1 + resolution: "@sideway/formula@npm:3.0.1" + checksum: e4beeebc9dbe2ff4ef0def15cec0165e00d1612e3d7cea0bc9ce5175c3263fc2c818b679bd558957f49400ee7be9d4e5ac90487e1625b4932e15c4aa7919c57a + languageName: node + linkType: hard + +"@sideway/pinpoint@npm:^2.0.0": + version: 2.0.0 + resolution: "@sideway/pinpoint@npm:2.0.0" + checksum: 0f4491e5897fcf5bf02c46f5c359c56a314e90ba243f42f0c100437935daa2488f20482f0f77186bd6bf43345095a95d8143ecf8b1f4d876a7bc0806aba9c3d2 + languageName: node + linkType: hard + +"@simplewebauthn/browser@npm:^7.2.0": + version: 7.2.0 + resolution: "@simplewebauthn/browser@npm:7.2.0" + dependencies: + "@simplewebauthn/typescript-types": "*" + checksum: 1eb8193e671307936cca3305b44860716e3c081effb83f11f35293cb411fc07e47c3e1ce0194322afab9d24b5e5cbde8c566daf8a21dcf8b2d872169cd04ce3d + languageName: node + linkType: hard + +"@simplewebauthn/iso-webcrypto@npm:^7.2.0": + version: 7.2.0 + resolution: "@simplewebauthn/iso-webcrypto@npm:7.2.0" + dependencies: + "@simplewebauthn/typescript-types": "*" + "@types/node": ^18.11.9 + checksum: 663b3cf8b88987e5a87126d3f35f92941756682aa13af4816fffe3a9547eb344fc433f817502a7e7066d6179270afdd5ea18638a629c4218b433c77f6669f14b + languageName: node + linkType: hard + +"@simplewebauthn/server@npm:^7.2.0": + version: 7.2.0 + resolution: "@simplewebauthn/server@npm:7.2.0" + dependencies: + "@hexagon/base64": ^1.1.25 + "@peculiar/asn1-android": ^2.3.3 + "@peculiar/asn1-ecc": ^2.3.4 + "@peculiar/asn1-rsa": ^2.3.4 + "@peculiar/asn1-schema": ^2.3.3 + "@peculiar/asn1-x509": ^2.3.4 + "@simplewebauthn/iso-webcrypto": ^7.2.0 + "@simplewebauthn/typescript-types": "*" + "@types/debug": ^4.1.7 + "@types/node": ^18.11.9 + cbor-x: ^1.4.1 + cross-fetch: ^3.1.5 + debug: ^4.3.2 + checksum: 6340e94be8f1705f59477d92324a0f3910fab48bbfff59e12e1660259161fdddb6d24b992915017277a18df6b0e3508d520918c2e4eac16eb299f498cfa3ccb6 + languageName: node + linkType: hard + +"@simplewebauthn/typescript-types@npm:*": + version: 7.0.0 + resolution: "@simplewebauthn/typescript-types@npm:7.0.0" + checksum: 11e55e74035e4a88287d5a615939c9cfffc6e05520dc947b68d77a1b5b301707137c2e29b5b279f91e3e4a9b472226fb6c7025144a6057990b6841145fdd4959 + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -1051,15 +1243,6 @@ __metadata: languageName: node linkType: hard -"@types/bson@npm:*": - version: 4.0.5 - resolution: "@types/bson@npm:4.0.5" - dependencies: - "@types/node": "*" - checksum: f6c74a68eec836010170e7091399b45fe39e2f7724372441cc00a0fbd0b9f44d901688504174d558edbf220922fa0c7c52fbc4aa0f2136194fa713101b8f2ec9 - languageName: node - linkType: hard - "@types/cleave.js@npm:^1.4.7": version: 1.4.7 resolution: "@types/cleave.js@npm:1.4.7" @@ -1096,6 +1279,15 @@ __metadata: languageName: node linkType: hard +"@types/debug@npm:^4.1.7": + version: 4.1.7 + resolution: "@types/debug@npm:4.1.7" + dependencies: + "@types/ms": "*" + checksum: 0a7b89d8ed72526858f0b61c6fd81f477853e8c4415bb97f48b1b5545248d2ae389931680b94b393b993a7cfe893537a200647d93defe6d87159b96812305adc + languageName: node + linkType: hard + "@types/dotenv@npm:^8.2.0": version: 8.2.0 resolution: "@types/dotenv@npm:8.2.0" @@ -1150,6 +1342,15 @@ __metadata: languageName: node linkType: hard +"@types/express-session@npm:^1.17.7": + version: 1.17.7 + resolution: "@types/express-session@npm:1.17.7" + dependencies: + "@types/express": "*" + checksum: 4764eafaf4e26842e891e83fa9fee30b92cc3724d91987aac3a658322f9a69fb544dae24fc3aba6e15fe9575ed862760ebecd7ff97a947afb695284967434a65 + languageName: node + linkType: hard + "@types/express@npm:*, @types/express@npm:^4.17.17": version: 4.17.17 resolution: "@types/express@npm:4.17.17" @@ -1199,13 +1400,19 @@ __metadata: languageName: node linkType: hard -"@types/mongodb@npm:^3.6.20": - version: 3.6.20 - resolution: "@types/mongodb@npm:3.6.20" +"@types/mongodb@npm:^4.0.7": + version: 4.0.7 + resolution: "@types/mongodb@npm:4.0.7" dependencies: - "@types/bson": "*" - "@types/node": "*" - checksum: e5397ada2ed728997f7c3f5424e8c28f682a635488be967c9c18a5de27b1641cf28bb42bc12026ac6d475c457a880e27097e13c8120350ba13219f4ccc030656 + mongodb: "*" + checksum: 10ebdfed5beaeb9354adcaded3ff31a0597eec96d665155bf029fdf81384534cc054ffd4f3a431911e55055cea8dcea6233b2265d6d90f45fe233fd59a31bd56 + languageName: node + linkType: hard + +"@types/ms@npm:*": + version: 0.7.31 + resolution: "@types/ms@npm:0.7.31" + checksum: daadd354aedde024cce6f5aa873fefe7b71b22cd0e28632a69e8b677aeb48ae8caa1c60e5919bb781df040d116b01cb4316335167a3fc0ef6a63fa3614c0f6da languageName: node linkType: hard @@ -1218,7 +1425,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:^18.15.11": +"@types/node@npm:*, @types/node@npm:^18.11.9, @types/node@npm:^18.15.11": version: 18.15.11 resolution: "@types/node@npm:18.15.11" checksum: 977b4ad04708897ff0eb049ecf82246d210939c82461922d20f7d2dcfd81bbc661582ba3af28869210f7e8b1934529dcd46bff7d448551400f9d48b9d3bddec3 @@ -1329,6 +1536,23 @@ __metadata: languageName: node linkType: hard +"@types/webidl-conversions@npm:*": + version: 7.0.0 + resolution: "@types/webidl-conversions@npm:7.0.0" + checksum: 60142c7ddd9eb6f907d232d6b3a81ecf990f73b5a62a004eba8bd0f54809a42ece68ce512e7e3e1d98af8b6393d66cddb96f3622d2fb223c4e9c8937c61bfed7 + languageName: node + linkType: hard + +"@types/whatwg-url@npm:^8.2.1": + version: 8.2.2 + resolution: "@types/whatwg-url@npm:8.2.2" + dependencies: + "@types/node": "*" + "@types/webidl-conversions": "*" + checksum: 5dc5afe078dfa1a8a266745586fa3db9baa8ce7cc904789211d1dca1d34d7f3dd17d0b7423c36bc9beab9d98aa99338f1fc60798c0af6cbb8356f20e20d9f243 + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.11.1": version: 1.11.1 resolution: "@webassemblyjs/ast@npm:1.11.1" @@ -1762,6 +1986,18 @@ __metadata: languageName: node linkType: hard +"asn1.js@npm:^5.4.1": + version: 5.4.1 + resolution: "asn1.js@npm:5.4.1" + dependencies: + bn.js: ^4.0.0 + inherits: ^2.0.1 + minimalistic-assert: ^1.0.0 + safer-buffer: ^2.1.0 + checksum: 3786a101ac6f304bd4e9a7df79549a7561950a13d4bcaec0c7790d44c80d147c1a94ba3d4e663673406064642a40b23fcd6c82a9952468e386c1a1376d747f9a + languageName: node + linkType: hard + "asn1@npm:^0.2.4": version: 0.2.6 resolution: "asn1@npm:0.2.6" @@ -1771,6 +2007,17 @@ __metadata: languageName: node linkType: hard +"asn1js@npm:^3.0.5": + version: 3.0.5 + resolution: "asn1js@npm:3.0.5" + dependencies: + pvtsutils: ^1.3.2 + pvutils: ^1.1.3 + tslib: ^2.4.0 + checksum: 3b6af1bbadd5762ef8ead5daf2f6bda1bc9e23bc825c4dcc996aa1f9521ad7390a64028565d95d98090d69c8431f004c71cccb866004759169d7c203cf9075eb + languageName: node + linkType: hard + "async@npm:^3.2.3": version: 3.2.4 resolution: "async@npm:3.2.4" @@ -1852,16 +2099,6 @@ __metadata: languageName: node linkType: hard -"bl@npm:^2.2.1": - version: 2.2.1 - resolution: "bl@npm:2.2.1" - dependencies: - readable-stream: ^2.3.5 - safe-buffer: ^5.1.1 - checksum: 4f5d9b258919646a8d02f1731379e53b6f6309e34596ae02afbc3aeb183910bd2d0b70681f889b7c620ca48f65dc1cd0992ee1266c90d6d7c3be60688d141233 - languageName: node - linkType: hard - "blob@npm:0.0.5": version: 0.0.5 resolution: "blob@npm:0.0.5" @@ -1869,6 +2106,13 @@ __metadata: languageName: node linkType: hard +"bn.js@npm:^4.0.0": + version: 4.12.0 + resolution: "bn.js@npm:4.12.0" + checksum: 39afb4f15f4ea537b55eaf1446c896af28ac948fdcf47171961475724d1bb65118cca49fa6e3d67706e4790955ec0e74de584e45c8f1ef89f46c812bee5b5a12 + languageName: node + linkType: hard + "body-parser@npm:1.20.1": version: 1.20.1 resolution: "body-parser@npm:1.20.1" @@ -1972,10 +2216,10 @@ __metadata: languageName: node linkType: hard -"bson@npm:^1.1.4": - version: 1.1.6 - resolution: "bson@npm:1.1.6" - checksum: 75762c9b7e0b3156cb0f38c7eb9ffcade53f0b04ac87dece9cba38f6dc570d9af91251de6a8988b294063cfaa21894c60ac9e85c34176accb3674acb092d66a7 +"bson@npm:^5.2.0": + version: 5.2.0 + resolution: "bson@npm:5.2.0" + checksum: 1297f8776a05fe3c03b5a1c73210389bc3e596ee6f1e772b76c021fff5537d0c91e523462ba196a8a839246613a87cffbd6486d387a849c7de7cde64d1af7812 languageName: node linkType: hard @@ -2117,6 +2361,49 @@ __metadata: languageName: node linkType: hard +"cbor-extract@npm:^2.1.1": + version: 2.1.1 + resolution: "cbor-extract@npm:2.1.1" + dependencies: + "@cbor-extract/cbor-extract-darwin-arm64": 2.1.1 + "@cbor-extract/cbor-extract-darwin-x64": 2.1.1 + "@cbor-extract/cbor-extract-linux-arm": 2.1.1 + "@cbor-extract/cbor-extract-linux-arm64": 2.1.1 + "@cbor-extract/cbor-extract-linux-x64": 2.1.1 + "@cbor-extract/cbor-extract-win32-x64": 2.1.1 + node-gyp: latest + node-gyp-build-optional-packages: 5.0.3 + dependenciesMeta: + "@cbor-extract/cbor-extract-darwin-arm64": + optional: true + "@cbor-extract/cbor-extract-darwin-x64": + optional: true + "@cbor-extract/cbor-extract-linux-arm": + optional: true + "@cbor-extract/cbor-extract-linux-arm64": + optional: true + "@cbor-extract/cbor-extract-linux-x64": + optional: true + "@cbor-extract/cbor-extract-win32-x64": + optional: true + bin: + download-cbor-prebuilds: bin/download-prebuilds.js + checksum: 283d9cdb3c716b171b5ad8666673f4ac373f975b51d9a38233d280c6f9381d66c6af4c011a561d993c4be6e427e34681bc3c5af194b9da0c9ab3401d424b7988 + languageName: node + linkType: hard + +"cbor-x@npm:^1.4.1": + version: 1.5.2 + resolution: "cbor-x@npm:1.5.2" + dependencies: + cbor-extract: ^2.1.1 + dependenciesMeta: + cbor-extract: + optional: true + checksum: 2c473622f61a3b2a1ff5fb493e2db00b8cd02d59704191c9f3af21ad97ec0019360ea1301fc18f35eb4138bc737986d04ee6f094a6b2dc71e6b4c69c6f584c0c + languageName: node + linkType: hard + "chalk@npm:^2.4.1": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -2435,6 +2722,19 @@ __metadata: languageName: node linkType: hard +"connect-mongo@npm:^5.0.0": + version: 5.0.0 + resolution: "connect-mongo@npm:5.0.0" + dependencies: + debug: ^4.3.1 + kruptein: ^3.0.0 + peerDependencies: + express-session: ^1.17.1 + mongodb: ^5.1.0 + checksum: 3f81eca3efbdb00d1d35d73551cf2405ad1cae5b9580566880007d7bfcddeb1ad9b0e8a6850c247534bf00bf93d1941ac610aa0a2df6dea4d475195c2c402f4e + languageName: node + linkType: hard + "console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" @@ -2482,6 +2782,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:0.4.2": + version: 0.4.2 + resolution: "cookie@npm:0.4.2" + checksum: a00833c998bedf8e787b4c342defe5fa419abd96b32f4464f718b91022586b8f1bafbddd499288e75c037642493c83083da426c6a9080d309e3bd90fd11baa9b + languageName: node + linkType: hard + "cookie@npm:0.5.0": version: 0.5.0 resolution: "cookie@npm:0.5.0" @@ -2489,13 +2796,6 @@ __metadata: languageName: node linkType: hard -"core-util-is@npm:~1.0.0": - version: 1.0.3 - resolution: "core-util-is@npm:1.0.3" - checksum: 9de8597363a8e9b9952491ebe18167e3b36e7707569eed0ebf14f8bba773611376466ae34575bca8cfe3c767890c859c74056084738f09d4e4a6f902b2ad7d99 - languageName: node - linkType: hard - "cors@npm:^2.8.5": version: 2.8.5 resolution: "cors@npm:2.8.5" @@ -2513,6 +2813,15 @@ __metadata: languageName: node linkType: hard +"cross-fetch@npm:^3.1.5": + version: 3.1.5 + resolution: "cross-fetch@npm:3.1.5" + dependencies: + node-fetch: 2.6.7 + checksum: f6b8c6ee3ef993ace6277fd789c71b6acf1b504fd5f5c7128df4ef2f125a429e29cd62dc8c127523f04a5f2fa4771ed80e3f3d9695617f441425045f505cf3bb + languageName: node + linkType: hard + "cross-spawn@npm:^6.0.5": version: 6.0.5 resolution: "cross-spawn@npm:6.0.5" @@ -2787,7 +3096,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -2855,14 +3164,7 @@ __metadata: languageName: node linkType: hard -"denque@npm:^1.4.1": - version: 1.5.1 - resolution: "denque@npm:1.5.1" - checksum: 4375ad19d5cea99f90effa82a8cecdaa10f4eb261fbcd7e47cd753ff2737f037aac8f7f4e031cc77f3966314c491c86a0d3b20c128aeee57f791b4662c45108e - languageName: node - linkType: hard - -"depd@npm:2.0.0, depd@npm:^2.0.0": +"depd@npm:2.0.0, depd@npm:^2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: abbe19c768c97ee2eed6282d8ce3031126662252c58d711f646921c9623f9052e3e1906443066beec1095832f534e57c523b7333f8e7e0d93051ab6baef5ab3a @@ -3341,32 +3643,32 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.17.15": - version: 0.17.15 - resolution: "esbuild@npm:0.17.15" +"esbuild@npm:^0.17.16": + version: 0.17.16 + resolution: "esbuild@npm:0.17.16" dependencies: - "@esbuild/android-arm": 0.17.15 - "@esbuild/android-arm64": 0.17.15 - "@esbuild/android-x64": 0.17.15 - "@esbuild/darwin-arm64": 0.17.15 - "@esbuild/darwin-x64": 0.17.15 - "@esbuild/freebsd-arm64": 0.17.15 - "@esbuild/freebsd-x64": 0.17.15 - "@esbuild/linux-arm": 0.17.15 - "@esbuild/linux-arm64": 0.17.15 - "@esbuild/linux-ia32": 0.17.15 - "@esbuild/linux-loong64": 0.17.15 - "@esbuild/linux-mips64el": 0.17.15 - "@esbuild/linux-ppc64": 0.17.15 - "@esbuild/linux-riscv64": 0.17.15 - "@esbuild/linux-s390x": 0.17.15 - "@esbuild/linux-x64": 0.17.15 - "@esbuild/netbsd-x64": 0.17.15 - "@esbuild/openbsd-x64": 0.17.15 - "@esbuild/sunos-x64": 0.17.15 - "@esbuild/win32-arm64": 0.17.15 - "@esbuild/win32-ia32": 0.17.15 - "@esbuild/win32-x64": 0.17.15 + "@esbuild/android-arm": 0.17.16 + "@esbuild/android-arm64": 0.17.16 + "@esbuild/android-x64": 0.17.16 + "@esbuild/darwin-arm64": 0.17.16 + "@esbuild/darwin-x64": 0.17.16 + "@esbuild/freebsd-arm64": 0.17.16 + "@esbuild/freebsd-x64": 0.17.16 + "@esbuild/linux-arm": 0.17.16 + "@esbuild/linux-arm64": 0.17.16 + "@esbuild/linux-ia32": 0.17.16 + "@esbuild/linux-loong64": 0.17.16 + "@esbuild/linux-mips64el": 0.17.16 + "@esbuild/linux-ppc64": 0.17.16 + "@esbuild/linux-riscv64": 0.17.16 + "@esbuild/linux-s390x": 0.17.16 + "@esbuild/linux-x64": 0.17.16 + "@esbuild/netbsd-x64": 0.17.16 + "@esbuild/openbsd-x64": 0.17.16 + "@esbuild/sunos-x64": 0.17.16 + "@esbuild/win32-arm64": 0.17.16 + "@esbuild/win32-ia32": 0.17.16 + "@esbuild/win32-x64": 0.17.16 dependenciesMeta: "@esbuild/android-arm": optional: true @@ -3414,7 +3716,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 4e3640d7bc8f6edb3465c076eb519ccb7684382714a1b883e000a7a592f8e285501ec7e82cb68441dfec8f7be7f70f40b00129ceb05057f6fa87f95d2187370a + checksum: c9787d8e05b9c4f762761be31a7847b5b4492b9b997808b7098479fef9a3260f1b8ca01e9b38376b6698f4394bfe088acb4f797a697b45b965cd664e103aafa7 languageName: node linkType: hard @@ -3516,6 +3818,22 @@ __metadata: languageName: node linkType: hard +"express-session@npm:^1.17.3": + version: 1.17.3 + resolution: "express-session@npm:1.17.3" + dependencies: + cookie: 0.4.2 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: ~2.0.0 + on-headers: ~1.0.2 + parseurl: ~1.3.3 + safe-buffer: 5.2.1 + uid-safe: ~2.1.5 + checksum: 1021a793433cbc6a1b32c803fcb2daa1e03a8f50dd907e8745ae57994370315a5cfde5b6ef7b062d9a9a0754ff268844bda211c08240b3a0e01014dcf1073ec5 + languageName: node + linkType: hard + "express@npm:^4.18.2": version: 4.18.2 resolution: "express@npm:4.18.2" @@ -3666,9 +3984,9 @@ __metadata: languageName: node linkType: hard -"flowbite-svelte@npm:^0.34.7": - version: 0.34.7 - resolution: "flowbite-svelte@npm:0.34.7" +"flowbite-svelte@npm:^0.34.9": + version: 0.34.9 + resolution: "flowbite-svelte@npm:0.34.9" dependencies: "@popperjs/core": ^2.11.6 classnames: ^2.3.2 @@ -3678,11 +3996,11 @@ __metadata: classnames: ^2.3.2 flowbite: ^1.6.3 svelte: ^3.55.1 - checksum: 4570c3071852cbca65302d93e1c8b8fa42dde55a2927f3782604e19f67bcbabeab0798c573c238610960f30595b50fffbd0da63952dcf4cd6cb02be454d097f4 + checksum: a628e3defc0e7415f4001e4cb5a5a6d13ec5d86007c8c198b7207bc8d215f77dedb1047f7fa6c2c5e572b73c3bfa7f6c1f50bd076a639ae4bd25b24548614665 languageName: node linkType: hard -"flowbite@npm:^1.6.3, flowbite@npm:^1.6.4": +"flowbite@npm:^1.6.3": version: 1.6.4 resolution: "flowbite@npm:1.6.4" dependencies: @@ -3692,6 +4010,16 @@ __metadata: languageName: node linkType: hard +"flowbite@npm:^1.6.5": + version: 1.6.5 + resolution: "flowbite@npm:1.6.5" + dependencies: + "@popperjs/core": ^2.9.3 + mini-svg-data-uri: ^1.4.3 + checksum: b6b7da2b3c07356e47dcec0ec4365462053b8bd0b8e6a7a5126588f0deffc31d783f5e7cd5288856a4a6562dab8d2555e0ea9b2c718e1d94a5c147655a8622f5 + languageName: node + linkType: hard + "fn.name@npm:1.x.x": version: 1.1.0 resolution: "fn.name@npm:1.1.0" @@ -4277,7 +4605,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 @@ -4330,6 +4658,13 @@ __metadata: languageName: node linkType: hard +"ipaddr.js@npm:^2.0.1": + version: 2.0.1 + resolution: "ipaddr.js@npm:2.0.1" + checksum: dd194a394a843d470f88d17191b0948f383ed1c8e320813f850c336a0fcb5e9215d97ec26ca35ab4fbbd31392c8b3467f3e8344628029ed3710b2ff6b5d1034e + languageName: node + linkType: hard + "is-array-buffer@npm:^3.0.1, is-array-buffer@npm:^3.0.2": version: 3.0.2 resolution: "is-array-buffer@npm:3.0.2" @@ -4586,13 +4921,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:~1.0.0": - version: 1.0.0 - resolution: "isarray@npm:1.0.0" - checksum: f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab - languageName: node - linkType: hard - "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -4627,6 +4955,19 @@ __metadata: languageName: node linkType: hard +"joi@npm:^17.9.1": + version: 17.9.1 + resolution: "joi@npm:17.9.1" + dependencies: + "@hapi/hoek": ^9.0.0 + "@hapi/topo": ^5.0.0 + "@sideway/address": ^4.1.3 + "@sideway/formula": ^3.0.1 + "@sideway/pinpoint": ^2.0.0 + checksum: 055df3841e00d7ed065ef1cc3330cf69097ab2ffec3083d8b1d6edfd2e25504bf2983f5249d6f0459bcad99fe21bb0c9f6f1cc03569713af27cd5eb00ee7bb7d + languageName: node + linkType: hard + "joycon@npm:^3.0.1, joycon@npm:^3.1.1": version: 3.1.1 resolution: "joycon@npm:3.1.1" @@ -4740,6 +5081,15 @@ __metadata: languageName: node linkType: hard +"kruptein@npm:^3.0.0": + version: 3.0.6 + resolution: "kruptein@npm:3.0.6" + dependencies: + asn1.js: ^5.4.1 + checksum: 65a31350fdb7b131aad717f4461d419c1ef52686b4497d9160c8d8e89a608f0966ffab7ff68ed6c8fa32dacfcdb7b77d8b5ea614a0d55151311156b09c1b4e30 + languageName: node + linkType: hard + "kuler@npm:^2.0.0": version: 2.0.0 resolution: "kuler@npm:2.0.0" @@ -5117,6 +5467,13 @@ __metadata: languageName: node linkType: hard +"minimalistic-assert@npm:^1.0.0": + version: 1.0.1 + resolution: "minimalistic-assert@npm:1.0.1" + checksum: cc7974a9268fbf130fb055aff76700d7e2d8be5f761fb5c60318d0ed010d839ab3661a533ad29a5d37653133385204c503bfac995aaa4236f4e847461ea32ba7 + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -5262,33 +5619,39 @@ __metadata: languageName: node linkType: hard -"mongodb@npm:^3.5.6, mongodb@npm:^3.7.3": - version: 3.7.3 - resolution: "mongodb@npm:3.7.3" +"mongodb-connection-string-url@npm:^2.6.0": + version: 2.6.0 + resolution: "mongodb-connection-string-url@npm:2.6.0" dependencies: - bl: ^2.2.1 - bson: ^1.1.4 - denque: ^1.4.1 - optional-require: ^1.1.8 - safe-buffer: ^5.1.2 - saslprep: ^1.0.0 + "@types/whatwg-url": ^8.2.1 + whatwg-url: ^11.0.0 + checksum: 1d662f0ecfe96f7a400f625c244b2e52914c98f3562ee7d19941127578b5f8237624433bdcea285a654041b945b518803512989690c74548aec5860c5541c605 + languageName: node + linkType: hard + +"mongodb@npm:*, mongodb@npm:^5.2.0": + version: 5.2.0 + resolution: "mongodb@npm:5.2.0" + dependencies: + bson: ^5.2.0 + mongodb-connection-string-url: ^2.6.0 + saslprep: ^1.0.3 + socks: ^2.7.1 + peerDependencies: + "@aws-sdk/credential-providers": ^3.201.0 + mongodb-client-encryption: ^2.3.0 + snappy: ^7.2.2 dependenciesMeta: saslprep: optional: true peerDependenciesMeta: - aws4: - optional: true - bson-ext: - optional: true - kerberos: + "@aws-sdk/credential-providers": optional: true mongodb-client-encryption: optional: true - mongodb-extjson: - optional: true snappy: optional: true - checksum: ef7690fe6ee7d1752f121b14e59b3fabfddc60ff0536babce6c945703ad0010de9e6fa7de4c91b99275c256876a72a06899ce27893aba0838c2b542088bd1044 + checksum: 2114175fab6a56c2d2902d97117c98c7b240155af6b7880174dbdf90bdcd8eb60c959ced7ed8b6327abfe226d6ae9933356344fc7d6e71798fef86e6eddd0173 languageName: node linkType: hard @@ -5379,6 +5742,31 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:2.6.7": + version: 2.6.7 + resolution: "node-fetch@npm:2.6.7" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 8d816ffd1ee22cab8301c7756ef04f3437f18dace86a1dae22cf81db8ef29c0bf6655f3215cb0cdb22b420b6fe141e64b26905e7f33f9377a7fa59135ea3e10b + languageName: node + linkType: hard + +"node-gyp-build-optional-packages@npm:5.0.3": + version: 5.0.3 + resolution: "node-gyp-build-optional-packages@npm:5.0.3" + bin: + node-gyp-build-optional-packages: bin.js + node-gyp-build-optional-packages-optional: optional.js + node-gyp-build-optional-packages-test: build-test.js + checksum: be3f0235925c8361e5bc1a03848f5e24815b0df8aa90bd13f1eac91cd86264bbb8b7689ca6cd083b02c8099c7b54f9fb83066c7bb77c2389dc4eceab921f084f + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 9.3.1 resolution: "node-gyp@npm:9.3.1" @@ -5617,15 +6005,6 @@ __metadata: languageName: node linkType: hard -"optional-require@npm:^1.1.8": - version: 1.1.8 - resolution: "optional-require@npm:1.1.8" - dependencies: - require-at: ^1.0.6 - checksum: 437db76f713052925185ae80837b593877f75101154e8937f50d33b0b07bd500c214efc9016748642109b6e3e1197eb0513a2963eb06bcf3890f88a2724b1c87 - languageName: node - linkType: hard - "opts@npm:>= 1.2.0": version: 2.0.2 resolution: "opts@npm:2.0.2" @@ -6648,13 +7027,6 @@ __metadata: languageName: node linkType: hard -"process-nextick-args@npm:~2.0.0": - version: 2.0.1 - resolution: "process-nextick-args@npm:2.0.1" - checksum: 1d38588e520dab7cea67cbbe2efdd86a10cc7a074c09657635e34f035277b59fbb57d09d8638346bf7090f8e8ebc070c96fa5fd183b777fff4f5edff5e9466cf - languageName: node - linkType: hard - "promise-inflight@npm:^1.0.1": version: 1.0.1 resolution: "promise-inflight@npm:1.0.1" @@ -6696,13 +7068,29 @@ __metadata: languageName: node linkType: hard -"punycode@npm:^2.1.0": +"punycode@npm:^2.1.0, punycode@npm:^2.1.1": version: 2.3.0 resolution: "punycode@npm:2.3.0" checksum: 39f760e09a2a3bbfe8f5287cf733ecdad69d6af2fe6f97ca95f24b8921858b91e9ea3c9eeec6e08cede96181b3bb33f95c6ffd8c77e63986508aa2e8159fa200 languageName: node linkType: hard +"pvtsutils@npm:^1.3.2": + version: 1.3.2 + resolution: "pvtsutils@npm:1.3.2" + dependencies: + tslib: ^2.4.0 + checksum: 9b8155611363e2f40276879f2454e60204b45be0cd0482f9373f369308a2e9c76d5d74cdf661a3f5aae8022d75ea159eb0ba38ee78fc782ee3051e4722db98d0 + languageName: node + linkType: hard + +"pvutils@npm:^1.1.3": + version: 1.1.3 + resolution: "pvutils@npm:1.1.3" + checksum: 2ee26a9e5176c348977d6ec00d8ee80bff62f51743b1c5fe8abeeb4c5d29d9959cdfe0ce146707a9e6801bce88190fed3002d720b072dc87d031c692820b44c9 + languageName: node + linkType: hard + "qrcode@npm:^1.5.1": version: 1.5.1 resolution: "qrcode@npm:1.5.1" @@ -6740,6 +7128,13 @@ __metadata: languageName: node linkType: hard +"random-bytes@npm:~1.0.0": + version: 1.0.0 + resolution: "random-bytes@npm:1.0.0" + checksum: 09faa256394aa2ca9754aa57e92a27c452c3e97ffb266e98bebb517332e9df7168fea393159f88d884febce949ba8bec8ddb02f03342da6c6023ecc7b155e0ae + languageName: node + linkType: hard + "randombytes@npm:^2.1.0": version: 2.1.0 resolution: "randombytes@npm:2.1.0" @@ -6800,21 +7195,6 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.3.5": - version: 2.3.8 - resolution: "readable-stream@npm:2.3.8" - dependencies: - core-util-is: ~1.0.0 - inherits: ~2.0.3 - isarray: ~1.0.0 - process-nextick-args: ~2.0.0 - safe-buffer: ~5.1.1 - string_decoder: ~1.1.1 - util-deprecate: ~1.0.1 - checksum: 65645467038704f0c8aaf026a72fbb588a9e2ef7a75cd57a01702ee9db1c4a1e4b03aaad36861a6a0926546a74d174149c8c207527963e0c2d3eee2f37678a42 - languageName: node - linkType: hard - "readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" @@ -6869,13 +7249,6 @@ __metadata: languageName: node linkType: hard -"require-at@npm:^1.0.6": - version: 1.0.6 - resolution: "require-at@npm:1.0.6" - checksum: 7753a6ebad99855ef015d5533a787c65e883c94c23371368eebf6f1c7e2a078811013b204823152cbab206a00e825e8e5ca09416fd835a489fa30bf064fbe6d9 - languageName: node - linkType: hard - "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -7146,14 +7519,14 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.1.2, safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": +"safe-buffer@npm:5.1.2": version: 5.1.2 resolution: "safe-buffer@npm:5.1.2" checksum: f2f1f7943ca44a594893a852894055cf619c1fbcb611237fc39e461ae751187e7baf4dc391a72125e0ac4fb2d8c5c0b3c71529622e6a58f46b960211e704903c languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 @@ -7185,7 +7558,7 @@ __metadata: languageName: node linkType: hard -"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:~2.1.0": +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" checksum: cab8f25ae6f1434abee8d80023d7e72b598cf1327164ddab31003c51215526801e40b66c5e65d658a0af1e9d6478cadcb4c745f4bd6751f97d8644786c0978b0 @@ -7204,7 +7577,7 @@ __metadata: languageName: node linkType: hard -"saslprep@npm:^1.0.0": +"saslprep@npm:^1.0.3": version: 1.0.3 resolution: "saslprep@npm:1.0.3" dependencies: @@ -7463,7 +7836,7 @@ __metadata: languageName: node linkType: hard -"socks@npm:^2.6.2": +"socks@npm:^2.6.2, socks@npm:^2.7.1": version: 2.7.1 resolution: "socks@npm:2.7.1" dependencies: @@ -7685,15 +8058,6 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:~1.1.1": - version: 1.1.1 - resolution: "string_decoder@npm:1.1.1" - dependencies: - safe-buffer: ~5.1.0 - checksum: 9ab7e56f9d60a28f2be697419917c50cac19f3e8e6c28ef26ed5f4852289fe0de5d6997d29becf59028556f2c62983790c1d9ba1e2a3cc401768ca12d5183a5b - languageName: node - linkType: hard - "strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -8049,6 +8413,22 @@ __metadata: languageName: node linkType: hard +"tr46@npm:^3.0.0": + version: 3.0.0 + resolution: "tr46@npm:3.0.0" + dependencies: + punycode: ^2.1.1 + checksum: 44c3cc6767fb800490e6e9fd64fd49041aa4e49e1f6a012b34a75de739cc9ed3a6405296072c1df8b6389ae139c5e7c6496f659cfe13a04a4bff3a1422981270 + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 726321c5eaf41b5002e17ffbd1fb7245999a073e8979085dacd47c4b4e8068ff5777142fc6726d6ca1fd2ff16921b48788b87225cbc57c72636f6efa8efbffe3 + languageName: node + linkType: hard + "tree-kill@npm:^1.2.2": version: 1.2.2 resolution: "tree-kill@npm:1.2.2" @@ -8145,7 +8525,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.0.2": +"typescript@npm:^5.0.4": version: 5.0.4 resolution: "typescript@npm:5.0.4" bin: @@ -8155,17 +8535,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.0.3": - version: 5.0.3 - resolution: "typescript@npm:5.0.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 3cce0576d218cb4277ff8b6adfef1a706e9114a98b4261a38ad658a7642f1b274a8396394f6cbff8c0ba852996d7ed2e233e9b8431d5d55ac7c2f6fea645af02 - languageName: node - linkType: hard - -"typescript@patch:typescript@^5.0.2#~builtin": +"typescript@patch:typescript@^5.0.4#~builtin": version: 5.0.4 resolution: "typescript@patch:typescript@npm%3A5.0.4#~builtin::version=5.0.4&hash=85af82" bin: @@ -8175,16 +8545,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@^5.0.3#~builtin": - version: 5.0.3 - resolution: "typescript@patch:typescript@npm%3A5.0.3#~builtin::version=5.0.3&hash=85af82" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 5580367025ff7ee1f2a61e5affdbddccfe6e893bc662aa33fefdbf12de7e493173fa7d47475e9e15121828691004c4ed13bcd115e57866baed97b54c60954e1c - languageName: node - linkType: hard - "u2f@npm:^0.1.3": version: 0.1.3 resolution: "u2f@npm:0.1.3" @@ -8208,6 +8568,15 @@ __metadata: languageName: node linkType: hard +"uid-safe@npm:~2.1.5": + version: 2.1.5 + resolution: "uid-safe@npm:2.1.5" + dependencies: + random-bytes: ~1.0.0 + checksum: 07536043da9a026f4a2bc397543d0ace7587449afa1d9d2c4fd3ce76af8a5263a678788bcc429dff499ef29d45843cd5ee9d05434450fcfc19cc661229f703d1 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.0.2": version: 1.0.2 resolution: "unbox-primitive@npm:1.0.2" @@ -8289,7 +8658,7 @@ __metadata: languageName: node linkType: hard -"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": +"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" checksum: 474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 @@ -8346,6 +8715,20 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: c92a0a6ab95314bde9c32e1d0a6dfac83b578f8fa5f21e675bc2706ed6981bc26b7eb7e6a1fab158e5ce4adf9caa4a0aee49a52505d4d13c7be545f15021b17c + languageName: node + linkType: hard + +"webidl-conversions@npm:^7.0.0": + version: 7.0.0 + resolution: "webidl-conversions@npm:7.0.0" + checksum: f05588567a2a76428515333eff87200fae6c83c3948a7482ebb109562971e77ef6dc49749afa58abb993391227c5697b3ecca52018793e0cb4620a48f10bd21b + languageName: node + linkType: hard + "webpack-cli@npm:^4.9.1": version: 4.10.0 resolution: "webpack-cli@npm:4.10.0" @@ -8453,6 +8836,26 @@ __metadata: languageName: node linkType: hard +"whatwg-url@npm:^11.0.0": + version: 11.0.0 + resolution: "whatwg-url@npm:11.0.0" + dependencies: + tr46: ^3.0.0 + webidl-conversions: ^7.0.0 + checksum: ed4826aaa57e66bb3488a4b25c9cd476c46ba96052747388b5801f137dd740b73fde91ad207d96baf9f17fbcc80fc1a477ad65181b5eb5fa718d27c69501d7af + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: ~0.0.3 + webidl-conversions: ^3.0.0 + checksum: b8daed4ad3356cc4899048a15b2c143a9aed0dfae1f611ebd55073310c7b910f522ad75d727346ad64203d7e6c79ef25eafd465f4d12775ca44b90fa82ed9e2c + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.0.2": version: 1.0.2 resolution: "which-boxed-primitive@npm:1.0.2"