Updating dependencies and switching to ESModules where possible
Some checks failed
CI / build (push) Has been cancelled

This commit is contained in:
Fabian Stamm
2025-09-15 22:04:57 +02:00
parent 8135190cd8
commit c6158fe2e2
66 changed files with 4540 additions and 3752 deletions

View File

@ -1,9 +1,12 @@
nodeLinker: node-modules nodeLinker: node-modules
npmRegistryServer: "https://npm.hibas123.de" npmRegistryServer: "https://npm.hibas123.de"
npmScopes:
plugins: "hibas123":
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs npmRegistryServer: "https://git.hibas.dev/api/packages/hibas123/npm/"
spec: "@yarnpkg/plugin-interactive-tools"
plugins:
yarnPath: .yarn/releases/yarn-3.5.0.cjs - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.5.0.cjs

View File

@ -3,12 +3,15 @@
"main": "lib/index.js", "main": "lib/index.js",
"author": "Fabian Stamm <dev@fabianstamm.de>", "author": "Fabian Stamm <dev@fabianstamm.de>",
"license": "MIT", "license": "MIT",
"type": "module",
"scripts": { "scripts": {
"build": "run-s build-ts build-doc", "build": "run-s build-ts build-doc",
"build-doc": "apidoc -i src/ -p apidoc/", "build-doc": "apidoc -i src/ -p apidoc/",
"build-ts": "tsc", "build-ts": "tsc",
"start": "node lib/index.js", "start": "node lib/index.js",
"dev": "nodemon -e ts --exec ts-node src/index.ts", "dev:js": "nodemon lib/index.ts",
"dev:ts": "tsc --watch",
"dev": "concurrently 'yarn run dev:js' 'yarn run dev:ts'",
"format": "prettier --write \"src/**\"" "format": "prettier --write \"src/**\""
}, },
"pipelines": { "pipelines": {
@ -20,58 +23,59 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@types/body-parser": "^1.19.2", "@types/body-parser": "^1.19.6",
"@types/compression": "^1.7.2", "@types/compression": "^1.8.1",
"@types/cookie-parser": "^1.4.3", "@types/cookie-parser": "^1.4.9",
"@types/dotenv": "^8.2.0", "@types/dotenv": "^8.2.3",
"@types/express": "^4.17.17", "@types/express": "^5.0.3",
"@types/express-session": "^1.17.7", "@types/express-serve-static-core": "^5.0.7",
"@types/i18n": "^0.13.6", "@types/express-session": "^1.18.2",
"@types/ini": "^1.3.31", "@types/i18n": "^0.13.12",
"@types/jsonwebtoken": "^9.0.1", "@types/ini": "^4.1.1",
"@types/jsonwebtoken": "^9.0.10",
"@types/mongodb": "^4.0.7", "@types/mongodb": "^4.0.7",
"@types/node": "^18.15.11", "@types/node": "^24.4.0",
"@types/node-rsa": "^1.1.1", "@types/node-rsa": "^1.1.4",
"@types/qrcode": "^1.5.0", "@types/qrcode": "^1.5.5",
"@types/speakeasy": "^2.0.7", "@types/speakeasy": "^2.0.10",
"@types/uuid": "^9.0.1", "@types/uuid": "^10.0.0",
"apidoc": "^0.54.0", "apidoc": "^1.2.0",
"concurrently": "^8.2.2", "concurrently": "^9.2.1",
"nodemon": "^3.0.1", "nodemon": "^3.1.10",
"prettier": "^2.8.7", "prettier": "^3.6.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.2",
"typescript": "^5.0.4" "typescript": "^5.9.2"
}, },
"dependencies": { "dependencies": {
"@hibas123/config": "^1.1.2", "@hibas123/config": "^1.1.2",
"@hibas123/nodelogging": "^3.1.3", "@hibas123/nodelogging": "^4.0.0",
"@hibas123/nodeloggingserver_client": "^1.1.2", "@hibas123/nodeloggingserver_client": "^1.1.2",
"@hibas123/openauth-internalapi": "workspace:^", "@hibas123/openauth-internalapi": "workspace:^",
"@hibas123/openauth-views-v1": "workspace:^", "@hibas123/openauth-views-v1": "workspace:^",
"@hibas123/safe_mongo": "2.0.1", "@hibas123/safe_mongo": "2.1.0",
"@simplewebauthn/server": "^7.2.0", "@simplewebauthn/server": "^13.2.0",
"body-parser": "^1.20.2", "body-parser": "^2.2.0",
"compression": "^1.7.4", "compression": "^1.8.1",
"connect-mongo": "^5.0.0", "connect-mongo": "^5.1.0",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.7",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.0.3", "dotenv": "^17.2.2",
"express": "^4.18.2", "express": "^5.1.0",
"express-session": "^1.17.3", "express-session": "^1.18.2",
"handlebars": "^4.7.7", "handlebars": "^4.7.8",
"i18n": "^0.15.1", "i18n": "^0.15.1",
"ini": "^4.1.1", "ini": "^5.0.0",
"joi": "^17.11.0", "joi": "^18.0.1",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.2",
"moment": "^2.29.4", "moment": "^2.30.1",
"mongodb": "^5.2.0", "mongodb": "^6.19.0",
"node-rsa": "^1.1.1", "node-rsa": "^1.1.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"qrcode": "^1.5.3", "qrcode": "^1.5.4",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.2.2",
"speakeasy": "^2.0.0", "speakeasy": "^2.0.0",
"u2f": "^0.1.3", "u2f": "^0.1.3",
"uuid": "^9.0.1" "uuid": "^13.0.0"
}, },
"packageManager": "yarn@3.5.0" "packageManager": "yarn@3.5.0"
} }

View File

@ -1,191 +1,191 @@
import { Router, Request } from "express"; import { Router, Request } from "express";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import promiseMiddleware from "../../helper/promiseMiddleware"; import promiseMiddleware from "../../helper/promiseMiddleware.js";
import Client from "../../models/client"; import Client from "../../models/client.js";
import verify, { Types } from "../middlewares/verify"; import verify, { Types } from "../middlewares/verify.js";
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
const ClientRouter: Router = Router(); const ClientRouter: Router = Router();
ClientRouter.route("/") ClientRouter.route("/")
/** /**
* @api {get} /admin/client * @api {get} /admin/client
* @apiName AdminGetClients * @apiName AdminGetClients
* *
* @apiGroup admin_client * @apiGroup admin_client
* @apiPermission admin * @apiPermission admin
* *
* @apiSuccess {Object[]} clients * @apiSuccess {Object[]} clients
* @apiSuccess {String} clients._id The internally used id * @apiSuccess {String} clients._id The internally used id
* @apiSuccess {String} clients.maintainer * @apiSuccess {String} clients.maintainer
* @apiSuccess {Boolean} clients.internal * @apiSuccess {Boolean} clients.internal
* @apiSuccess {String} clients.name * @apiSuccess {String} clients.name
* @apiSuccess {String} clients.redirect_url * @apiSuccess {String} clients.redirect_url
* @apiSuccess {String} clients.website * @apiSuccess {String} clients.website
* @apiSuccess {String} clients.logo * @apiSuccess {String} clients.logo
* @apiSuccess {String} clients.client_id Client ID used outside of DB * @apiSuccess {String} clients.client_id Client ID used outside of DB
* @apiSuccess {String} clients.client_secret * @apiSuccess {String} clients.client_secret
*/ */
.get( .get(
promiseMiddleware(async (req, res) => { promiseMiddleware(async (req, res) => {
let clients = await Client.find({}); let clients = await Client.find({});
//ToDo check if user is required! //ToDo check if user is required!
res.json(clients); res.json(clients);
}) })
) )
/** /**
* @api {get} /admin/client * @api {get} /admin/client
* @apiName AdminAddClients * @apiName AdminAddClients
* *
* @apiGroup admin_client * @apiGroup admin_client
* @apiPermission admin * @apiPermission admin
* *
* @apiParam {Boolean} internal Is it an internal app * @apiParam {Boolean} internal Is it an internal app
* @apiParam {String} name * @apiParam {String} name
* @apiParam {String} redirect_url * @apiParam {String} redirect_url
* @apiParam {String} website * @apiParam {String} website
* @apiParam {String} logo * @apiParam {String} logo
* *
* @apiSuccess {Object[]} clients * @apiSuccess {Object[]} clients
* @apiSuccess {String} clients._id The internally used id * @apiSuccess {String} clients._id The internally used id
* @apiSuccess {String} clients.maintainer * @apiSuccess {String} clients.maintainer
* @apiSuccess {Boolean} clients.internal * @apiSuccess {Boolean} clients.internal
* @apiSuccess {String} clients.name * @apiSuccess {String} clients.name
* @apiSuccess {String} clients.redirect_url * @apiSuccess {String} clients.redirect_url
* @apiSuccess {String} clients.website * @apiSuccess {String} clients.website
* @apiSuccess {String} clients.logo * @apiSuccess {String} clients.logo
* @apiSuccess {String} clients.client_id Client ID used outside of DB * @apiSuccess {String} clients.client_id Client ID used outside of DB
* @apiSuccess {String} clients.client_secret * @apiSuccess {String} clients.client_secret
*/ */
.post( .post(
verify( verify(
{ {
internal: { internal: {
type: Types.BOOLEAN, type: Types.BOOLEAN,
optional: true, optional: true,
}, },
name: { name: {
type: Types.STRING, type: Types.STRING,
}, },
redirect_url: { redirect_url: {
type: Types.STRING, type: Types.STRING,
}, },
website: { website: {
type: Types.STRING, type: Types.STRING,
}, },
logo: { logo: {
type: Types.STRING, type: Types.STRING,
optional: true, optional: true,
}, },
featured: { featured: {
type: Types.BOOLEAN, type: Types.BOOLEAN,
optional: true, optional: true,
}, },
description: { description: {
type: Types.STRING, type: Types.STRING,
optional: true, optional: true,
}, },
}, },
true true
), ),
promiseMiddleware(async (req, res) => { promiseMiddleware(async (req, res) => {
req.body.client_secret = randomBytes(32).toString("hex"); req.body.client_secret = randomBytes(32).toString("hex");
let client = Client.new(req.body); let client = Client.new(req.body);
client.maintainer = req.user._id; client.maintainer = req.user._id;
await Client.save(client); await Client.save(client);
res.json(client); res.json(client);
}) })
); );
ClientRouter.route("/:id") ClientRouter.route("/:id")
/** /**
* @api {delete} /admin/client/:id * @api {delete} /admin/client/:id
* @apiParam {String} id Client _id * @apiParam {String} id Client _id
* @apiName AdminDeleteClient * @apiName AdminDeleteClient
* *
* @apiGroup admin_client * @apiGroup admin_client
* @apiPermission admin * @apiPermission admin
* *
* @apiSuccess {Boolean} success * @apiSuccess {Boolean} success
*/ */
.delete( .delete(
promiseMiddleware(async (req, res) => { promiseMiddleware(async (req, res) => {
let { id } = req.params; let { id } = req.params;
await Client.delete(id); await Client.delete(id);
res.json({ success: true }); res.json({ success: true });
}) })
) )
/** /**
* @api {put} /admin/client/:id * @api {put} /admin/client/:id
* @apiParam {String} id Client _id * @apiParam {String} id Client _id
* @apiName AdminUpdateClient * @apiName AdminUpdateClient
* *
* @apiGroup admin_client * @apiGroup admin_client
* @apiPermission admin * @apiPermission admin
* *
* @apiParam {Boolean} internal Is it an internal app * @apiParam {Boolean} internal Is it an internal app
* @apiParam {String} name * @apiParam {String} name
* @apiParam {String} redirect_url * @apiParam {String} redirect_url
* @apiParam {String} website * @apiParam {String} website
* @apiParam {String} logo * @apiParam {String} logo
* *
* @apiSuccess {String} _id The internally used id * @apiSuccess {String} _id The internally used id
* @apiSuccess {String} maintainer UserID of client maintainer * @apiSuccess {String} maintainer UserID of client maintainer
* @apiSuccess {Boolean} internal Defines if it is a internal client * @apiSuccess {Boolean} internal Defines if it is a internal client
* @apiSuccess {String} name The name of the Client * @apiSuccess {String} name The name of the Client
* @apiSuccess {String} redirect_url Redirect URL after login * @apiSuccess {String} redirect_url Redirect URL after login
* @apiSuccess {String} website Website of Client * @apiSuccess {String} website Website of Client
* @apiSuccess {String} logo The Logo of the Client (optional) * @apiSuccess {String} logo The Logo of the Client (optional)
* @apiSuccess {String} client_id Client ID used outside of DB * @apiSuccess {String} client_id Client ID used outside of DB
* @apiSuccess {String} client_secret The client secret, that can be used to obtain token * @apiSuccess {String} client_secret The client secret, that can be used to obtain token
*/ */
.put( .put(
verify( verify(
{ {
internal: { internal: {
type: Types.BOOLEAN, type: Types.BOOLEAN,
optional: true, optional: true,
}, },
name: { name: {
type: Types.STRING, type: Types.STRING,
optional: true, optional: true,
}, },
redirect_url: { redirect_url: {
type: Types.STRING, type: Types.STRING,
optional: true, optional: true,
}, },
website: { website: {
type: Types.STRING, type: Types.STRING,
optional: true, optional: true,
}, },
logo: { logo: {
type: Types.STRING, type: Types.STRING,
optional: true, optional: true,
}, },
featured: { featured: {
type: Types.BOOLEAN, type: Types.BOOLEAN,
optional: true, optional: true,
}, },
description: { description: {
type: Types.STRING, type: Types.STRING,
optional: true, optional: true,
}, },
}, },
true true
), ),
promiseMiddleware(async (req, res) => { promiseMiddleware(async (req, res) => {
let { id } = req.query as { [key: string]: string }; let { id } = req.query as { [key: string]: string };
let client = await Client.findById(id); let client = await Client.findById(id);
if (!client) if (!client)
throw new RequestError( throw new RequestError(
req.__("Client not found"), req.__("Client not found"),
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
for (let key in req.body) { for (let key in req.body) {
client[key] = req.body[key]; client[key] = req.body[key];
} }
await Client.save(client); await Client.save(client);
res.json(client); res.json(client);
}) })
); );
export default ClientRouter; export default ClientRouter;

View File

@ -1,24 +1,24 @@
import { Request, Router } from "express"; import { Request, Router } from "express";
import ClientRoute from "./client"; import ClientRoute from "./client.js";
import UserRoute from "./user"; import UserRoute from "./user.js";
import RegCodeRoute from "./regcode"; import RegCodeRoute from "./regcode.js";
import PermissionRoute from "./permission"; import PermissionRoute from "./permission.js";
import { GetUserMiddleware } from "../middlewares/user"; import { GetUserMiddleware } from "../middlewares/user.js";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
const AdminRoute: Router = Router(); const AdminRoute: Router = Router();
AdminRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => { AdminRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
if (!req.isAdmin) if (!req.isAdmin)
throw new RequestError( throw new RequestError(
"You have no permission to access this API", "You have no permission to access this API",
HttpStatusCode.FORBIDDEN HttpStatusCode.FORBIDDEN
); );
else next(); else next();
}); });
AdminRoute.use("/client", ClientRoute); AdminRoute.use("/client", ClientRoute);
AdminRoute.use("/regcode", RegCodeRoute); AdminRoute.use("/regcode", RegCodeRoute);
AdminRoute.use("/user", UserRoute); AdminRoute.use("/user", UserRoute);
AdminRoute.use("/permission", PermissionRoute); AdminRoute.use("/permission", PermissionRoute);
export default AdminRoute; export default AdminRoute;

View File

@ -1,10 +1,9 @@
import { Request, Router } from "express"; import { Request, Router } from "express";
import { GetUserMiddleware } from "../middlewares/user"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import promiseMiddleware from "../../helper/promiseMiddleware.js";
import promiseMiddleware from "../../helper/promiseMiddleware"; import Permission from "../../models/permissions.js";
import Permission from "../../models/permissions"; import verify, { Types } from "../middlewares/verify.js";
import verify, { Types } from "../middlewares/verify"; import Client from "../../models/client.js";
import Client from "../../models/client";
import { ObjectId } from "bson"; import { ObjectId } from "bson";
const PermissionRoute: Router = Router(); const PermissionRoute: Router = Router();

View File

@ -1,69 +1,67 @@
import { Request, Router } from "express"; import { Request, Router } from "express";
import promiseMiddleware from "../../helper/promiseMiddleware"; import promiseMiddleware from "../../helper/promiseMiddleware.js";
import RegCode from "../../models/regcodes"; import RegCode from "../../models/regcodes.js";
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
import moment = require("moment"); import moment = require("moment");
import { GetUserMiddleware } from "../middlewares/user";
import { HttpStatusCode } from "../../helper/request_error"; const RegCodeRoute: Router = Router();
RegCodeRoute.route("/")
const RegCodeRoute: Router = Router(); /**
RegCodeRoute.route("/") * @api {get} /admin/regcode
/** * @apiName AdminGetRegcodes
* @api {get} /admin/regcode *
* @apiName AdminGetRegcodes * @apiGroup admin_regcode
* * @apiPermission admin
* @apiGroup admin_regcode *
* @apiPermission admin * @apiSuccess {Object[]} regcodes
* * @apiSuccess {String} permissions._id The ID
* @apiSuccess {Object[]} regcodes * @apiSuccess {String} permissions.token The Regcode Token
* @apiSuccess {String} permissions._id The ID * @apiSuccess {String} permissions.valid Defines if the Regcode is valid
* @apiSuccess {String} permissions.token The Regcode Token * @apiSuccess {String} permissions.validTill Expiration date of RegCode
* @apiSuccess {String} permissions.valid Defines if the Regcode is valid */
* @apiSuccess {String} permissions.validTill Expiration date of RegCode .get(
*/ promiseMiddleware(async (req, res) => {
.get( let regcodes = await RegCode.find({});
promiseMiddleware(async (req, res) => { res.json(regcodes);
let regcodes = await RegCode.find({}); })
res.json(regcodes); )
}) /**
) * @api {delete} /admin/regcode
/** * @apiName AdminDeleteRegcode
* @api {delete} /admin/regcode *
* @apiName AdminDeleteRegcode * @apiParam {String} id The id of the RegCode
* *
* @apiParam {String} id The id of the RegCode * @apiGroup admin_regcode
* * @apiPermission admin
* @apiGroup admin_regcode *
* @apiPermission admin * @apiSuccess {Boolean} success
* */
* @apiSuccess {Boolean} success .delete(
*/ promiseMiddleware(async (req, res) => {
.delete( let { id } = req.query as { [key: string]: string };
promiseMiddleware(async (req, res) => { await RegCode.delete(id);
let { id } = req.query as { [key: string]: string }; res.json({ success: true });
await RegCode.delete(id); })
res.json({ success: true }); )
}) /**
) * @api {post} /admin/regcode
/** * @apiName AdminAddRegcode
* @api {post} /admin/regcode *
* @apiName AdminAddRegcode * @apiGroup admin_regcode
* * @apiPermission admin
* @apiGroup admin_regcode *
* @apiPermission admin * @apiSuccess {String} code The newly created code
* */
* @apiSuccess {String} code The newly created code .post(
*/ promiseMiddleware(async (req, res) => {
.post( let regcode = RegCode.new({
promiseMiddleware(async (req, res) => { token: randomBytes(10).toString("hex"),
let regcode = RegCode.new({ valid: true,
token: randomBytes(10).toString("hex"), validTill: moment().add("1", "month").toDate(),
valid: true, });
validTill: moment().add("1", "month").toDate(), await RegCode.save(regcode);
}); res.json({ code: regcode.token });
await RegCode.save(regcode); })
res.json({ code: regcode.token }); );
})
); export default RegCodeRoute;
export default RegCodeRoute;

View File

@ -1,93 +1,93 @@
import { Request, Router } from "express"; import { Request, Router } from "express";
import { GetUserMiddleware } from "../middlewares/user"; import { GetUserMiddleware } from "../middlewares/user.js";
import { HttpStatusCode } from "../../helper/request_error"; import { HttpStatusCode } from "../../helper/request_error.js";
import promiseMiddleware from "../../helper/promiseMiddleware"; import promiseMiddleware from "../../helper/promiseMiddleware.js";
import User from "../../models/user"; import User from "../../models/user.js";
import Mail from "../../models/mail"; import Mail from "../../models/mail.js";
import RefreshToken from "../../models/refresh_token"; import RefreshToken from "../../models/refresh_token.js";
import LoginToken from "../../models/login_token"; import LoginToken from "../../models/login_token.js";
const UserRoute: Router = Router(); const UserRoute: Router = Router();
UserRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => { UserRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN); if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN);
else next(); else next();
}); });
UserRoute.route("/") UserRoute.route("/")
/** /**
* @api {get} /admin/user * @api {get} /admin/user
* @apiName AdminGetUsers * @apiName AdminGetUsers
* *
* @apiGroup admin_user * @apiGroup admin_user
* @apiPermission admin * @apiPermission admin
* @apiSuccess {Object[]} user * @apiSuccess {Object[]} user
* @apiSuccess {String} user._id The internal id of the user * @apiSuccess {String} user._id The internal id of the user
* @apiSuccess {String} user.uid The public UID of the user * @apiSuccess {String} user.uid The public UID of the user
* @apiSuccess {String} user.username The username * @apiSuccess {String} user.username The username
* @apiSuccess {String} user.name The real name * @apiSuccess {String} user.name The real name
* @apiSuccess {Date} user.birthday The birthday * @apiSuccess {Date} user.birthday The birthday
* @apiSuccess {Number} user.gender 0 = none, 1 = male, 2 = female, 3 = other * @apiSuccess {Number} user.gender 0 = none, 1 = male, 2 = female, 3 = other
* @apiSuccess {Boolean} user.admin Is admin or not * @apiSuccess {Boolean} user.admin Is admin or not
*/ */
.get( .get(
promiseMiddleware(async (req, res) => { promiseMiddleware(async (req, res) => {
let users = await User.find({}); let users = await User.find({});
users.forEach( users.forEach(
(e) => delete e.password && delete e.salt && delete e.encryption_key (e) => delete e.password && delete e.salt && delete e.encryption_key
); );
res.json(users); res.json(users);
}) })
) )
/** /**
* @api {delete} /admin/user * @api {delete} /admin/user
* @apiName AdminDeleteUser * @apiName AdminDeleteUser
* *
* @apiParam {String} id The User ID * @apiParam {String} id The User ID
* *
* @apiGroup admin_user * @apiGroup admin_user
* @apiPermission admin * @apiPermission admin
* *
* @apiSuccess {Boolean} success * @apiSuccess {Boolean} success
*/ */
.delete( .delete(
promiseMiddleware(async (req, res) => { promiseMiddleware(async (req, res) => {
let { id } = req.query as { [key: string]: string }; let { id } = req.query as { [key: string]: string };
let user = await User.findById(id); let user = await User.findById(id);
await Promise.all([ await Promise.all([
user.mails.map((mail) => Mail.delete(mail)), user.mails.map((mail) => Mail.delete(mail)),
[ [
RefreshToken.deleteFilter({ user: user._id }), RefreshToken.deleteFilter({ user: user._id }),
LoginToken.deleteFilter({ user: user._id }), LoginToken.deleteFilter({ user: user._id }),
], ],
]); ]);
await User.delete(user); await User.delete(user);
res.json({ success: true }); res.json({ success: true });
}) })
) )
/** /**
* @api {put} /admin/user * @api {put} /admin/user
* @apiName AdminChangeUser * @apiName AdminChangeUser
* *
* @apiParam {String} id The User ID * @apiParam {String} id The User ID
* *
* @apiGroup admin_user * @apiGroup admin_user
* @apiPermission admin * @apiPermission admin
* *
* @apiSuccess {Boolean} success * @apiSuccess {Boolean} success
* *
* @apiDescription Flipps the user role: * @apiDescription Flipps the user role:
* admin -> user * admin -> user
* user -> admin * user -> admin
*/ */
.put( .put(
promiseMiddleware(async (req, res) => { promiseMiddleware(async (req, res) => {
let { id } = req.query as { [key: string]: string }; let { id } = req.query as { [key: string]: string };
let user = await User.findById(id); let user = await User.findById(id);
user.admin = !user.admin; user.admin = !user.admin;
await User.save(user); await User.save(user);
res.json({ success: true }); res.json({ success: true });
}) })
); );
export default UserRoute; export default UserRoute;

View File

@ -1,110 +1,110 @@
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import Stacker from "../middlewares/stacker"; import Stacker from "../middlewares/stacker.js";
import { import {
GetClientAuthMiddleware, GetClientAuthMiddleware,
GetClientApiAuthMiddleware, GetClientApiAuthMiddleware,
} from "../middlewares/client"; } from "../middlewares/client.js";
import { GetUserMiddleware } from "../middlewares/user"; import { GetUserMiddleware } from "../middlewares/user.js";
import { createJWT } from "../../keys"; import { createJWT } from "../../keys.js";
import Client from "../../models/client"; import Client from "../../models/client.js";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import config from "../../config"; import config from "../../config.js";
import Mail from "../../models/mail"; import Mail from "../../models/mail.js";
const ClientRouter = Router(); const ClientRouter = Router();
/** /**
* @api {get} /client/user * @api {get} /client/user
* *
* @apiDescription Can be used for simple authentication of user. It will redirect the user to the redirect URI with a very short lived jwt. * @apiDescription Can be used for simple authentication of user. It will redirect the user to the redirect URI with a very short lived jwt.
* *
* @apiParam {String} redirect_uri URL to redirect to on success * @apiParam {String} redirect_uri URL to redirect to on success
* @apiParam {String} state A optional state, that will be included in the JWT and redirect_uri as parameter * @apiParam {String} state A optional state, that will be included in the JWT and redirect_uri as parameter
* *
* @apiName ClientUser * @apiName ClientUser
* @apiGroup client * @apiGroup client
* *
* @apiPermission user_client Requires ClientID and Authenticated User * @apiPermission user_client Requires ClientID and Authenticated User
*/ */
ClientRouter.get( ClientRouter.get(
"/user", "/user",
Stacker( Stacker(
GetClientAuthMiddleware(false), GetClientAuthMiddleware(false),
GetUserMiddleware(false, false), GetUserMiddleware(false, false),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
let { redirect_uri, state } = req.query; let { redirect_uri, state } = req.query;
if (redirect_uri !== req.client.redirect_url) if (redirect_uri !== req.client.redirect_url)
throw new RequestError( throw new RequestError(
"Invalid redirect URI", "Invalid redirect URI",
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
let jwt = await createJWT( let jwt = await createJWT(
{ {
client: req.client.client_id, client: req.client.client_id,
uid: req.user.uid, uid: req.user.uid,
username: req.user.username, username: req.user.username,
state: state, state: state,
}, },
{ {
expiresIn: 30, expiresIn: 30,
issuer: config.core.url, issuer: config.core.url,
algorithm: "RS256", algorithm: "RS256",
subject: req.user.uid, subject: req.user.uid,
audience: req.client.client_id, audience: req.client.client_id,
} }
); //after 30 seconds this token is invalid ); //after 30 seconds this token is invalid
res.redirect( res.redirect(
redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : "") redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : "")
); );
} }
) )
); );
ClientRouter.get( ClientRouter.get(
"/account", "/account",
Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => { Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => {
let mails = await Promise.all( let mails = await Promise.all(
req.user.mails.map((id) => Mail.findById(id)) req.user.mails.map((id) => Mail.findById(id))
); );
let mail = mails.find((e) => e.primary) || mails[0]; let mail = mails.find((e) => e.primary) || mails[0];
res.json({ res.json({
user: { user: {
username: req.user.username, username: req.user.username,
name: req.user.name, name: req.user.name,
email: mail, email: mail,
}, },
}); });
}) })
); );
/** /**
* @api {get} /client/featured * @api {get} /client/featured
* *
* @apiDescription Get a list of clients, that want to be featured on the home page * @apiDescription Get a list of clients, that want to be featured on the home page
* *
* @apiName GetFeaturedClients * @apiName GetFeaturedClients
* @apiGroup client * @apiGroup client
*/ */
ClientRouter.get( ClientRouter.get(
"/featured", "/featured",
Stacker(async (req: Request, res) => { Stacker(async (req: Request, res) => {
let clients = await Client.find({ let clients = await Client.find({
featured: true, featured: true,
}); });
res.json({ res.json({
clients: clients.map(({ name, logo, website, description }) => ({ clients: clients.map(({ name, logo, website, description }) => ({
name, name,
logo, logo,
website, website,
description, description,
})), })),
}); });
}) })
); );
export default ClientRouter; export default ClientRouter;

View File

@ -1,14 +1,14 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import Stacker from "../middlewares/stacker"; import Stacker from "../middlewares/stacker.js";
import { import {
ClientAuthMiddleware, ClientAuthMiddleware,
GetClientAuthMiddleware, GetClientAuthMiddleware,
} from "../middlewares/client"; } from "../middlewares/client.js";
import Permission from "../../models/permissions"; import Permission from "../../models/permissions.js";
import User from "../../models/user"; import User from "../../models/user.js";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import Grant from "../../models/grants"; import Grant from "../../models/grants.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
export const GetPermissions = Stacker( export const GetPermissions = Stacker(

View File

@ -1,12 +1,12 @@
import * as express from "express"; import * as express from "express";
import AdminRoute from "./admin"; import AdminRoute from "./admin/index.js";
import UserRoute from "./user"; import UserRoute from "./user/index.js";
import InternalRoute from "./internal"; import InternalRoute from "./internal/index.js";
import ClientRouter from "./client"; import ClientRouter from "./client/index.js";
import cors from "cors"; import cors from "cors";
import OAuthRoute from "./oauth"; import OAuthRoute from "./oauth/index.js";
import config from "../config"; import config from "../config.js";
import JRPCEndpoint from "./jrpc"; import JRPCEndpoint from "./jrpc/index.js";
const ApiRouter: express.IRouter = express.Router(); const ApiRouter: express.IRouter = express.Router();
ApiRouter.use("/admin", AdminRoute); ApiRouter.use("/admin", AdminRoute);
@ -41,10 +41,10 @@ ApiRouter.post("/jrpc", JRPCEndpoint);
ApiRouter.use("/", ClientRouter); ApiRouter.use("/", ClientRouter);
ApiRouter.get("/config.json", (req, res) => { ApiRouter.get("/config.json", (req, res) => {
return res.json({ return res.json({
name: config.core.name, name: config.core.name,
url: config.core.url, url: config.core.url,
}); });
}); });
export default ApiRouter; export default ApiRouter;

View File

@ -1,30 +1,30 @@
import { Router } from "express"; import { Router } from "express";
import { OAuthInternalApp } from "./oauth"; import { OAuthInternalApp } from "./oauth.js";
import PasswordAuth from "./password"; import PasswordAuth from "./password.js";
const InternalRoute: Router = Router(); const InternalRoute: Router = Router();
/** /**
* @api {get} /internal/oauth * @api {get} /internal/oauth
* @apiName ClientInteralOAuth * @apiName ClientInteralOAuth
* *
* @apiGroup client_internal * @apiGroup client_internal
* @apiPermission client_internal Only ClientID * @apiPermission client_internal Only ClientID
* *
* @apiParam {String} redirect_uri Redirect URI called after success * @apiParam {String} redirect_uri Redirect URI called after success
* @apiParam {String} state State will be set in RedirectURI for the client to check * @apiParam {String} state State will be set in RedirectURI for the client to check
*/ */
InternalRoute.get("/oauth", OAuthInternalApp); InternalRoute.get("/oauth", OAuthInternalApp);
/** /**
* @api {post} /internal/password * @api {post} /internal/password
* @apiName ClientInteralPassword * @apiName ClientInteralPassword
* *
* @apiGroup client_internal * @apiGroup client_internal
* @apiPermission client_internal Requires ClientID and Secret * @apiPermission client_internal Requires ClientID and Secret
* *
* @apiParam {String} username Username (either username or UID) * @apiParam {String} username Username (either username or UID)
* @apiParam {String} uid User ID (either username or UID) * @apiParam {String} uid User ID (either username or UID)
* @apiParam {String} password Hashed and Salted according to specification * @apiParam {String} password Hashed and Salted according to specification
*/ */
InternalRoute.post("/password", PasswordAuth); InternalRoute.post("/password", PasswordAuth);
export default InternalRoute; export default InternalRoute;

View File

@ -1,9 +1,9 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import Stacker from "../middlewares/stacker"; import Stacker from "../middlewares/stacker.js";
import { GetClientAuthMiddleware } from "../middlewares/client"; import { GetClientAuthMiddleware } from "../middlewares/client.js";
import { UserMiddleware } from "../middlewares/user"; import { UserMiddleware } from "../middlewares/user.js";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import ClientCode from "../../models/client_code"; import ClientCode from "../../models/client_code.js";
import moment = require("moment"); import moment = require("moment");
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
export const OAuthInternalApp = Stacker( export const OAuthInternalApp = Stacker(

View File

@ -1,35 +1,35 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { GetClientAuthMiddleware } from "../middlewares/client"; import { GetClientAuthMiddleware } from "../middlewares/client.js";
import Stacker from "../middlewares/stacker"; import Stacker from "../middlewares/stacker.js";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import User from "../../models/user"; import User from "../../models/user.js";
const PasswordAuth = Stacker( const PasswordAuth = Stacker(
GetClientAuthMiddleware(true, true), GetClientAuthMiddleware(true, true),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
let { let {
username, username,
password, password,
uid, uid,
}: { username: string; password: string; uid: string } = req.body; }: { username: string; password: string; uid: string } = req.body;
let query: any = { password: password }; let query: any = { password: password };
if (username) { if (username) {
query.username = username.toLowerCase(); query.username = username.toLowerCase();
} else if (uid) { } else if (uid) {
query.uid = uid; query.uid = uid;
} else { } else {
throw new RequestError( throw new RequestError(
req.__("No username or uid set"), req.__("No username or uid set"),
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
} }
let user = await User.findOne(query); let user = await User.findOne(query);
if (!user) { if (!user) {
res.json({ error: req.__("Password or username wrong") }); res.json({ error: req.__("Password or username wrong") });
} else { } else {
res.json({ success: true, uid: user.uid }); res.json({ success: true, uid: user.uid });
} }
} }
); );
export default PasswordAuth; export default PasswordAuth;

View File

@ -1,13 +1,13 @@
import { Format } from "@hibas123/logging"; import { Format } from "@hibas123/logging";
import Logging from "@hibas123/nodelogging"; import Logging from "@hibas123/nodelogging";
import { Server, } from "@hibas123/openauth-internalapi"; import { Server, } from "@hibas123/openauth-internalapi";
import { RequestObject, ResponseObject } from "@hibas123/openauth-internalapi/lib/service_base"; import { RequestObject, ResponseObject } from "@hibas123/openauth-internalapi/lib/service_base.js";
import { Request, Response } from "express"; import { Request, Response } from "express";
import Stacker from "../middlewares/stacker"; import Stacker from "../middlewares/stacker.js";
import AccountService from "./services/account"; import AccountService from "./services/account.js";
import LoginService from "./services/login"; import LoginService from "./services/login.js";
import SecurityService from "./services/security"; import SecurityService from "./services/security.js";
import TFAService from "./services/twofactor"; import TFAService from "./services/twofactor.js";
export type SessionContext = Request; export type SessionContext = Request;

View File

@ -1,8 +1,8 @@
import { Profile, ContactInfo, Gender, Server, UserRegisterInfo } from "@hibas123/openauth-internalapi"; import { Profile, ContactInfo, Gender, Server, UserRegisterInfo } from "@hibas123/openauth-internalapi";
import type { SessionContext } from "../index"; import type { SessionContext } from "../index.js";
import Mail from "../../../models/mail"; import Mail from "../../../models/mail.js";
import User from "../../../models/user"; import User from "../../../models/user.js";
import { RequireLogin } from "../../../helper/login"; import { RequireLogin } from "../../../helper/login.js";
export default class AccountService extends Server.AccountService<SessionContext> { export default class AccountService extends Server.AccountService<SessionContext> {
Register(regcode: string, info: UserRegisterInfo, ctx: SessionContext): Promise<void> { Register(regcode: string, info: UserRegisterInfo, ctx: SessionContext): Promise<void> {

View File

@ -1,13 +1,13 @@
import { Server, LoginState, TFAOption, TFAType } from "@hibas123/openauth-internalapi"; import { Server, LoginState, TFAOption, TFAType } from "@hibas123/openauth-internalapi";
import type { SessionContext } from "../index"; import type { SessionContext } from "../index.js";
import Logging from "@hibas123/nodelogging"; import Logging from "@hibas123/nodelogging";
import User, { IUser } from "../../../models/user"; import User, { IUser } from "../../../models/user.js";
import moment from "moment"; import moment from "moment";
import crypto from "node:crypto"; import crypto from "node:crypto";
import TwoFactor, { ITwoFactor, IWebAuthn } from "../../../models/twofactor"; import TwoFactor, { ITwoFactor, IWebAuthn } from "../../../models/twofactor.js";
import speakeasy from "speakeasy"; import speakeasy from "speakeasy";
import { generateAuthenticationOptions, verifyAuthenticationResponse } from "@simplewebauthn/server"; import { generateAuthenticationOptions, verifyAuthenticationResponse } from "@simplewebauthn/server";
import config from "../../../config"; import config from "../../../config.js";
//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. //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.
@ -212,13 +212,12 @@ export default class LoginService extends Server.LoginService<SessionContext> {
const rpID = new URL(config.core.url).hostname; const rpID = new URL(config.core.url).hostname;
let options = generateAuthenticationOptions({ let options = await generateAuthenticationOptions({
timeout: 60000, timeout: 60000,
userVerification: "discouraged", userVerification: "discouraged",
rpID, rpID,
allowCredentials: [{ allowCredentials: [{
id: tfa.data.device.credentialID.buffer, id: typeof tfa.data.device.credentialID === "string" ? tfa.data.device.credentialID : Buffer.from(tfa.data.device.credentialID.buffer).toString("base64url"),
type: "public-key",
transports: tfa.data.device.transports transports: tfa.data.device.transports
}] }]
}) })
@ -241,10 +240,10 @@ export default class LoginService extends Server.LoginService<SessionContext> {
let verification = await verifyAuthenticationResponse({ let verification = await verifyAuthenticationResponse({
response: JSON.parse(response), response: JSON.parse(response),
authenticator: { credential: {
id: typeof tfa.data.device.credentialID === "string" ? tfa.data.device.credentialID : Buffer.from(tfa.data.device.credentialID.buffer).toString("base64url"),
publicKey: Buffer.from(tfa.data.device.credentialPublicKey.buffer),
counter: tfa.data.device.counter, counter: tfa.data.device.counter,
credentialID: tfa.data.device.credentialID.buffer,
credentialPublicKey: tfa.data.device.credentialPublicKey.buffer,
transports: tfa.data.device.transports transports: tfa.data.device.transports
}, },
expectedChallenge: ctx.session.login_state.webauthn_challenge, expectedChallenge: ctx.session.login_state.webauthn_challenge,

View File

@ -1,9 +1,9 @@
import { Server, Session } from "@hibas123/openauth-internalapi"; import { Server, Session } from "@hibas123/openauth-internalapi";
import type { SessionContext } from "../index"; import type { SessionContext } from "../index.js";
import Logging from "@hibas123/nodelogging"; import Logging from "@hibas123/nodelogging";
import { RequireLogin } from "../../../helper/login"; import { RequireLogin } from "../../../helper/login.js";
import crypto from "node:crypto"; import crypto from "node:crypto";
import User from "../../../models/user"; import User from "../../../models/user.js";
export default class SecurityService extends Server.SecurityService<SessionContext> { export default class SecurityService extends Server.SecurityService<SessionContext> {
@RequireLogin() @RequireLogin()

View File

@ -1,15 +1,15 @@
import { TFANewTOTP, Server, TFAOption, UserRegisterInfo, TFAWebAuthRegister } from "@hibas123/openauth-internalapi"; import { TFANewTOTP, Server, TFAOption, UserRegisterInfo, TFAWebAuthRegister } from "@hibas123/openauth-internalapi";
import type { SessionContext } from "../index"; import type { SessionContext } from "../index.js";
import TwoFactorModel, { ITOTP, IWebAuthn, TFATypes } from "../../../models/twofactor"; import TwoFactorModel, { ITOTP, IWebAuthn, TFATypes } from "../../../models/twofactor.js";
import moment = require("moment"); import moment = require("moment");
import * as speakeasy from "speakeasy"; import * as speakeasy from "speakeasy";
import * as qrcode from "qrcode"; import * as qrcode from "qrcode";
import config from "../../../config"; import config from "../../../config.js";
import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server'; import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server';
import type { RegistrationResponseJSON } from '@simplewebauthn/typescript-types'; // import type { RegistrationResponseJSON } from '@simplewebauthn/typescript-types';
import Logging from "@hibas123/nodelogging"; import Logging from "@hibas123/nodelogging";
import { Binary } from "mongodb"; import { Binary } from "mongodb";
import { RequireLogin } from "../../../helper/login"; import { RequireLogin } from "../../../helper/login.js";
export default class TFAService extends Server.TFAService<SessionContext> { export default class TFAService extends Server.TFAService<SessionContext> {
@ -111,10 +111,10 @@ export default class TFAService extends Server.TFAService<SessionContext> {
// TODO: Get already registered options // TODO: Get already registered options
const rpID = new URL(config.core.url).hostname; const rpID = new URL(config.core.url).hostname;
const options = generateRegistrationOptions({ const options = await generateRegistrationOptions({
rpName: config.core.name, rpName: config.core.name,
rpID, rpID,
userID: ctx.user.uid, userID: Buffer.from(ctx.user.uid, "utf-8"),
userName: ctx.user.username, userName: ctx.user.username,
attestationType: 'direct', attestationType: 'direct',
userDisplayName: ctx.user.name, userDisplayName: ctx.user.name,
@ -156,7 +156,7 @@ export default class TFAService extends Server.TFAService<SessionContext> {
const rpID = new URL(config.core.url).hostname; const rpID = new URL(config.core.url).hostname;
const response = JSON.parse(registration) as RegistrationResponseJSON; const response = JSON.parse(registration); // as RegistrationResponseJSON;
let verification = await verifyRegistrationResponse({ let verification = await verifyRegistrationResponse({
response, response,
@ -167,7 +167,7 @@ export default class TFAService extends Server.TFAService<SessionContext> {
}); });
if (verification.verified) { if (verification.verified) {
const { credentialPublicKey, credentialID, counter } = verification.registrationInfo; const { credential, } = verification.registrationInfo;
//TODO: Check if already registered! //TODO: Check if already registered!
// TwoFactorModel.find({ // TwoFactorModel.find({
@ -177,10 +177,11 @@ export default class TFAService extends Server.TFAService<SessionContext> {
twofactor.data = { twofactor.data = {
device: { device: {
credentialPublicKey: new Binary(credentialPublicKey), counter: credential.counter,
credentialID: new Binary(credentialID), credentialPublicKey: new Binary(credential.publicKey),
counter: verification.registrationInfo.counter, credentialID: credential.id,
transports: response.response.transports as any[] // counter: verification.registrationInfo.counter,
transports: response.response.transports as any[],
} }
} }

View File

@ -1,123 +1,122 @@
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import Client from "../../models/client"; import Client from "../../models/client.js";
import { validateJWT } from "../../keys"; import { validateJWT } from "../../keys.js";
import User from "../../models/user"; import User from "../../models/user.js";
import Mail from "../../models/mail"; import { OAuthJWT } from "../../helper/jwt.js";
import { OAuthJWT } from "../../helper/jwt"; import Logging from "@hibas123/nodelogging";
import Logging from "@hibas123/nodelogging";
export function GetClientAuthMiddleware(
export function GetClientAuthMiddleware( checksecret = true,
checksecret = true, internal = false,
internal = false, checksecret_if_available = false
checksecret_if_available = false ) {
) { return async (req: Request, res: Response, next: NextFunction) => {
return async (req: Request, res: Response, next: NextFunction) => { try {
try { let client_id = req.query.client_id || req.body.client_id;
let client_id = req.query.client_id || req.body.client_id; let client_secret = req.query.client_secret || req.body.client_secret;
let client_secret = req.query.client_secret || req.body.client_secret;
if (!client_id && !client_secret && req.headers.authorization) {
if (!client_id && !client_secret && req.headers.authorization) { let header = req.headers.authorization;
let header = req.headers.authorization; let [type, val] = header.split(" ");
let [type, val] = header.split(" "); if (val) {
if (val) { let str = Buffer.from(val, "base64").toString("utf-8");
let str = Buffer.from(val, "base64").toString("utf-8"); let [id, secret] = str.split(":");
let [id, secret] = str.split(":"); client_id = id;
client_id = id; client_secret = secret;
client_secret = secret; }
} }
}
if (!client_id || (!client_secret && checksecret)) {
if (!client_id || (!client_secret && checksecret)) { throw new RequestError(
throw new RequestError( "No client credentials",
"No client credentials", HttpStatusCode.BAD_REQUEST
HttpStatusCode.BAD_REQUEST );
); }
} let w = { client_id: client_id, client_secret: client_secret };
let w = { client_id: client_id, client_secret: client_secret }; if (!checksecret && !(checksecret_if_available && client_secret))
if (!checksecret && !(checksecret_if_available && client_secret)) delete w.client_secret;
delete w.client_secret;
let client = await Client.findOne(w);
let client = await Client.findOne(w);
if (!client) {
if (!client) { throw new RequestError(
throw new RequestError( "Invalid client_id" + (checksecret ? "or client_secret" : ""),
"Invalid client_id" + (checksecret ? "or client_secret" : ""), HttpStatusCode.BAD_REQUEST
HttpStatusCode.BAD_REQUEST );
); }
}
if (internal && !client.internal) {
if (internal && !client.internal) { throw new RequestError(
throw new RequestError( req.__("Client has no permission for access"),
req.__("Client has no permission for access"), HttpStatusCode.FORBIDDEN
HttpStatusCode.FORBIDDEN );
); }
} req.client = client;
req.client = client; next();
next(); } catch (e) {
} catch (e) { if (next) next(e);
if (next) next(e); else throw e;
else throw e; }
} };
}; }
}
export const ClientAuthMiddleware = GetClientAuthMiddleware();
export const ClientAuthMiddleware = GetClientAuthMiddleware();
export function GetClientApiAuthMiddleware(permissions?: string[]) {
export function GetClientApiAuthMiddleware(permissions?: string[]) { return async (req: Request, res: Response, next: NextFunction) => {
return async (req: Request, res: Response, next: NextFunction) => { try {
try { const invalid_err = new RequestError(
const invalid_err = new RequestError( req.__("Unauthorized"),
req.__("Unauthorized"), HttpStatusCode.UNAUTHORIZED
HttpStatusCode.UNAUTHORIZED );
); let token =
let token = (req.query.access_token as string) ||
(req.query.access_token as string) || (req.headers.authorization as string);
(req.headers.authorization as string); if (!token) {
if (!token) { Logging.debug("No token found. Searched in query (access_token) and header (authorization)");
Logging.debug("No token found. Searched in query (access_token) and header (authorization)"); throw invalid_err;
throw invalid_err; }
}
if (token.toLowerCase().startsWith("bearer "))
if (token.toLowerCase().startsWith("bearer ")) token = token.substring(7);
token = token.substring(7);
let data: OAuthJWT;
let data: OAuthJWT; try {
try { data = await validateJWT(token);
data = await validateJWT(token); } catch (err) {
} catch (err) { Logging.debug("Invalid JWT", err.message);
Logging.debug("Invalid JWT", err.message); throw invalid_err;
throw invalid_err; }
}
let user = await User.findOne({ uid: data.user });
let user = await User.findOne({ uid: data.user });
if (!user) {
if (!user) { Logging.debug("User not found");
Logging.debug("User not found"); throw invalid_err;
throw invalid_err; }
}
let client = await Client.findOne({ client_id: data.application });
let client = await Client.findOne({ client_id: data.application }); if (!client) {
if (!client) { Logging.debug("Client not found");
Logging.debug("Client not found"); throw invalid_err;
throw invalid_err; }
}
if (
if ( permissions &&
permissions && (!data.permissions ||
(!data.permissions || !permissions.every((e) => data.permissions.indexOf(e) >= 0))
!permissions.every((e) => data.permissions.indexOf(e) >= 0)) ) {
) { Logging.debug("Invalid permissions");
Logging.debug("Invalid permissions"); throw invalid_err;
throw invalid_err; }
}
req.user = user;
req.user = user; req.client = client;
req.client = client; next();
next(); } catch (e) {
} catch (e) { if (next) next(e);
if (next) next(e); else throw e;
else throw e; }
} };
}; }
}

View File

@ -1,28 +1,28 @@
import { Request, Response, NextFunction, RequestHandler } from "express"; import { Request, Response, NextFunction, RequestHandler } from "express";
import promiseMiddleware from "../../helper/promiseMiddleware"; import promiseMiddleware from "../../helper/promiseMiddleware.js";
type RH = (req: Request, res: Response, next?: NextFunction) => any; type RH = (req: Request, res: Response, next?: NextFunction) => any;
function call(handler: RH, req: Request, res: Response) { function call(handler: RH, req: Request, res: Response) {
return new Promise<void>((yes, no) => { return new Promise<void>((yes, no) => {
let p = handler(req, res, (err) => { let p = handler(req, res, (err) => {
if (err) no(err); if (err) no(err);
else yes(); else yes();
}); });
if (p && p.catch) p.catch((err) => no(err)); if (p && p.catch) p.catch((err) => no(err));
}); });
} }
const Stacker = (...handler: RH[]) => { const Stacker = (...handler: RH[]) => {
return promiseMiddleware( return promiseMiddleware(
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
let hc = handler.concat(); let hc = handler.concat();
while (hc.length > 0) { while (hc.length > 0) {
let h = hc.shift(); let h = hc.shift();
await call(h, req, res); await call(h, req, res);
} }
next(); next();
} }
); );
}; };
export default Stacker; export default Stacker;

View File

@ -1,8 +1,8 @@
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import Logging from "@hibas123/nodelogging"; import Logging from "@hibas123/nodelogging";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import promiseMiddleware from "../../helper/promiseMiddleware"; import promiseMiddleware from "../../helper/promiseMiddleware.js";
import { requireLoginState } from "../../helper/login"; import { requireLoginState } from "../../helper/login.js";
class Invalid extends Error { } class Invalid extends Error { }

View File

@ -1,142 +1,141 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import Logging from "@hibas123/nodelogging"; import Logging from "@hibas123/nodelogging";
import { import {
isString, types
isDate, } from "util";
} from "util"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
export enum Types {
export enum Types { STRING,
STRING, NUMBER,
NUMBER, BOOLEAN,
BOOLEAN, EMAIL,
EMAIL, OBJECT,
OBJECT, DATE,
DATE, ARRAY,
ARRAY, ENUM,
ENUM, }
}
function isEmail(value: any): boolean {
function isEmail(value: any): boolean { return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test( value
value );
); }
}
export interface CheckObject {
export interface CheckObject { type: Types;
type: Types; query?: boolean;
query?: boolean; optional?: boolean;
optional?: boolean;
/**
/** * Only when Type.ENUM
* Only when Type.ENUM *
* * values to check before
* values to check before */
*/ values?: string[];
values?: string[];
/**
/** * Only when Type.STRING
* Only when Type.STRING */
*/ notempty?: boolean; // Only STRING
notempty?: boolean; // Only STRING }
}
export interface Checks {
export interface Checks { [index: string]: CheckObject; // | Types
[index: string]: CheckObject; // | Types }
}
// req: Request, res: Response, next: NextFunction
// req: Request, res: Response, next: NextFunction export default function (fields: Checks, noadditional = false) {
export default function (fields: Checks, noadditional = false) { return (req: Request, res: Response, next: NextFunction) => {
return (req: Request, res: Response, next: NextFunction) => { let errors: { message: string; field: string }[] = [];
let errors: { message: string; field: string }[] = [];
function check(data: any, field_name: string, field: CheckObject) {
function check(data: any, field_name: string, field: CheckObject) { if (data !== undefined && data !== null) {
if (data !== undefined && data !== null) { switch (field.type) {
switch (field.type) { case Types.STRING:
case Types.STRING: if (typeof data === "string") {
if (isString(data)) { if (!field.notempty) return;
if (!field.notempty) return; if (data !== "") return;
if (data !== "") return; }
} break;
break; case Types.NUMBER:
case Types.NUMBER: if (typeof data == "number") return;
if (typeof data == "number") return; break;
break; case Types.EMAIL:
case Types.EMAIL: if (isEmail(data)) return;
if (isEmail(data)) return; break;
break; case Types.BOOLEAN:
case Types.BOOLEAN: if (typeof data == "boolean") return;
if (typeof data == "boolean") return; break;
break; case Types.OBJECT:
case Types.OBJECT: if (typeof data == "object") return;
if (typeof data == "object") return; break;
break; case Types.ARRAY:
case Types.ARRAY: if (Array.isArray(data)) return;
if (Array.isArray(data)) return; break;
break; case Types.DATE:
case Types.DATE: if (types.isDate(data)) return;
if (isDate(data)) return; break;
break; case Types.ENUM:
case Types.ENUM: if (typeof data == "string") {
if (typeof data == "string") { if (field.values.indexOf(data) >= 0) return;
if (field.values.indexOf(data) >= 0) return; }
} break;
break; default:
default: Logging.error(
Logging.error( `Invalid type to check: ${field.type} ${Types[field.type]}`
`Invalid type to check: ${field.type} ${Types[field.type]}` );
); }
} errors.push({
errors.push({ message: res.__(
message: res.__( "Field {{field}} has wrong type. It should be from type {{type}}",
"Field {{field}} has wrong type. It should be from type {{type}}", { field: field_name, type: Types[field.type].toLowerCase() }
{ field: field_name, type: Types[field.type].toLowerCase() } ),
), field: field_name,
field: field_name, });
}); } else {
} else { if (!field.optional)
if (!field.optional) errors.push({
errors.push({ message: res.__("Field {{field}} is not defined", {
message: res.__("Field {{field}} is not defined", { field: field_name,
field: field_name, }),
}), field: field_name,
field: field_name, });
}); }
} }
}
for (let field_name in fields) {
for (let field_name in fields) { let field = fields[field_name];
let field = fields[field_name]; let data = fields[field_name].query
let data = fields[field_name].query ? req.query[field_name]
? req.query[field_name] : req.body[field_name];
: req.body[field_name]; check(data, field_name, field);
check(data, field_name, field); }
}
if (noadditional) {
if (noadditional) { //Checks if the data given has additional parameters
//Checks if the data given has additional parameters let should = Object.keys(fields);
let should = Object.keys(fields); should = should.filter((e) => !fields[e].query); //Query parameters should not exist on body
should = should.filter((e) => !fields[e].query); //Query parameters should not exist on body let has = Object.keys(req.body);
let has = Object.keys(req.body);
has.every((e) => {
has.every((e) => { if (should.indexOf(e) >= 0) {
if (should.indexOf(e) >= 0) { return true;
return true; } else {
} else { errors.push({
errors.push({ message: res.__("Field {{field}} should not be there", {
message: res.__("Field {{field}} should not be there", { field: e,
field: e, }),
}), field: e,
field: e, });
}); return false;
return false; }
} });
}); }
}
if (errors.length > 0) {
if (errors.length > 0) { let err = new RequestError(errors, HttpStatusCode.BAD_REQUEST, true);
let err = new RequestError(errors, HttpStatusCode.BAD_REQUEST, true); next(err);
next(err); } else next();
} else next(); };
}; }
}

View File

@ -1,15 +1,15 @@
import Stacker from "../middlewares/stacker"; import Stacker from "../middlewares/stacker.js";
import { GetUserMiddleware } from "../middlewares/user"; import { GetUserMiddleware } from "../middlewares/user.js";
import { Request, Response } from "express"; import { Request, Response } from "express";
import Client from "../../models/client"; import Client from "../../models/client.js";
import Logging from "@hibas123/nodelogging"; import Logging from "@hibas123/nodelogging";
import Permission, { IPermission } from "../../models/permissions"; import Permission, { IPermission } from "../../models/permissions.js";
import ClientCode from "../../models/client_code"; import ClientCode from "../../models/client_code.js";
import moment = require("moment"); import moment = require("moment");
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
// import { ObjectId } from "bson"; // import { ObjectId } from "bson";
import Grant, { IGrant } from "../../models/grants"; import Grant, { IGrant } from "../../models/grants.js";
import GetAuthPage from "../../views/authorize"; import GetAuthPage from "../../views/authorize.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
// const AuthRoute = Stacker(GetUserMiddleware(true), async (req: Request, res: Response) => { // const AuthRoute = Stacker(GetUserMiddleware(true), async (req: Request, res: Response) => {

View File

@ -1,73 +1,73 @@
import { Router } from "express"; import { Router } from "express";
import GetAuthRoute from "./auth"; import GetAuthRoute from "./auth.js";
import JWTRoute from "./jwt"; import JWTRoute from "./jwt.js";
import Public from "./public"; import Public from "./public.js";
import RefreshTokenRoute from "./refresh"; import RefreshTokenRoute from "./refresh.js";
import ProfileRoute from "./profile"; import ProfileRoute from "./profile.js";
const OAuthRoute: Router = Router(); const OAuthRoute: Router = Router();
/** /**
* @api {post} /oauth/auth * @api {post} /oauth/auth
* @apiName OAuthAuth * @apiName OAuthAuth
* *
* @apiGroup oauth * @apiGroup oauth
* @apiPermission user Special required * @apiPermission user Special required
* *
* @apiParam {String} response_type must be "code" others are not supported * @apiParam {String} response_type must be "code" others are not supported
* @apiParam {String} client_id ClientID * @apiParam {String} client_id ClientID
* @apiParam {String} redirect_uri The URI to redirect with code * @apiParam {String} redirect_uri The URI to redirect with code
* @apiParam {String} scope Scope that contains the requested permissions (comma seperated list of permissions) * @apiParam {String} scope Scope that contains the requested permissions (comma seperated list of permissions)
* @apiParam {String} state State, that will be passed to redirect_uri for client * @apiParam {String} state State, that will be passed to redirect_uri for client
* @apiParam {String} nored Deactivates the Redirect response from server and instead returns the redirect URI in JSON response * @apiParam {String} nored Deactivates the Redirect response from server and instead returns the redirect URI in JSON response
*/ */
OAuthRoute.post("/auth", GetAuthRoute(false)); OAuthRoute.post("/auth", GetAuthRoute(false));
/** /**
* @api {get} /oauth/jwt * @api {get} /oauth/jwt
* @apiName OAuthJwt * @apiName OAuthJwt
* *
* @apiGroup oauth * @apiGroup oauth
* @apiPermission none * @apiPermission none
* *
* @apiParam {String} refreshtoken * @apiParam {String} refreshtoken
* *
* @apiSuccess {String} token The JWT that allowes the application to access the recources granted for refresh token * @apiSuccess {String} token The JWT that allowes the application to access the recources granted for refresh token
*/ */
OAuthRoute.get("/jwt", JWTRoute); OAuthRoute.get("/jwt", JWTRoute);
/** /**
* @api {get} /oauth/public * @api {get} /oauth/public
* @apiName OAuthPublic * @apiName OAuthPublic
* *
* @apiGroup oauth * @apiGroup oauth
* @apiPermission none * @apiPermission none
* *
* @apiSuccess {String} public_key The applications public_key. Used to verify JWT. * @apiSuccess {String} public_key The applications public_key. Used to verify JWT.
*/ */
OAuthRoute.get("/public", Public); OAuthRoute.get("/public", Public);
/** /**
* @api {get} /oauth/refresh * @api {get} /oauth/refresh
* @apiName OAuthRefreshGet * @apiName OAuthRefreshGet
* *
* @apiGroup oauth * @apiGroup oauth
*/ */
OAuthRoute.get("/refresh", RefreshTokenRoute); OAuthRoute.get("/refresh", RefreshTokenRoute);
/** /**
* @api {post} /oauth/refresh * @api {post} /oauth/refresh
* @apiName OAuthRefreshPost * @apiName OAuthRefreshPost
* *
* @apiGroup oauth * @apiGroup oauth
*/ */
OAuthRoute.post("/refresh", RefreshTokenRoute); OAuthRoute.post("/refresh", RefreshTokenRoute);
/** /**
* @api {get} /oauth/profile * @api {get} /oauth/profile
* @apiName OAuthProfile * @apiName OAuthProfile
* *
* @apiGroup oauth * @apiGroup oauth
*/ */
OAuthRoute.get("/profile", ProfileRoute); OAuthRoute.get("/profile", ProfileRoute);
export default OAuthRoute; export default OAuthRoute;

View File

@ -1,43 +1,43 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import promiseMiddleware from "../../helper/promiseMiddleware"; import promiseMiddleware from "../../helper/promiseMiddleware.js";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import RefreshToken from "../../models/refresh_token"; import RefreshToken from "../../models/refresh_token.js";
import User from "../../models/user"; import User from "../../models/user.js";
import Client from "../../models/client"; import Client from "../../models/client.js";
import { getAccessTokenJWT } from "../../helper/jwt"; import { getAccessTokenJWT } from "../../helper/jwt.js";
const JWTRoute = promiseMiddleware(async (req: Request, res: Response) => { const JWTRoute = promiseMiddleware(async (req: Request, res: Response) => {
let { refreshtoken } = req.query as { [key: string]: string }; let { refreshtoken } = req.query as { [key: string]: string };
if (!refreshtoken) if (!refreshtoken)
throw new RequestError( throw new RequestError(
req.__("Refresh token not set"), req.__("Refresh token not set"),
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
let token = await RefreshToken.findOne({ token: refreshtoken }); let token = await RefreshToken.findOne({ token: refreshtoken });
if (!token) if (!token)
throw new RequestError( throw new RequestError(
req.__("Invalid token"), req.__("Invalid token"),
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
let user = await User.findById(token.user); let user = await User.findById(token.user);
if (!user) { if (!user) {
token.valid = false; token.valid = false;
await RefreshToken.save(token); await RefreshToken.save(token);
throw new RequestError( throw new RequestError(
req.__("Invalid token"), req.__("Invalid token"),
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
} }
let client = await Client.findById(token.client); let client = await Client.findById(token.client);
let jwt = await getAccessTokenJWT({ let jwt = await getAccessTokenJWT({
user, user,
permissions: token.permissions, permissions: token.permissions,
client, client,
}); });
res.json({ token: jwt }); res.json({ token: jwt });
}); });
export default JWTRoute; export default JWTRoute;

View File

@ -1,6 +1,6 @@
import Mail from "../../models/mail"; import Mail from "../../models/mail.js";
import { GetClientApiAuthMiddleware } from "../middlewares/client"; import { GetClientApiAuthMiddleware } from "../middlewares/client.js";
import Stacker from "../middlewares/stacker"; import Stacker from "../middlewares/stacker.js";
import { Request, Response } from "express"; import { Request, Response } from "express";
import Logging from "@hibas123/nodelogging"; import Logging from "@hibas123/nodelogging";

View File

@ -1,6 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { public_key } from "../../keys"; import { public_key } from "../../keys.js";
export default function Public(req: Request, res: Response) { export default function Public(req: Request, res: Response) {
res.json({ public_key: public_key }); res.json({ public_key: public_key });
} }

View File

@ -1,122 +1,122 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import RequestError, { HttpStatusCode } from "../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import User from "../../models/user"; import User from "../../models/user.js";
import Client from "../../models/client"; import Client from "../../models/client.js";
import { import {
getAccessTokenJWT, getAccessTokenJWT,
getIDToken, getIDToken,
AccessTokenJWTExp, AccessTokenJWTExp,
} from "../../helper/jwt"; } from "../../helper/jwt.js";
import Stacker from "../middlewares/stacker"; import Stacker from "../middlewares/stacker.js";
import { GetClientAuthMiddleware } from "../middlewares/client"; import { GetClientAuthMiddleware } from "../middlewares/client.js";
import ClientCode from "../../models/client_code"; import ClientCode from "../../models/client_code.js";
import Mail from "../../models/mail"; import Mail from "../../models/mail.js";
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
import moment = require("moment"); import moment = require("moment");
// import { JWTExpDur } from "../../keys"; // import { JWTExpDur } from "../../keys";
import RefreshToken from "../../models/refresh_token"; import RefreshToken from "../../models/refresh_token.js";
import { getEncryptionKey } from "../../helper/user_key"; import { getEncryptionKey } from "../../helper/user_key.js";
import { refreshTokenValidTime } from "../../config"; import { refreshTokenValidTime } from "../../config.js";
// TODO: // TODO:
/* /*
For example, the authorization server could employ refresh token For example, the authorization server could employ refresh token
rotation in which a new refresh token is issued with every access rotation in which a new refresh token is issued with every access
token refresh response. The previous refresh token is invalidated but retained by the authorization server. If a refresh token is token refresh response. The previous refresh token is invalidated but retained by the authorization server. If a refresh token is
compromised and subsequently used by both the attacker and the compromised and subsequently used by both the attacker and the
legitimate client, one of them will present an invalidated refresh legitimate client, one of them will present an invalidated refresh
token, which will inform the authorization server of the breach. token, which will inform the authorization server of the breach.
*/ */
const RefreshTokenRoute = Stacker( const RefreshTokenRoute = Stacker(
GetClientAuthMiddleware(false, false, true), GetClientAuthMiddleware(false, false, true),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
let grant_type = req.query.grant_type || req.body.grant_type; let grant_type = req.query.grant_type || req.body.grant_type;
if (!grant_type || grant_type === "authorization_code") { if (!grant_type || grant_type === "authorization_code") {
let code = req.query.code || req.body.code; let code = req.query.code || req.body.code;
let nonce = req.query.nonce || req.body.nonce; let nonce = req.query.nonce || req.body.nonce;
let c = await ClientCode.findOne({ code: code }); let c = await ClientCode.findOne({ code: code });
if (!c || moment(c.validTill).isBefore()) { if (!c || moment(c.validTill).isBefore()) {
throw new RequestError( throw new RequestError(
req.__("Invalid code"), req.__("Invalid code"),
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
} }
let client = await Client.findById(c.client); let client = await Client.findById(c.client);
let user = await User.findById(c.user); let user = await User.findById(c.user);
let mails = await Promise.all(user.mails.map((m) => Mail.findOne(m))); let mails = await Promise.all(user.mails.map((m) => Mail.findOne(m)));
let token = RefreshToken.new({ let token = RefreshToken.new({
user: c.user, user: c.user,
client: c.client, client: c.client,
permissions: c.permissions, permissions: c.permissions,
token: randomBytes(16).toString("hex"), token: randomBytes(16).toString("hex"),
valid: true, valid: true,
validTill: moment().add(refreshTokenValidTime).toDate(), validTill: moment().add(refreshTokenValidTime).toDate(),
}); });
await RefreshToken.save(token); await RefreshToken.save(token);
await ClientCode.delete(c); await ClientCode.delete(c);
let mail = mails.find((e) => e.primary); let mail = mails.find((e) => e.primary);
if (!mail) mail = mails[0]; if (!mail) mail = mails[0];
res.json({ res.json({
refresh_token: token.token, refresh_token: token.token,
token: token.token, token: token.token,
access_token: await getAccessTokenJWT({ access_token: await getAccessTokenJWT({
client: client, client: client,
user: user, user: user,
permissions: c.permissions, permissions: c.permissions,
}), }),
token_type: "bearer", token_type: "bearer",
expires_in: AccessTokenJWTExp.asSeconds(), expires_in: AccessTokenJWTExp.asSeconds(),
profile: { profile: {
uid: user.uid, uid: user.uid,
email: mail ? mail.mail : "", email: mail ? mail.mail : "",
name: user.name, name: user.name,
enc_key: getEncryptionKey(user, client), enc_key: getEncryptionKey(user, client),
}, },
id_token: getIDToken(user, client.client_id, nonce), id_token: getIDToken(user, client.client_id, nonce),
}); });
} else if (grant_type === "refresh_token") { } else if (grant_type === "refresh_token") {
let refresh_token = req.query.refresh_token || req.body.refresh_token; let refresh_token = req.query.refresh_token || req.body.refresh_token;
if (!refresh_token) if (!refresh_token)
throw new RequestError( throw new RequestError(
req.__("refresh_token not set"), req.__("refresh_token not set"),
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
let token = await RefreshToken.findOne({ token: refresh_token }); let token = await RefreshToken.findOne({ token: refresh_token });
if (!token || !token.valid || moment(token.validTill).isBefore()) if (!token || !token.valid || moment(token.validTill).isBefore())
throw new RequestError( throw new RequestError(
req.__("Invalid token"), req.__("Invalid token"),
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
token.validTill = moment().add(refreshTokenValidTime).toDate(); token.validTill = moment().add(refreshTokenValidTime).toDate();
await RefreshToken.save(token); await RefreshToken.save(token);
let user = await User.findById(token.user); let user = await User.findById(token.user);
let client = await Client.findById(token.client); let client = await Client.findById(token.client);
let jwt = await getAccessTokenJWT({ let jwt = await getAccessTokenJWT({
user, user,
client, client,
permissions: token.permissions, permissions: token.permissions,
}); });
res.json({ res.json({
access_token: jwt, access_token: jwt,
expires_in: AccessTokenJWTExp.asSeconds(), expires_in: AccessTokenJWTExp.asSeconds(),
}); });
} else { } else {
throw new RequestError( throw new RequestError(
"invalid grant_type", "invalid grant_type",
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
} }
} }
); );
export default RefreshTokenRoute; export default RefreshTokenRoute;

View File

@ -1,6 +1,6 @@
import { Router } from "express"; import { Router } from "express";
import Register from "./register"; import Register from "./register.js";
import OAuthRoute from "./oauth"; import OAuthRoute from "./oauth/index.js";
const UserRoute: Router = Router(); const UserRoute: Router = Router();

View File

@ -1,21 +1,21 @@
import RequestError, { HttpStatusCode } from "../../../helper/request_error"; import RequestError, { HttpStatusCode } from "../../../helper/request_error.js";
import Client, { IClient } from "../../../models/client"; import Client, { IClient } from "../../../models/client.js";
export async function getClientWithOrigin(client_id: string, origin: string) { export async function getClientWithOrigin(client_id: string, origin: string) {
const client = await Client.findOne({ const client = await Client.findOne({
client_id, client_id,
}); });
const clientNotFoundError = new RequestError( const clientNotFoundError = new RequestError(
"Client not found!", "Client not found!",
HttpStatusCode.BAD_REQUEST HttpStatusCode.BAD_REQUEST
); );
if (!client) throw clientNotFoundError; if (!client) throw clientNotFoundError;
const clientUrl = new URL(client.redirect_url); const clientUrl = new URL(client.redirect_url);
if (clientUrl.hostname !== origin) throw clientNotFoundError; if (clientUrl.hostname !== origin) throw clientNotFoundError;
return client; return client;
} }

View File

@ -1,12 +1,12 @@
import { Router } from "express"; import { Router } from "express";
import { GetJWTByUser } from "./jwt"; import { GetJWTByUser } from "./jwt.js";
import { GetPermissionsForAuthRequest } from "./permissions"; import { GetPermissionsForAuthRequest } from "./permissions.js";
import { GetTokenByUser } from "./refresh_token"; import { GetTokenByUser } from "./refresh_token.js";
const router = Router(); const router = Router();
router.get("/jwt", GetJWTByUser); router.get("/jwt", GetJWTByUser);
router.get("/permissions", GetPermissionsForAuthRequest); router.get("/permissions", GetPermissionsForAuthRequest);
router.get("/refresh_token", GetTokenByUser); router.get("/refresh_token", GetTokenByUser);
export default router; export default router;

View File

@ -1,25 +1,23 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import Stacker from "../../middlewares/stacker"; import Stacker from "../../middlewares/stacker.js";
import { GetUserMiddleware } from "../../middlewares/user"; import { GetUserMiddleware } from "../../middlewares/user.js";
import { URL } from "url";
import Client from "../../../models/client"; import { getAccessTokenJWT } from "../../../helper/jwt.js";
import RequestError, { HttpStatusCode } from "../../../helper/request_error"; import { getClientWithOrigin } from "./_helper.js";
import { getAccessTokenJWT } from "../../../helper/jwt";
import { getClientWithOrigin } from "./_helper"; export const GetJWTByUser = Stacker(
GetUserMiddleware(true, false),
export const GetJWTByUser = Stacker( async (req: Request, res: Response) => {
GetUserMiddleware(true, false), const { client_id, origin } = req.query as { [key: string]: string };
async (req: Request, res: Response) => {
const { client_id, origin } = req.query as { [key: string]: string }; const client = await getClientWithOrigin(client_id, origin);
const client = await getClientWithOrigin(client_id, origin); const jwt = await getAccessTokenJWT({
user: req.user,
const jwt = await getAccessTokenJWT({ client: client,
user: req.user, permissions: [],
client: client, });
permissions: [],
}); res.json({ jwt });
}
res.json({ jwt }); );
}
);

View File

@ -1,15 +1,9 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import Stacker from "../../middlewares/stacker"; import Stacker from "../../middlewares/stacker.js";
import { GetUserMiddleware } from "../../middlewares/user"; import { GetUserMiddleware } from "../../middlewares/user.js";
import { URL } from "url"; import RequestError, { HttpStatusCode } from "../../../helper/request_error.js";
import Client from "../../../models/client"; import { getClientWithOrigin } from "./_helper.js";
import RequestError, { HttpStatusCode } from "../../../helper/request_error"; import Permission from "../../../models/permissions.js";
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( export const GetPermissionsForAuthRequest = Stacker(
GetUserMiddleware(true, false), GetUserMiddleware(true, false),

View File

@ -1,49 +1,47 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import Stacker from "../../middlewares/stacker"; import Stacker from "../../middlewares/stacker.js";
import { GetUserMiddleware } from "../../middlewares/user"; import { GetUserMiddleware } from "../../middlewares/user.js";
import { URL } from "url"; import RequestError, { HttpStatusCode } from "../../../helper/request_error.js";
import Client from "../../../models/client"; import { randomBytes } from "crypto";
import RequestError, { HttpStatusCode } from "../../../helper/request_error"; import moment = require("moment");
import { randomBytes } from "crypto"; import RefreshToken from "../../../models/refresh_token.js";
import moment = require("moment"); import { refreshTokenValidTime } from "../../../config.js";
import RefreshToken from "../../../models/refresh_token"; import { getClientWithOrigin } from "./_helper.js";
import { refreshTokenValidTime } from "../../../config"; import Permission from "../../../models/permissions.js";
import { getClientWithOrigin } from "./_helper";
import Permission from "../../../models/permissions"; export const GetTokenByUser = Stacker(
GetUserMiddleware(true, false),
export const GetTokenByUser = Stacker( async (req: Request, res: Response) => {
GetUserMiddleware(true, false), const { client_id, origin, permissions } = req.query as {
async (req: Request, res: Response) => { [key: string]: string;
const { client_id, origin, permissions } = req.query as { };
[key: string]: string;
}; const client = await getClientWithOrigin(client_id, origin);
const client = await getClientWithOrigin(client_id, origin); const perm = permissions.split(",").filter((e) => !!e);
const perm = permissions.split(",").filter((e) => !!e); const resolved = await Promise.all(
perm.map((p) => Permission.findById(p))
const resolved = await Promise.all( );
perm.map((p) => Permission.findById(p))
); if (resolved.some((e) => e.grant_type !== "user")) {
throw new RequestError(
if (resolved.some((e) => e.grant_type !== "user")) { "Invalid Permission requested",
throw new RequestError( HttpStatusCode.BAD_REQUEST
"Invalid Permission requested", );
HttpStatusCode.BAD_REQUEST }
);
} let token = RefreshToken.new({
user: req.user._id,
let token = RefreshToken.new({ client: client._id,
user: req.user._id, permissions: resolved.map((e) => e._id),
client: client._id, token: randomBytes(16).toString("hex"),
permissions: resolved.map((e) => e._id), valid: true,
token: randomBytes(16).toString("hex"), validTill: moment().add(refreshTokenValidTime).toDate(),
valid: true, });
validTill: moment().add(refreshTokenValidTime).toDate(),
}); await RefreshToken.save(token);
await RefreshToken.save(token); res.json({ token });
}
res.json({ token }); );
}
);

View File

@ -1,155 +1,155 @@
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import Stacker from "../middlewares/stacker"; import Stacker from "../middlewares/stacker.js";
import verify, { Types } from "../middlewares/verify"; import verify, { Types } from "../middlewares/verify.js";
import promiseMiddleware from "../../helper/promiseMiddleware"; import promiseMiddleware from "../../helper/promiseMiddleware.js";
import User, { Gender } from "../../models/user"; import User, { Gender } from "../../models/user.js";
import { HttpStatusCode } from "../../helper/request_error"; import { HttpStatusCode } from "../../helper/request_error.js";
import Mail from "../../models/mail"; import Mail from "../../models/mail.js";
import RegCode from "../../models/regcodes"; import RegCode from "../../models/regcodes.js";
const Register = Stacker( const Register = Stacker(
verify({ verify({
mail: { mail: {
type: Types.EMAIL, type: Types.EMAIL,
notempty: true, notempty: true,
}, },
username: { username: {
type: Types.STRING, type: Types.STRING,
notempty: true, notempty: true,
}, },
password: { password: {
type: Types.STRING, type: Types.STRING,
notempty: true, notempty: true,
}, },
salt: { salt: {
type: Types.STRING, type: Types.STRING,
notempty: true, notempty: true,
}, },
regcode: { regcode: {
type: Types.STRING, type: Types.STRING,
notempty: true, notempty: true,
}, },
gender: { gender: {
type: Types.STRING, type: Types.STRING,
notempty: true, notempty: true,
}, },
name: { name: {
type: Types.STRING, type: Types.STRING,
notempty: true, notempty: true,
}, },
// birthday: { // birthday: {
// type: Types.DATE // type: Types.DATE
// } // }
}), }),
promiseMiddleware(async (req: Request, res: Response) => { promiseMiddleware(async (req: Request, res: Response) => {
let { let {
username, username,
password, password,
salt, salt,
mail, mail,
gender, gender,
name, name,
birthday, birthday,
regcode, regcode,
} = req.body; } = req.body;
let u = await User.findOne({ username: username.toLowerCase() }); let u = await User.findOne({ username: username.toLowerCase() });
if (u) { if (u) {
let err = { let err = {
message: [ message: [
{ {
message: req.__("Username taken"), message: req.__("Username taken"),
field: "username", field: "username",
}, },
], ],
status: HttpStatusCode.BAD_REQUEST, status: HttpStatusCode.BAD_REQUEST,
nolog: true, nolog: true,
}; };
throw err; throw err;
} }
let m = await Mail.findOne({ mail: mail }); let m = await Mail.findOne({ mail: mail });
if (m) { if (m) {
let err = { let err = {
message: [ message: [
{ {
message: req.__("Mail linked with other account"), message: req.__("Mail linked with other account"),
field: "mail", field: "mail",
}, },
], ],
status: HttpStatusCode.BAD_REQUEST, status: HttpStatusCode.BAD_REQUEST,
nolog: true, nolog: true,
}; };
throw err; throw err;
} }
let regc = await RegCode.findOne({ token: regcode }); let regc = await RegCode.findOne({ token: regcode });
if (!regc) { if (!regc) {
let err = { let err = {
message: [ message: [
{ {
message: req.__("Invalid registration code"), message: req.__("Invalid registration code"),
field: "regcode", field: "regcode",
}, },
], ],
status: HttpStatusCode.BAD_REQUEST, status: HttpStatusCode.BAD_REQUEST,
nolog: true, nolog: true,
}; };
throw err; throw err;
} }
if (!regc.valid) { if (!regc.valid) {
let err = { let err = {
message: [ message: [
{ {
message: req.__("Registration code already used"), message: req.__("Registration code already used"),
field: "regcode", field: "regcode",
}, },
], ],
status: HttpStatusCode.BAD_REQUEST, status: HttpStatusCode.BAD_REQUEST,
nolog: true, nolog: true,
}; };
throw err; throw err;
} }
let g = -1; let g = -1;
switch (gender) { switch (gender) {
case "male": case "male":
g = Gender.male; g = Gender.male;
break; break;
case "female": case "female":
g = Gender.female; g = Gender.female;
break; break;
case "other": case "other":
g = Gender.other; g = Gender.other;
break; break;
default: default:
g = Gender.none; g = Gender.none;
break; break;
} }
let user = User.new({ let user = User.new({
username: username.toLowerCase(), username: username.toLowerCase(),
password: password, password: password,
salt: salt, salt: salt,
gender: g, gender: g,
name: name, name: name,
// birthday: birthday, // birthday: birthday,
admin: false, admin: false,
}); });
regc.valid = false; regc.valid = false;
await RegCode.save(regc); await RegCode.save(regc);
let ml = Mail.new({ let ml = Mail.new({
mail: mail, mail: mail,
primary: true, primary: true,
}); });
await Mail.save(ml); await Mail.save(ml);
user.mails.push(ml._id); user.mails.push(ml._id);
await User.save(user); await User.save(user);
res.json({ success: true }); res.json({ success: true });
}) })
); );
export default Register; export default Register;

View File

@ -1,5 +1,5 @@
import SafeMongo from "@hibas123/safe_mongo"; import SafeMongo from "@hibas123/safe_mongo";
import Config from "./config"; import Config from "./config.js";
const host = Config.database.host || "localhost"; const host = Config.database.host || "localhost";

View File

@ -1,5 +1,5 @@
import { IUser } from "./models/user"; import { IUser } from "./models/user.js";
import { IClient } from "./models/client"; import { IClient } from "./models/client.js";
declare module "express" { declare module "express" {
interface Request { interface Request {

View File

@ -1,8 +1,8 @@
import { IUser, Gender } from "../models/user"; import { IUser, Gender } from "../models/user.js";
import { ObjectId } from "bson"; import { ObjectId } from "bson";
import { createJWT } from "../keys"; import { createJWT } from "../keys.js";
import { IClient } from "../models/client"; import { IClient } from "../models/client.js";
import config from "../config"; import config from "../config.js";
import moment = require("moment"); import moment = require("moment");
export interface OAuthJWT { export interface OAuthJWT {

View File

@ -1,4 +1,4 @@
import { SessionContext } from "../api/jrpc"; import { SessionContext } from "../api/jrpc/index.js";
export function requireLoginState(ctx: SessionContext, validated: boolean = true, special: boolean = false): boolean { export function requireLoginState(ctx: SessionContext, validated: boolean = true, special: boolean = false): boolean {
if (!ctx.user) return false; if (!ctx.user) return false;

View File

@ -1,18 +1,18 @@
// import * as crypto from "crypto-js" // import * as crypto from "crypto-js"
import { IUser } from "../models/user"; import { IUser } from "../models/user.js";
import { IClient } from "../models/client"; import { IClient } from "../models/client.js";
import * as crypto from "crypto"; import * as crypto from "crypto";
function sha512(text: string) { function sha512(text: string) {
let hash = crypto.createHash("sha512"); let hash = crypto.createHash("sha512");
hash.update(text); hash.update(text);
return hash.digest("base64"); return hash.digest("base64");
} }
export function getEncryptionKey(user: IUser, client: IClient) { export function getEncryptionKey(user: IUser, client: IClient) {
return sha512( return sha512(
sha512(user.encryption_key) + sha512(user.encryption_key) +
sha512(client._id.toHexString()) + sha512(client._id.toHexString()) +
sha512(client.client_id) sha512(client.client_id)
); );
} }

View File

@ -1,90 +1,90 @@
import Logging from "@hibas123/nodelogging"; import Logging from "@hibas123/nodelogging";
import config from "./config"; import config from "./config.js";
// import NLS from "@hibas123/nodeloggingserver_client"; // import NLS from "@hibas123/nodeloggingserver_client";
// if (config.logging) { // if (config.logging) {
// let s = NLS(Logging, config.logging.server, config.logging.appid, config.logging.token); // let s = NLS(Logging, config.logging.server, config.logging.appid, config.logging.token);
// s.send(`[${new Date().toLocaleTimeString()}] Starting application`); // s.send(`[${new Date().toLocaleTimeString()}] Starting application`);
// } // }
// if (!config.database) { // if (!config.database) {
// Logging.error("No database config set. Terminating.") // Logging.error("No database config set. Terminating.")
// process.exit(); // process.exit();
// } // }
if (!config.web) { if (!config.web) {
Logging.error("No web config set. Terminating."); Logging.error("No web config set. Terminating.");
process.exit(); process.exit();
} }
import * as i18n from "i18n"; import * as i18n from "i18n";
i18n.configure({ i18n.configure({
locales: ["en", "de"], locales: ["en", "de"],
directory: "./locales", directory: "./locales",
}); });
import Web from "./web"; import Web from "./web.js";
import TestData from "./testdata"; import TestData from "./testdata.js";
import DB from "./database"; import DB from "./database.js";
Logging.log("Connecting to Database"); Logging.log("Connecting to Database");
if (config.core.dev) { if (config.core.dev) {
Logging.warning("Running in dev mode! Database will be cleared!"); Logging.warning("Running in dev mode! Database will be cleared!");
} }
DB.connect() DB.connect()
.then(async () => { .then(async () => {
Logging.log("Database connected", config); Logging.log("Database connected", config);
if (config.core.dev) await TestData(); if (config.core.dev) await TestData();
let web = new Web(config.web); let web = new Web(config.web);
web.listen(); web.listen();
let already = new Set(); let already = new Set();
function print(path, layer) { function print(path, layer) {
if (layer.route) { if (layer.route) {
layer.route.stack.forEach( layer.route.stack.forEach(
print.bind(null, path.concat(split(layer.route.path))) print.bind(null, path.concat(split(layer.route.path)))
); );
} else if (layer.name === "router" && layer.handle.stack) { } else if (layer.name === "router" && layer.handle.stack) {
layer.handle.stack.forEach( layer.handle.stack.forEach(
print.bind(null, path.concat(split(layer.regexp))) print.bind(null, path.concat(split(layer.regexp)))
); );
} else if (layer.method) { } else if (layer.method) {
let me: string = layer.method.toUpperCase(); let me: string = layer.method.toUpperCase();
me += " ".repeat(6 - me.length); me += " ".repeat(6 - me.length);
let msg = `${me} /${path let msg = `${me} /${path
.concat(split(layer.regexp)) .concat(split(layer.regexp))
.filter(Boolean) .filter(Boolean)
.join("/")}`; .join("/")}`;
if (!already.has(msg)) { if (!already.has(msg)) {
already.add(msg); already.add(msg);
Logging.log(msg); Logging.log(msg);
} }
} }
} }
function split(thing) { function split(thing) {
if (typeof thing === "string") { if (typeof thing === "string") {
return thing.split("/"); return thing.split("/");
} else if (thing.fast_slash) { } else if (thing.fast_slash) {
return ""; return "";
} else { } else {
var match = thing var match = thing
.toString() .toString()
.replace("\\/?", "") .replace("\\/?", "")
.replace("(?=\\/|$)", "$") .replace("(?=\\/|$)", "$")
.match( .match(
/^\/\^((?:\\[.*+?^${}()|[\]\\\/]|[^.*+?^${}()|[\]\\\/])*)\$\// /^\/\^((?:\\[.*+?^${}()|[\]\\\/]|[^.*+?^${}()|[\]\\\/])*)\$\//
); );
return match return match
? match[1].replace(/\\(.)/g, "$1").split("/") ? match[1].replace(/\\(.)/g, "$1").split("/")
: "<complex:" + thing.toString() + ">"; : "<complex:" + thing.toString() + ">";
} }
} }
// Logging.log("--- Endpoints: ---"); // Logging.log("--- Endpoints: ---");
// web.server._router.stack.forEach(print.bind(null, [])) // web.server._router.stack.forEach(print.bind(null, []))
// Logging.log("--- Endpoints end ---") // Logging.log("--- Endpoints end ---")
}) })
.catch((e) => { .catch((e) => {
Logging.error(e); Logging.error(e);
process.exit(); process.exit();
}); });

View File

@ -14,7 +14,6 @@ export function verify(message: Buffer, signature: Buffer): boolean {
export let public_key: string; export let public_key: string;
import * as jwt from "jsonwebtoken"; import * as jwt from "jsonwebtoken";
import config from "./config";
export function createJWT(payload: any, options: jwt.SignOptions) { export function createJWT(payload: any, options: jwt.SignOptions) {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {

View File

@ -1,5 +1,5 @@
import DB from "../database"; import DB from "../database.js";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; import { ModelDataBase } from "@hibas123/safe_mongo/lib/model.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
import { v4 } from "uuid"; import { v4 } from "uuid";

View File

@ -1,7 +1,6 @@
import DB from "../database"; import DB from "../database.js";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; import { ModelDataBase } from "@hibas123/safe_mongo/lib/model.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
import { v4 } from "uuid";
export interface IClientCode extends ModelDataBase { export interface IClientCode extends ModelDataBase {
user: ObjectId; user: ObjectId;

View File

@ -1,5 +1,5 @@
import DB from "../database"; import DB from "../database.js";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; import { ModelDataBase } from "@hibas123/safe_mongo/lib/model.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
export interface IGrant extends ModelDataBase { export interface IGrant extends ModelDataBase {

View File

@ -1,5 +1,5 @@
import DB from "../database"; import DB from "../database.js";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; import { ModelDataBase } from "@hibas123/safe_mongo/lib/model.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
import moment = require("moment"); import moment = require("moment");

View File

@ -1,24 +1,24 @@
import DB from "../database"; import DB from "../database.js";
import { ModelDataBase } from "@hibas123/safe_mongo"; import { ModelDataBase } from "@hibas123/safe_mongo";
export interface IMail extends ModelDataBase { export interface IMail extends ModelDataBase {
mail: string; mail: string;
verified: boolean; verified: boolean;
primary: boolean; primary: boolean;
} }
const Mail = DB.addModel<IMail>({ const Mail = DB.addModel<IMail>({
name: "mail", name: "mail",
versions: [ versions: [
{ {
migration: () => {}, migration: () => { },
schema: { schema: {
mail: { type: String }, mail: { type: String },
verified: { type: Boolean, default: false }, verified: { type: Boolean, default: false },
primary: { type: Boolean }, primary: { type: Boolean },
}, },
}, },
], ],
}); });
export default Mail; export default Mail;

View File

@ -1,5 +1,5 @@
import DB from "../database"; import DB from "../database.js";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; import { ModelDataBase } from "@hibas123/safe_mongo/lib/model.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
export interface IPermission extends ModelDataBase { export interface IPermission extends ModelDataBase {

View File

@ -1,5 +1,5 @@
import DB from "../database"; import DB from "../database.js";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; import { ModelDataBase } from "@hibas123/safe_mongo/lib/model.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
import { v4 } from "uuid"; import { v4 } from "uuid";

View File

@ -1,5 +1,5 @@
import DB from "../database"; import DB from "../database.js";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; import { ModelDataBase } from "@hibas123/safe_mongo/lib/model.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
import { v4 } from "uuid"; import { v4 } from "uuid";

View File

@ -1,6 +1,6 @@
import { TFAType } from "@hibas123/openauth-internalapi"; import { TFAType } from "@hibas123/openauth-internalapi";
import DB from "../database"; import DB from "../database.js";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; import { ModelDataBase } from "@hibas123/safe_mongo/lib/model.js";
import { ObjectId } from "bson"; import { ObjectId } from "bson";
import { Binary } from "mongodb"; import { Binary } from "mongodb";
@ -30,7 +30,7 @@ export interface IWebAuthn extends ITwoFactor {
data: { data: {
challenge?: any; challenge?: any;
device?: { device?: {
credentialID: Binary; credentialID: Binary | string;
credentialPublicKey: Binary; credentialPublicKey: Binary;
counter: number; counter: number;
transports: AuthenticatorTransport[] transports: AuthenticatorTransport[]

View File

@ -1,8 +1,9 @@
import DB from "../database"; import DB from "../database.js";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { randomString } from "../helper/random"; import { randomString } from "../helper/random.js";
export enum Gender { export enum Gender {
none, none,

View File

@ -1,15 +1,15 @@
import User, { Gender } from "./models/user"; import User, { Gender } from "./models/user.js";
import Client from "./models/client"; import Client from "./models/client.js";
import Logging from "@hibas123/nodelogging"; import Logging from "@hibas123/nodelogging";
import RegCode from "./models/regcodes"; import RegCode from "./models/regcodes.js";
import moment from "moment"; import moment from "moment";
import Permission from "./models/permissions"; import Permission from "./models/permissions.js";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
import DB from "./database"; import DB from "./database.js";
import TwoFactor from "./models/twofactor"; import TwoFactor from "./models/twofactor.js";
import LoginToken from "./models/login_token"; import LoginToken from "./models/login_token.js";
import Mail from "./models/mail"; import Mail from "./models/mail.js";
export default async function TestData() { export default async function TestData() {
Logging.warn("Running in dev mode! Database will be cleared!"); Logging.warn("Running in dev mode! Database will be cleared!");

View File

@ -1,8 +1,8 @@
import { __ as i__ } from "i18n"; import { __ as i__ } from "i18n";
import config from "../config"; import config from "../config.js";
import * as viewsv1 from "@hibas123/openauth-views-v1"; import * as viewsv1 from "@hibas123/openauth-views-v1";
export default function GetAdminPage(__: typeof i__): string { export default function GetAdminPage(__: typeof i__): string {
let data = {}; let data = {};
return viewsv1.admin(config.core.dev)(data, { helpers: { i18n: __ } }); return viewsv1.admin(config.core.dev)(data, { helpers: { i18n: __ } });
} }

View File

@ -1,22 +1,22 @@
import { __ as i__ } from "i18n"; import { __ as i__ } from "i18n";
import config from "../config"; import config from "../config.js";
import * as viewsv1 from "@hibas123/openauth-views-v1"; import * as viewsv1 from "@hibas123/openauth-views-v1";
export default function GetAuthPage( export default function GetAuthPage(
__: typeof i__, __: typeof i__,
appname: string, appname: string,
scopes: { name: string; description: string; logo: string }[] scopes: { name: string; description: string; logo: string }[]
): string { ): string {
return viewsv1.authorize(config.core.dev)( return viewsv1.authorize(config.core.dev)(
{ {
title: __("Authorize %s", appname), title: __("Authorize %s", appname),
information: __( information: __(
"By clicking on ALLOW, you allow this app to access the requested recources." "By clicking on ALLOW, you allow this app to access the requested recources."
), ),
scopes: scopes, scopes: scopes,
// request: request // request: request
}, },
{ helpers: { i18n: __ } } { helpers: { i18n: __ } }
); );
} }

View File

@ -7,12 +7,12 @@ import {
} from "express"; } from "express";
import * as Handlebars from "handlebars"; import * as Handlebars from "handlebars";
import moment = require("moment"); import moment = require("moment");
import { GetUserMiddleware, UserMiddleware } from "../api/middlewares/user"; import { GetUserMiddleware, UserMiddleware } from "../api/middlewares/user.js";
import GetAuthRoute from "../api/oauth/auth"; import GetAuthRoute from "../api/oauth/auth.js";
import config from "../config"; import config from "../config.js";
import { HttpStatusCode } from "../helper/request_error"; import { HttpStatusCode } from "../helper/request_error.js";
import GetAdminPage from "./admin"; import GetAdminPage from "./admin.js";
import GetRegistrationPage from "./register"; import GetRegistrationPage from "./register.js";
import * as path from "path"; import * as path from "path";
const viewsv2_location = path.join(path.dirname(require.resolve("@hibas123/openauth-views-v2")), "build"); const viewsv2_location = path.join(path.dirname(require.resolve("@hibas123/openauth-views-v2")), "build");

View File

@ -1,9 +1,9 @@
import { __ as i__ } from "i18n"; import { __ as i__ } from "i18n";
import config from "../config"; import config from "../config.js";
import * as viewsv1 from "@hibas123/openauth-views-v1"; import * as viewsv1 from "@hibas123/openauth-views-v1";
export default function GetRegistrationPage(__: typeof i__): string { export default function GetRegistrationPage(__: typeof i__): string {
let data = {}; let data = {};
return viewsv1.register(config.core.dev)(data, { helpers: { i18n: __ } }); return viewsv1.register(config.core.dev)(data, { helpers: { i18n: __ } });
} }

View File

@ -1,4 +1,4 @@
import config, { WebConfig } from "./config"; import config, { WebConfig } from "./config.js";
import express from "express"; import express from "express";
import { Express } from "express"; import { Express } from "express";
@ -11,14 +11,12 @@ import session from "express-session";
import MongoStore from "connect-mongo"; import MongoStore from "connect-mongo";
import i18n from "i18n"; import i18n from "i18n";
import compression from "compression"; import ApiRouter from "./api/index.js";
import ApiRouter from "./api"; import ViewRouter from "./views/index.js";
import ViewRouter from "./views"; import RequestError, { HttpStatusCode } from "./helper/request_error.js";
import RequestError, { HttpStatusCode } from "./helper/request_error"; import DB from "./database.js";
import DB from "./database"; import promiseMiddleware from "./helper/promiseMiddleware.js";
import promiseMiddleware from "./helper/promiseMiddleware"; import User from "./models/user.js";
import User from "./models/user";
import LoginToken, { CheckToken } from "./models/login_token";
export default class Web { export default class Web {
server: Express; server: Express;
@ -41,7 +39,7 @@ export default class Web {
} }
private registerMiddleware() { private registerMiddleware() {
this.server.use(session({ const sess = session({
secret: config.core.secret, secret: config.core.secret,
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
@ -57,7 +55,9 @@ export default class Web {
secure: !config.core.dev, secure: !config.core.dev,
sameSite: "strict", sameSite: "strict",
} }
})) });
this.server.use(sess as any) // FIXME: These types seem to be brokenb, but they shouldn't
this.server.use(cookieparser()); this.server.use(cookieparser());
this.server.use( this.server.use(
bodyparser.json(), bodyparser.json(),
@ -103,16 +103,17 @@ export default class Web {
next(); next();
}); });
this.server.use( // Compression will be handled by the reverse proxy!
compression({ // this.server.use(
filter: (req, res) => { // compression({
if (req.headers["x-no-compression"]) { // filter: (req, res) => {
return false; // if (req.headers["x-no-compression"]) {
} // return false;
return compression.filter(req, res); // }
}, // return compression.filter(req, res);
}) // },
); // })
// );
} }
private registerEndpoints() { private registerEndpoints() {

View File

@ -1,7 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "esnext",
"module": "commonjs", "isolatedModules": true,
"noEmit": false,
"allowImportingTsExtensions": false,
"module": "nodenext",
"moduleResolution": "nodenext",
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,
"outDir": "./lib", "outDir": "./lib",
@ -11,7 +15,14 @@
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"esModuleInterop": true "esModuleInterop": true
}, },
"exclude": ["node_modules/"], "exclude": [
"files": ["src/express.d.ts"], "node_modules/",
"include": ["./src"] "../node_modules/",
],
"files": [
"src/express.d.ts"
],
"include": [
"./src"
]
} }

View File

@ -6,36 +6,36 @@
"@hibas123/theme": "^2.0.7", "@hibas123/theme": "^2.0.7",
"@hibas123/utils": "^2.2.18", "@hibas123/utils": "^2.2.18",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-html": "^1.0.3", "@rollup/plugin-html": "^2.0.0",
"@rollup/plugin-image": "^3.0.3", "@rollup/plugin-image": "^3.0.3",
"@rollup/plugin-node-resolve": "^15.0.2", "@rollup/plugin-node-resolve": "^16.0.1",
"@simplewebauthn/browser": "^7.2.0", "@simplewebauthn/browser": "^13.2.0",
"@tsconfig/svelte": "^4.0.1", "@tsconfig/svelte": "^5.0.5",
"@types/cleave.js": "^1.4.7", "@types/cleave.js": "^1.4.12",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.21",
"classnames": "^2.3.2", "classnames": "^2.5.1",
"cleave.js": "^1.6.0", "cleave.js": "^1.6.0",
"cssnano": "^6.0.1", "cssnano": "^7.1.1",
"esbuild": "^0.17.16", "esbuild": "^0.25.9",
"flowbite": "^1.6.5", "flowbite": "^3.1.2",
"flowbite-svelte": "^0.34.9", "flowbite-svelte": "^1.13.8",
"joi": "^17.11.0", "joi": "^18.0.1",
"postcss": "^8.4.31", "postcss": "^8.5.6",
"postcss-import": "^15.1.0", "postcss-import": "^16.1.1",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"rollup": "^3.20.2", "rollup": "^4.50.2",
"rollup-plugin-esbuild": "^5.0.0", "rollup-plugin-esbuild": "^6.2.1",
"rollup-plugin-hash": "^1.3.0", "rollup-plugin-hash": "^1.3.0",
"rollup-plugin-livereload": "^2.0.5", "rollup-plugin-livereload": "^2.0.5",
"rollup-plugin-postcss": "^4.0.2", "rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-sizes": "^1.0.6", "rollup-plugin-sizes": "^1.1.0",
"rollup-plugin-svelte": "^7.1.4", "rollup-plugin-svelte": "^7.2.3",
"rollup-plugin-visualizer": "^5.9.0", "rollup-plugin-visualizer": "^6.0.3",
"svelte": "^3.58.0", "svelte": "^5.38.10",
"svelte-preprocess": "^5.0.3", "svelte-preprocess": "^6.0.3",
"tailwindcss": "^3.3.1", "tailwindcss": "^4.1.13",
"typescript": "^5.0.4", "typescript": "^5.9.2",
"what-the-pack": "^2.0.3" "what-the-pack": "^2.0.3"
}, },
"scripts": { "scripts": {

View File

@ -8,21 +8,21 @@
"watch": "node build.js watch" "watch": "node build.js watch"
}, },
"dependencies": { "dependencies": {
"handlebars": "^4.7.7" "handlebars": "^4.7.8"
}, },
"devDependencies": { "devDependencies": {
"@material/button": "^5.1.0", "@material/button": "^14.0.0",
"@material/form-field": "^5.1.0", "@material/form-field": "^14.0.0",
"@material/radio": "^5.1.0", "@material/radio": "^14.0.0",
"chokidar": "^3.5.3", "chokidar": "^4.0.3",
"gzip-size": "^6.0.0", "gzip-size": "^7.0.0",
"html-minifier": "^4.0.0", "html-minifier": "^4.0.0",
"preact": "^10.13.2", "preact": "^10.27.2",
"rollup": "^3.20.2", "rollup": "^4.50.2",
"rollup-plugin-includepaths": "^0.2.4", "rollup-plugin-includepaths": "^0.2.4",
"rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript2": "^0.34.1", "rollup-plugin-typescript2": "^0.36.0",
"sass": "^1.61.0", "sass": "^1.92.1",
"typescript": "^5.0.4" "typescript": "^5.9.2"
} }
} }

View File

@ -14,6 +14,6 @@
"author": "Fabian Stamm <Fabian.Stamm@polizei.hessen.de>", "author": "Fabian Stamm <Fabian.Stamm@polizei.hessen.de>",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"typescript": "^5.0.4" "typescript": "^5.9.2"
} }
} }

View File

@ -1,21 +1,22 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "ESNext", "module": "ESNext",
"target": "esnext", "target": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"outDir": "esm/", "outDir": "esm/",
"sourceMap": true, "sourceMap": true,
"declaration": true, "declaration": true,
"noImplicitAny": false, "noImplicitAny": false,
"esModuleInterop": true, "esModuleInterop": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"preserveWatchOutput": true "preserveWatchOutput": true
}, },
"exclude": [ "exclude": [
"node_modules" "node_modules",
], "../node_modules"
"include": [ ],
"src" "include": [
] "src"
} ]
}

View File

@ -1,21 +1,22 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "commonjs", "module": "commonjs",
"target": "esnext", "target": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"outDir": "lib/", "outDir": "lib/",
"sourceMap": true, "sourceMap": true,
"declaration": true, "declaration": true,
"noImplicitAny": false, "noImplicitAny": false,
"esModuleInterop": true, "esModuleInterop": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"preserveWatchOutput": true "preserveWatchOutput": true
}, },
"exclude": [ "exclude": [
"node_modules" "node_modules",
], "../node_modules"
"include": [ ],
"src" "include": [
] "src"
} ]
}

View File

@ -18,6 +18,6 @@
"_API" "_API"
], ],
"dependencies": { "dependencies": {
"@hibas123/jrpcgen": "^1.2.14" "@hibas123/jrpcgen": "^1.2.20"
} }
} }

4667
yarn.lock

File diff suppressed because it is too large Load Diff