Restructuring the Project

Updating dependencies
This commit is contained in:
Fabian Stamm 2023-04-07 19:54:47 +02:00
parent 532107c479
commit 0453e461c9
121 changed files with 16380 additions and 6701 deletions

11
.dockerignore Normal file
View File

@ -0,0 +1,11 @@
node_modules/
Backend/node_modules
Backend/keys
Backend/logs
Backend/lib
Backend/doc
Backend/config.ini
Frontend/build
Frontend/node_modules
FrontendLegacy/node_modules
FrontendLegacy/out

5
.gitignore vendored
View File

@ -9,4 +9,7 @@ logs/
yarn-error\.log
config.ini
.env
doc/
doc/
.yarn/cache
.yarn/install-state.gz

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "views_repo"]
path = views_repo
url = ../OpenAuth_views

File diff suppressed because one or more lines are too long

873
.yarn/releases/yarn-3.5.0.cjs vendored Normal file

File diff suppressed because one or more lines are too long

9
.yarnrc.yml Normal file
View File

@ -0,0 +1,9 @@
nodeLinker: node-modules
npmRegistryServer: "https://npm.hibas123.de"
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.5.0.cjs

72
Backend/package.json Normal file
View File

@ -0,0 +1,72 @@
{
"name": "@hibas123/openauth-backend",
"version": "1.1.2",
"main": "lib/index.js",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"license": "MIT",
"scripts": {
"build": "run-s build-ts build-doc",
"build-doc": "apidoc -i src/ -p apidoc/",
"build-ts": "tsc",
"start": "node lib/index.js",
"dev": "nodemon -e ts --exec ts-node src/index.ts",
"format": "prettier --write \"src/**\""
},
"pipelines": {
"install": [
"cd views && npm install",
"git submodule init",
"git submodule update",
"cd views_repo && npm install"
]
},
"devDependencies": {
"@types/body-parser": "^1.19.2",
"@types/compression": "^1.7.2",
"@types/cookie-parser": "^1.4.3",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.17",
"@types/i18n": "^0.13.6",
"@types/ini": "^1.3.31",
"@types/jsonwebtoken": "^9.0.1",
"@types/mongodb": "^3.6.20",
"@types/node": "^18.15.11",
"@types/node-rsa": "^1.1.1",
"@types/qrcode": "^1.5.0",
"@types/speakeasy": "^2.0.7",
"@types/uuid": "^9.0.1",
"apidoc": "^0.54.0",
"concurrently": "^8.0.1",
"nodemon": "^2.0.22",
"prettier": "^2.8.7",
"ts-node": "^10.9.1",
"typescript": "^5.0.3"
},
"dependencies": {
"@hibas123/config": "^1.1.2",
"@hibas123/nodelogging": "^3.1.3",
"@hibas123/nodeloggingserver_client": "^1.1.2",
"@hibas123/openauth-views-v1": "workspace:^",
"@hibas123/safe_mongo": "^1.7.1",
"body-parser": "^1.20.2",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"handlebars": "^4.7.7",
"i18n": "^0.15.1",
"ini": "^4.0.0",
"jsonwebtoken": "^9.0.0",
"moment": "^2.29.4",
"mongodb": "^3.7.3",
"node-rsa": "^1.1.1",
"npm-run-all": "^4.1.5",
"qrcode": "^1.5.1",
"reflect-metadata": "^0.1.13",
"speakeasy": "^2.0.0",
"u2f": "^0.1.3",
"uuid": "^9.0.0"
},
"packageManager": "yarn@3.5.0"
}

View File

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

View File

@ -1,33 +1,33 @@
import * as express from "express";
import AdminRoute from "./admin";
import UserRoute from "./user";
import InternalRoute from "./internal";
import Login from "./user/login";
import ClientRouter from "./client";
import * as cors from "cors";
import OAuthRoute from "./oauth";
import config from "../config";
const ApiRouter: express.IRouter = express.Router();
ApiRouter.use("/admin", AdminRoute);
ApiRouter.use(cors());
ApiRouter.use("/user", UserRoute);
ApiRouter.use("/internal", InternalRoute);
ApiRouter.use("/oauth", OAuthRoute);
ApiRouter.use("/client", ClientRouter);
// Legacy reasons (deprecated)
ApiRouter.use("/", ClientRouter);
// Legacy reasons (deprecated)
ApiRouter.post("/login", Login);
ApiRouter.get("/config.json", (req, res) => {
return res.json({
name: config.core.name,
url: config.core.url,
});
});
export default ApiRouter;
import * as express from "express";
import AdminRoute from "./admin";
import UserRoute from "./user";
import InternalRoute from "./internal";
import Login from "./user/login";
import ClientRouter from "./client";
import * as cors from "cors";
import OAuthRoute from "./oauth";
import config from "../config";
const ApiRouter: express.IRouter = express.Router();
ApiRouter.use("/admin", AdminRoute);
ApiRouter.use(cors());
ApiRouter.use("/user", UserRoute);
ApiRouter.use("/internal", InternalRoute);
ApiRouter.use("/oauth", OAuthRoute);
ApiRouter.use("/client", ClientRouter);
// Legacy reasons (deprecated)
ApiRouter.use("/", ClientRouter);
// Legacy reasons (deprecated)
ApiRouter.post("/login", Login);
ApiRouter.get("/config.json", (req, res) => {
return res.json({
name: config.core.name,
url: config.core.url,
});
});
export default ApiRouter;

View File

@ -1,106 +1,106 @@
import { NextFunction, Request, Response } from "express";
import LoginToken, { CheckToken } from "../../models/login_token";
import Logging from "@hibas123/nodelogging";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
import User from "../../models/user";
import promiseMiddleware from "../../helper/promiseMiddleware";
class Invalid extends Error {}
/**
* Returns customized Middleware function, that could also be called directly
* by code and will return true or false depending on the token. In the false
* case it will also send error and redirect if json is not set
* @param json Default false. Checks if requests wants an json or html for returning errors
* @param special_required Default false. If true, a special token is required
* @param redirect_uri Default current uri. Sets the uri to redirect, if json is not set and user not logged in
* @param validated Default true. If false, the token must not be validated
*/
export function GetUserMiddleware(
json = false,
special_required: boolean = false,
redirect_uri?: string,
validated = true
) {
return promiseMiddleware(async function (
req: Request,
res: Response,
next?: NextFunction
) {
const invalid = (message: string) => {
throw new Invalid(req.__(message));
};
try {
let { login, special } = req.query as { [key: string]: string };
if (!login) {
login = req.cookies.login;
special = req.cookies.special;
}
if (!login) invalid("No login token");
if (!special && special_required) invalid("No special token");
let token = await LoginToken.findOne({ token: login, valid: true });
if (!(await CheckToken(token, validated)))
invalid("Login token invalid");
let user = await User.findById(token.user);
if (!user) {
token.valid = false;
await LoginToken.save(token);
invalid("Login token invalid");
}
let special_token;
if (special) {
Logging.debug("Special found");
special_token = await LoginToken.findOne({
token: special,
special: true,
valid: true,
user: token.user,
});
if (!(await CheckToken(special_token, validated)))
invalid("Special token invalid");
req.special = true;
}
req.user = user;
req.isAdmin = user.admin;
req.token = {
login: token,
special: special_token,
};
if (next) next();
return true;
} catch (e) {
if (e instanceof Invalid) {
if (req.method === "GET" && !json) {
res.status(HttpStatusCode.UNAUTHORIZED);
res.redirect(
"/login?base64=true&state=" +
Buffer.from(
redirect_uri ? redirect_uri : req.originalUrl
).toString("base64")
);
} else {
throw new RequestError(
req.__(
"You are not logged in or your login is expired" +
` (${e.message})`
),
HttpStatusCode.UNAUTHORIZED,
undefined,
{ auth: true }
);
}
} else {
if (next) next(e);
else throw e;
}
return false;
}
});
}
export const UserMiddleware = GetUserMiddleware();
import { NextFunction, Request, Response } from "express";
import LoginToken, { CheckToken } from "../../models/login_token";
import Logging from "@hibas123/nodelogging";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
import User from "../../models/user";
import promiseMiddleware from "../../helper/promiseMiddleware";
class Invalid extends Error {}
/**
* Returns customized Middleware function, that could also be called directly
* by code and will return true or false depending on the token. In the false
* case it will also send error and redirect if json is not set
* @param json Default false. Checks if requests wants an json or html for returning errors
* @param special_required Default false. If true, a special token is required
* @param redirect_uri Default current uri. Sets the uri to redirect, if json is not set and user not logged in
* @param validated Default true. If false, the token must not be validated
*/
export function GetUserMiddleware(
json = false,
special_required: boolean = false,
redirect_uri?: string,
validated = true
) {
return promiseMiddleware(async function (
req: Request,
res: Response,
next?: NextFunction
) {
const invalid = (message: string) => {
throw new Invalid(req.__(message));
};
try {
let { login, special } = req.query as { [key: string]: string };
if (!login) {
login = req.cookies.login;
special = req.cookies.special;
}
if (!login) invalid("No login token");
if (!special && special_required) invalid("No special token");
let token = await LoginToken.findOne({ token: login, valid: true });
if (!(await CheckToken(token, validated)))
invalid("Login token invalid");
let user = await User.findById(token.user);
if (!user) {
token.valid = false;
await LoginToken.save(token);
invalid("Login token invalid");
}
let special_token;
if (special) {
Logging.debug("Special found");
special_token = await LoginToken.findOne({
token: special,
special: true,
valid: true,
user: token.user,
});
if (!(await CheckToken(special_token, validated)))
invalid("Special token invalid");
req.special = true;
}
req.user = user;
req.isAdmin = user.admin;
req.token = {
login: token,
special: special_token,
};
if (next) next();
return true;
} catch (e) {
if (e instanceof Invalid) {
if (req.method === "GET" && !json) {
res.status(HttpStatusCode.UNAUTHORIZED);
res.redirect(
"/login?base64=true&state=" +
Buffer.from(
redirect_uri ? redirect_uri : req.originalUrl
).toString("base64")
);
} else {
throw new RequestError(
req.__(
"You are not logged in or your login is expired" +
` (${e.message})`
),
HttpStatusCode.UNAUTHORIZED,
undefined,
{ auth: true }
);
}
} else {
if (next) next(e);
else throw e;
}
return false;
}
});
}
export const UserMiddleware = GetUserMiddleware();

View File

@ -1,13 +1,8 @@
import { Request, Response, NextFunction } from "express";
import { Logging } from "@hibas123/nodelogging";
import Logging from "@hibas123/nodelogging";
import {
isBoolean,
isString,
isNumber,
isObject,
isDate,
isArray,
isSymbol,
} from "util";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
@ -65,25 +60,25 @@ export default function (fields: Checks, noadditional = false) {
}
break;
case Types.NUMBER:
if (isNumber(data)) return;
if (typeof data == "number") return;
break;
case Types.EMAIL:
if (isEmail(data)) return;
break;
case Types.BOOLEAN:
if (isBoolean(data)) return;
if (typeof data == "boolean") return;
break;
case Types.OBJECT:
if (isObject(data)) return;
if (typeof data == "object") return;
break;
case Types.ARRAY:
if (isArray(data)) return;
if (Array.isArray(data)) return;
break;
case Types.DATE:
if (isDate(data)) return;
break;
case Types.ENUM:
if (isString(data)) {
if (typeof data == "string") {
if (field.values.indexOf(data) >= 0) return;
}
break;

View File

@ -1,19 +1,19 @@
import { Request, Response } from "express";
import Stacker from "../middlewares/stacker";
import { GetUserMiddleware } from "../middlewares/user";
import LoginToken, { CheckToken } from "../../models/login_token";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
export const GetAccount = Stacker(
GetUserMiddleware(true, true),
async (req: Request, res: Response) => {
let user = {
id: req.user.uid,
name: req.user.name,
username: req.user.username,
birthday: req.user.birthday,
gender: req.user.gender,
};
res.json({ user });
}
);
import { Request, Response } from "express";
import Stacker from "../middlewares/stacker";
import { GetUserMiddleware } from "../middlewares/user";
import LoginToken, { CheckToken } from "../../models/login_token";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
export const GetAccount = Stacker(
GetUserMiddleware(true, true),
async (req: Request, res: Response) => {
let user = {
id: req.user.uid,
name: req.user.name,
username: req.user.username,
birthday: req.user.birthday,
gender: req.user.gender,
};
res.json({ user });
}
);

View File

@ -1,132 +1,132 @@
import { Router } from "express";
import { GetAccount } from "./account";
import { GetContactInfos } from "./contact";
import Login from "./login";
import Register from "./register";
import { DeleteToken, GetToken } from "./token";
import TwoFactorRoute from "./twofactor";
import OAuthRoute from "./oauth";
const UserRoute: Router = Router();
/**
* @api {post} /user/register
* @apiName UserRegister
*
* @apiGroup user
* @apiPermission none
*
* @apiParam {String} mail EMail linked to this Account
* @apiParam {String} username The new Username
* @apiParam {String} password Password hashed and salted like specification
* @apiParam {String} salt The Salt used for password hashing
* @apiParam {String} regcode The regcode, that should be used
* @apiParam {String} gender Gender can be: "male", "female", "other", "none"
* @apiParam {String} name The real name of the User
*
* @apiSuccess {Boolean} success
*
* @apiErrorExample {Object} Error-Response:
{
error: [
{
message: "Some Error",
field: "username"
}
],
status: 400
}
*/
UserRoute.post("/register", Register);
/**
* @api {post} /user/login?type=:type
* @apiName UserLogin
*
* @apiParam {String} type Type could be either "username" or "password"
*
* @apiGroup user
* @apiPermission none
*
* @apiParam {String} username Username (either username or uid required)
* @apiParam {String} uid (either username or uid required)
* @apiParam {String} password Password hashed and salted like specification (only on type password)
* @apiParam {Number} time in milliseconds used to hash password. This is used to make passwords "expire"
*
* @apiSuccess {String} uid On type = "username"
* @apiSuccess {String} salt On type = "username"
*
* @apiSuccess {String} login On type = "password". Login Token
* @apiSuccess {String} special On type = "password". Special Token
* @apiSuccess {Object[]} tfa Will be set when TwoFactorAuthentication is required
* @apiSuccess {String} tfa.id The ID of the TFA Method
* @apiSuccess {String} tfa.name The name of the TFA Method
* @apiSuccess {String} tfa.type The type of the TFA Method
*/
UserRoute.post("/login", Login);
UserRoute.use("/twofactor", TwoFactorRoute);
/**
* @api {get} /user/token
* @apiName UserGetToken
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Object[]} token
* @apiSuccess {String} token.id The Token ID
* @apiSuccess {String} token.special Identifies Special Token
* @apiSuccess {String} token.ip IP the token was optained from
* @apiSuccess {String} token.browser The Browser the token was optained from (User Agent)
* @apiSuccess {Boolean} token.isthis Shows if it is token used by this session
*/
UserRoute.get("/token", GetToken);
/**
* @api {delete} /user/token/:id
* @apiParam {String} id The id of the token to be deleted
*
* @apiName UserDeleteToken
*
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Boolean} success
*/
UserRoute.delete("/token/:id", DeleteToken);
/**
* @api {delete} /user/account
* @apiName UserGetAccount
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Boolean} success
* @apiSuccess {Object[]} user
* @apiSuccess {String} user.id User ID
* @apiSuccess {String} user.name Full name of the user
* @apiSuccess {String} user.username Username of user
* @apiSuccess {Date} user.birthday Birthday
* @apiSuccess {Number} user.gender Gender of user (none = 0, male = 1, female = 2, other = 3)
*/
UserRoute.get("/account", GetAccount);
/**
* @api {delete} /user/account
* @apiName UserGetAccount
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Boolean} success
* @apiSuccess {Object} contact
* @apiSuccess {Object[]} user.mail EMail addresses
* @apiSuccess {Object[]} user.phone Phone numbers
*/
UserRoute.get("/contact", GetContactInfos);
UserRoute.use("/oauth", OAuthRoute);
export default UserRoute;
import { Router } from "express";
import { GetAccount } from "./account";
import { GetContactInfos } from "./contact";
import Login from "./login";
import Register from "./register";
import { DeleteToken, GetToken } from "./token";
import TwoFactorRoute from "./twofactor";
import OAuthRoute from "./oauth";
const UserRoute: Router = Router();
/**
* @api {post} /user/register
* @apiName UserRegister
*
* @apiGroup user
* @apiPermission none
*
* @apiParam {String} mail EMail linked to this Account
* @apiParam {String} username The new Username
* @apiParam {String} password Password hashed and salted like specification
* @apiParam {String} salt The Salt used for password hashing
* @apiParam {String} regcode The regcode, that should be used
* @apiParam {String} gender Gender can be: "male", "female", "other", "none"
* @apiParam {String} name The real name of the User
*
* @apiSuccess {Boolean} success
*
* @apiErrorExample {Object} Error-Response:
{
error: [
{
message: "Some Error",
field: "username"
}
],
status: 400
}
*/
UserRoute.post("/register", Register);
/**
* @api {post} /user/login?type=:type
* @apiName UserLogin
*
* @apiParam {String} type Type could be either "username" or "password"
*
* @apiGroup user
* @apiPermission none
*
* @apiParam {String} username Username (either username or uid required)
* @apiParam {String} uid (either username or uid required)
* @apiParam {String} password Password hashed and salted like specification (only on type password)
* @apiParam {Number} time in milliseconds used to hash password. This is used to make passwords "expire"
*
* @apiSuccess {String} uid On type = "username"
* @apiSuccess {String} salt On type = "username"
*
* @apiSuccess {String} login On type = "password". Login Token
* @apiSuccess {String} special On type = "password". Special Token
* @apiSuccess {Object[]} tfa Will be set when TwoFactorAuthentication is required
* @apiSuccess {String} tfa.id The ID of the TFA Method
* @apiSuccess {String} tfa.name The name of the TFA Method
* @apiSuccess {String} tfa.type The type of the TFA Method
*/
UserRoute.post("/login", Login);
UserRoute.use("/twofactor", TwoFactorRoute);
/**
* @api {get} /user/token
* @apiName UserGetToken
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Object[]} token
* @apiSuccess {String} token.id The Token ID
* @apiSuccess {String} token.special Identifies Special Token
* @apiSuccess {String} token.ip IP the token was optained from
* @apiSuccess {String} token.browser The Browser the token was optained from (User Agent)
* @apiSuccess {Boolean} token.isthis Shows if it is token used by this session
*/
UserRoute.get("/token", GetToken);
/**
* @api {delete} /user/token/:id
* @apiParam {String} id The id of the token to be deleted
*
* @apiName UserDeleteToken
*
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Boolean} success
*/
UserRoute.delete("/token/:id", DeleteToken);
/**
* @api {delete} /user/account
* @apiName UserGetAccount
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Boolean} success
* @apiSuccess {Object[]} user
* @apiSuccess {String} user.id User ID
* @apiSuccess {String} user.name Full name of the user
* @apiSuccess {String} user.username Username of user
* @apiSuccess {Date} user.birthday Birthday
* @apiSuccess {Number} user.gender Gender of user (none = 0, male = 1, female = 2, other = 3)
*/
UserRoute.get("/account", GetAccount);
/**
* @api {delete} /user/account
* @apiName UserGetAccount
*
* @apiGroup user
* @apiPermission user
*
* @apiSuccess {Boolean} success
* @apiSuccess {Object} contact
* @apiSuccess {Object[]} user.mail EMail addresses
* @apiSuccess {Object[]} user.phone Phone numbers
*/
UserRoute.get("/contact", GetContactInfos);
UserRoute.use("/oauth", OAuthRoute);
export default UserRoute;

View File

@ -1,134 +1,134 @@
import { Request, Response } from "express";
import User, { IUser } from "../../models/user";
import { randomBytes } from "crypto";
import moment = require("moment");
import LoginToken from "../../models/login_token";
import promiseMiddleware from "../../helper/promiseMiddleware";
import TwoFactor, { TFATypes, TFANames } from "../../models/twofactor";
import * as crypto from "crypto";
import Logging from "@hibas123/nodelogging";
const Login = promiseMiddleware(async (req: Request, res: Response) => {
let type = req.query.type as string;
if (type === "username") {
let { username, uid } = req.query as { [key: string]: string };
let user = await User.findOne(
username ? { username: username.toLowerCase() } : { uid: uid }
);
if (!user) {
res.json({ error: req.__("User not found") });
} else {
res.json({ salt: user.salt, uid: user.uid });
}
return;
} else if (type === "password") {
const sendToken = async (user: IUser, tfa?: any[]) => {
let ip =
req.headers["x-forwarded-for"] || req.connection.remoteAddress;
let client = {
ip: Array.isArray(ip) ? ip[0] : ip,
browser: req.headers["user-agent"],
};
let token_str = randomBytes(16).toString("hex");
let tfa_exp = moment().add(5, "minutes").toDate();
let token_exp = moment().add(6, "months").toDate();
let token = LoginToken.new({
token: token_str,
valid: true,
validTill: tfa ? tfa_exp : token_exp,
user: user._id,
validated: tfa ? false : true,
...client,
});
await LoginToken.save(token);
let special_str = randomBytes(24).toString("hex");
let special_exp = moment().add(30, "minutes").toDate();
let special = LoginToken.new({
token: special_str,
valid: true,
validTill: tfa ? tfa_exp : special_exp,
special: true,
user: user._id,
validated: tfa ? false : true,
...client,
});
await LoginToken.save(special);
res.json({
login: { token: token_str, expires: token.validTill.toUTCString() },
special: {
token: special_str,
expires: special.validTill.toUTCString(),
},
tfa,
});
};
let { username, password, uid, date } = req.body;
let user = await User.findOne(
username ? { username: username.toLowerCase() } : { uid: uid }
);
if (!user) {
res.json({ error: req.__("User not found") });
} else {
let upw = user.password;
if (date) {
if (
!moment(date).isBetween(
moment().subtract(1, "minute"),
moment().add(1, "minute")
)
) {
res.json({
error: req.__(
"Invalid timestamp. Please check your devices time!"
),
});
return;
} else {
upw = crypto
.createHash("sha512")
.update(upw + date.toString())
.digest("hex");
}
}
if (upw !== password) {
res.json({ error: req.__("Password or username wrong") });
} else {
let twofactor = await TwoFactor.find({
user: user._id,
valid: true,
});
let expired = twofactor.filter((e) =>
e.expires ? moment().isAfter(moment(e.expires)) : false
);
await Promise.all(
expired.map((e) => {
e.valid = false;
return TwoFactor.save(e);
})
);
twofactor = twofactor.filter((e) => e.valid);
if (twofactor && twofactor.length > 0) {
let tfa = twofactor.map((e) => {
return {
id: e._id,
name: e.name || TFANames.get(e.type),
type: e.type,
};
});
await sendToken(user, tfa);
} else {
await sendToken(user);
}
}
}
} else {
res.json({ error: req.__("Invalid type!") });
}
});
export default Login;
import { Request, Response } from "express";
import User, { IUser } from "../../models/user";
import { randomBytes } from "crypto";
import moment = require("moment");
import LoginToken from "../../models/login_token";
import promiseMiddleware from "../../helper/promiseMiddleware";
import TwoFactor, { TFATypes, TFANames } from "../../models/twofactor";
import * as crypto from "crypto";
import Logging from "@hibas123/nodelogging";
const Login = promiseMiddleware(async (req: Request, res: Response) => {
let type = req.query.type as string;
if (type === "username") {
let { username, uid } = req.query as { [key: string]: string };
let user = await User.findOne(
username ? { username: username.toLowerCase() } : { uid: uid }
);
if (!user) {
res.json({ error: req.__("User not found") });
} else {
res.json({ salt: user.salt, uid: user.uid });
}
return;
} else if (type === "password") {
const sendToken = async (user: IUser, tfa?: any[]) => {
let ip =
req.headers["x-forwarded-for"] || req.connection.remoteAddress;
let client = {
ip: Array.isArray(ip) ? ip[0] : ip,
browser: req.headers["user-agent"],
};
let token_str = randomBytes(16).toString("hex");
let tfa_exp = moment().add(5, "minutes").toDate();
let token_exp = moment().add(6, "months").toDate();
let token = LoginToken.new({
token: token_str,
valid: true,
validTill: tfa ? tfa_exp : token_exp,
user: user._id,
validated: tfa ? false : true,
...client,
});
await LoginToken.save(token);
let special_str = randomBytes(24).toString("hex");
let special_exp = moment().add(30, "minutes").toDate();
let special = LoginToken.new({
token: special_str,
valid: true,
validTill: tfa ? tfa_exp : special_exp,
special: true,
user: user._id,
validated: tfa ? false : true,
...client,
});
await LoginToken.save(special);
res.json({
login: { token: token_str, expires: token.validTill.toUTCString() },
special: {
token: special_str,
expires: special.validTill.toUTCString(),
},
tfa,
});
};
let { username, password, uid, date } = req.body;
let user = await User.findOne(
username ? { username: username.toLowerCase() } : { uid: uid }
);
if (!user) {
res.json({ error: req.__("User not found") });
} else {
let upw = user.password;
if (date) {
if (
!moment(date).isBetween(
moment().subtract(1, "minute"),
moment().add(1, "minute")
)
) {
res.json({
error: req.__(
"Invalid timestamp. Please check your devices time!"
),
});
return;
} else {
upw = crypto
.createHash("sha512")
.update(upw + date.toString())
.digest("hex");
}
}
if (upw !== password) {
res.json({ error: req.__("Password or username wrong") });
} else {
let twofactor = await TwoFactor.find({
user: user._id,
valid: true,
});
let expired = twofactor.filter((e) =>
e.expires ? moment().isAfter(moment(e.expires)) : false
);
await Promise.all(
expired.map((e) => {
e.valid = false;
return TwoFactor.save(e);
})
);
twofactor = twofactor.filter((e) => e.valid);
if (twofactor && twofactor.length > 0) {
let tfa = twofactor.map((e) => {
return {
id: e._id,
name: e.name || TFANames.get(e.type),
type: e.type,
};
});
await sendToken(user, tfa);
} else {
await sendToken(user);
}
}
}
} else {
res.json({ error: req.__("Invalid type!") });
}
});
export default Login;

View File

@ -1,45 +1,45 @@
import { Request, Response } from "express";
import Stacker from "../middlewares/stacker";
import { GetUserMiddleware } from "../middlewares/user";
import LoginToken, { CheckToken } from "../../models/login_token";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
export const GetToken = Stacker(
GetUserMiddleware(true, true),
async (req: Request, res: Response) => {
let raw_token = await LoginToken.find({
user: req.user._id,
valid: true,
});
let token = await Promise.all(
raw_token
.map(async (token) => {
await CheckToken(token);
return {
id: token._id,
special: token.special,
ip: token.ip,
browser: token.browser,
isthis: token._id.equals(
token.special ? req.token.special._id : req.token.login._id
),
};
})
.filter((t) => t !== undefined)
);
res.json({ token });
}
);
export const DeleteToken = Stacker(
GetUserMiddleware(true, true),
async (req: Request, res: Response) => {
let { id } = req.params;
let token = await LoginToken.findById(id);
if (!token || !token.user.equals(req.user._id))
throw new RequestError("Invalid ID", HttpStatusCode.BAD_REQUEST);
token.valid = false;
await LoginToken.save(token);
res.json({ success: true });
}
);
import { Request, Response } from "express";
import Stacker from "../middlewares/stacker";
import { GetUserMiddleware } from "../middlewares/user";
import LoginToken, { CheckToken } from "../../models/login_token";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
export const GetToken = Stacker(
GetUserMiddleware(true, true),
async (req: Request, res: Response) => {
let raw_token = await LoginToken.find({
user: req.user._id,
valid: true,
});
let token = await Promise.all(
raw_token
.map(async (token) => {
await CheckToken(token);
return {
id: token._id,
special: token.special,
ip: token.ip,
browser: token.browser,
isthis: token._id.equals(
token.special ? req.token.special._id : req.token.login._id
),
};
})
.filter((t) => t !== undefined)
);
res.json({ token });
}
);
export const DeleteToken = Stacker(
GetUserMiddleware(true, true),
async (req: Request, res: Response) => {
let { id } = req.params;
let token = await LoginToken.findById(id);
if (!token || !token.user.equals(req.user._id))
throw new RequestError("Invalid ID", HttpStatusCode.BAD_REQUEST);
token.valid = false;
await LoginToken.save(token);
res.json({ success: true });
}
);

View File

@ -1,100 +1,100 @@
import { Router } from "express";
import Stacker from "../../../middlewares/stacker";
import { GetUserMiddleware } from "../../../middlewares/user";
import TwoFactor, {
TFATypes as TwoFATypes,
IBackupCode,
} from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import { upgradeToken } from "../helper";
import * as crypto from "crypto";
import Logging from "@hibas123/nodelogging";
const BackupCodeRoute = Router();
// TODO: Further checks if this is good enough randomness
function generateCode(length: number) {
let bytes = crypto.randomBytes(length);
let nrs = "";
bytes.forEach((b, idx) => {
let nr = Math.floor((b / 255) * 9.9999);
if (nr > 9) nr = 9;
nrs += String(nr);
});
return nrs;
}
BackupCodeRoute.post(
"/",
Stacker(GetUserMiddleware(true, true), async (req, res) => {
//Generating new
let codes = Array(10).map(() => generateCode(8));
console.log(codes);
let twofactor = TwoFactor.new(<IBackupCode>{
user: req.user._id,
type: TwoFATypes.OTC,
valid: true,
data: codes,
name: "",
});
await TwoFactor.save(twofactor);
res.json({
codes,
id: twofactor._id,
});
})
);
BackupCodeRoute.put(
"/",
Stacker(
GetUserMiddleware(true, false, undefined, false),
async (req, res) => {
let { login, special } = req.token;
let { id, code }: { id: string; code: string } = req.body;
let twofactor: IBackupCode = await TwoFactor.findById(id);
if (
!twofactor ||
!twofactor.valid ||
!twofactor.user.equals(req.user._id) ||
twofactor.type !== TwoFATypes.OTC
) {
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
code = code.replace(/\s/g, "");
let valid = twofactor.data.find((c) => c === code);
if (valid) {
twofactor.data = twofactor.data.filter((c) => c !== code);
await TwoFactor.save(twofactor);
let [login_exp, special_exp] = await Promise.all([
upgradeToken(login),
upgradeToken(special),
]);
res.json({ success: true, login_exp, special_exp });
} else {
throw new RequestError(
"Invalid or already used code!",
HttpStatusCode.BAD_REQUEST
);
}
}
)
);
export default BackupCodeRoute;
import { Router } from "express";
import Stacker from "../../../middlewares/stacker";
import { GetUserMiddleware } from "../../../middlewares/user";
import TwoFactor, {
TFATypes as TwoFATypes,
IBackupCode,
} from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import { upgradeToken } from "../helper";
import * as crypto from "crypto";
import Logging from "@hibas123/nodelogging";
const BackupCodeRoute = Router();
// TODO: Further checks if this is good enough randomness
function generateCode(length: number) {
let bytes = crypto.randomBytes(length);
let nrs = "";
bytes.forEach((b, idx) => {
let nr = Math.floor((b / 255) * 9.9999);
if (nr > 9) nr = 9;
nrs += String(nr);
});
return nrs;
}
BackupCodeRoute.post(
"/",
Stacker(GetUserMiddleware(true, true), async (req, res) => {
//Generating new
let codes = Array(10).map(() => generateCode(8));
console.log(codes);
let twofactor = TwoFactor.new(<IBackupCode>{
user: req.user._id,
type: TwoFATypes.OTC,
valid: true,
data: codes,
name: "",
});
await TwoFactor.save(twofactor);
res.json({
codes,
id: twofactor._id,
});
})
);
BackupCodeRoute.put(
"/",
Stacker(
GetUserMiddleware(true, false, undefined, false),
async (req, res) => {
let { login, special } = req.token;
let { id, code }: { id: string; code: string } = req.body;
let twofactor: IBackupCode = await TwoFactor.findById(id);
if (
!twofactor ||
!twofactor.valid ||
!twofactor.user.equals(req.user._id) ||
twofactor.type !== TwoFATypes.OTC
) {
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
code = code.replace(/\s/g, "");
let valid = twofactor.data.find((c) => c === code);
if (valid) {
twofactor.data = twofactor.data.filter((c) => c !== code);
await TwoFactor.save(twofactor);
let [login_exp, special_exp] = await Promise.all([
upgradeToken(login),
upgradeToken(special),
]);
res.json({ success: true, login_exp, special_exp });
} else {
throw new RequestError(
"Invalid or already used code!",
HttpStatusCode.BAD_REQUEST
);
}
}
)
);
export default BackupCodeRoute;

View File

@ -1,16 +1,16 @@
import LoginToken, { ILoginToken } from "../../../models/login_token";
import moment = require("moment");
export async function upgradeToken(token: ILoginToken) {
token.data = undefined;
token.valid = true;
token.validated = true;
//TODO durations from config
let expires = (token.special
? moment().add(30, "minute")
: moment().add(6, "months")
).toDate();
token.validTill = expires;
await LoginToken.save(token);
return expires;
}
import LoginToken, { ILoginToken } from "../../../models/login_token";
import moment = require("moment");
export async function upgradeToken(token: ILoginToken) {
token.data = undefined;
token.valid = true;
token.validated = true;
//TODO durations from config
let expires = (token.special
? moment().add(30, "minute")
: moment().add(6, "months")
).toDate();
token.validTill = expires;
await LoginToken.save(token);
return expires;
}

View File

@ -1,56 +1,56 @@
import { Router } from "express";
import YubiKeyRoute from "./yubikey";
import { GetUserMiddleware } from "../../middlewares/user";
import Stacker from "../../middlewares/stacker";
import TwoFactor from "../../../models/twofactor";
import * as moment from "moment";
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
import OTCRoute from "./otc";
import BackupCodeRoute from "./backup";
const TwoFactorRouter = Router();
TwoFactorRouter.get(
"/",
Stacker(GetUserMiddleware(true, true), async (req, res) => {
let twofactor = await TwoFactor.find({ user: req.user._id, valid: true });
let expired = twofactor.filter((e) =>
e.expires ? moment().isAfter(moment(e.expires)) : false
);
await Promise.all(
expired.map((e) => {
e.valid = false;
return TwoFactor.save(e);
})
);
twofactor = twofactor.filter((e) => e.valid);
let tfa = twofactor.map((e) => {
return {
id: e._id,
name: e.name,
type: e.type,
};
});
res.json({ methods: tfa });
})
);
TwoFactorRouter.delete(
"/:id",
Stacker(GetUserMiddleware(true, true), async (req, res) => {
let { id } = req.params;
let tfa = await TwoFactor.findById(id);
if (!tfa || !tfa.user.equals(req.user._id)) {
throw new RequestError("Invalid id", HttpStatusCode.BAD_REQUEST);
}
tfa.valid = false;
await TwoFactor.save(tfa);
res.json({ success: true });
})
);
TwoFactorRouter.use("/yubikey", YubiKeyRoute);
TwoFactorRouter.use("/otc", OTCRoute);
TwoFactorRouter.use("/backup", BackupCodeRoute);
export default TwoFactorRouter;
import { Router } from "express";
import YubiKeyRoute from "./yubikey";
import { GetUserMiddleware } from "../../middlewares/user";
import Stacker from "../../middlewares/stacker";
import TwoFactor from "../../../models/twofactor";
import * as moment from "moment";
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
import OTCRoute from "./otc";
import BackupCodeRoute from "./backup";
const TwoFactorRouter = Router();
TwoFactorRouter.get(
"/",
Stacker(GetUserMiddleware(true, true), async (req, res) => {
let twofactor = await TwoFactor.find({ user: req.user._id, valid: true });
let expired = twofactor.filter((e) =>
e.expires ? moment().isAfter(moment(e.expires)) : false
);
await Promise.all(
expired.map((e) => {
e.valid = false;
return TwoFactor.save(e);
})
);
twofactor = twofactor.filter((e) => e.valid);
let tfa = twofactor.map((e) => {
return {
id: e._id,
name: e.name,
type: e.type,
};
});
res.json({ methods: tfa });
})
);
TwoFactorRouter.delete(
"/:id",
Stacker(GetUserMiddleware(true, true), async (req, res) => {
let { id } = req.params;
let tfa = await TwoFactor.findById(id);
if (!tfa || !tfa.user.equals(req.user._id)) {
throw new RequestError("Invalid id", HttpStatusCode.BAD_REQUEST);
}
tfa.valid = false;
await TwoFactor.save(tfa);
res.json({ success: true });
})
);
TwoFactorRouter.use("/yubikey", YubiKeyRoute);
TwoFactorRouter.use("/otc", OTCRoute);
TwoFactorRouter.use("/backup", BackupCodeRoute);
export default TwoFactorRouter;

View File

@ -1,135 +1,135 @@
import { Router } from "express";
import Stacker from "../../../middlewares/stacker";
import { GetUserMiddleware } from "../../../middlewares/user";
import TwoFactor, {
TFATypes as TwoFATypes,
IOTC,
} from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import { upgradeToken } from "../helper";
import Logging from "@hibas123/nodelogging";
import * as speakeasy from "speakeasy";
import * as qrcode from "qrcode";
import config from "../../../../config";
const OTCRoute = Router();
OTCRoute.post(
"/",
Stacker(GetUserMiddleware(true, true), async (req, res) => {
const { type } = req.query;
if (type === "create") {
//Generating new
let secret = speakeasy.generateSecret({
name: config.core.name,
issuer: config.core.name,
});
let twofactor = TwoFactor.new(<IOTC>{
user: req.user._id,
type: TwoFATypes.OTC,
valid: false,
data: secret.base32,
});
let dataurl = await qrcode.toDataURL(secret.otpauth_url);
await TwoFactor.save(twofactor);
res.json({
image: dataurl,
id: twofactor._id,
});
} else if (type === "validate") {
// Checking code and marking as valid
const { code, id } = req.body;
Logging.debug(req.body, id);
let twofactor: IOTC = await TwoFactor.findById(id);
const err = () => {
throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST);
};
if (
!twofactor ||
!twofactor.user.equals(req.user._id) ||
twofactor.type !== TwoFATypes.OTC ||
!twofactor.data ||
twofactor.valid
) {
Logging.debug("Not found or wrong user", twofactor);
err();
}
if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) {
await TwoFactor.delete(twofactor);
Logging.debug("Expired!", twofactor);
err();
}
let valid = speakeasy.totp.verify({
secret: twofactor.data,
encoding: "base32",
token: code,
});
if (valid) {
twofactor.expires = undefined;
twofactor.valid = true;
await TwoFactor.save(twofactor);
res.json({ success: true });
} else {
throw new RequestError("Invalid Code!", HttpStatusCode.BAD_REQUEST);
}
} else {
throw new RequestError("Invalid type", HttpStatusCode.BAD_REQUEST);
}
})
);
OTCRoute.put(
"/",
Stacker(
GetUserMiddleware(true, false, undefined, false),
async (req, res) => {
let { login, special } = req.token;
let { id, code } = req.body;
let twofactor: IOTC = await TwoFactor.findById(id);
if (
!twofactor ||
!twofactor.valid ||
!twofactor.user.equals(req.user._id) ||
twofactor.type !== TwoFATypes.OTC
) {
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
let valid = speakeasy.totp.verify({
secret: twofactor.data,
encoding: "base32",
token: code,
});
if (valid) {
let [login_exp, special_exp] = await Promise.all([
upgradeToken(login),
upgradeToken(special),
]);
res.json({ success: true, login_exp, special_exp });
} else {
throw new RequestError("Invalid Code", HttpStatusCode.BAD_REQUEST);
}
}
)
);
export default OTCRoute;
import { Router } from "express";
import Stacker from "../../../middlewares/stacker";
import { GetUserMiddleware } from "../../../middlewares/user";
import TwoFactor, {
TFATypes as TwoFATypes,
IOTC,
} from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import { upgradeToken } from "../helper";
import Logging from "@hibas123/nodelogging";
import * as speakeasy from "speakeasy";
import * as qrcode from "qrcode";
import config from "../../../../config";
const OTCRoute = Router();
OTCRoute.post(
"/",
Stacker(GetUserMiddleware(true, true), async (req, res) => {
const { type } = req.query;
if (type === "create") {
//Generating new
let secret = speakeasy.generateSecret({
name: config.core.name,
issuer: config.core.name,
});
let twofactor = TwoFactor.new(<IOTC>{
user: req.user._id,
type: TwoFATypes.OTC,
valid: false,
data: secret.base32,
});
let dataurl = await qrcode.toDataURL(secret.otpauth_url);
await TwoFactor.save(twofactor);
res.json({
image: dataurl,
id: twofactor._id,
});
} else if (type === "validate") {
// Checking code and marking as valid
const { code, id } = req.body;
Logging.debug(req.body, id);
let twofactor: IOTC = await TwoFactor.findById(id);
const err = () => {
throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST);
};
if (
!twofactor ||
!twofactor.user.equals(req.user._id) ||
twofactor.type !== TwoFATypes.OTC ||
!twofactor.data ||
twofactor.valid
) {
Logging.debug("Not found or wrong user", twofactor);
err();
}
if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) {
await TwoFactor.delete(twofactor);
Logging.debug("Expired!", twofactor);
err();
}
let valid = speakeasy.totp.verify({
secret: twofactor.data,
encoding: "base32",
token: code,
});
if (valid) {
twofactor.expires = undefined;
twofactor.valid = true;
await TwoFactor.save(twofactor);
res.json({ success: true });
} else {
throw new RequestError("Invalid Code!", HttpStatusCode.BAD_REQUEST);
}
} else {
throw new RequestError("Invalid type", HttpStatusCode.BAD_REQUEST);
}
})
);
OTCRoute.put(
"/",
Stacker(
GetUserMiddleware(true, false, undefined, false),
async (req, res) => {
let { login, special } = req.token;
let { id, code } = req.body;
let twofactor: IOTC = await TwoFactor.findById(id);
if (
!twofactor ||
!twofactor.valid ||
!twofactor.user.equals(req.user._id) ||
twofactor.type !== TwoFATypes.OTC
) {
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
let valid = speakeasy.totp.verify({
secret: twofactor.data,
encoding: "base32",
token: code,
});
if (valid) {
let [login_exp, special_exp] = await Promise.all([
upgradeToken(login),
upgradeToken(special),
]);
res.json({ success: true, login_exp, special_exp });
} else {
throw new RequestError("Invalid Code", HttpStatusCode.BAD_REQUEST);
}
}
)
);
export default OTCRoute;

View File

@ -1,206 +1,206 @@
import { Router, Request } from "express";
import Stacker from "../../../middlewares/stacker";
import { UserMiddleware, GetUserMiddleware } from "../../../middlewares/user";
import * as u2f from "u2f";
import config from "../../../../config";
import TwoFactor, {
TFATypes as TwoFATypes,
IYubiKey,
} from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import LoginToken from "../../../../models/login_token";
import { upgradeToken } from "../helper";
import Logging from "@hibas123/nodelogging";
const U2FRoute = Router();
/**
* Registerinf a new YubiKey
*/
U2FRoute.post(
"/",
Stacker(GetUserMiddleware(true, true), async (req, res) => {
const { type } = req.query;
if (type === "challenge") {
const registrationRequest = u2f.request(config.core.url);
let twofactor = TwoFactor.new(<IYubiKey>{
user: req.user._id,
type: TwoFATypes.U2F,
valid: false,
data: {
registration: registrationRequest,
},
});
await TwoFactor.save(twofactor);
res.json({
request: registrationRequest,
id: twofactor._id,
appid: config.core.url,
});
} else {
const { response, id } = req.body;
Logging.debug(req.body, id);
let twofactor: IYubiKey = await TwoFactor.findById(id);
const err = () => {
throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST);
};
if (
!twofactor ||
!twofactor.user.equals(req.user._id) ||
twofactor.type !== TwoFATypes.U2F ||
!twofactor.data.registration ||
twofactor.valid
) {
Logging.debug("Not found or wrong user", twofactor);
err();
}
if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) {
await TwoFactor.delete(twofactor);
Logging.debug("Expired!", twofactor);
err();
}
const result = u2f.checkRegistration(
twofactor.data.registration,
response
);
if (result.successful) {
twofactor.data = {
keyHandle: result.keyHandle,
publicKey: result.publicKey,
};
twofactor.expires = undefined;
twofactor.valid = true;
await TwoFactor.save(twofactor);
res.json({ success: true });
} else {
throw new RequestError(
result.errorMessage,
HttpStatusCode.BAD_REQUEST
);
}
}
})
);
U2FRoute.get(
"/",
Stacker(
GetUserMiddleware(true, false, undefined, false),
async (req, res) => {
let { login, special } = req.token;
let twofactor: IYubiKey = await TwoFactor.findOne({
user: req.user._id,
type: TwoFATypes.U2F,
valid: true,
});
if (!twofactor) {
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
if (twofactor.expires) {
if (moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
}
let request = u2f.request(config.core.url, twofactor.data.keyHandle);
login.data = {
type: "ykr",
request,
};
let r;
if (special) {
special.data = login.data;
r = LoginToken.save(special);
}
await Promise.all([r, LoginToken.save(login)]);
res.json({ request });
}
)
);
U2FRoute.put(
"/",
Stacker(
GetUserMiddleware(true, false, undefined, false),
async (req, res) => {
let { login, special } = req.token;
let twofactor: IYubiKey = await TwoFactor.findOne({
user: req.user._id,
type: TwoFATypes.U2F,
valid: true,
});
let { response } = req.body;
if (
!twofactor ||
!login.data ||
login.data.type !== "ykr" ||
(special && (!special.data || special.data.type !== "ykr"))
) {
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
let login_exp;
let special_exp;
let result = u2f.checkSignature(
login.data.request,
response,
twofactor.data.publicKey
);
if (result.successful) {
if (special) {
let result = u2f.checkSignature(
special.data.request,
response,
twofactor.data.publicKey
);
if (result.successful) {
special_exp = await upgradeToken(special);
} else {
throw new RequestError(
result.errorMessage,
HttpStatusCode.BAD_REQUEST
);
}
}
login_exp = await upgradeToken(login);
} else {
throw new RequestError(
result.errorMessage,
HttpStatusCode.BAD_REQUEST
);
}
res.json({ success: true, login_exp, special_exp });
}
)
);
export default U2FRoute;
import { Router, Request } from "express";
import Stacker from "../../../middlewares/stacker";
import { UserMiddleware, GetUserMiddleware } from "../../../middlewares/user";
import * as u2f from "u2f";
import config from "../../../../config";
import TwoFactor, {
TFATypes as TwoFATypes,
IYubiKey,
} from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import LoginToken from "../../../../models/login_token";
import { upgradeToken } from "../helper";
import Logging from "@hibas123/nodelogging";
const U2FRoute = Router();
/**
* Registerinf a new YubiKey
*/
U2FRoute.post(
"/",
Stacker(GetUserMiddleware(true, true), async (req, res) => {
const { type } = req.query;
if (type === "challenge") {
const registrationRequest = u2f.request(config.core.url);
let twofactor = TwoFactor.new(<IYubiKey>{
user: req.user._id,
type: TwoFATypes.U2F,
valid: false,
data: {
registration: registrationRequest,
},
});
await TwoFactor.save(twofactor);
res.json({
request: registrationRequest,
id: twofactor._id,
appid: config.core.url,
});
} else {
const { response, id } = req.body;
Logging.debug(req.body, id);
let twofactor: IYubiKey = await TwoFactor.findById(id);
const err = () => {
throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST);
};
if (
!twofactor ||
!twofactor.user.equals(req.user._id) ||
twofactor.type !== TwoFATypes.U2F ||
!twofactor.data.registration ||
twofactor.valid
) {
Logging.debug("Not found or wrong user", twofactor);
err();
}
if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) {
await TwoFactor.delete(twofactor);
Logging.debug("Expired!", twofactor);
err();
}
const result = u2f.checkRegistration(
twofactor.data.registration,
response
);
if (result.successful) {
twofactor.data = {
keyHandle: result.keyHandle,
publicKey: result.publicKey,
};
twofactor.expires = undefined;
twofactor.valid = true;
await TwoFactor.save(twofactor);
res.json({ success: true });
} else {
throw new RequestError(
result.errorMessage,
HttpStatusCode.BAD_REQUEST
);
}
}
})
);
U2FRoute.get(
"/",
Stacker(
GetUserMiddleware(true, false, undefined, false),
async (req, res) => {
let { login, special } = req.token;
let twofactor: IYubiKey = await TwoFactor.findOne({
user: req.user._id,
type: TwoFATypes.U2F,
valid: true,
});
if (!twofactor) {
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
if (twofactor.expires) {
if (moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
}
let request = u2f.request(config.core.url, twofactor.data.keyHandle);
login.data = {
type: "ykr",
request,
};
let r;
if (special) {
special.data = login.data;
r = LoginToken.save(special);
}
await Promise.all([r, LoginToken.save(login)]);
res.json({ request });
}
)
);
U2FRoute.put(
"/",
Stacker(
GetUserMiddleware(true, false, undefined, false),
async (req, res) => {
let { login, special } = req.token;
let twofactor: IYubiKey = await TwoFactor.findOne({
user: req.user._id,
type: TwoFATypes.U2F,
valid: true,
});
let { response } = req.body;
if (
!twofactor ||
!login.data ||
login.data.type !== "ykr" ||
(special && (!special.data || special.data.type !== "ykr"))
) {
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
twofactor.valid = false;
await TwoFactor.save(twofactor);
throw new RequestError(
"Invalid Method!",
HttpStatusCode.BAD_REQUEST
);
}
let login_exp;
let special_exp;
let result = u2f.checkSignature(
login.data.request,
response,
twofactor.data.publicKey
);
if (result.successful) {
if (special) {
let result = u2f.checkSignature(
special.data.request,
response,
twofactor.data.publicKey
);
if (result.successful) {
special_exp = await upgradeToken(special);
} else {
throw new RequestError(
result.errorMessage,
HttpStatusCode.BAD_REQUEST
);
}
}
login_exp = await upgradeToken(login);
} else {
throw new RequestError(
result.errorMessage,
HttpStatusCode.BAD_REQUEST
);
}
res.json({ success: true, login_exp, special_exp });
}
)
);
export default U2FRoute;

View File

@ -1,75 +1,75 @@
import { parse } from "@hibas123/config";
import { Logging } from "@hibas123/nodelogging";
import * as dotenv from "dotenv";
import moment = require("moment");
export const refreshTokenValidTime = moment.duration(6, "month");
dotenv.config();
export interface DatabaseConfig {
host: string;
database: string;
}
export interface WebConfig {
port: string;
secure: "true" | "false" | undefined;
}
export interface CoreConfig {
name: string;
url: string;
dev: boolean;
}
export interface Config {
core: CoreConfig;
database: DatabaseConfig;
web: WebConfig;
}
const config = (parse(
{
core: {
dev: {
default: false,
type: Boolean,
},
name: {
type: String,
default: "Open Auth",
},
url: String,
},
database: {
database: {
type: String,
default: "openauth",
},
host: {
type: String,
default: "localhost",
},
},
web: {
port: {
type: Number,
default: 3004,
},
secure: {
type: Boolean,
default: false,
},
},
},
"config.ini"
) as any) as Config;
if (process.env.DEV === "true") config.core.dev = true;
if (config.core.dev)
Logging.warning(
"DEV mode active. This can cause major performance issues, data loss and vulnerabilities! "
);
export default config;
import { parse } from "@hibas123/config";
import Logging from "@hibas123/nodelogging";
import * as dotenv from "dotenv";
import moment = require("moment");
export const refreshTokenValidTime = moment.duration(6, "month");
dotenv.config();
export interface DatabaseConfig {
host: string;
database: string;
}
export interface WebConfig {
port: string;
secure: "true" | "false" | undefined;
}
export interface CoreConfig {
name: string;
url: string;
dev: boolean;
}
export interface Config {
core: CoreConfig;
database: DatabaseConfig;
web: WebConfig;
}
const config = (parse(
{
core: {
dev: {
default: false,
type: Boolean,
},
name: {
type: String,
default: "Open Auth",
},
url: String,
},
database: {
database: {
type: String,
default: "openauth",
},
host: {
type: String,
default: "localhost",
},
},
web: {
port: {
type: Number,
default: 3004,
},
secure: {
type: Boolean,
default: false,
},
},
},
"config.ini"
) as any) as Config;
if (process.env.DEV === "true") config.core.dev = true;
if (config.core.dev)
Logging.warning(
"DEV mode active. This can cause major performance issues, data loss and vulnerabilities! "
);
export default config;

View File

@ -1,13 +1,13 @@
import SafeMongo from "@hibas123/safe_mongo";
import Config from "./config";
let dbname = "openauth";
let host = "localhost";
if (Config.database) {
if (Config.database.database) dbname = Config.database.database;
if (Config.database.host) host = Config.database.host;
}
if (Config.core.dev) dbname += "_dev";
const DB = new SafeMongo("mongodb://" + host, dbname, {
useUnifiedTopology: true,
});
export default DB;
import SafeMongo from "@hibas123/safe_mongo";
import Config from "./config";
let dbname = "openauth";
let host = "localhost";
if (Config.database) {
if (Config.database.database) dbname = Config.database.database;
if (Config.database.host) host = Config.database.host;
}
if (Config.core.dev) dbname += "_dev";
const DB = new SafeMongo("mongodb://" + host, dbname, {
useUnifiedTopology: true,
});
export default DB;

View File

@ -1,390 +1,390 @@
/**
* Hypertext Transfer Protocol (HTTP) response status codes.
* @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
*/
export enum HttpStatusCode {
/**
* The server has received the request headers and the client should proceed to send the request body
* (in the case of a request for which a body needs to be sent; for example, a POST request).
* Sending a large request body to a server after a request has been rejected for inappropriate headers would be inefficient.
* To have a server check the request's headers, a client must send Expect: 100-continue as a header in its initial request
* and receive a 100 Continue status code in response before sending the body. The response 417 Expectation Failed indicates the request should not be continued.
*/
CONTINUE = 100,
/**
* The requester has asked the server to switch protocols and the server has agreed to do so.
*/
SWITCHING_PROTOCOLS = 101,
/**
* A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request.
* This code indicates that the server has received and is processing the request, but no response is available yet.
* This prevents the client from timing out and assuming the request was lost.
*/
PROCESSING = 102,
/**
* Standard response for successful HTTP requests.
* The actual response will depend on the request method used.
* In a GET request, the response will contain an entity corresponding to the requested resource.
* In a POST request, the response will contain an entity describing or containing the result of the action.
*/
OK = 200,
/**
* The request has been fulfilled, resulting in the creation of a new resource.
*/
CREATED = 201,
/**
* The request has been accepted for processing, but the processing has not been completed.
* The request might or might not be eventually acted upon, and may be disallowed when processing occurs.
*/
ACCEPTED = 202,
/**
* SINCE HTTP/1.1
* The server is a transforming proxy that received a 200 OK from its origin,
* but is returning a modified version of the origin's response.
*/
NON_AUTHORITATIVE_INFORMATION = 203,
/**
* The server successfully processed the request and is not returning any content.
*/
NO_CONTENT = 204,
/**
* The server successfully processed the request, but is not returning any content.
* Unlike a 204 response, this response requires that the requester reset the document view.
*/
RESET_CONTENT = 205,
/**
* The server is delivering only part of the resource (byte serving) due to a range header sent by the client.
* The range header is used by HTTP clients to enable resuming of interrupted downloads,
* or split a download into multiple simultaneous streams.
*/
PARTIAL_CONTENT = 206,
/**
* The message body that follows is an XML message and can contain a number of separate response codes,
* depending on how many sub-requests were made.
*/
MULTI_STATUS = 207,
/**
* The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response,
* and are not being included again.
*/
ALREADY_REPORTED = 208,
/**
* The server has fulfilled a request for the resource,
* and the response is a representation of the result of one or more instance-manipulations applied to the current instance.
*/
IM_USED = 226,
/**
* Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation).
* For example, this code could be used to present multiple video format options,
* to list files with different filename extensions, or to suggest word-sense disambiguation.
*/
MULTIPLE_CHOICES = 300,
/**
* This and all future requests should be directed to the given URI.
*/
MOVED_PERMANENTLY = 301,
/**
* This is an example of industry practice contradicting the standard.
* The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect
* (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302
* with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307
* to distinguish between the two behaviours. However, some Web applications and frameworks
* use the 302 status code as if it were the 303.
*/
FOUND = 302,
/**
* SINCE HTTP/1.1
* The response to the request can be found under another URI using a GET method.
* When received in response to a POST (or PUT/DELETE), the client should presume that
* the server has received the data and should issue a redirect with a separate GET message.
*/
SEE_OTHER = 303,
/**
* Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.
* In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy.
*/
NOT_MODIFIED = 304,
/**
* SINCE HTTP/1.1
* The requested resource is available only through a proxy, the address for which is provided in the response.
* Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly handle responses with this status code, primarily for security reasons.
*/
USE_PROXY = 305,
/**
* No longer used. Originally meant "Subsequent requests should use the specified proxy."
*/
SWITCH_PROXY = 306,
/**
* SINCE HTTP/1.1
* In this case, the request should be repeated with another URI; however, future requests should still use the original URI.
* In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request.
* For example, a POST request should be repeated using another POST request.
*/
TEMPORARY_REDIRECT = 307,
/**
* The request and all future requests should be repeated using another URI.
* 307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP method to change.
* So, for example, submitting a form to a permanently redirected resource may continue smoothly.
*/
PERMANENT_REDIRECT = 308,
/**
* The server cannot or will not process the request due to an apparent client error
* (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing).
*/
BAD_REQUEST = 400,
/**
* Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet
* been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the
* requested resource. See Basic access authentication and Digest access authentication. 401 semantically means
* "unauthenticated",i.e. the user does not have the necessary credentials.
*/
UNAUTHORIZED = 401,
/**
* Reserved for future use. The original intention was that this code might be used as part of some form of digital
* cash or micro payment scheme, but that has not happened, and this code is not usually used.
* Google Developers API uses this status if a particular developer has exceeded the daily limit on requests.
*/
PAYMENT_REQUIRED = 402,
/**
* The request was valid, but the server is refusing action.
* The user might not have the necessary permissions for a resource.
*/
FORBIDDEN = 403,
/**
* The requested resource could not be found but may be available in the future.
* Subsequent requests by the client are permissible.
*/
NOT_FOUND = 404,
/**
* A request method is not supported for the requested resource;
* for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource.
*/
METHOD_NOT_ALLOWED = 405,
/**
* The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.
*/
NOT_ACCEPTABLE = 406,
/**
* The client must first authenticate itself with the proxy.
*/
PROXY_AUTHENTICATION_REQUIRED = 407,
/**
* The server timed out waiting for the request.
* According to HTTP specifications:
* "The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time."
*/
REQUEST_TIMEOUT = 408,
/**
* Indicates that the request could not be processed because of conflict in the request,
* such as an edit conflict between multiple simultaneous updates.
*/
CONFLICT = 409,
/**
* Indicates that the resource requested is no longer available and will not be available again.
* This should be used when a resource has been intentionally removed and the resource should be purged.
* Upon receiving a 410 status code, the client should not request the resource in the future.
* Clients such as search engines should remove the resource from their indices.
* Most use cases do not require clients and search engines to purge the resource, and a "404 Not Found" may be used instead.
*/
GONE = 410,
/**
* The request did not specify the length of its content, which is required by the requested resource.
*/
LENGTH_REQUIRED = 411,
/**
* The server does not meet one of the preconditions that the requester put on the request.
*/
PRECONDITION_FAILED = 412,
/**
* The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".
*/
PAYLOAD_TOO_LARGE = 413,
/**
* The URI provided was too long for the server to process. Often the result of too much data being encoded as a query-string of a GET request,
* in which case it should be converted to a POST request.
* Called "Request-URI Too Long" previously.
*/
URI_TOO_LONG = 414,
/**
* The request entity has a media type which the server or resource does not support.
* For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format.
*/
UNSUPPORTED_MEDIA_TYPE = 415,
/**
* The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.
* For example, if the client asked for a part of the file that lies beyond the end of the file.
* Called "Requested Range Not Satisfiable" previously.
*/
RANGE_NOT_SATISFIABLE = 416,
/**
* The server cannot meet the requirements of the Expect request-header field.
*/
EXPECTATION_FAILED = 417,
/**
* This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol,
* and is not expected to be implemented by actual HTTP servers. The RFC specifies this code should be returned by
* teapots requested to brew coffee. This HTTP status is used as an Easter egg in some websites, including Google.com.
*/
I_AM_A_TEAPOT = 418,
/**
* The request was directed at a server that is not able to produce a response (for example because a connection reuse).
*/
MISDIRECTED_REQUEST = 421,
/**
* The request was well-formed but was unable to be followed due to semantic errors.
*/
UNPROCESSABLE_ENTITY = 422,
/**
* The resource that is being accessed is locked.
*/
LOCKED = 423,
/**
* The request failed due to failure of a previous request (e.g., a PROPPATCH).
*/
FAILED_DEPENDENCY = 424,
/**
* The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.
*/
UPGRADE_REQUIRED = 426,
/**
* The origin server requires the request to be conditional.
* Intended to prevent "the 'lost update' problem, where a client
* GETs a resource's state, modifies it, and PUTs it back to the server,
* when meanwhile a third party has modified the state on the server, leading to a conflict."
*/
PRECONDITION_REQUIRED = 428,
/**
* The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes.
*/
TOO_MANY_REQUESTS = 429,
/**
* The server is unwilling to process the request because either an individual header field,
* or all the header fields collectively, are too large.
*/
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
/**
* A server operator has received a legal demand to deny access to a resource or to a set of resources
* that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451.
*/
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
/**
* A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
*/
INTERNAL_SERVER_ERROR = 500,
/**
* The server either does not recognize the request method, or it lacks the ability to fulfill the request.
* Usually this implies future availability (e.g., a new feature of a web-service API).
*/
NOT_IMPLEMENTED = 501,
/**
* The server was acting as a gateway or proxy and received an invalid response from the upstream server.
*/
BAD_GATEWAY = 502,
/**
* The server is currently unavailable (because it is overloaded or down for maintenance).
* Generally, this is a temporary state.
*/
SERVICE_UNAVAILABLE = 503,
/**
* The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
*/
GATEWAY_TIMEOUT = 504,
/**
* The server does not support the HTTP protocol version used in the request
*/
HTTP_VERSION_NOT_SUPPORTED = 505,
/**
* Transparent content negotiation for the request results in a circular reference.
*/
VARIANT_ALSO_NEGOTIATES = 506,
/**
* The server is unable to store the representation needed to complete the request.
*/
INSUFFICIENT_STORAGE = 507,
/**
* The server detected an infinite loop while processing the request.
*/
LOOP_DETECTED = 508,
/**
* Further extensions to the request are required for the server to fulfill it.
*/
NOT_EXTENDED = 510,
/**
* The client needs to authenticate to gain network access.
* Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used
* to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot).
*/
NETWORK_AUTHENTICATION_REQUIRED = 511,
}
export default class RequestError extends Error {
constructor(
message: any,
public status: HttpStatusCode,
public nolog: boolean = false,
public additional: any = undefined
) {
super("");
this.message = message;
}
}
/**
* Hypertext Transfer Protocol (HTTP) response status codes.
* @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
*/
export enum HttpStatusCode {
/**
* The server has received the request headers and the client should proceed to send the request body
* (in the case of a request for which a body needs to be sent; for example, a POST request).
* Sending a large request body to a server after a request has been rejected for inappropriate headers would be inefficient.
* To have a server check the request's headers, a client must send Expect: 100-continue as a header in its initial request
* and receive a 100 Continue status code in response before sending the body. The response 417 Expectation Failed indicates the request should not be continued.
*/
CONTINUE = 100,
/**
* The requester has asked the server to switch protocols and the server has agreed to do so.
*/
SWITCHING_PROTOCOLS = 101,
/**
* A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request.
* This code indicates that the server has received and is processing the request, but no response is available yet.
* This prevents the client from timing out and assuming the request was lost.
*/
PROCESSING = 102,
/**
* Standard response for successful HTTP requests.
* The actual response will depend on the request method used.
* In a GET request, the response will contain an entity corresponding to the requested resource.
* In a POST request, the response will contain an entity describing or containing the result of the action.
*/
OK = 200,
/**
* The request has been fulfilled, resulting in the creation of a new resource.
*/
CREATED = 201,
/**
* The request has been accepted for processing, but the processing has not been completed.
* The request might or might not be eventually acted upon, and may be disallowed when processing occurs.
*/
ACCEPTED = 202,
/**
* SINCE HTTP/1.1
* The server is a transforming proxy that received a 200 OK from its origin,
* but is returning a modified version of the origin's response.
*/
NON_AUTHORITATIVE_INFORMATION = 203,
/**
* The server successfully processed the request and is not returning any content.
*/
NO_CONTENT = 204,
/**
* The server successfully processed the request, but is not returning any content.
* Unlike a 204 response, this response requires that the requester reset the document view.
*/
RESET_CONTENT = 205,
/**
* The server is delivering only part of the resource (byte serving) due to a range header sent by the client.
* The range header is used by HTTP clients to enable resuming of interrupted downloads,
* or split a download into multiple simultaneous streams.
*/
PARTIAL_CONTENT = 206,
/**
* The message body that follows is an XML message and can contain a number of separate response codes,
* depending on how many sub-requests were made.
*/
MULTI_STATUS = 207,
/**
* The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response,
* and are not being included again.
*/
ALREADY_REPORTED = 208,
/**
* The server has fulfilled a request for the resource,
* and the response is a representation of the result of one or more instance-manipulations applied to the current instance.
*/
IM_USED = 226,
/**
* Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation).
* For example, this code could be used to present multiple video format options,
* to list files with different filename extensions, or to suggest word-sense disambiguation.
*/
MULTIPLE_CHOICES = 300,
/**
* This and all future requests should be directed to the given URI.
*/
MOVED_PERMANENTLY = 301,
/**
* This is an example of industry practice contradicting the standard.
* The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect
* (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302
* with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307
* to distinguish between the two behaviours. However, some Web applications and frameworks
* use the 302 status code as if it were the 303.
*/
FOUND = 302,
/**
* SINCE HTTP/1.1
* The response to the request can be found under another URI using a GET method.
* When received in response to a POST (or PUT/DELETE), the client should presume that
* the server has received the data and should issue a redirect with a separate GET message.
*/
SEE_OTHER = 303,
/**
* Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.
* In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy.
*/
NOT_MODIFIED = 304,
/**
* SINCE HTTP/1.1
* The requested resource is available only through a proxy, the address for which is provided in the response.
* Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly handle responses with this status code, primarily for security reasons.
*/
USE_PROXY = 305,
/**
* No longer used. Originally meant "Subsequent requests should use the specified proxy."
*/
SWITCH_PROXY = 306,
/**
* SINCE HTTP/1.1
* In this case, the request should be repeated with another URI; however, future requests should still use the original URI.
* In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request.
* For example, a POST request should be repeated using another POST request.
*/
TEMPORARY_REDIRECT = 307,
/**
* The request and all future requests should be repeated using another URI.
* 307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP method to change.
* So, for example, submitting a form to a permanently redirected resource may continue smoothly.
*/
PERMANENT_REDIRECT = 308,
/**
* The server cannot or will not process the request due to an apparent client error
* (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing).
*/
BAD_REQUEST = 400,
/**
* Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet
* been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the
* requested resource. See Basic access authentication and Digest access authentication. 401 semantically means
* "unauthenticated",i.e. the user does not have the necessary credentials.
*/
UNAUTHORIZED = 401,
/**
* Reserved for future use. The original intention was that this code might be used as part of some form of digital
* cash or micro payment scheme, but that has not happened, and this code is not usually used.
* Google Developers API uses this status if a particular developer has exceeded the daily limit on requests.
*/
PAYMENT_REQUIRED = 402,
/**
* The request was valid, but the server is refusing action.
* The user might not have the necessary permissions for a resource.
*/
FORBIDDEN = 403,
/**
* The requested resource could not be found but may be available in the future.
* Subsequent requests by the client are permissible.
*/
NOT_FOUND = 404,
/**
* A request method is not supported for the requested resource;
* for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource.
*/
METHOD_NOT_ALLOWED = 405,
/**
* The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.
*/
NOT_ACCEPTABLE = 406,
/**
* The client must first authenticate itself with the proxy.
*/
PROXY_AUTHENTICATION_REQUIRED = 407,
/**
* The server timed out waiting for the request.
* According to HTTP specifications:
* "The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time."
*/
REQUEST_TIMEOUT = 408,
/**
* Indicates that the request could not be processed because of conflict in the request,
* such as an edit conflict between multiple simultaneous updates.
*/
CONFLICT = 409,
/**
* Indicates that the resource requested is no longer available and will not be available again.
* This should be used when a resource has been intentionally removed and the resource should be purged.
* Upon receiving a 410 status code, the client should not request the resource in the future.
* Clients such as search engines should remove the resource from their indices.
* Most use cases do not require clients and search engines to purge the resource, and a "404 Not Found" may be used instead.
*/
GONE = 410,
/**
* The request did not specify the length of its content, which is required by the requested resource.
*/
LENGTH_REQUIRED = 411,
/**
* The server does not meet one of the preconditions that the requester put on the request.
*/
PRECONDITION_FAILED = 412,
/**
* The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".
*/
PAYLOAD_TOO_LARGE = 413,
/**
* The URI provided was too long for the server to process. Often the result of too much data being encoded as a query-string of a GET request,
* in which case it should be converted to a POST request.
* Called "Request-URI Too Long" previously.
*/
URI_TOO_LONG = 414,
/**
* The request entity has a media type which the server or resource does not support.
* For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format.
*/
UNSUPPORTED_MEDIA_TYPE = 415,
/**
* The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.
* For example, if the client asked for a part of the file that lies beyond the end of the file.
* Called "Requested Range Not Satisfiable" previously.
*/
RANGE_NOT_SATISFIABLE = 416,
/**
* The server cannot meet the requirements of the Expect request-header field.
*/
EXPECTATION_FAILED = 417,
/**
* This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol,
* and is not expected to be implemented by actual HTTP servers. The RFC specifies this code should be returned by
* teapots requested to brew coffee. This HTTP status is used as an Easter egg in some websites, including Google.com.
*/
I_AM_A_TEAPOT = 418,
/**
* The request was directed at a server that is not able to produce a response (for example because a connection reuse).
*/
MISDIRECTED_REQUEST = 421,
/**
* The request was well-formed but was unable to be followed due to semantic errors.
*/
UNPROCESSABLE_ENTITY = 422,
/**
* The resource that is being accessed is locked.
*/
LOCKED = 423,
/**
* The request failed due to failure of a previous request (e.g., a PROPPATCH).
*/
FAILED_DEPENDENCY = 424,
/**
* The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.
*/
UPGRADE_REQUIRED = 426,
/**
* The origin server requires the request to be conditional.
* Intended to prevent "the 'lost update' problem, where a client
* GETs a resource's state, modifies it, and PUTs it back to the server,
* when meanwhile a third party has modified the state on the server, leading to a conflict."
*/
PRECONDITION_REQUIRED = 428,
/**
* The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes.
*/
TOO_MANY_REQUESTS = 429,
/**
* The server is unwilling to process the request because either an individual header field,
* or all the header fields collectively, are too large.
*/
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
/**
* A server operator has received a legal demand to deny access to a resource or to a set of resources
* that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451.
*/
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
/**
* A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
*/
INTERNAL_SERVER_ERROR = 500,
/**
* The server either does not recognize the request method, or it lacks the ability to fulfill the request.
* Usually this implies future availability (e.g., a new feature of a web-service API).
*/
NOT_IMPLEMENTED = 501,
/**
* The server was acting as a gateway or proxy and received an invalid response from the upstream server.
*/
BAD_GATEWAY = 502,
/**
* The server is currently unavailable (because it is overloaded or down for maintenance).
* Generally, this is a temporary state.
*/
SERVICE_UNAVAILABLE = 503,
/**
* The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
*/
GATEWAY_TIMEOUT = 504,
/**
* The server does not support the HTTP protocol version used in the request
*/
HTTP_VERSION_NOT_SUPPORTED = 505,
/**
* Transparent content negotiation for the request results in a circular reference.
*/
VARIANT_ALSO_NEGOTIATES = 506,
/**
* The server is unable to store the representation needed to complete the request.
*/
INSUFFICIENT_STORAGE = 507,
/**
* The server detected an infinite loop while processing the request.
*/
LOOP_DETECTED = 508,
/**
* Further extensions to the request are required for the server to fulfill it.
*/
NOT_EXTENDED = 510,
/**
* The client needs to authenticate to gain network access.
* Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used
* to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot).
*/
NETWORK_AUTHENTICATION_REQUIRED = 511,
}
export default class RequestError extends Error {
constructor(
message: any,
public status: HttpStatusCode,
public nolog: boolean = false,
public additional: any = undefined
) {
super("");
this.message = message;
}
}

View File

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

View File

@ -1,76 +1,76 @@
import DB from "../database";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
import { ObjectID } from "mongodb";
import moment = require("moment");
export interface ILoginToken extends ModelDataBase {
token: string;
special: boolean;
user: ObjectID;
validTill: Date;
valid: boolean;
validated: boolean;
data: any;
ip: string;
browser: string;
}
const LoginToken = DB.addModel<ILoginToken>({
name: "login_token",
versions: [
{
migration: () => {},
schema: {
token: { type: String },
special: { type: Boolean, default: () => false },
user: { type: ObjectID },
validTill: { type: Date },
valid: { type: Boolean },
},
},
{
migration: (doc: ILoginToken) => {
doc.validated = true;
},
schema: {
token: { type: String },
special: { type: Boolean, default: () => false },
user: { type: ObjectID },
validTill: { type: Date },
valid: { type: Boolean },
validated: { type: Boolean, default: false },
},
},
{
migration: (doc: ILoginToken) => {
doc.validated = true;
},
schema: {
token: { type: String },
special: { type: Boolean, default: () => false },
user: { type: ObjectID },
validTill: { type: Date },
valid: { type: Boolean },
validated: { type: Boolean, default: false },
data: { type: "any", optional: true },
ip: { type: String, optional: true },
browser: { type: String, optional: true },
},
},
],
});
export async function CheckToken(
token: ILoginToken,
validated: boolean = true
): Promise<boolean> {
if (!token || !token.valid) return false;
if (validated && !token.validated) return false;
if (moment().isAfter(token.validTill)) {
token.valid = false;
await LoginToken.save(token);
return false;
}
return true;
}
export default LoginToken;
import DB from "../database";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
import { ObjectID } from "mongodb";
import moment = require("moment");
export interface ILoginToken extends ModelDataBase {
token: string;
special: boolean;
user: ObjectID;
validTill: Date;
valid: boolean;
validated: boolean;
data: any;
ip: string;
browser: string;
}
const LoginToken = DB.addModel<ILoginToken>({
name: "login_token",
versions: [
{
migration: () => {},
schema: {
token: { type: String },
special: { type: Boolean, default: () => false },
user: { type: ObjectID },
validTill: { type: Date },
valid: { type: Boolean },
},
},
{
migration: (doc: ILoginToken) => {
doc.validated = true;
},
schema: {
token: { type: String },
special: { type: Boolean, default: () => false },
user: { type: ObjectID },
validTill: { type: Date },
valid: { type: Boolean },
validated: { type: Boolean, default: false },
},
},
{
migration: (doc: ILoginToken) => {
doc.validated = true;
},
schema: {
token: { type: String },
special: { type: Boolean, default: () => false },
user: { type: ObjectID },
validTill: { type: Date },
valid: { type: Boolean },
validated: { type: Boolean, default: false },
data: { type: "any", optional: true },
ip: { type: String, optional: true },
browser: { type: String, optional: true },
},
},
],
});
export async function CheckToken(
token: ILoginToken,
validated: boolean = true
): Promise<boolean> {
if (!token || !token.valid) return false;
if (validated && !token.validated) return false;
if (moment().isAfter(token.validTill)) {
token.valid = false;
await LoginToken.save(token);
return false;
}
return true;
}
export default LoginToken;

View File

@ -1,69 +1,69 @@
import DB from "../database";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
import { ObjectID } from "bson";
export enum TFATypes {
OTC,
BACKUP_CODE,
U2F,
APP_ALLOW,
}
export const TFANames = new Map<TFATypes, string>();
TFANames.set(TFATypes.OTC, "Authenticator");
TFANames.set(TFATypes.BACKUP_CODE, "Backup Codes");
TFANames.set(TFATypes.U2F, "Security Key (U2F)");
TFANames.set(TFATypes.APP_ALLOW, "App Push");
export interface ITwoFactor extends ModelDataBase {
user: ObjectID;
valid: boolean;
expires?: Date;
name?: string;
type: TFATypes;
data: any;
}
export interface IOTC extends ITwoFactor {
data: string;
}
export interface IYubiKey extends ITwoFactor {
data: {
registration?: any;
publicKey: string;
keyHandle: string;
};
}
export interface IU2F extends ITwoFactor {
data: {
challenge?: string;
publicKey: string;
keyHandle: string;
registration?: string;
};
}
export interface IBackupCode extends ITwoFactor {
data: string[];
}
const TwoFactor = DB.addModel<ITwoFactor>({
name: "twofactor",
versions: [
{
migration: (e) => {},
schema: {
user: { type: ObjectID },
valid: { type: Boolean },
expires: { type: Date, optional: true },
name: { type: String, optional: true },
type: { type: Number },
data: { type: "any" },
},
},
],
});
export default TwoFactor;
import DB from "../database";
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
import { ObjectID } from "bson";
export enum TFATypes {
OTC,
BACKUP_CODE,
U2F,
APP_ALLOW,
}
export const TFANames = new Map<TFATypes, string>();
TFANames.set(TFATypes.OTC, "Authenticator");
TFANames.set(TFATypes.BACKUP_CODE, "Backup Codes");
TFANames.set(TFATypes.U2F, "Security Key (U2F)");
TFANames.set(TFATypes.APP_ALLOW, "App Push");
export interface ITwoFactor extends ModelDataBase {
user: ObjectID;
valid: boolean;
expires?: Date;
name?: string;
type: TFATypes;
data: any;
}
export interface IOTC extends ITwoFactor {
data: string;
}
export interface IYubiKey extends ITwoFactor {
data: {
registration?: any;
publicKey: string;
keyHandle: string;
};
}
export interface IU2F extends ITwoFactor {
data: {
challenge?: string;
publicKey: string;
keyHandle: string;
registration?: string;
};
}
export interface IBackupCode extends ITwoFactor {
data: string[];
}
const TwoFactor = DB.addModel<ITwoFactor>({
name: "twofactor",
versions: [
{
migration: (e) => {},
schema: {
user: { type: ObjectID },
valid: { type: Boolean },
expires: { type: Date, optional: true },
name: { type: String, optional: true },
type: { type: Number },
data: { type: "any" },
},
},
],
});
export default TwoFactor;

View File

@ -1,143 +1,146 @@
import User, { Gender } from "./models/user";
import Client from "./models/client";
import { Logging } from "@hibas123/nodelogging";
import RegCode from "./models/regcodes";
import * as moment from "moment";
import Permission from "./models/permissions";
import { ObjectID } from "bson";
import DB from "./database";
import TwoFactor from "./models/twofactor";
import * as speakeasy from "speakeasy";
import LoginToken from "./models/login_token";
import Mail from "./models/mail";
export default async function TestData() {
await DB.db.dropDatabase();
let mail = await Mail.findOne({ mail: "test@test.de" });
if (!mail) {
mail = Mail.new({
mail: "test@test.de",
primary: true,
verified: true,
});
await Mail.save(mail);
}
let u = await User.findOne({ username: "test" });
if (!u) {
Logging.log("Adding test user");
u = User.new({
username: "test",
birthday: new Date(),
gender: Gender.male,
name: "Test Test",
password:
"125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc",
salt: "test",
admin: true,
phones: [
{ phone: "+4915962855955", primary: true, verified: true },
{ phone: "+4915962855932", primary: false, verified: false },
],
mails: [mail._id],
});
await User.save(u);
}
let c = await Client.findOne({ client_id: "test001" });
if (!c) {
Logging.log("Adding test client");
c = Client.new({
client_id: "test001",
client_secret: "test001",
internal: true,
maintainer: u._id,
name: "Test Client",
website: "http://example.com",
redirect_url: "http://example.com",
featured: true,
description:
"This client is just for testing purposes. It does not have any functionality.",
});
await Client.save(c);
}
let perm = await Permission.findById("507f1f77bcf86cd799439011");
if (!perm) {
Logging.log("Adding test permission");
perm = Permission.new({
_id: new ObjectID("507f1f77bcf86cd799439011"),
name: "TestPerm",
description: "Permission just for testing purposes",
client: c._id,
});
await (await (Permission as any)._collection).insertOne(perm);
// Permission.save(perm);
}
let r = await RegCode.findOne({ token: "test" });
if (!r) {
Logging.log("Adding test reg_code");
r = RegCode.new({
token: "test",
valid: true,
validTill: moment().add("1", "year").toDate(),
});
await RegCode.save(r);
}
let t = await TwoFactor.findOne({ user: u._id, type: 0 });
if (!t) {
t = TwoFactor.new({
user: u._id,
type: 0,
valid: true,
data: "IIRW2P2UJRDDO2LDIRYW4LSREZLWMOKDNBJES2LLHRREK3R6KZJQ",
expires: null,
});
TwoFactor.save(t);
}
let login_token = await LoginToken.findOne({ token: "test01" });
if (login_token) await LoginToken.delete(login_token);
login_token = LoginToken.new({
browser: "DEMO",
ip: "10.0.0.1",
special: false,
token: "test01",
valid: true,
validTill: moment().add("10", "years").toDate(),
user: u._id,
validated: true,
});
await LoginToken.save(login_token);
let special_token = await LoginToken.findOne({ token: "test02" });
if (special_token) await LoginToken.delete(special_token);
special_token = LoginToken.new({
browser: "DEMO",
ip: "10.0.0.1",
special: true,
token: "test02",
valid: true,
validTill: moment().add("10", "years").toDate(),
user: u._id,
validated: true,
});
await LoginToken.save(special_token);
// setInterval(() => {
// let code = speakeasy.totp({
// secret: t.data,
// encoding: "base32"
// })
// Logging.debug("OTC Code is:", code);
// }, 1000)
}
import User, { Gender } from "./models/user";
import Client from "./models/client";
import Logging from "@hibas123/nodelogging";
import RegCode from "./models/regcodes";
import * as moment from "moment";
import Permission from "./models/permissions";
import { ObjectID } from "bson";
import DB from "./database";
import TwoFactor from "./models/twofactor";
import * as speakeasy from "speakeasy";
import LoginToken from "./models/login_token";
import Mail from "./models/mail";
export default async function TestData() {
Logging.warn("Running in dev mode! Database will be cleared!");
await DB.db.dropDatabase();
let mail = await Mail.findOne({ mail: "test@test.de" });
if (!mail) {
mail = Mail.new({
mail: "test@test.de",
primary: true,
verified: true,
});
await Mail.save(mail);
}
let u = await User.findOne({ username: "test" });
if (!u) {
Logging.log("Adding test user");
u = User.new({
username: "test",
birthday: new Date(),
gender: Gender.male,
name: "Test Test",
password:
"125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc",
salt: "test",
admin: true,
phones: [
{ phone: "+4915962855955", primary: true, verified: true },
{ phone: "+4915962855932", primary: false, verified: false },
],
mails: [mail._id],
});
await User.save(u);
}
let c = await Client.findOne({ client_id: "test001" });
if (!c) {
Logging.log("Adding test client");
c = Client.new({
client_id: "test001",
client_secret: "test001",
internal: true,
maintainer: u._id,
name: "Test Client",
website: "http://example.com",
redirect_url: "http://example.com",
featured: true,
description:
"This client is just for testing purposes. It does not have any functionality.",
});
await Client.save(c);
}
let perm = await Permission.findById("507f1f77bcf86cd799439011");
if (!perm) {
Logging.log("Adding test permission");
perm = Permission.new({
_id: new ObjectID("507f1f77bcf86cd799439011"),
name: "TestPerm",
description: "Permission just for testing purposes",
client: c._id,
});
await (await (Permission as any)._collection).insertOne(perm);
// Permission.save(perm);
}
let r = await RegCode.findOne({ token: "test" });
if (!r) {
Logging.log("Adding test reg_code");
r = RegCode.new({
token: "test",
valid: true,
validTill: moment().add("1", "year").toDate(),
});
await RegCode.save(r);
}
let t = await TwoFactor.findOne({ user: u._id, type: 0 });
if (!t) {
t = TwoFactor.new({
user: u._id,
type: 0,
valid: true,
data: "IIRW2P2UJRDDO2LDIRYW4LSREZLWMOKDNBJES2LLHRREK3R6KZJQ",
expires: null,
});
TwoFactor.save(t);
}
let login_token = await LoginToken.findOne({ token: "test01" });
if (login_token) await LoginToken.delete(login_token);
login_token = LoginToken.new({
browser: "DEMO",
ip: "10.0.0.1",
special: false,
token: "test01",
valid: true,
validTill: moment().add("10", "years").toDate(),
user: u._id,
validated: true,
});
await LoginToken.save(login_token);
let special_token = await LoginToken.findOne({ token: "test02" });
if (special_token) await LoginToken.delete(special_token);
special_token = LoginToken.new({
browser: "DEMO",
ip: "10.0.0.1",
special: true,
token: "test02",
valid: true,
validTill: moment().add("10", "years").toDate(),
user: u._id,
validated: true,
});
await LoginToken.save(special_token);
// setInterval(() => {
// let code = speakeasy.totp({
// secret: t.data,
// encoding: "base32"
// })
// Logging.debug("OTC Code is:", code);
// }, 1000)
console.log("Finished adding test data")
}

View File

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

View File

@ -1,34 +1,22 @@
import { compile, TemplateDelegate } from "handlebars";
import { readFileSync } from "fs";
import { __ as i__ } from "i18n";
import config from "../config";
let template: TemplateDelegate<any>;
function loadStatic() {
let html = readFileSync("./views/out/authorize/authorize.html").toString();
template = compile(html);
}
export default function GetAuthPage(
__: typeof i__,
appname: string,
scopes: { name: string; description: string; logo: string }[]
): string {
if (config.core.dev) {
loadStatic();
}
return template(
{
title: __("Authorize %s", appname),
information: __(
"By clicking on ALLOW, you allow this app to access the requested recources."
),
scopes: scopes,
// request: request
},
{ helpers: { i18n: __ } }
);
}
loadStatic();
import { __ as i__ } from "i18n";
import config from "../config";
import * as viewsv1 from "@hibas123/openauth-views-v1";
export default function GetAuthPage(
__: typeof i__,
appname: string,
scopes: { name: string; description: string; logo: string }[]
): string {
return viewsv1.authorize(config.core.dev)(
{
title: __("Authorize %s", appname),
information: __(
"By clicking on ALLOW, you allow this app to access the requested recources."
),
scopes: scopes,
// request: request
},
{ helpers: { i18n: __ } }
);
}

View File

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

View File

@ -1,112 +1,115 @@
import {
IRouter,
Request,
RequestHandler,
Router,
static as ServeStatic,
} from "express";
import * as Handlebars from "handlebars";
import * as moment from "moment";
import { GetUserMiddleware, UserMiddleware } from "../api/middlewares/user";
import GetAuthRoute from "../api/oauth/auth";
import config from "../config";
import { HttpStatusCode } from "../helper/request_error";
import GetAdminPage from "./admin";
import GetAuthPage from "./authorize";
import GetPopupPage from "./popup";
import GetRegistrationPage from "./register";
Handlebars.registerHelper("appname", () => config.core.name);
const cacheTime = !config.core.dev
? moment.duration(1, "month").asSeconds()
: 1000;
const addCache: RequestHandler = (req, res, next) => {
res.setHeader("cache-control", "public, max-age=" + cacheTime);
next();
};
const ViewRouter: IRouter = Router();
ViewRouter.get("/", UserMiddleware, (req, res) => {
res.send("This is the main page");
});
ViewRouter.get("/register", (req, res) => {
res.setHeader("Cache-Control", "public, max-age=" + cacheTime);
res.send(GetRegistrationPage(req.__));
});
ViewRouter.use(
"/login",
addCache,
ServeStatic("./views_repo/build/Login", { cacheControl: false })
);
ViewRouter.use(
"/user",
addCache,
ServeStatic("./views_repo/build/User", { cacheControl: false })
);
ViewRouter.get("/code", (req, res) => {
res.setHeader("Cache-Control", "no-cache");
if (req.query.error) res.send("Some error occured: " + req.query.error);
else res.send(`Your code is: ${req.query.code}`);
});
ViewRouter.get(
"/admin",
GetUserMiddleware(false, true),
(req: Request, res, next) => {
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN);
else next();
},
(req, res) => {
res.send(GetAdminPage(req.__));
}
);
ViewRouter.get("/auth", GetAuthRoute(true));
ViewRouter.use(
"/popup",
GetUserMiddleware(false, false),
addCache,
ServeStatic("./views_repo/build/Popup", { cacheControl: false })
);
// ViewRouter.get("/popup", UserMiddleware, (req, res) => {
// res.send(GetPopupPage(req.__));
// });
// if (config.core.dev) {
// const logo =
// "data:image/png;base64,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";
// ViewRouter.get("/devauth", (req, res) => {
// res.send(
// GetAuthPage(req.__, "Test 05265", [
// {
// name: "Access Profile",
// description:
// "It allows the application to know who you are. Required for all applications. And a lot of more Text, because why not? This will not stop, till it is multiple lines long and maybe kill the layout, so keep reading as long as you like, but I promise it will get boring after some time. So this should be enougth.",
// logo: logo,
// },
// {
// name: "Test 1",
// description:
// "This is not an real permission. This is used just to verify the layout",
// logo: logo,
// },
// {
// name: "Test 2",
// description:
// "This is not an real permission. This is used just to verify the layout",
// logo: logo,
// },
// ])
// );
// });
// }
export default ViewRouter;
import {
IRouter,
Request,
RequestHandler,
Router,
static as ServeStatic,
} from "express";
import * as Handlebars from "handlebars";
import * as moment from "moment";
import { GetUserMiddleware, UserMiddleware } from "../api/middlewares/user";
import GetAuthRoute from "../api/oauth/auth";
import config from "../config";
import { HttpStatusCode } from "../helper/request_error";
import GetAdminPage from "./admin";
import GetRegistrationPage from "./register";
import * as path from "path";
const viewsv2_location = path.join(path.dirname(require.resolve("@hibas123/openauth-views-v2")), "build");
Handlebars.registerHelper("appname", () => config.core.name);
const cacheTime = !config.core.dev
? moment.duration(1, "month").asSeconds()
: 1000;
const addCache: RequestHandler = (req, res, next) => {
res.setHeader("cache-control", "public, max-age=" + cacheTime);
next();
};
const ViewRouter: IRouter = Router();
ViewRouter.get("/", UserMiddleware, (req, res) => {
res.send("This is the main page");
});
ViewRouter.get("/register", (req, res) => {
res.setHeader("Cache-Control", "public, max-age=" + cacheTime);
res.send(GetRegistrationPage(req.__));
});
ViewRouter.use(
"/login",
addCache,
ServeStatic(path.join(viewsv2_location, "login"), { cacheControl: false })
);
ViewRouter.use(
"/user",
addCache,
ServeStatic(path.join(viewsv2_location, "user"), { cacheControl: false })
);
ViewRouter.get("/code", (req, res) => {
res.setHeader("Cache-Control", "no-cache");
if (req.query.error) res.send("Some error occured: " + req.query.error);
else res.send(`Your code is: ${req.query.code}`);
});
ViewRouter.get(
"/admin",
GetUserMiddleware(false, true),
(req: Request, res, next) => {
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN);
else next();
},
(req, res) => {
res.send(GetAdminPage(req.__));
}
);
ViewRouter.get("/auth", GetAuthRoute(true));
ViewRouter.use(
"/popup",
GetUserMiddleware(false, false),
addCache,
ServeStatic(path.join(viewsv2_location, "popup"), { cacheControl: false })
);
// ViewRouter.get("/popup", UserMiddleware, (req, res) => {
// res.send(GetPopupPage(req.__));
// });
// if (config.core.dev) {
// const logo =
// "data:image/png;base64,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";
// ViewRouter.get("/devauth", (req, res) => {
// res.send(
// GetAuthPage(req.__, "Test 05265", [
// {
// name: "Access Profile",
// description:
// "It allows the application to know who you are. Required for all applications. And a lot of more Text, because why not? This will not stop, till it is multiple lines long and maybe kill the layout, so keep reading as long as you like, but I promise it will get boring after some time. So this should be enougth.",
// logo: logo,
// },
// {
// name: "Test 1",
// description:
// "This is not an real permission. This is used just to verify the layout",
// logo: logo,
// },
// {
// name: "Test 2",
// description:
// "This is not an real permission. This is used just to verify the layout",
// logo: logo,
// },
// ])
// );
// });
// }
export default ViewRouter;

View File

@ -1,123 +1,123 @@
import { WebConfig } from "./config";
import * as express from "express";
import { Express } from "express";
import Logging from "@hibas123/nodelogging";
import * as bodyparser from "body-parser";
import * as cookieparser from "cookie-parser";
import * as i18n from "i18n";
import * as compression from "compression";
import ApiRouter from "./api";
import ViewRouter from "./views/views";
import RequestError, { HttpStatusCode } from "./helper/request_error";
export default class Web {
server: Express;
private port: number;
constructor(config: WebConfig) {
this.server = express();
this.port = Number(config.port);
this.registerMiddleware();
this.registerEndpoints();
this.registerErrorHandler();
}
listen() {
this.server.listen(this.port, () => {
Logging.log(`Server listening on port ${this.port}`);
});
}
private registerMiddleware() {
this.server.use(cookieparser());
this.server.use(
bodyparser.json(),
bodyparser.urlencoded({ extended: true })
);
this.server.use(i18n.init);
//Logging Middleware
this.server.use((req, res, next) => {
let start = process.hrtime();
let finished = false;
let to = false;
let listener = () => {
if (finished) return;
finished = true;
let td = process.hrtime(start);
let time = !to ? (td[0] * 1e3 + td[1] / 1e6).toFixed(2) : "--.--";
let resColor = "";
if (res.statusCode >= 200 && res.statusCode < 300)
resColor = "\x1b[32m";
//Green
else if (res.statusCode === 304 || res.statusCode === 302)
resColor = "\x1b[33m";
else if (res.statusCode >= 400 && res.statusCode < 500)
resColor = "\x1b[36m";
//Cyan
else if (res.statusCode >= 500 && res.statusCode < 600)
resColor = "\x1b[31m"; //Red
let m = req.method;
while (m.length < 4) m += " ";
Logging.log(
`${m} ${req.originalUrl} ${
(req as any).language || ""
} ${resColor}${res.statusCode}\x1b[0m - ${time}ms`
);
res.removeListener("finish", listener);
};
res.on("finish", listener);
setTimeout(() => {
to = true;
listener();
}, 2000);
next();
});
this.server.use(
compression({
filter: (req, res) => {
if (req.headers["x-no-compression"]) {
return false;
}
return compression.filter(req, res);
},
})
);
}
private registerEndpoints() {
this.server.use("/api", ApiRouter);
this.server.use("/", ViewRouter);
}
private registerErrorHandler() {
this.server.use((error, req: express.Request, res, next) => {
if (!(error instanceof RequestError)) {
error = new RequestError(
error.message,
error.status || HttpStatusCode.INTERNAL_SERVER_ERROR,
error.nolog || false
);
}
if (error.status === 500 && !(<any>error).nolog) {
Logging.error(error);
} else {
Logging.log("Responded with Error", error.status);
}
if (req.accepts(["json"])) {
res.json_status = error.status || 500;
res.json({
error: error.message,
status: error.status || 500,
additional: error.additional,
});
} else res.status(error.status || 500).send(error.message);
});
}
}
import { WebConfig } from "./config";
import * as express from "express";
import { Express } from "express";
import Logging from "@hibas123/nodelogging";
import * as bodyparser from "body-parser";
import * as cookieparser from "cookie-parser";
import * as i18n from "i18n";
import * as compression from "compression";
import ApiRouter from "./api";
import ViewRouter from "./views/views";
import RequestError, { HttpStatusCode } from "./helper/request_error";
export default class Web {
server: Express;
private port: number;
constructor(config: WebConfig) {
this.server = express();
this.port = Number(config.port);
this.registerMiddleware();
this.registerEndpoints();
this.registerErrorHandler();
}
listen() {
this.server.listen(this.port, () => {
Logging.log(`Server listening on port ${this.port}`);
});
}
private registerMiddleware() {
this.server.use(cookieparser());
this.server.use(
bodyparser.json(),
bodyparser.urlencoded({ extended: true })
);
this.server.use(i18n.init);
//Logging Middleware
this.server.use((req, res, next) => {
let start = process.hrtime();
let finished = false;
let to = false;
let listener = () => {
if (finished) return;
finished = true;
let td = process.hrtime(start);
let time = !to ? (td[0] * 1e3 + td[1] / 1e6).toFixed(2) : "--.--";
let resColor = "";
if (res.statusCode >= 200 && res.statusCode < 300)
resColor = "\x1b[32m";
//Green
else if (res.statusCode === 304 || res.statusCode === 302)
resColor = "\x1b[33m";
else if (res.statusCode >= 400 && res.statusCode < 500)
resColor = "\x1b[36m";
//Cyan
else if (res.statusCode >= 500 && res.statusCode < 600)
resColor = "\x1b[31m"; //Red
let m = req.method;
while (m.length < 4) m += " ";
Logging.log(
`${m} ${req.originalUrl} ${
(req as any).language || ""
} ${resColor}${res.statusCode}\x1b[0m - ${time}ms`
);
res.removeListener("finish", listener);
};
res.on("finish", listener);
setTimeout(() => {
to = true;
listener();
}, 2000);
next();
});
this.server.use(
compression({
filter: (req, res) => {
if (req.headers["x-no-compression"]) {
return false;
}
return compression.filter(req, res);
},
})
);
}
private registerEndpoints() {
this.server.use("/api", ApiRouter);
this.server.use("/", ViewRouter);
}
private registerErrorHandler() {
this.server.use((error, req: express.Request, res, next) => {
if (!(error instanceof RequestError)) {
error = new RequestError(
error.message,
error.status || HttpStatusCode.INTERNAL_SERVER_ERROR,
error.nolog || false
);
}
if (error.status === 500 && !(<any>error).nolog) {
Logging.error(error);
} else {
Logging.log("Responded with Error", error.status);
}
if (req.accepts(["json"])) {
res.json_status = error.status || 500;
res.json({
error: error.message,
status: error.status || 500,
additional: error.additional,
});
} else res.status(error.status || 500).send(error.message);
});
}
}

View File

@ -1,25 +1,31 @@
FROM node:12
FROM node:18-alpine
LABEL maintainer="Fabian Stamm <dev@fabianstamm.de>"
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN npm config set registry https://npm.hibas123.de
# COPY ["package.json", "yarn.lock", ".yarnrc.yml", "/usr/src/app/"]
# COPY .yarn /usr/src/app/.yarn
# COPY Backend /usr/src/app/Backend
# COPY Frontend /usr/src/app/Frontend
# COPY FrontendLegacy /usr/src/app/FrontendLegacy
COPY ["package.json", "package-lock.json", "tsconfig.json", "/usr/src/app/"]
COPY . /usr/src/app
ENV NODE_ENV=production
# RUN rm -rf /usr/src/app/Backend/node_modules &&\
# rm -rf /usr/src/app/Frontend/node_modules &&\
# rm -rf /usr/src/app/FrontendLegacy/node_modules &&\
# rm -rf /usr/src/app/Backend/logs &&\
# rm -rf /usr/src/app/Backend/keys
RUN npm install
RUN yarn install
RUN yarn build
COPY lib/ /usr/src/app/lib
COPY views/out /usr/src/app/views/out/
COPY views_repo/build /usr/src/app/views_repo/build
RUN ln -s /usr/src/app/logs /usr/src/app/Backend/logs && ln -s /usr/src/app/keys /usr/src/app/Backend/keys
VOLUME [ "/usr/src/app/logs", "/usr/src/app/keys"]
EXPOSE 3004/tcp
WORKDIR /usr/src/app/Backend
CMD ["npm", "run", "start"]

View File

@ -1,170 +1,171 @@
const {
lstatSync,
readdirSync,
mkdirSync,
copyFileSync,
writeFileSync,
readFileSync,
exists,
} = require("fs");
const { join, basename, dirname } = require("path");
const isDirectory = (source) => lstatSync(source).isDirectory();
const getDirectories = (source) =>
readdirSync(source)
.map((name) => join(source, name))
.filter(isDirectory);
function ensureDir(folder) {
try {
if (!isDirectory(folder)) mkdirSync(folder);
} catch (e) {
mkdirSync(folder);
}
}
const fileExists = (filename) =>
new Promise((yes, no) => exists(filename, (exi) => yes(exi)));
ensureDir("./out");
const sass = require("sass");
function findHead(elm) {
if (elm.tagName === "head") return elm;
for (let i = 0; i < elm.childNodes.length; i++) {
let res = findHead(elm.childNodes[i]);
if (res) return res;
}
return undefined;
}
const rollup = require("rollup");
const includepaths = require("rollup-plugin-includepaths");
const typescript = require("rollup-plugin-typescript2");
const resolve = require("rollup-plugin-node-resolve");
const minify = require("html-minifier").minify;
const gzipSize = require("gzip-size");
async function file_name(folder, name, exts) {
for (let ext of exts) {
let basefile = `${folder}/${name}.${ext}`;
if (await fileExists(basefile)) return basefile;
}
return null;
}
async function buildPage(folder) {
const pagename = basename(folder);
const outpath = "./out/" + pagename;
ensureDir(outpath);
const basefile = await file_name(folder, pagename, ["tsx", "ts", "js"]);
let bundle = await rollup.rollup({
input: basefile,
plugins: [
includepaths({
paths: ["shared", "node_modules"],
}),
typescript(),
resolve({
// not all files you want to resolve are .js files
extensions: [".mjs", ".js", ".jsx", ".json"], // Default: [ '.mjs', '.js', '.json', '.node' ]
// whether to prefer built-in modules (e.g. `fs`, `path`) or
// local ones with the same names
preferBuiltins: false, // Default: true
}),
],
treeshake: true,
});
let { output } = await bundle.generate({
format: "iife",
compact: true,
});
let { code } = output[0];
let sass_res = sass.renderSync({
file: folder + `/${pagename}.scss`,
includePaths: ["./node_modules", folder, "./shared"],
outputStyle: "compressed",
});
let css = "<style>\n" + sass_res.css.toString("utf8") + "\n</style>\n";
let script = "<script>\n" + code + "\n</script>\n";
let html = readFileSync(`${folder}/${pagename}.hbs`).toString("utf8");
let idx = html.indexOf("</head>");
if (idx < 0) throw new Error("No head element found");
let idx2 = html.indexOf("</body>");
if (idx2 < 0) throw new Error("No body element found");
if (idx < idx2) {
let part1 = html.slice(0, idx);
let part2 = html.slice(idx, idx2);
let part3 = html.slice(idx2, html.length);
html = part1 + css + part2 + script + part3;
} else {
let part1 = html.slice(0, idx2);
let part2 = html.slice(idx2, idx);
let part3 = html.slice(idx, html.length);
html = part1 + script + part2 + css + part3;
}
let result = minify(html, {
removeAttributeQuotes: true,
collapseWhitespace: true,
html5: true,
keepClosingSlash: true,
minifyCSS: false,
minifyJS: false,
removeComments: true,
useShortDoctype: true,
});
let gzips = await gzipSize(result);
writeFileSync(`${outpath}/${pagename}.html`, result);
let stats = {
sass: sass_res.stats,
js: {
chars: code.length,
},
css: {
chars: css.length,
},
bundle_size: result.length,
gzip_size: gzips,
};
writeFileSync(outpath + `/stats.json`, JSON.stringify(stats, null, " "));
}
async function run() {
console.log("Start compiling!");
let pages = getDirectories("./src");
await Promise.all(
pages.map(async (e) => {
try {
await buildPage(e);
} catch (er) {
console.error("Failed compiling", basename(e));
console.log(er);
}
})
);
console.log("Finished compiling!");
}
const chokidar = require("chokidar");
if (process.argv.join(" ").toLowerCase().indexOf("watch") >= 0)
chokidar
.watch(
["./src", "./node_modules", "./package.json", "./package-lock.json"],
{
ignoreInitial: true,
}
)
.on("all", () => run());
run();
const {
lstatSync,
readdirSync,
mkdirSync,
copyFileSync,
writeFileSync,
readFileSync,
exists,
} = require("fs");
const { join, basename, dirname } = require("path");
const isDirectory = (source) => lstatSync(source).isDirectory();
const getDirectories = (source) =>
readdirSync(source)
.map((name) => join(source, name))
.filter(isDirectory);
function ensureDir(folder) {
try {
if (!isDirectory(folder)) mkdirSync(folder);
} catch (e) {
mkdirSync(folder);
}
}
const fileExists = (filename) =>
new Promise((yes, no) => exists(filename, (exi) => yes(exi)));
ensureDir("./out");
const sass = require("sass");
function findHead(elm) {
if (elm.tagName === "head") return elm;
for (let i = 0; i < elm.childNodes.length; i++) {
let res = findHead(elm.childNodes[i]);
if (res) return res;
}
return undefined;
}
const rollup = require("rollup");
const includepaths = require("rollup-plugin-includepaths");
const typescript = require("rollup-plugin-typescript2");
const resolve = require("rollup-plugin-node-resolve");
const minify = require("html-minifier").minify;
const gzipSize = require("gzip-size");
async function file_name(folder, name, exts) {
for (let ext of exts) {
let basefile = `${folder}/${name}.${ext}`;
if (await fileExists(basefile)) return basefile;
}
return null;
}
async function buildPage(folder) {
const pagename = basename(folder);
const outpath = "./out/" + pagename;
ensureDir(outpath);
const basefile = await file_name(folder, pagename, ["tsx", "ts", "js"]);
let bundle = await rollup.rollup({
input: basefile,
plugins: [
includepaths({
paths: ["shared", "node_modules"],
}),
typescript(),
resolve({
// not all files you want to resolve are .js files
extensions: [".mjs", ".js", ".jsx", ".json"], // Default: [ '.mjs', '.js', '.json', '.node' ]
// whether to prefer built-in modules (e.g. `fs`, `path`) or
// local ones with the same names
preferBuiltins: false, // Default: true
}),
],
treeshake: true,
});
let { output } = await bundle.generate({
format: "iife",
compact: true,
});
let { code } = output[0];
let sass_res = sass.renderSync({
file: folder + `/${pagename}.scss`,
includePaths: ["./node_modules", folder, "./shared", "../node_modules"],
outputStyle: "compressed",
});
let css = "<style>\n" + sass_res.css.toString("utf8") + "\n</style>\n";
let script = "<script>\n" + code + "\n</script>\n";
let html = readFileSync(`${folder}/${pagename}.hbs`).toString("utf8");
let idx = html.indexOf("</head>");
if (idx < 0) throw new Error("No head element found");
let idx2 = html.indexOf("</body>");
if (idx2 < 0) throw new Error("No body element found");
if (idx < idx2) {
let part1 = html.slice(0, idx);
let part2 = html.slice(idx, idx2);
let part3 = html.slice(idx2, html.length);
html = part1 + css + part2 + script + part3;
} else {
let part1 = html.slice(0, idx2);
let part2 = html.slice(idx2, idx);
let part3 = html.slice(idx, html.length);
html = part1 + script + part2 + css + part3;
}
let result = minify(html, {
removeAttributeQuotes: true,
collapseWhitespace: true,
html5: true,
keepClosingSlash: true,
minifyCSS: false,
minifyJS: false,
removeComments: true,
useShortDoctype: true,
});
let gzips = await gzipSize(result);
writeFileSync(`${outpath}/${pagename}.html`, result);
let stats = {
sass: sass_res.stats,
js: {
chars: code.length,
},
css: {
chars: css.length,
},
bundle_size: result.length,
gzip_size: gzips,
};
writeFileSync(outpath + `/stats.json`, JSON.stringify(stats, null, " "));
}
async function run() {
console.log("Start compiling!");
let pages = getDirectories("./src");
await Promise.all(
pages.map(async (e) => {
try {
await buildPage(e);
} catch (er) {
console.error("Failed compiling", basename(e));
console.log(er);
process.exitCode = 1;
}
})
);
console.log("Finished compiling!");
}
const chokidar = require("chokidar");
if (process.argv.join(" ").toLowerCase().indexOf("watch") >= 0)
chokidar
.watch(
["./src", "./node_modules", "./package.json", "./package-lock.json"],
{
ignoreInitial: true,
}
)
.on("all", () => run());
run();

3
FrontendLegacy/index.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export const register: (dev?: boolean) => Handlebars.TemplateDelegate<any>
export const admin: (dev?: boolean) => Handlebars.TemplateDelegate<any>
export const authorize: (dev?: boolean) => Handlebars.TemplateDelegate<any>

20
FrontendLegacy/index.js Normal file
View File

@ -0,0 +1,20 @@
const handlebars = require("handlebars");
const { readFileSync } = require("fs");
const path = require("path");
const CACHE = {};
function get_template(name, dev) {
if (!dev && CACHE[name]) return CACHE[name];
const template = handlebars.compile(
readFileSync(path.join(__dirname, `./out/${name}/${name}.html`), "utf8")
);
CACHE[name] = template;
return template;
}
exports.register = (dev) => get_template("register", dev);
exports.admin = (dev) => get_template("admin", dev);
exports.authorize = (dev) => get_template("authorize", dev);

View File

@ -0,0 +1,484 @@
<html><head><title>{{i18n "Administration"}}</title><meta charset=utf8 /><meta name=viewport content="width=device-width,initial-scale=1"/><script src=https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js type=text/javascript></script><script src=https://unpkg.com/popper.js@1.12.6/dist/umd/popper.js type=text/javascript></script><script src=https://unpkg.com/bootstrap-material-design@4.1.1/dist/js/bootstrap-material-design.js type=text/javascript></script><script src=https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.11/handlebars.min.js type=text/javascript></script><script>$(document).ready(() => $('body').bootstrapMaterialDesign())</script><style>@import"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons";@import"https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css";.btn-primary{color:#fff !important;background-color:#1e88e5 !important}.error_card{color:#ff2f00;padding:1rem;font-size:1rem}.bg-primary{background-color:#1e88e5 !important}.spinner{-webkit-animation:rotation 1.35s linear infinite;animation:rotation 1.35s linear infinite;stroke:#1e88e5}@-webkit-keyframes rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}}@keyframes rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}}.circle{stroke-dasharray:180;stroke-dashoffset:0;-webkit-transform-origin:center;-ms-transform-origin:center;transform-origin:center;-webkit-animation:turn 1.35s ease-in-out infinite;animation:turn 1.35s ease-in-out infinite}@-webkit-keyframes turn{0%{stroke-dashoffset:180}50%{stroke-dashoffset:45;-webkit-transform:rotate(135deg);transform:rotate(135deg)}100%{stroke-dashoffset:180;-webkit-transform:rotate(450deg);transform:rotate(450deg)}}@keyframes turn{0%{stroke-dashoffset:180}50%{stroke-dashoffset:45;-webkit-transform:rotate(135deg);transform:rotate(135deg)}100%{stroke-dashoffset:180;-webkit-transform:rotate(450deg);transform:rotate(450deg)}}header{margin-bottom:8px;padding:8px 16px;padding-bottom:0}table{word-wrap:break-word;table-layout:fixed}table td{vertical-align:inherit !important;width:auto}.col.form-group{padding-left:0 !important;margin-left:5px !important}</style></head><body><header class=bg-primary style="display: flex; justify-content: space-between;"><h3 style="display: inline">{{appname}} {{i18n "Administration"}} <span id=sitename>LOADING</span></h3><ul class="nav nav-tabs" style="display: inline-block; margin-left: auto; margin-top: -8px;"><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" data-toggle=dropdown href=# role=button aria-haspopup=true aria-expanded=false>Model</a><div class=dropdown-menu><a class=dropdown-item href="?type=user">User</a> <a class=dropdown-item href="?type=regcode">RegCode</a> <a class=dropdown-item href="?type=client">Client</a></div></li></ul></header><div id=content><div class=container><div id=error_cont class=row style="margin-bottom: 24px;display: none;"><div class=col-sm><div class="card error_card"><div id=error_msg class=card-body></div></div></div></div><div id=custom_data_cont class=row style="margin-bottom: 24px; display: none"><div class=col-sm><div class=card><div id=custom_data class=card-body></div></div></div></div><div class=row><div class=col-sm><div class=card><div class=card-body><div id=table-body><div style="width: 65px; height: 65px; margin: 0 auto;"><svg class=spinner viewBox="0 0 66 66" xmlns=http://www.w3.org/2000/svg><circle class=circle fill=none stroke-width=6 stroke-linecap=round cx=33 cy=33 r=30></circle></svg></div></div></div></div></div></div></div></div><script id=template-spinner type=text/x-handlebars-template><div style="width: 65px; height: 65px; margin: 0 auto;">
<svg class="spinner" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
<circle class="circle" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
</svg>
</div></script><script id=template-user-list type=text/x-handlebars-template><table class="table table-bordered" style="margin-bottom: 0">
<thead>
<tr>
<th scope="col">Username</th>
<th scope="col">Name</th>
<th scope="col">Gender</th>
<th scope="col">Role</th>
<th scope="col" style="width: 2.5rem"></th>
</tr>
</thead>
<tbody>
\{{#users}}
<tr>
<td>\{{ username }}</td>
<td>\{{ name }}</td>
<!-- ToDo: Make helper to resolve number to human readaby text-->
<td>\{{humangender gender}}</td>
<td onclick="userOnChangeType('\{{_id}}')">
\{{#if admin}}
<span class="badge badge-danger">Admin</span>
\{{else}}
<span class="badge badge-success">User</span>
\{{/if}}
</td>
<td style="padding: 0.25em">
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="deleteUser('\{{_id}}')">
<i class="material-icons" style="font-size: 2rem; display: inline">
delete
</i>
</button>
</td>
</tr>
\{{/users}}
</tbody>
</table></script><script id=template-regcode-list type=text/x-handlebars-template><button class="btn btn-raised btn-primary" onclick="createRegcode()">Create</button>
<table class="table table-bordered" style="margin-bottom: 0">
<thead>
<tr>
<th scope="col">Code</th>
<th scope="col">Valid</th>
<th scope="col">Till</th>
<th scope="col" style="width: 2.5rem"></th>
</tr>
</thead>
<tbody>
\{{#regcodes}}
<tr>
<td>\{{ token }}</td>
<td>\{{ valid }}</td>
<!-- ToDo: Make helper to resolve number to human readaby text-->
<td>\{{formatDate validTill }}</td>
<td style="padding: 0.25em">
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="deleteRegcode('\{{_id}}')">
<i class="material-icons" style="font-size: 2rem; display: inline">
delete
</i>
</button>
</td>
</tr>
\{{/regcodes}}
</tbody>
</table></script><script id=template-client-list type=text/x-handlebars-template><button class="btn btn-raised btn-primary" onclick="createClient()">Create</button>
<table class="table table-bordered" style="margin-bottom: 0">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Secret</th>
<th scope="col">Maintainer</th>
<th scope="col">Name</th>
<th scope="col" style="width: 80px">Type</th>
<th scope="col">Website</th>
<th scope="col" style="width: 2.5rem">
<div></div>
</th>
<th scope="col" style="width: 2.5rem"></th>
<th scope="col" style="width: 2.5rem"></th>
</tr>
</thead>
<tbody>
\{{#clients}}
<tr>
<td>\{{ client_id }}</td>
<td>\{{ client_secret }}</td>
<td>\{{ maintainer.username }}</td>
<td>\{{ name }}</td>
<td>
\{{#if internal}}
<span class="badge badge-success">Internal</span>
\{{else}}
<span class="badge badge-danger">External</span>
\{{/if}}
</td>
<td>
<a href="\{{ website }}">\{{ website }}</a>
</td>
<td style="padding: 0.25em">
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="permissionsClient('\{{_id}}')">
perm
</button>
</td>
<td style="padding: 0.25em">
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="editClient('\{{_id}}')">
<i class="material-icons" style="font-size: 2rem; display: inline">
edit
</i>
</button>
</td>
<td style="padding: 0.25em">
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="deleteClient('\{{_id}}')">
<i class="material-icons" style="font-size: 2rem; display: inline">
delete
</i>
</button>
</td>
</tr>
\{{/clients}}
</tbody>
</table></script><script id=template-client-form type=text/x-handlebars-template><form class="form" action="JavaScript:void(null)" onsubmit="createClientSubmit(this)" style="margin-bottom: 0">
<input type=hidden value="\{{_id}}" name=id />
<div class="form-group">
<label for="name_input" class="bmd-label-floating">Name</label>
<input type="text" class="form-control" id="name_input" name=name value="\{{name}}">
</div>
<div class="form-group">
<label for="redirect_input" class="bmd-label-floating">Redirect Url</label>
<input type="text" class="form-control" id="redirect_input" name=redirect_url value="\{{redirect_url}}">
</div>
<div class="form-group">
<label for="website_input" class="bmd-label-floating">Website</label>
<input type="text" class="form-control" id="website_input" name=website value="\{{website}}">
</div>
<div class="form-group">
<label for="logo_input" class="bmd-label-floating">Logo</label>
<input type="text" class="form-control" id="logo_input" name=logo value="\{{logo}}">
</div>
<div class="form-group">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="internal_check" \{{#if internal}} checked="checked" \{{/if}} name=internal>
<label class="form-check-label" for="internal_check">Internal</label>
</div>
</div>
<span class="form-group bmd-form-group">
<!-- needed to match padding for floating labels -->
<button type="submit" class="btn btn-raised btn-primary">Save</button>
</span>
</form></script><script id=template-permission-list type=text/x-handlebars-template><h2><button class="btn btn-raised btn-primary" onclick="gotoClients()">back</button> to \{{client_name}} </h2>
<button class="btn btn-raised btn-primary" onclick="createPermission('\{{ client_id }}')">Create</button>
<table class="table table-bordered" style="margin-bottom: 0">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">Description</th>
<th scope="col" style="width: 10ch">Type</th>
<th scope="col" style="width: 2.5rem"></th>
</tr>
</thead>
<tbody>
\{{#permissions}}
<tr>
<td>\{{ _id }}</td>
<td>\{{ name }}</td>
<td>\{{ description }}</td>
<td>\{{ grant_type }}</td>
<td style="padding: 0.25em">
<button style="border: 0; background-color: rgba(0, 0, 0, 0); padding: 0; text-align: center;" onclick="deletePermission('\{{_id}}')">
<i class="material-icons" style="font-size: 2rem; display: inline">
delete
</i>
</button>
</td>
</tr>
\{{/permissions}}
</tbody>
</table></script><script id=template-permission-form type=text/x-handlebars-template><form class="form" action="JavaScript:void(null)" onsubmit="createPermissionSubmit(this)" style="margin-bottom: 0">
<input type=hidden value="\{{client_id}}" name=client />
<div class="form-group">
<label for="name_input" class="bmd-label-floating">Name</label>
<input type="text" class="form-control" id="name_input" name=name value="">
</div>
<div class="form-group">
<label for=description class="bmd-label-floating">Description</label>
<input type="text" class="form-control" id=description name=description value="">
</div>
<div class="form-group">
<label for=type class="bmd-label-floating">Type</label>
<select type="text" class="form-control" id=type name=type>
<option value="user">User granted</option>
<option value="client">Client granted</option>
</select>
</div>
<span class="form-group bmd-form-group">
<!-- needed to match padding for floating labels -->
<button type="submit" class="btn btn-raised btn-primary">Save</button>
</span>
</form></script><script>(function(){'use strict';function request(endpoint, method, data) {
var headers = new Headers();
headers.set("Content-Type", "application/json");
return fetch(endpoint, {
method: method,
body: JSON.stringify(data),
headers: headers,
credentials: "include",
})
.then(async (e) => {
if (e.status !== 200)
throw new Error((await e.text()) || e.statusText);
return e.json();
})
.then((e) => {
if (e.error)
return Promise.reject(
new Error(
typeof e.error === "string"
? e.error
: JSON.stringify(e.error)
)
);
return e;
});
}function getFormData(element) {
let data = {};
if (
element.name !== undefined &&
element.name !== null &&
element.name !== ""
) {
if (typeof element.name === "string") {
if (element.type === "checkbox") data[element.name] = element.checked;
else data[element.name] = element.value;
}
}
element.childNodes.forEach((child) => {
let res = getFormData(child);
data = Object.assign(data, res);
});
return data;
}Handlebars.registerHelper("humangender", function (value, options) {
switch (value) {
case 1:
return "male";
case 2:
return "female";
case 3:
return "other";
default:
case 0:
return "none";
}
});
// Deprecated since version 0.8.0
Handlebars.registerHelper("formatDate", function (datetime, format) {
return new Date(datetime).toLocaleString();
});
(() => {
const tableb = document.getElementById("table-body");
function setTitle(title) {
document.getElementById("sitename").innerText = title;
}
const cc = document.getElementById("custom_data");
const ccc = document.getElementById("custom_data_cont");
function setCustomCard(content) {
if (!content) {
cc.innerHTML = "";
ccc.style.display = "none";
} else {
cc.innerHTML = content;
ccc.style.display = "";
}
}
const error_cont = document.getElementById("error_cont");
const error_msg = document.getElementById("error_msg");
function catchError(error) {
error_cont.style.display = "";
error_msg.innerText = error.message;
console.log(error);
}
async function renderUser() {
console.log("Rendering User");
setTitle("User");
const listt = Handlebars.compile(
document.getElementById("template-user-list").innerText
);
async function loadList() {
let data = await request("/api/admin/user", "GET");
tableb.innerHTML = listt({
users: data,
});
}
window.userOnChangeType = (id) => {
request("/api/admin/user?id=" + id, "PUT")
.then(() => loadList())
.catch(catchError);
};
window.deleteUser = (id) => {
request("/api/admin/user?id=" + id, "DELETE")
.then(() => loadList())
.catch(catchError);
};
await loadList();
}
async function renderPermissions(client_id, client_name) {
const listt = Handlebars.compile(
document.getElementById("template-permission-list").innerText
);
const formt = Handlebars.compile(
document.getElementById("template-permission-form").innerText
);
setCustomCard();
async function loadList() {
try {
let data = await request(
"/api/admin/permission?client=" + client_id,
"GET"
);
tableb.innerHTML = listt({
client_id: client_id,
client_name: client_name,
permissions: data,
});
} catch (err) {
catchError(err);
}
}
window.gotoClients = () => {
renderClient();
};
window.deletePermission = (id) => {
request("/api/admin/permission?id=" + id, "DELETE")
.then(() => loadList())
.catch(catchError);
};
window.createPermission = () => {
try {
setCustomCard(formt({ client_id: client_id }));
} catch (err) {
console.log("Err", err);
}
};
window.createPermissionSubmit = (elm) => {
console.log(elm);
let data = getFormData(elm);
console.log(data);
request("/api/admin/permission", "POST", data)
.then(() => setCustomCard())
.then(() => loadList())
.catch(catchError);
};
await loadList();
}
async function renderClient() {
console.log("Rendering Client");
setTitle("Client");
const listt = Handlebars.compile(
document.getElementById("template-client-list").innerText
);
const formt = Handlebars.compile(
document.getElementById("template-client-form").innerText
);
let clients = [];
async function loadList() {
let data = await request("/api/admin/client", "GET");
clients = data;
tableb.innerHTML = listt({
clients: data,
});
}
window.permissionsClient = (id) => {
renderPermissions(id, clients.find((e) => e._id === id).name);
};
window.deleteClient = (id) => {
request("/api/admin/client/id=" + id, "DELETE")
.then(() => loadList())
.catch(catchError);
};
window.createClientSubmit = (elm) => {
console.log(elm);
let data = getFormData(elm);
console.log(data);
let id = data.id;
delete data.id;
if (id && id !== "") {
request("/api/admin/client?id=" + id, "PUT", data)
.then(() => setCustomCard())
.then(() => loadList())
.catch(catchError);
} else {
request("/api/admin/client", "POST", data)
.then(() => setCustomCard())
.then(() => loadList())
.catch(catchError);
}
};
window.createClient = () => {
setCustomCard(formt());
};
window.editClient = (id) => {
let client = clients.find((e) => e._id === id);
if (!client) return catchError(new Error("Client does not exist!!"));
setCustomCard(formt(client));
};
await loadList().catch(catchError);
}
async function renderRegCode() {
console.log("Rendering RegCode");
setTitle("RegCode");
const listt = Handlebars.compile(
document.getElementById("template-regcode-list").innerText
);
async function loadList() {
let data = await request("/api/admin/regcode", "GET");
tableb.innerHTML = listt({
regcodes: data,
});
}
window.deleteRegcode = (id) => {
request("/api/admin/regcode?id=" + id, "DELETE")
.then(() => loadList())
.catch(catchError);
};
window.createRegcode = () => {
request("/api/admin/regcode", "POST")
.then(() => loadList())
.catch(catchError);
};
await loadList().catch(catchError);
}
const type = new URL(window.location.href).searchParams.get("type");
switch (type) {
case "client":
renderClient().catch(catchError);
break;
case "regcode":
renderRegCode().catch(catchError);
break;
case "user":
default:
renderUser().catch(catchError);
break;
}
})();})();</script></body></html>

View File

@ -0,0 +1,21 @@
{
"sass": {
"entry": "src\\admin/admin.scss",
"start": 1680888864800,
"end": 1680888864814,
"duration": 14,
"includedFiles": [
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\FrontendLegacy\\src\\admin\\admin.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\FrontendLegacy\\shared\\mat_bs.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\FrontendLegacy\\shared\\style.scss"
]
},
"js": {
"chars": 7975
},
"css": {
"chars": 1665
},
"bundle_size": 21873,
"gzip_size": 4359
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,52 @@
{
"sass": {
"entry": "src\\authorize/authorize.scss",
"start": 1680888863485,
"end": 1680888864104,
"duration": 619,
"includedFiles": [
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\FrontendLegacy\\src\\authorize\\authorize.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\button\\mdc-button.import.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\base\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\feature-targeting\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\feature-targeting\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\feature-targeting\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\theme\\_constants.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\theme\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\theme\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\animation\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\elevation\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\ripple\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\rtl\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\touch-target\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\typography\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\typography\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\shape\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\density\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\button\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\button\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\elevation\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\theme\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\elevation\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\ripple\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\animation\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\ripple\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\ripple\\_keyframes.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\rtl\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\touch-target\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\typography\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\shape\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\shape\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\density\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\button\\mdc-button.scss"
]
},
"js": {
"chars": 576
},
"css": {
"chars": 9933
},
"bundle_size": 11387,
"gzip_size": 2740
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
{
"sass": {
"entry": "src\\login/login.scss",
"start": 1596809618526,
"end": 1596809618741,
"duration": 215,
"includedFiles": [
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\src\\login\\login.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\button\\mdc-button.import.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\base\\_mixins.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\feature-targeting\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\feature-targeting\\_mixins.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\feature-targeting\\_functions.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\theme\\_constants.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\theme\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\theme\\_functions.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\animation\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\elevation\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\ripple\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\rtl\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\touch-target\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\typography\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\typography\\_functions.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\shape\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\density\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\button\\_variables.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\button\\_mixins.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\elevation\\_mixins.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\theme\\_mixins.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\elevation\\_functions.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\ripple\\_mixins.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\animation\\_functions.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\ripple\\_functions.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\ripple\\_keyframes.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\rtl\\_mixins.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\touch-target\\_mixins.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\typography\\_mixins.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\shape\\_mixins.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\shape\\_functions.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\density\\_functions.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\node_modules\\@material\\button\\mdc-button.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\shared\\inputs.scss",
"C:\\Users\\micro\\Documents\\Projekte\\OpenAuth\\server\\views\\shared\\style.scss"
]
},
"js": {
"chars": 68059
},
"css": {
"chars": 11795
},
"bundle_size": 81043,
"gzip_size": 20007
}

View File

@ -0,0 +1 @@
<html><head><style></style></head><body><script>(function(){'use strict';console.log("Hello World");})();</script></body></html>

View File

@ -0,0 +1,19 @@
{
"sass": {
"entry": "src\\main/main.scss",
"start": 1680888864122,
"end": 1680888864124,
"duration": 2,
"includedFiles": [
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\FrontendLegacy\\src\\main\\main.scss"
]
},
"js": {
"chars": 57
},
"css": {
"chars": 18
},
"bundle_size": 128,
"gzip_size": 123
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,63 @@
{
"sass": {
"entry": "src\\register/register.scss",
"start": 1680888864210,
"end": 1680888864775,
"duration": 565,
"includedFiles": [
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\FrontendLegacy\\src\\register\\register.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\button\\mdc-button.import.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\base\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\feature-targeting\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\feature-targeting\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\feature-targeting\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\theme\\_constants.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\theme\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\theme\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\animation\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\elevation\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\ripple\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\rtl\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\touch-target\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\typography\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\typography\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\shape\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\density\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\button\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\button\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\elevation\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\theme\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\elevation\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\ripple\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\animation\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\ripple\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\ripple\\_keyframes.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\rtl\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\touch-target\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\typography\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\shape\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\shape\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\density\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\button\\mdc-button.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\form-field\\mdc-form-field.import.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\form-field\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\form-field\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\form-field\\mdc-form-field.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\radio\\mdc-radio.import.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\radio\\_variables.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\radio\\_mixins.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\radio\\_functions.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\node_modules\\@material\\radio\\mdc-radio.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\FrontendLegacy\\shared\\inputs.scss",
"D:\\Projekte\\OpenServer\\OpenAuth\\server\\FrontendLegacy\\shared\\style.scss"
]
},
"js": {
"chars": 21204
},
"css": {
"chars": 19609
},
"bundle_size": 44031,
"gzip_size": 9858
}

View File

@ -1,5 +1,5 @@
{
"name": "open_auth_service_views",
"name": "@hibas123/openauth-views-v1",
"version": "1.0.0",
"main": "index.js",
"author": "Fabian Stamm <dev@fabianstamm.de>",
@ -9,20 +9,21 @@
"watch": "node build.js watch"
},
"dependencies": {
"handlebars": "^4.7.7"
},
"devDependencies": {
"@material/button": "^5.1.0",
"@material/form-field": "^5.1.0",
"@material/radio": "^5.1.0",
"preact": "^10.3.3"
},
"devDependencies": {
"chokidar": "^3.3.1",
"gzip-size": "^5.1.1",
"chokidar": "^3.5.3",
"gzip-size": "^6.0.0",
"html-minifier": "^4.0.0",
"rollup": "^2.0.2",
"rollup-plugin-includepaths": "^0.2.3",
"preact": "^10.13.2",
"rollup": "^3.20.2",
"rollup-plugin-includepaths": "^0.2.4",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript2": "^0.26.0",
"sass": "^1.26.2",
"typescript": "^3.8.3"
"rollup-plugin-typescript2": "^0.34.1",
"sass": "^1.61.0",
"typescript": "^5.0.3"
}
}

Some files were not shown because too many files have changed in this diff Show More