Updating a bunch of stuff.
This version has partially support for TwoFactorAuthentication.
This commit is contained in:
parent
d98588d1c3
commit
11f460406b
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Program",
|
||||||
|
"program": "${workspaceFolder}/lib/index.js",
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceFolder}/**/*.js"
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
@ -1,37 +1,39 @@
|
|||||||
{
|
{
|
||||||
"User not found": "Benutzer nicht gefunden",
|
"User not found": "Benutzer nicht gefunden",
|
||||||
"Password or username wrong": "Passwort oder Benutzername falsch",
|
"Password or username wrong": "Passwort oder Benutzername falsch",
|
||||||
"Authorize %s": "Authorize %s",
|
"Authorize %s": "Authorize %s",
|
||||||
"Login": "Einloggen",
|
"Login": "Einloggen",
|
||||||
"You are not logged in or your login is expired": "Du bist nicht länger angemeldet oder deine Anmeldung ist abgelaufen.",
|
"You are not logged in or your login is expired": "Du bist nicht länger angemeldet oder deine Anmeldung ist abgelaufen.",
|
||||||
"Username or Email": "Benutzername oder Email",
|
"Username or Email": "Benutzername oder Email",
|
||||||
"Password": "Passwort",
|
"Password": "Passwort",
|
||||||
"Next": "Weiter",
|
"Next": "Weiter",
|
||||||
"Register": "Registrieren",
|
"Register": "Registrieren",
|
||||||
"Mail": "Mail",
|
"Mail": "Mail",
|
||||||
"Repeat Password": "Passwort wiederholen",
|
"Repeat Password": "Passwort wiederholen",
|
||||||
"Username": "Benutzername",
|
"Username": "Benutzername",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Registration code": "Registrierungs Schlüssel",
|
"Registration code": "Registrierungs Schlüssel",
|
||||||
"You need to select one of the options": "Du musst eine der Optionen auswälen",
|
"You need to select one of the options": "Du musst eine der Optionen auswälen",
|
||||||
"Male": "Mann",
|
"Male": "Mann",
|
||||||
"Female": "Frau",
|
"Female": "Frau",
|
||||||
"Other": "Anderes",
|
"Other": "Anderes",
|
||||||
"Registration code required": "Registrierungs Schlüssel benötigt",
|
"Registration code required": "Registrierungs Schlüssel benötigt",
|
||||||
"Username required": "Benutzername benötigt",
|
"Username required": "Benutzername benötigt",
|
||||||
"Name required": "Name benötigt",
|
"Name required": "Name benötigt",
|
||||||
"Mail required": "Mail benötigt",
|
"Mail required": "Mail benötigt",
|
||||||
"The passwords do not match": "Die Passwörter stimmen nicht überein",
|
"The passwords do not match": "Die Passwörter stimmen nicht überein",
|
||||||
"Password is required": "Password benötigt",
|
"Password is required": "Password benötigt",
|
||||||
"Invalid registration code": "Ungültiger Registrierungs Schlüssel",
|
"Invalid registration code": "Ungültiger Registrierungs Schlüssel",
|
||||||
"Username taken": "Benutzername nicht verfügbar",
|
"Username taken": "Benutzername nicht verfügbar",
|
||||||
"Mail linked with other account": "Mail ist bereits mit einem anderen Account verbunden",
|
"Mail linked with other account": "Mail ist bereits mit einem anderen Account verbunden",
|
||||||
"Registration code already used": "Registrierungs Schlüssel wurde bereits verwendet",
|
"Registration code already used": "Registrierungs Schlüssel wurde bereits verwendet",
|
||||||
"Administration": "Administration",
|
"Administration": "Administration",
|
||||||
"Field {{field}} is not defined": "Feld {{field}} fehlt",
|
"Field {{field}} is not defined": "Feld {{field}} fehlt",
|
||||||
"Field {{field}} has wrong type. It should be from type {{type}}": "Feld {{field}} hat den falschen Typ. Es sollte vom Typ {{type}} sein",
|
"Field {{field}} has wrong type. It should be from type {{type}}": "Feld {{field}} hat den falschen Typ. Es sollte vom Typ {{type}} sein",
|
||||||
"Client has no permission for acces password auth": "Dieser Client hat keine Berechtigung password auth zu benutzen",
|
"Client has no permission for acces password auth": "Dieser Client hat keine Berechtigung password auth zu benutzen",
|
||||||
"Invalid token": "Ungültiger Token",
|
"Invalid token": "Ungültiger Token",
|
||||||
"By clicking on ALLOW, you allow this app to access the requested recources.": "Wenn sie ALLOW drücken, berechtigen sie die Applikation die beantragten Resourcen zu benutzen.",
|
"By clicking on ALLOW, you allow this app to access the requested recources.": "Wenn sie ALLOW drücken, berechtigen sie die Applikation die beantragten Resourcen zu benutzen.",
|
||||||
"User": "User"
|
"User": "User",
|
||||||
|
"No special token": "No special token",
|
||||||
|
"Login token invalid": "Login token invalid"
|
||||||
}
|
}
|
@ -3,5 +3,11 @@
|
|||||||
"Username or Email": "Username or Email",
|
"Username or Email": "Username or Email",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"Next": "Next",
|
"Next": "Next",
|
||||||
"Invalid code": "Invalid code"
|
"Invalid code": "Invalid code",
|
||||||
|
"You are not logged in or your login is expired": "You are not logged in or your login is expired",
|
||||||
|
"User not found": "User not found",
|
||||||
|
"No special token": "No special token",
|
||||||
|
"You are not logged in or your login is expired(No special token)": "You are not logged in or your login is expired(No special token)",
|
||||||
|
"Special token invalid": "Special token invalid",
|
||||||
|
"You are not logged in or your login is expired(Special token invalid)": "You are not logged in or your login is expired(Special token invalid)"
|
||||||
}
|
}
|
1009
package-lock.json
generated
1009
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@ -17,44 +17,44 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/body-parser": "^1.17.0",
|
"@types/body-parser": "^1.17.0",
|
||||||
"@types/compression": "^0.0.36",
|
"@types/compression": "^1.0.0",
|
||||||
"@types/cookie-parser": "^1.4.1",
|
"@types/cookie-parser": "^1.4.1",
|
||||||
"@types/dotenv": "^6.1.0",
|
"@types/dotenv": "^6.1.1",
|
||||||
"@types/express": "^4.16.1",
|
"@types/express": "^4.17.0",
|
||||||
"@types/i18n": "^0.8.5",
|
"@types/i18n": "^0.8.5",
|
||||||
"@types/ini": "^1.3.30",
|
"@types/ini": "^1.3.30",
|
||||||
"@types/jsonwebtoken": "^8.3.2",
|
"@types/jsonwebtoken": "^8.3.2",
|
||||||
"@types/mongodb": "^3.1.22",
|
"@types/mongodb": "^3.1.31",
|
||||||
"@types/node": "^11.11.3",
|
"@types/node": "^12.6.9",
|
||||||
"@types/node-rsa": "^1.0.0",
|
"@types/node-rsa": "^1.0.0",
|
||||||
"@types/qrcode": "^1.3.1",
|
"@types/qrcode": "^1.3.3",
|
||||||
"@types/speakeasy": "^2.0.4",
|
"@types/speakeasy": "^2.0.5",
|
||||||
"@types/uuid": "^3.4.4",
|
"@types/uuid": "^3.4.5",
|
||||||
"apidoc": "^0.17.7",
|
"apidoc": "^0.17.7",
|
||||||
"concurrently": "^4.1.0",
|
"concurrently": "^4.1.1",
|
||||||
"nodemon": "^1.18.10",
|
"nodemon": "^1.19.1",
|
||||||
"typescript": "^3.3.3333"
|
"speakeasy": "^2.0.0",
|
||||||
|
"typescript": "^3.5.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hibas123/nodelogging": "^1.3.21",
|
"@hibas123/nodelogging": "^2.1.0",
|
||||||
"@hibas123/nodeloggingserver_client": "^1.1.2",
|
"@hibas123/nodeloggingserver_client": "^1.1.2",
|
||||||
"@hibas123/safe_mongo": "^1.5.3",
|
"@hibas123/safe_mongo": "^1.6.1",
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.19.0",
|
||||||
"compression": "^1.7.3",
|
"compression": "^1.7.4",
|
||||||
"cookie-parser": "^1.4.4",
|
"cookie-parser": "^1.4.4",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^7.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"express": "^4.16.4",
|
"express": "^4.17.1",
|
||||||
"handlebars": "^4.1.0",
|
"handlebars": "^4.1.2",
|
||||||
"i18n": "^0.8.3",
|
"i18n": "^0.8.3",
|
||||||
"ini": "^1.3.5",
|
"ini": "^1.3.5",
|
||||||
"jsonwebtoken": "^8.5.0",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"mongodb": "^3.1.13",
|
"mongodb": "^3.2.7",
|
||||||
"node-rsa": "^1.0.5",
|
"node-rsa": "^1.0.5",
|
||||||
"qrcode": "^1.3.3",
|
"qrcode": "^1.4.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"speakeasy": "^2.0.0",
|
|
||||||
"u2f": "^0.1.3",
|
"u2f": "^0.1.3",
|
||||||
"uuid": "^3.3.2"
|
"uuid": "^3.3.2"
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,40 @@
|
|||||||
import { Request, Response, Router } from "express"
|
import { Request, Response, Router } from "express"
|
||||||
import Stacker from "../middlewares/stacker";
|
import Stacker from "../middlewares/stacker";
|
||||||
import { GetClientAuthMiddleware } from "../middlewares/client";
|
import { GetClientAuthMiddleware } from "../middlewares/client";
|
||||||
import { GetUserMiddleware } from "../middlewares/user";
|
import { GetUserMiddleware } from "../middlewares/user";
|
||||||
import { createJWT } from "../../keys";
|
import { createJWT } from "../../keys";
|
||||||
|
import Client from "../../models/client";
|
||||||
|
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||||
const ClientRouter = Router();
|
|
||||||
/**
|
|
||||||
* @api {get} /client/user
|
const ClientRouter = Router();
|
||||||
* @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
|
/**
|
||||||
*
|
* @api {get} /client/user
|
||||||
* @apiName ClientUser
|
*
|
||||||
* @apiGroup client
|
* @apiDescription Can be used for simple authentication of user. It will redirect the user to the redirect URI with a very short lived jwt.
|
||||||
*
|
*
|
||||||
* @apiPermission user_client Requires ClientID and Authenticated User
|
* @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
|
||||||
ClientRouter.get("/user", Stacker(GetClientAuthMiddleware(false), GetUserMiddleware(false, false), async (req: Request, res: Response) => {
|
*
|
||||||
let { redirect_uri, state } = req.query;
|
* @apiName ClientUser
|
||||||
let jwt = await createJWT({
|
* @apiGroup client
|
||||||
client: req.client.client_id,
|
*
|
||||||
uid: req.user.uid,
|
* @apiPermission user_client Requires ClientID and Authenticated User
|
||||||
username: req.user.username,
|
*/
|
||||||
state: state
|
ClientRouter.get("/user", Stacker(GetClientAuthMiddleware(false), GetUserMiddleware(false, false), async (req: Request, res: Response) => {
|
||||||
}, 30); //after 30 seconds this token is invalid
|
let { redirect_uri, state } = req.query;
|
||||||
res.redirect(redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : ""));
|
|
||||||
}));
|
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
|
||||||
|
}, 30); //after 30 seconds this token is invalid
|
||||||
|
res.redirect(redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : ""));
|
||||||
|
}));
|
||||||
|
|
||||||
export default ClientRouter;
|
export default ClientRouter;
|
@ -1,74 +1,79 @@
|
|||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import LoginToken, { CheckToken } from "../../models/login_token";
|
import LoginToken, { CheckToken } from "../../models/login_token";
|
||||||
import Logging from "@hibas123/nodelogging";
|
import Logging from "@hibas123/nodelogging";
|
||||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||||
import User from "../../models/user";
|
import User from "../../models/user";
|
||||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||||
|
|
||||||
class Invalid extends Error { }
|
class Invalid extends Error { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns customized Middleware function, that could also be called directly
|
* 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
|
* 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
|
* case it will also send error and redirect if json is not set
|
||||||
* @param json Checks if requests wants an json or html for returning errors
|
* @param json Default false. Checks if requests wants an json or html for returning errors
|
||||||
* @param redirect_uri Sets the uri to redirect, if json is not set and user not logged in
|
* @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
|
||||||
export function GetUserMiddleware(json = false, special_required: boolean = false, redirect_uri?: string, validated = true) {
|
* @param validated Default true. If false, the token must not be validated
|
||||||
return promiseMiddleware(async function (req: Request, res: Response, next?: NextFunction) {
|
*/
|
||||||
const invalid = () => {
|
export function GetUserMiddleware(json = false, special_required: boolean = false, redirect_uri?: string, validated = true) {
|
||||||
throw new Invalid();
|
return promiseMiddleware(async function (req: Request, res: Response, next?: NextFunction) {
|
||||||
}
|
const invalid = (message: string) => {
|
||||||
try {
|
throw new Invalid(req.__(message));
|
||||||
let { login, special } = req.cookies
|
}
|
||||||
if (!login) invalid()
|
try {
|
||||||
|
let { login, special } = req.cookies
|
||||||
let token = await LoginToken.findOne({ token: login, valid: true })
|
if (!login) {
|
||||||
if (!await CheckToken(token, validated)) invalid();
|
login = req.query.login;
|
||||||
|
special = req.query.special;
|
||||||
let user = await User.findById(token.user);
|
}
|
||||||
if (!user) {
|
if (!login) invalid("No login token")
|
||||||
token.valid = false;
|
if (!special && special_required) invalid("No special token")
|
||||||
await LoginToken.save(token);
|
|
||||||
invalid();
|
let token = await LoginToken.findOne({ token: login, valid: true })
|
||||||
}
|
if (!await CheckToken(token, validated)) invalid("Login token invalid");
|
||||||
|
|
||||||
let special_token;
|
let user = await User.findById(token.user);
|
||||||
if (special) {
|
if (!user) {
|
||||||
Logging.debug("Special found")
|
token.valid = false;
|
||||||
special_token = await LoginToken.findOne({ token: special, special: true, valid: true, user: token.user })
|
await LoginToken.save(token);
|
||||||
if (!await CheckToken(special_token, validated))
|
invalid("Login token invalid");
|
||||||
invalid();
|
}
|
||||||
req.special = true;
|
|
||||||
}
|
let special_token;
|
||||||
|
if (special) {
|
||||||
if (special_required && !req.special) invalid();
|
Logging.debug("Special found")
|
||||||
|
special_token = await LoginToken.findOne({ token: special, special: true, valid: true, user: token.user })
|
||||||
req.user = user
|
if (!await CheckToken(special_token, validated))
|
||||||
req.isAdmin = user.admin;
|
invalid("Special token invalid");
|
||||||
req.token = {
|
req.special = true;
|
||||||
login: token,
|
}
|
||||||
special: special_token
|
|
||||||
}
|
req.user = user
|
||||||
|
req.isAdmin = user.admin;
|
||||||
if (next)
|
req.token = {
|
||||||
next()
|
login: token,
|
||||||
return true;
|
special: special_token
|
||||||
} catch (e) {
|
}
|
||||||
if (e instanceof Invalid) {
|
|
||||||
if (req.method === "GET" && !json) {
|
if (next)
|
||||||
res.status(HttpStatusCode.UNAUTHORIZED)
|
next()
|
||||||
res.redirect("/login?base64=true&state=" + Buffer.from(redirect_uri ? redirect_uri : req.originalUrl).toString("base64"))
|
return true;
|
||||||
} else {
|
} catch (e) {
|
||||||
throw new RequestError(req.__("You are not logged in or your login is expired"), HttpStatusCode.UNAUTHORIZED)
|
if (e instanceof Invalid) {
|
||||||
}
|
if (req.method === "GET" && !json) {
|
||||||
} else {
|
res.status(HttpStatusCode.UNAUTHORIZED)
|
||||||
if (next) next(e);
|
res.redirect("/login?base64=true&state=" + Buffer.from(redirect_uri ? redirect_uri : req.originalUrl).toString("base64"))
|
||||||
else throw e;
|
} else {
|
||||||
}
|
throw new RequestError(req.__("You are not logged in or your login is expired" + ` (${e.message})`), HttpStatusCode.UNAUTHORIZED, undefined, { auth: true })
|
||||||
return false;
|
}
|
||||||
}
|
} else {
|
||||||
});
|
if (next) next(e);
|
||||||
}
|
else throw e;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const UserMiddleware = GetUserMiddleware();
|
export const UserMiddleware = GetUserMiddleware();
|
16
src/api/user/account.ts
Normal file
16
src/api/user/account.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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 });
|
||||||
|
});
|
@ -1,95 +1,113 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import Register from "./register";
|
import Register from "./register";
|
||||||
import Login from "./login";
|
import Login from "./login";
|
||||||
import TwoFactorRoute from "./twofactor";
|
import TwoFactorRoute from "./twofactor";
|
||||||
import { GetToken, DeleteToken } from "./token";
|
import { GetToken, DeleteToken } from "./token";
|
||||||
|
import { GetAccount } from "./account";
|
||||||
const UserRoute: Router = Router();
|
|
||||||
|
const UserRoute: Router = Router();
|
||||||
/**
|
|
||||||
* @api {post} /user/register
|
/**
|
||||||
* @apiName UserRegister
|
* @api {post} /user/register
|
||||||
*
|
* @apiName UserRegister
|
||||||
* @apiGroup user
|
*
|
||||||
* @apiPermission none
|
* @apiGroup user
|
||||||
*
|
* @apiPermission none
|
||||||
* @apiParam {String} mail EMail linked to this Account
|
*
|
||||||
* @apiParam {String} username The new Username
|
* @apiParam {String} mail EMail linked to this Account
|
||||||
* @apiParam {String} password Password hashed and salted like specification
|
* @apiParam {String} username The new Username
|
||||||
* @apiParam {String} salt The Salt used for password hashing
|
* @apiParam {String} password Password hashed and salted like specification
|
||||||
* @apiParam {String} regcode The regcode, that should be used
|
* @apiParam {String} salt The Salt used for password hashing
|
||||||
* @apiParam {String} gender Gender can be: "male", "female", "other", "none"
|
* @apiParam {String} regcode The regcode, that should be used
|
||||||
* @apiParam {String} name The real name of the User
|
* @apiParam {String} gender Gender can be: "male", "female", "other", "none"
|
||||||
*
|
* @apiParam {String} name The real name of the User
|
||||||
* @apiSuccess {Boolean} success
|
*
|
||||||
*
|
* @apiSuccess {Boolean} success
|
||||||
* @apiErrorExample {Object} Error-Response:
|
*
|
||||||
{
|
* @apiErrorExample {Object} Error-Response:
|
||||||
error: [
|
{
|
||||||
{
|
error: [
|
||||||
message: "Some Error",
|
{
|
||||||
field: "username"
|
message: "Some Error",
|
||||||
}
|
field: "username"
|
||||||
],
|
}
|
||||||
status: 400
|
],
|
||||||
}
|
status: 400
|
||||||
*/
|
}
|
||||||
UserRoute.post("/register", Register);
|
*/
|
||||||
|
UserRoute.post("/register", Register);
|
||||||
/**
|
|
||||||
* @api {post} /user/login?type=:type
|
/**
|
||||||
* @apiName UserLogin
|
* @api {post} /user/login?type=:type
|
||||||
*
|
* @apiName UserLogin
|
||||||
* @apiParam {String} type Type could be either "username" or "password"
|
*
|
||||||
*
|
* @apiParam {String} type Type could be either "username" or "password"
|
||||||
* @apiGroup user
|
*
|
||||||
* @apiPermission none
|
* @apiGroup user
|
||||||
*
|
* @apiPermission none
|
||||||
* @apiParam {String} username Username (either username or uid required)
|
*
|
||||||
* @apiParam {String} uid (either username or uid required)
|
* @apiParam {String} username Username (either username or uid required)
|
||||||
* @apiParam {String} password Password hashed and salted like specification (only on type password)
|
* @apiParam {String} uid (either username or uid required)
|
||||||
*
|
* @apiParam {String} password Password hashed and salted like specification (only on type password)
|
||||||
* @apiSuccess {String} uid On type = "username"
|
*
|
||||||
* @apiSuccess {String} salt On type = "username"
|
* @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 {String} login On type = "password". Login Token
|
||||||
* @apiSuccess {Object[]} tfa Will be set when TwoFactorAuthentication is required
|
* @apiSuccess {String} special On type = "password". Special Token
|
||||||
* @apiSuccess {String} tfa.id The ID of the TFA Method
|
* @apiSuccess {Object[]} tfa Will be set when TwoFactorAuthentication is required
|
||||||
* @apiSuccess {String} tfa.name The name of the TFA Method
|
* @apiSuccess {String} tfa.id The ID of the TFA Method
|
||||||
* @apiSuccess {String} tfa.type The type 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);
|
UserRoute.post("/login", Login)
|
||||||
|
UserRoute.use("/twofactor", TwoFactorRoute);
|
||||||
/**
|
|
||||||
* @api {get} /user/token
|
/**
|
||||||
* @apiName UserGetToken
|
* @api {get} /user/token
|
||||||
*
|
* @apiName UserGetToken
|
||||||
* @apiGroup user
|
*
|
||||||
* @apiPermission user
|
* @apiGroup user
|
||||||
*
|
* @apiPermission user
|
||||||
* @apiSuccess {Object[]} token
|
*
|
||||||
* @apiSuccess {String} token.id The Token ID
|
* @apiSuccess {Object[]} token
|
||||||
* @apiSuccess {String} token.special Identifies Special Token
|
* @apiSuccess {String} token.id The Token ID
|
||||||
* @apiSuccess {String} token.ip IP the token was optained from
|
* @apiSuccess {String} token.special Identifies Special Token
|
||||||
* @apiSuccess {String} token.browser The Browser the token was optained from (User Agent)
|
* @apiSuccess {String} token.ip IP the token was optained from
|
||||||
* @apiSuccess {Boolean} token.isthis Shows if it is token used by this session
|
* @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);
|
*/
|
||||||
|
UserRoute.get("/token", GetToken);
|
||||||
/**
|
|
||||||
* @api {delete} /user/token/:id
|
/**
|
||||||
* @apiParam {String} id The id of the token to be deleted
|
* @api {delete} /user/token/:id
|
||||||
*
|
* @apiParam {String} id The id of the token to be deleted
|
||||||
* @apiName UserDeleteToken
|
*
|
||||||
*
|
* @apiName UserDeleteToken
|
||||||
* @apiParam {String} type Type could be either "username" or "password"
|
*
|
||||||
*
|
*
|
||||||
* @apiGroup user
|
* @apiGroup user
|
||||||
* @apiPermission user
|
* @apiPermission user
|
||||||
*
|
*
|
||||||
* @apiSuccess {Boolean} success
|
* @apiSuccess {Boolean} success
|
||||||
*/
|
*/
|
||||||
UserRoute.delete("/token/:id", DeleteToken);
|
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} token.name Full name of the user
|
||||||
|
* @apiSuccess {String} token.username Username of user
|
||||||
|
* @apiSuccess {Date} token.birthday Birthday
|
||||||
|
* @apiSuccess {Number} token.gender Gender of user (none = 0, male = 1, female = 2, other = 3)
|
||||||
|
*/
|
||||||
|
UserRoute.get("/account", GetAccount);
|
||||||
export default UserRoute;
|
export default UserRoute;
|
@ -1,98 +1,96 @@
|
|||||||
import { Request, Response } from "express"
|
import { Request, Response } from "express"
|
||||||
import User, { IUser } from "../../models/user";
|
import User, { IUser } from "../../models/user";
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import moment = require("moment");
|
import moment = require("moment");
|
||||||
import LoginToken from "../../models/login_token";
|
import LoginToken from "../../models/login_token";
|
||||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||||
import TwoFactor from "../../models/twofactor";
|
import TwoFactor, { TFATypes, TFANames } from "../../models/twofactor";
|
||||||
|
|
||||||
const Login = promiseMiddleware(async (req: Request, res: Response) => {
|
const Login = promiseMiddleware(async (req: Request, res: Response) => {
|
||||||
let type = req.query.type;
|
let type = req.query.type;
|
||||||
if (type === "username") {
|
if (type === "username") {
|
||||||
let { username, uid } = req.query;
|
let { username, uid } = req.query;
|
||||||
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid });
|
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
res.json({ error: req.__("User not found") })
|
res.json({ error: req.__("User not found") })
|
||||||
} else {
|
} else {
|
||||||
res.json({ salt: user.salt, uid: user.uid });
|
res.json({ salt: user.salt, uid: user.uid });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (type === "password") {
|
} else if (type === "password") {
|
||||||
const sendToken = async (user: IUser, tfa?: any[]) => {
|
const sendToken = async (user: IUser, tfa?: any[]) => {
|
||||||
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
|
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
|
||||||
let client = {
|
let client = {
|
||||||
ip: Array.isArray(ip) ? ip[0] : ip,
|
ip: Array.isArray(ip) ? ip[0] : ip,
|
||||||
browser: req.headers["user-agent"]
|
browser: req.headers["user-agent"]
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_str = randomBytes(16).toString("hex");
|
let token_str = randomBytes(16).toString("hex");
|
||||||
let tfa_exp = moment().add(5, "minutes").toDate()
|
let tfa_exp = moment().add(5, "minutes").toDate()
|
||||||
let token_exp = moment().add(6, "months").toDate()
|
let token_exp = moment().add(6, "months").toDate()
|
||||||
let token = LoginToken.new({
|
let token = LoginToken.new({
|
||||||
token: token_str,
|
token: token_str,
|
||||||
valid: true,
|
valid: true,
|
||||||
validTill: tfa ? tfa_exp : token_exp,
|
validTill: tfa ? tfa_exp : token_exp,
|
||||||
user: user._id,
|
user: user._id,
|
||||||
validated: tfa ? false : true,
|
validated: tfa ? false : true,
|
||||||
...client
|
...client
|
||||||
});
|
});
|
||||||
await LoginToken.save(token);
|
await LoginToken.save(token);
|
||||||
|
|
||||||
let special_str = randomBytes(24).toString("hex");
|
let special_str = randomBytes(24).toString("hex");
|
||||||
let special_exp = moment().add(30, "minutes").toDate()
|
let special_exp = moment().add(30, "minutes").toDate()
|
||||||
let special = LoginToken.new({
|
let special = LoginToken.new({
|
||||||
token: special_str,
|
token: special_str,
|
||||||
valid: true,
|
valid: true,
|
||||||
validTill: tfa ? tfa_exp : special_exp,
|
validTill: tfa ? tfa_exp : special_exp,
|
||||||
special: true,
|
special: true,
|
||||||
user: user._id,
|
user: user._id,
|
||||||
validated: tfa ? false : true,
|
validated: tfa ? false : true,
|
||||||
...client
|
...client
|
||||||
});
|
});
|
||||||
await LoginToken.save(special);
|
await LoginToken.save(special);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
login: { token: token_str, expires: token.validTill.toUTCString() },
|
login: { token: token_str, expires: token.validTill.toUTCString() },
|
||||||
special: { token: special_str, expires: special.validTill.toUTCString() },
|
special: { token: special_str, expires: special.validTill.toUTCString() },
|
||||||
tfa
|
tfa
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let { username, password, uid } = req.body;
|
||||||
|
|
||||||
let { username, password, uid } = req.body;
|
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid })
|
||||||
|
if (!user) {
|
||||||
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid })
|
res.json({ error: req.__("User not found") })
|
||||||
if (!user) {
|
} else {
|
||||||
res.json({ error: req.__("User not found") })
|
if (user.password !== password) {
|
||||||
} else {
|
res.json({ error: req.__("Password or username wrong") })
|
||||||
if (user.password !== password) {
|
} else {
|
||||||
res.json({ error: req.__("Password or username wrong") })
|
let twofactor = await TwoFactor.find({ user: user._id, valid: true })
|
||||||
} else {
|
let expired = twofactor.filter(e => e.expires ? moment().isAfter(moment(e.expires)) : false)
|
||||||
let twofactor = await TwoFactor.find({ user: user._id, valid: true })
|
await Promise.all(expired.map(e => {
|
||||||
let expired = twofactor.filter(e => e.expires ? moment().isAfter(moment(e.expires)) : false)
|
e.valid = false;
|
||||||
await Promise.all(expired.map(e => {
|
return TwoFactor.save(e);
|
||||||
e.valid = false;
|
}));
|
||||||
return TwoFactor.save(e);
|
twofactor = twofactor.filter(e => e.valid);
|
||||||
}));
|
if (twofactor && twofactor.length > 0) {
|
||||||
twofactor = twofactor.filter(e => e.valid);
|
let tfa = twofactor.map(e => {
|
||||||
if (twofactor && twofactor.length > 0) {
|
return {
|
||||||
let tfa = twofactor.map(e => {
|
id: e._id,
|
||||||
return {
|
name: e.name || TFANames.get(e.type),
|
||||||
id: e._id,
|
type: e.type
|
||||||
name: e.name,
|
}
|
||||||
type: e.type
|
})
|
||||||
}
|
await sendToken(user, tfa);
|
||||||
})
|
} else {
|
||||||
await sendToken(user, tfa);
|
await sendToken(user);
|
||||||
} else {
|
}
|
||||||
await sendToken(user);
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
}
|
res.json({ error: req.__("Invalid type!") });
|
||||||
} else {
|
}
|
||||||
res.json({ error: req.__("Invalid type!") });
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Login;
|
export default Login;
|
@ -1,29 +1,29 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import Stacker from "../middlewares/stacker";
|
import Stacker from "../middlewares/stacker";
|
||||||
import { GetUserMiddleware } from "../middlewares/user";
|
import { GetUserMiddleware } from "../middlewares/user";
|
||||||
import LoginToken, { CheckToken } from "../../models/login_token";
|
import LoginToken, { CheckToken } from "../../models/login_token";
|
||||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||||
|
|
||||||
export const GetToken = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
|
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 raw_token = await LoginToken.find({ user: req.user._id, valid: true });
|
||||||
let token = await Promise.all(raw_token.map(async token => {
|
let token = await Promise.all(raw_token.map(async token => {
|
||||||
await CheckToken(token);
|
await CheckToken(token);
|
||||||
return {
|
return {
|
||||||
id: token._id,
|
id: token._id,
|
||||||
special: token.special,
|
special: token.special,
|
||||||
ip: token.ip,
|
ip: token.ip,
|
||||||
browser: token.browser,
|
browser: token.browser,
|
||||||
isthis: token._id.equals(token.special ? req.token.special._id : req.token.login._id)
|
isthis: token._id.equals(token.special ? req.token.special._id : req.token.login._id)
|
||||||
}
|
}
|
||||||
}).filter(t => t !== undefined));
|
}).filter(t => t !== undefined));
|
||||||
res.json({ token });
|
res.json({ token });
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DeleteToken = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
|
export const DeleteToken = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
|
||||||
let { id } = req.query;
|
let { id } = req.params;
|
||||||
let token = await LoginToken.findById(id);
|
let token = await LoginToken.findById(id);
|
||||||
if (!token || !token.user.equals(req.user._id)) throw new RequestError("Invalid ID", HttpStatusCode.BAD_REQUEST);
|
if (!token || !token.user.equals(req.user._id)) throw new RequestError("Invalid ID", HttpStatusCode.BAD_REQUEST);
|
||||||
token.valid = false;
|
token.valid = false;
|
||||||
await LoginToken.save(token);
|
await LoginToken.save(token);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
});
|
});
|
74
src/api/user/twofactor/backup/index.ts
Normal file
74
src/api/user/twofactor/backup/index.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
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;
|
@ -1,79 +1,13 @@
|
|||||||
import LoginToken, { ILoginToken } from "../../../models/login_token";
|
import LoginToken, { ILoginToken } from "../../../models/login_token";
|
||||||
import moment = require("moment");
|
import moment = require("moment");
|
||||||
|
|
||||||
// export async function unlockToken() {
|
export async function upgradeToken(token: ILoginToken) {
|
||||||
// let { type, code, login, special } = req.body;
|
token.data = undefined;
|
||||||
|
token.valid = true;
|
||||||
// let [login_t, special_t] = await Promise.all([LoginToken.findOne({ token: login }), LoginToken.findOne({ token: special })]);
|
token.validated = true;
|
||||||
|
//TODO durations from config
|
||||||
// if ((login && !login_t) || (special && !special_t)) {
|
let expires = (token.special ? moment().add(30, "minute") : moment().add(6, "months")).toDate();
|
||||||
// res.json({ error: req.__("Token not found!") });
|
token.validTill = expires;
|
||||||
// } else {
|
await LoginToken.save(token);
|
||||||
// let atoken = special_t || login_t;
|
return expires;
|
||||||
|
|
||||||
// let user = await User.findById(atoken.user);
|
|
||||||
|
|
||||||
// let tf = await TwoFactor.find({ user: user._id, valid: true })
|
|
||||||
|
|
||||||
// let valid = false;
|
|
||||||
// switch (type) {
|
|
||||||
// case TokenTypes.OTC: {
|
|
||||||
// let twofactor = await TwoFactor.findOne({ type, valid: true })
|
|
||||||
// if (twofactor) {
|
|
||||||
// valid = speakeasy.totp.verify({
|
|
||||||
// secret: twofactor.token,
|
|
||||||
// encoding: "base64",
|
|
||||||
// token: code
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// case TokenTypes.BACKUP_CODE: {
|
|
||||||
// let twofactor = await TwoFactor.findOne({ type, valid: true, token: code })
|
|
||||||
// if (twofactor) {
|
|
||||||
// twofactor.valid = false;
|
|
||||||
// await TwoFactor.save(twofactor);
|
|
||||||
// valid = true;
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case TokenTypes.APP_ALLOW:
|
|
||||||
// case TokenTypes.YUBI_KEY:
|
|
||||||
// default:
|
|
||||||
// res.json({ error: req.__("Invalid twofactor!") });
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!valid) {
|
|
||||||
// res.json({ error: req.__("Invalid code!") });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let result: any = {};
|
|
||||||
// if (login_t) {
|
|
||||||
// login_t.validated = true
|
|
||||||
// await LoginToken.save(login_t)
|
|
||||||
// result.login = { token: login_t.token, expires: login_t.validTill.toUTCString() }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (special_t) {
|
|
||||||
// special_t.validated = true;
|
|
||||||
// await LoginToken.save(special_t);
|
|
||||||
// result.special = { token: special_t.token, expires: special_t.validTill.toUTCString() }
|
|
||||||
// }
|
|
||||||
// res.json(result);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
@ -1,42 +1,46 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import YubiKeyRoute from "./yubikey";
|
import YubiKeyRoute from "./yubikey";
|
||||||
import { GetUserMiddleware } from "../../middlewares/user";
|
import { GetUserMiddleware } from "../../middlewares/user";
|
||||||
import Stacker from "../../middlewares/stacker";
|
import Stacker from "../../middlewares/stacker";
|
||||||
import TwoFactor from "../../../models/twofactor";
|
import TwoFactor from "../../../models/twofactor";
|
||||||
import * as moment from "moment"
|
import * as moment from "moment"
|
||||||
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
|
||||||
|
import OTCRoute from "./otc";
|
||||||
const TwoFactorRouter = Router();
|
import BackupCodeRoute from "./backup";
|
||||||
|
|
||||||
TwoFactorRouter.get("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
const TwoFactorRouter = Router();
|
||||||
let twofactor = await TwoFactor.find({ user: req.user._id, valid: true })
|
|
||||||
let expired = twofactor.filter(e => e.expires ? moment().isAfter(moment(e.expires)) : false)
|
TwoFactorRouter.get("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
||||||
await Promise.all(expired.map(e => {
|
let twofactor = await TwoFactor.find({ user: req.user._id, valid: true })
|
||||||
e.valid = false;
|
let expired = twofactor.filter(e => e.expires ? moment().isAfter(moment(e.expires)) : false)
|
||||||
return TwoFactor.save(e);
|
await Promise.all(expired.map(e => {
|
||||||
}));
|
e.valid = false;
|
||||||
twofactor = twofactor.filter(e => e.valid);
|
return TwoFactor.save(e);
|
||||||
let tfa = twofactor.map(e => {
|
}));
|
||||||
return {
|
twofactor = twofactor.filter(e => e.valid);
|
||||||
id: e._id,
|
let tfa = twofactor.map(e => {
|
||||||
name: e.name,
|
return {
|
||||||
type: e.type
|
id: e._id,
|
||||||
}
|
name: e.name,
|
||||||
})
|
type: e.type
|
||||||
res.json({ methods: tfa });
|
}
|
||||||
}));
|
})
|
||||||
|
res.json({ methods: tfa });
|
||||||
TwoFactorRouter.delete("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
}));
|
||||||
let { id } = req.query;
|
|
||||||
let tfa = await TwoFactor.findById(id);
|
TwoFactorRouter.delete("/:id", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
||||||
if (!tfa || !tfa.user.equals(req.user._id)) {
|
let { id } = req.params;
|
||||||
throw new RequestError("Invalid id", HttpStatusCode.BAD_REQUEST);
|
let tfa = await TwoFactor.findById(id);
|
||||||
}
|
if (!tfa || !tfa.user.equals(req.user._id)) {
|
||||||
tfa.valid = false;
|
throw new RequestError("Invalid id", HttpStatusCode.BAD_REQUEST);
|
||||||
await TwoFactor.save(tfa);
|
}
|
||||||
res.json({ success: true });
|
tfa.valid = false;
|
||||||
}));
|
await TwoFactor.save(tfa);
|
||||||
|
res.json({ success: true });
|
||||||
TwoFactorRouter.use("/yubikey", YubiKeyRoute);
|
}));
|
||||||
|
|
||||||
|
TwoFactorRouter.use("/yubikey", YubiKeyRoute);
|
||||||
|
TwoFactorRouter.use("/otc", OTCRoute);
|
||||||
|
TwoFactorRouter.use("/backup", BackupCodeRoute);
|
||||||
|
|
||||||
export default TwoFactorRouter;
|
export default TwoFactorRouter;
|
105
src/api/user/twofactor/otc/index.ts
Normal file
105
src/api/user/twofactor/otc/index.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
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;
|
@ -1,131 +1,134 @@
|
|||||||
import { Router, Request } from "express"
|
import { Router, Request } from "express"
|
||||||
import Stacker from "../../../middlewares/stacker";
|
import Stacker from "../../../middlewares/stacker";
|
||||||
import { UserMiddleware, GetUserMiddleware } from "../../../middlewares/user";
|
import { UserMiddleware, GetUserMiddleware } from "../../../middlewares/user";
|
||||||
import * as u2f from "u2f";
|
import * as u2f from "u2f";
|
||||||
import config from "../../../../config";
|
import config from "../../../../config";
|
||||||
import TwoFactor, { TFATypes as TwoFATypes, IYubiKey } from "../../../../models/twofactor";
|
import TwoFactor, { TFATypes as TwoFATypes, IYubiKey } from "../../../../models/twofactor";
|
||||||
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
|
||||||
import moment = require("moment");
|
import moment = require("moment");
|
||||||
import LoginToken from "../../../../models/login_token";
|
import LoginToken from "../../../../models/login_token";
|
||||||
import { upgradeToken } from "../helper";
|
import { upgradeToken } from "../helper";
|
||||||
import Logging from "@hibas123/nodelogging";
|
import Logging from "@hibas123/nodelogging";
|
||||||
|
|
||||||
const U2FRoute = Router();
|
const U2FRoute = Router();
|
||||||
|
|
||||||
U2FRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
/**
|
||||||
const { type } = req.query;
|
* Registerinf a new YubiKey
|
||||||
if (type === "challenge") {
|
*/
|
||||||
const registrationRequest = u2f.request(config.core.url);
|
U2FRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
||||||
|
const { type } = req.query;
|
||||||
let twofactor = TwoFactor.new(<IYubiKey>{
|
if (type === "challenge") {
|
||||||
user: req.user._id,
|
const registrationRequest = u2f.request(config.core.url);
|
||||||
type: TwoFATypes.U2F,
|
|
||||||
valid: false,
|
let twofactor = TwoFactor.new(<IYubiKey>{
|
||||||
data: {
|
user: req.user._id,
|
||||||
registration: registrationRequest
|
type: TwoFATypes.U2F,
|
||||||
}
|
valid: false,
|
||||||
})
|
data: {
|
||||||
await TwoFactor.save(twofactor);
|
registration: registrationRequest
|
||||||
res.json({ request: registrationRequest, id: twofactor._id, appid: config.core.url });
|
}
|
||||||
} else {
|
})
|
||||||
const { response, id } = req.body;
|
await TwoFactor.save(twofactor);
|
||||||
Logging.debug(req.body, id);
|
res.json({ request: registrationRequest, id: twofactor._id, appid: config.core.url });
|
||||||
let twofactor: IYubiKey = await TwoFactor.findById(id);
|
} else {
|
||||||
const err = () => { throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST) };
|
const { response, id } = req.body;
|
||||||
if (!twofactor || !twofactor.user.equals(req.user._id) || twofactor.type !== TwoFATypes.U2F || !twofactor.data.registration || twofactor.valid) {
|
Logging.debug(req.body, id);
|
||||||
Logging.debug("Not found or wrong user", twofactor);
|
let twofactor: IYubiKey = await TwoFactor.findById(id);
|
||||||
err();
|
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);
|
||||||
if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) {
|
err();
|
||||||
await TwoFactor.delete(twofactor);
|
}
|
||||||
Logging.debug("Expired!", twofactor);
|
|
||||||
err();
|
if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) {
|
||||||
}
|
await TwoFactor.delete(twofactor);
|
||||||
|
Logging.debug("Expired!", twofactor);
|
||||||
const result = u2f.checkRegistration(twofactor.data.registration, response);
|
err();
|
||||||
|
}
|
||||||
if (result.successful) {
|
|
||||||
twofactor.data = {
|
const result = u2f.checkRegistration(twofactor.data.registration, response);
|
||||||
keyHandle: result.keyHandle,
|
|
||||||
publicKey: result.publicKey
|
if (result.successful) {
|
||||||
}
|
twofactor.data = {
|
||||||
twofactor.expires = undefined;
|
keyHandle: result.keyHandle,
|
||||||
twofactor.valid = true;
|
publicKey: result.publicKey
|
||||||
await TwoFactor.save(twofactor);
|
}
|
||||||
res.json({ success: true });
|
twofactor.expires = undefined;
|
||||||
} else {
|
twofactor.valid = true;
|
||||||
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
|
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 })
|
|
||||||
|
U2FRoute.get("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
|
||||||
if (!twofactor) {
|
let { login, special } = req.token;
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, type: TwoFATypes.U2F, valid: true })
|
||||||
}
|
|
||||||
|
if (!twofactor) {
|
||||||
if (twofactor.expires) {
|
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
||||||
if (moment().isAfter(twofactor.expires)) {
|
}
|
||||||
twofactor.valid = false;
|
|
||||||
await TwoFactor.save(twofactor);
|
if (twofactor.expires) {
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
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 request = u2f.request(config.core.url, twofactor.data.keyHandle);
|
||||||
};
|
login.data = {
|
||||||
let r;;
|
type: "ykr",
|
||||||
if (special) {
|
request
|
||||||
special.data = login.data;
|
};
|
||||||
r = LoginToken.save(special);
|
let r;;
|
||||||
}
|
if (special) {
|
||||||
|
special.data = login.data;
|
||||||
await Promise.all([r, LoginToken.save(login)]);
|
r = LoginToken.save(special);
|
||||||
res.json({ request });
|
}
|
||||||
}))
|
|
||||||
|
await Promise.all([r, LoginToken.save(login)]);
|
||||||
U2FRoute.put("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
|
res.json({ request });
|
||||||
let { login, special } = req.token;
|
}))
|
||||||
let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, type: TwoFATypes.U2F, valid: true })
|
|
||||||
|
U2FRoute.put("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
|
||||||
|
let { login, special } = req.token;
|
||||||
let { response } = req.body;
|
let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, type: TwoFATypes.U2F, valid: true })
|
||||||
if (!twofactor || !login.data || login.data.type !== "ykr" || special && (!special.data || special.data.type !== "ykr")) {
|
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
|
||||||
}
|
let { response } = req.body;
|
||||||
|
if (!twofactor || !login.data || login.data.type !== "ykr" || special && (!special.data || special.data.type !== "ykr")) {
|
||||||
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
|
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
||||||
twofactor.valid = false;
|
}
|
||||||
await TwoFactor.save(twofactor);
|
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
|
||||||
}
|
twofactor.valid = false;
|
||||||
|
await TwoFactor.save(twofactor);
|
||||||
let login_exp;
|
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
||||||
let special_exp;
|
}
|
||||||
let result = u2f.checkSignature(login.data.request, response, twofactor.data.publicKey);
|
|
||||||
if (result.successful) {
|
let login_exp;
|
||||||
if (special) {
|
let special_exp;
|
||||||
let result = u2f.checkSignature(special.data.request, response, twofactor.data.publicKey);
|
let result = u2f.checkSignature(login.data.request, response, twofactor.data.publicKey);
|
||||||
if (result.successful) {
|
if (result.successful) {
|
||||||
special_exp = await upgradeToken(special);
|
if (special) {
|
||||||
}
|
let result = u2f.checkSignature(special.data.request, response, twofactor.data.publicKey);
|
||||||
else {
|
if (result.successful) {
|
||||||
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
|
special_exp = await upgradeToken(special);
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
login_exp = await upgradeToken(login);
|
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
|
login_exp = await upgradeToken(login);
|
||||||
}
|
}
|
||||||
res.json({ success: true, login_exp, special_exp })
|
else {
|
||||||
}))
|
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
|
||||||
|
}
|
||||||
export default U2FRoute;
|
res.json({ success: true, login_exp, special_exp })
|
||||||
|
}))
|
||||||
|
|
||||||
|
export default U2FRoute;
|
||||||
|
@ -1,388 +1,388 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hypertext Transfer Protocol (HTTP) response status codes.
|
* Hypertext Transfer Protocol (HTTP) response status codes.
|
||||||
* @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
|
* @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
|
||||||
*/
|
*/
|
||||||
export enum HttpStatusCode {
|
export enum HttpStatusCode {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server has received the request headers and the client should proceed to send the request body
|
* 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).
|
* (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.
|
* 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
|
* 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.
|
* 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,
|
CONTINUE = 100,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The requester has asked the server to switch protocols and the server has agreed to do so.
|
* The requester has asked the server to switch protocols and the server has agreed to do so.
|
||||||
*/
|
*/
|
||||||
SWITCHING_PROTOCOLS = 101,
|
SWITCHING_PROTOCOLS = 101,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request.
|
* 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 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.
|
* This prevents the client from timing out and assuming the request was lost.
|
||||||
*/
|
*/
|
||||||
PROCESSING = 102,
|
PROCESSING = 102,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard response for successful HTTP requests.
|
* Standard response for successful HTTP requests.
|
||||||
* The actual response will depend on the request method used.
|
* 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 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.
|
* In a POST request, the response will contain an entity describing or containing the result of the action.
|
||||||
*/
|
*/
|
||||||
OK = 200,
|
OK = 200,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request has been fulfilled, resulting in the creation of a new resource.
|
* The request has been fulfilled, resulting in the creation of a new resource.
|
||||||
*/
|
*/
|
||||||
CREATED = 201,
|
CREATED = 201,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request has been accepted for processing, but the processing has not been completed.
|
* 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.
|
* The request might or might not be eventually acted upon, and may be disallowed when processing occurs.
|
||||||
*/
|
*/
|
||||||
ACCEPTED = 202,
|
ACCEPTED = 202,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SINCE HTTP/1.1
|
* SINCE HTTP/1.1
|
||||||
* The server is a transforming proxy that received a 200 OK from its origin,
|
* 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.
|
* but is returning a modified version of the origin's response.
|
||||||
*/
|
*/
|
||||||
NON_AUTHORITATIVE_INFORMATION = 203,
|
NON_AUTHORITATIVE_INFORMATION = 203,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server successfully processed the request and is not returning any content.
|
* The server successfully processed the request and is not returning any content.
|
||||||
*/
|
*/
|
||||||
NO_CONTENT = 204,
|
NO_CONTENT = 204,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server successfully processed the request, but is not returning any content.
|
* 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.
|
* Unlike a 204 response, this response requires that the requester reset the document view.
|
||||||
*/
|
*/
|
||||||
RESET_CONTENT = 205,
|
RESET_CONTENT = 205,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server is delivering only part of the resource (byte serving) due to a range header sent by the client.
|
* 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,
|
* The range header is used by HTTP clients to enable resuming of interrupted downloads,
|
||||||
* or split a download into multiple simultaneous streams.
|
* or split a download into multiple simultaneous streams.
|
||||||
*/
|
*/
|
||||||
PARTIAL_CONTENT = 206,
|
PARTIAL_CONTENT = 206,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The message body that follows is an XML message and can contain a number of separate response codes,
|
* 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.
|
* depending on how many sub-requests were made.
|
||||||
*/
|
*/
|
||||||
MULTI_STATUS = 207,
|
MULTI_STATUS = 207,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response,
|
* The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response,
|
||||||
* and are not being included again.
|
* and are not being included again.
|
||||||
*/
|
*/
|
||||||
ALREADY_REPORTED = 208,
|
ALREADY_REPORTED = 208,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server has fulfilled a request for the resource,
|
* 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.
|
* and the response is a representation of the result of one or more instance-manipulations applied to the current instance.
|
||||||
*/
|
*/
|
||||||
IM_USED = 226,
|
IM_USED = 226,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation).
|
* 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,
|
* 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.
|
* to list files with different filename extensions, or to suggest word-sense disambiguation.
|
||||||
*/
|
*/
|
||||||
MULTIPLE_CHOICES = 300,
|
MULTIPLE_CHOICES = 300,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This and all future requests should be directed to the given URI.
|
* This and all future requests should be directed to the given URI.
|
||||||
*/
|
*/
|
||||||
MOVED_PERMANENTLY = 301,
|
MOVED_PERMANENTLY = 301,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an example of industry practice contradicting the standard.
|
* 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 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
|
* (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
|
* 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
|
* to distinguish between the two behaviours. However, some Web applications and frameworks
|
||||||
* use the 302 status code as if it were the 303.
|
* use the 302 status code as if it were the 303.
|
||||||
*/
|
*/
|
||||||
FOUND = 302,
|
FOUND = 302,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SINCE HTTP/1.1
|
* SINCE HTTP/1.1
|
||||||
* The response to the request can be found under another URI using a GET method.
|
* 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
|
* 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.
|
* the server has received the data and should issue a redirect with a separate GET message.
|
||||||
*/
|
*/
|
||||||
SEE_OTHER = 303,
|
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.
|
* 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.
|
* In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy.
|
||||||
*/
|
*/
|
||||||
NOT_MODIFIED = 304,
|
NOT_MODIFIED = 304,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SINCE HTTP/1.1
|
* SINCE HTTP/1.1
|
||||||
* The requested resource is available only through a proxy, the address for which is provided in the response.
|
* 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.
|
* 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,
|
USE_PROXY = 305,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No longer used. Originally meant "Subsequent requests should use the specified proxy."
|
* No longer used. Originally meant "Subsequent requests should use the specified proxy."
|
||||||
*/
|
*/
|
||||||
SWITCH_PROXY = 306,
|
SWITCH_PROXY = 306,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SINCE HTTP/1.1
|
* 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 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.
|
* 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.
|
* For example, a POST request should be repeated using another POST request.
|
||||||
*/
|
*/
|
||||||
TEMPORARY_REDIRECT = 307,
|
TEMPORARY_REDIRECT = 307,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request and all future requests should be repeated using another URI.
|
* 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.
|
* 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.
|
* So, for example, submitting a form to a permanently redirected resource may continue smoothly.
|
||||||
*/
|
*/
|
||||||
PERMANENT_REDIRECT = 308,
|
PERMANENT_REDIRECT = 308,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server cannot or will not process the request due to an apparent client error
|
* 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).
|
* (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing).
|
||||||
*/
|
*/
|
||||||
BAD_REQUEST = 400,
|
BAD_REQUEST = 400,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet
|
* 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
|
* 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
|
* requested resource. See Basic access authentication and Digest access authentication. 401 semantically means
|
||||||
* "unauthenticated",i.e. the user does not have the necessary credentials.
|
* "unauthenticated",i.e. the user does not have the necessary credentials.
|
||||||
*/
|
*/
|
||||||
UNAUTHORIZED = 401,
|
UNAUTHORIZED = 401,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reserved for future use. The original intention was that this code might be used as part of some form of digital
|
* 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.
|
* 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.
|
* Google Developers API uses this status if a particular developer has exceeded the daily limit on requests.
|
||||||
*/
|
*/
|
||||||
PAYMENT_REQUIRED = 402,
|
PAYMENT_REQUIRED = 402,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request was valid, but the server is refusing action.
|
* The request was valid, but the server is refusing action.
|
||||||
* The user might not have the necessary permissions for a resource.
|
* The user might not have the necessary permissions for a resource.
|
||||||
*/
|
*/
|
||||||
FORBIDDEN = 403,
|
FORBIDDEN = 403,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The requested resource could not be found but may be available in the future.
|
* The requested resource could not be found but may be available in the future.
|
||||||
* Subsequent requests by the client are permissible.
|
* Subsequent requests by the client are permissible.
|
||||||
*/
|
*/
|
||||||
NOT_FOUND = 404,
|
NOT_FOUND = 404,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A request method is not supported for the requested resource;
|
* 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.
|
* 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,
|
METHOD_NOT_ALLOWED = 405,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.
|
* The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.
|
||||||
*/
|
*/
|
||||||
NOT_ACCEPTABLE = 406,
|
NOT_ACCEPTABLE = 406,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The client must first authenticate itself with the proxy.
|
* The client must first authenticate itself with the proxy.
|
||||||
*/
|
*/
|
||||||
PROXY_AUTHENTICATION_REQUIRED = 407,
|
PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server timed out waiting for the request.
|
* The server timed out waiting for the request.
|
||||||
* According to HTTP specifications:
|
* 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."
|
* "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,
|
REQUEST_TIMEOUT = 408,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that the request could not be processed because of conflict in the request,
|
* Indicates that the request could not be processed because of conflict in the request,
|
||||||
* such as an edit conflict between multiple simultaneous updates.
|
* such as an edit conflict between multiple simultaneous updates.
|
||||||
*/
|
*/
|
||||||
CONFLICT = 409,
|
CONFLICT = 409,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that the resource requested is no longer available and will not be available again.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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,
|
GONE = 410,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request did not specify the length of its content, which is required by the requested resource.
|
* The request did not specify the length of its content, which is required by the requested resource.
|
||||||
*/
|
*/
|
||||||
LENGTH_REQUIRED = 411,
|
LENGTH_REQUIRED = 411,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server does not meet one of the preconditions that the requester put on the request.
|
* The server does not meet one of the preconditions that the requester put on the request.
|
||||||
*/
|
*/
|
||||||
PRECONDITION_FAILED = 412,
|
PRECONDITION_FAILED = 412,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".
|
* The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".
|
||||||
*/
|
*/
|
||||||
PAYLOAD_TOO_LARGE = 413,
|
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,
|
* 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.
|
* in which case it should be converted to a POST request.
|
||||||
* Called "Request-URI Too Long" previously.
|
* Called "Request-URI Too Long" previously.
|
||||||
*/
|
*/
|
||||||
URI_TOO_LONG = 414,
|
URI_TOO_LONG = 414,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request entity has a media type which the server or resource does not support.
|
* 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.
|
* 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,
|
UNSUPPORTED_MEDIA_TYPE = 415,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.
|
* 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.
|
* 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.
|
* Called "Requested Range Not Satisfiable" previously.
|
||||||
*/
|
*/
|
||||||
RANGE_NOT_SATISFIABLE = 416,
|
RANGE_NOT_SATISFIABLE = 416,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server cannot meet the requirements of the Expect request-header field.
|
* The server cannot meet the requirements of the Expect request-header field.
|
||||||
*/
|
*/
|
||||||
EXPECTATION_FAILED = 417,
|
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,
|
* 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
|
* 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.
|
* 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,
|
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).
|
* The request was directed at a server that is not able to produce a response (for example because a connection reuse).
|
||||||
*/
|
*/
|
||||||
MISDIRECTED_REQUEST = 421,
|
MISDIRECTED_REQUEST = 421,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request was well-formed but was unable to be followed due to semantic errors.
|
* The request was well-formed but was unable to be followed due to semantic errors.
|
||||||
*/
|
*/
|
||||||
UNPROCESSABLE_ENTITY = 422,
|
UNPROCESSABLE_ENTITY = 422,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The resource that is being accessed is locked.
|
* The resource that is being accessed is locked.
|
||||||
*/
|
*/
|
||||||
LOCKED = 423,
|
LOCKED = 423,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request failed due to failure of a previous request (e.g., a PROPPATCH).
|
* The request failed due to failure of a previous request (e.g., a PROPPATCH).
|
||||||
*/
|
*/
|
||||||
FAILED_DEPENDENCY = 424,
|
FAILED_DEPENDENCY = 424,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.
|
* The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.
|
||||||
*/
|
*/
|
||||||
UPGRADE_REQUIRED = 426,
|
UPGRADE_REQUIRED = 426,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The origin server requires the request to be conditional.
|
* The origin server requires the request to be conditional.
|
||||||
* Intended to prevent "the 'lost update' problem, where a client
|
* Intended to prevent "the 'lost update' problem, where a client
|
||||||
* GETs a resource's state, modifies it, and PUTs it back to the server,
|
* 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."
|
* when meanwhile a third party has modified the state on the server, leading to a conflict."
|
||||||
*/
|
*/
|
||||||
PRECONDITION_REQUIRED = 428,
|
PRECONDITION_REQUIRED = 428,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes.
|
* The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes.
|
||||||
*/
|
*/
|
||||||
TOO_MANY_REQUESTS = 429,
|
TOO_MANY_REQUESTS = 429,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server is unwilling to process the request because either an individual header field,
|
* The server is unwilling to process the request because either an individual header field,
|
||||||
* or all the header fields collectively, are too large.
|
* or all the header fields collectively, are too large.
|
||||||
*/
|
*/
|
||||||
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
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
|
* 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.
|
* that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451.
|
||||||
*/
|
*/
|
||||||
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
|
* A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
|
||||||
*/
|
*/
|
||||||
INTERNAL_SERVER_ERROR = 500,
|
INTERNAL_SERVER_ERROR = 500,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server either does not recognize the request method, or it lacks the ability to fulfill the request.
|
* 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).
|
* Usually this implies future availability (e.g., a new feature of a web-service API).
|
||||||
*/
|
*/
|
||||||
NOT_IMPLEMENTED = 501,
|
NOT_IMPLEMENTED = 501,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server was acting as a gateway or proxy and received an invalid response from the upstream server.
|
* The server was acting as a gateway or proxy and received an invalid response from the upstream server.
|
||||||
*/
|
*/
|
||||||
BAD_GATEWAY = 502,
|
BAD_GATEWAY = 502,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server is currently unavailable (because it is overloaded or down for maintenance).
|
* The server is currently unavailable (because it is overloaded or down for maintenance).
|
||||||
* Generally, this is a temporary state.
|
* Generally, this is a temporary state.
|
||||||
*/
|
*/
|
||||||
SERVICE_UNAVAILABLE = 503,
|
SERVICE_UNAVAILABLE = 503,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
|
* The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
|
||||||
*/
|
*/
|
||||||
GATEWAY_TIMEOUT = 504,
|
GATEWAY_TIMEOUT = 504,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server does not support the HTTP protocol version used in the request
|
* The server does not support the HTTP protocol version used in the request
|
||||||
*/
|
*/
|
||||||
HTTP_VERSION_NOT_SUPPORTED = 505,
|
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transparent content negotiation for the request results in a circular reference.
|
* Transparent content negotiation for the request results in a circular reference.
|
||||||
*/
|
*/
|
||||||
VARIANT_ALSO_NEGOTIATES = 506,
|
VARIANT_ALSO_NEGOTIATES = 506,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server is unable to store the representation needed to complete the request.
|
* The server is unable to store the representation needed to complete the request.
|
||||||
*/
|
*/
|
||||||
INSUFFICIENT_STORAGE = 507,
|
INSUFFICIENT_STORAGE = 507,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server detected an infinite loop while processing the request.
|
* The server detected an infinite loop while processing the request.
|
||||||
*/
|
*/
|
||||||
LOOP_DETECTED = 508,
|
LOOP_DETECTED = 508,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Further extensions to the request are required for the server to fulfill it.
|
* Further extensions to the request are required for the server to fulfill it.
|
||||||
*/
|
*/
|
||||||
NOT_EXTENDED = 510,
|
NOT_EXTENDED = 510,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The client needs to authenticate to gain network access.
|
* 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
|
* 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).
|
* to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot).
|
||||||
*/
|
*/
|
||||||
NETWORK_AUTHENTICATION_REQUIRED = 511
|
NETWORK_AUTHENTICATION_REQUIRED = 511
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default class RequestError extends Error {
|
export default class RequestError extends Error {
|
||||||
constructor(message: any, public status: HttpStatusCode, public nolog: boolean = false) {
|
constructor(message: any, public status: HttpStatusCode, public nolog: boolean = false, public additional: any = undefined) {
|
||||||
super("")
|
super("")
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
150
src/index.ts
150
src/index.ts
@ -1,73 +1,79 @@
|
|||||||
import Logging from "@hibas123/nodelogging";
|
import Logging from "@hibas123/nodelogging";
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
|
|
||||||
import NLS from "@hibas123/nodeloggingserver_client";
|
import NLS from "@hibas123/nodeloggingserver_client";
|
||||||
if (config.logging) {
|
if (config.logging) {
|
||||||
let s = NLS(Logging, config.logging.server, config.logging.appid, config.logging.token);
|
let s = NLS(Logging, config.logging.server, config.logging.appid, config.logging.token);
|
||||||
s.send(`[${new Date().toLocaleTimeString()}] Starting application`);
|
s.send(`[${new Date().toLocaleTimeString()}] Starting application`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (!config.database) {
|
// if (!config.database) {
|
||||||
// Logging.error("No database config set. Terminating.")
|
// Logging.error("No database config set. Terminating.")
|
||||||
// process.exit();
|
// process.exit();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (!config.web) {
|
if (!config.web) {
|
||||||
Logging.error("No web config set. Terminating.")
|
Logging.error("No web config set. Terminating.")
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
import * as i18n from "i18n"
|
import * as i18n from "i18n"
|
||||||
i18n.configure({
|
i18n.configure({
|
||||||
locales: ["en", "de"],
|
locales: ["en", "de"],
|
||||||
directory: "./locales",
|
directory: "./locales",
|
||||||
})
|
})
|
||||||
|
|
||||||
import Web from "./web";
|
import Web from "./web";
|
||||||
import TestData from "./testdata";
|
import TestData from "./testdata";
|
||||||
import DB from "./database";
|
import DB from "./database";
|
||||||
|
|
||||||
Logging.log("Connecting to Database")
|
Logging.log("Connecting to Database")
|
||||||
if (config.dev) {
|
if (config.dev) {
|
||||||
Logging.warning("Running in dev mode! Database will be cleared!")
|
Logging.warning("Running in dev mode! Database will be cleared!")
|
||||||
}
|
}
|
||||||
DB.connect().then(async () => {
|
DB.connect().then(async () => {
|
||||||
Logging.log("Database connected")
|
Logging.log("Database connected")
|
||||||
if (config.dev)
|
if (config.dev)
|
||||||
await TestData()
|
await TestData()
|
||||||
let web = new Web(config.web)
|
let web = new Web(config.web)
|
||||||
web.listen()
|
web.listen()
|
||||||
function print(path, layer) {
|
|
||||||
if (layer.route) {
|
let already = new Set();
|
||||||
layer.route.stack.forEach(print.bind(null, path.concat(split(layer.route.path))))
|
function print(path, layer) {
|
||||||
} else if (layer.name === 'router' && layer.handle.stack) {
|
if (layer.route) {
|
||||||
layer.handle.stack.forEach(print.bind(null, path.concat(split(layer.regexp))))
|
layer.route.stack.forEach(print.bind(null, path.concat(split(layer.route.path))))
|
||||||
} else if (layer.method) {
|
} else if (layer.name === 'router' && layer.handle.stack) {
|
||||||
let me: string = layer.method.toUpperCase();
|
layer.handle.stack.forEach(print.bind(null, path.concat(split(layer.regexp))))
|
||||||
me += " ".repeat(6 - me.length);
|
} else if (layer.method) {
|
||||||
Logging.log(`${me} /${path.concat(split(layer.regexp)).filter(Boolean).join('/')}`);
|
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)) {
|
||||||
function split(thing) {
|
already.add(msg);
|
||||||
if (typeof thing === 'string') {
|
Logging.log(msg);
|
||||||
return thing.split('/')
|
}
|
||||||
} else if (thing.fast_slash) {
|
}
|
||||||
return ''
|
}
|
||||||
} else {
|
|
||||||
var match = thing.toString()
|
function split(thing) {
|
||||||
.replace('\\/?', '')
|
if (typeof thing === 'string') {
|
||||||
.replace('(?=\\/|$)', '$')
|
return thing.split('/')
|
||||||
.match(/^\/\^((?:\\[.*+?^${}()|[\]\\\/]|[^.*+?^${}()|[\]\\\/])*)\$\//)
|
} else if (thing.fast_slash) {
|
||||||
return match
|
return ''
|
||||||
? match[1].replace(/\\(.)/g, '$1').split('/')
|
} else {
|
||||||
: '<complex:' + thing.toString() + '>'
|
var match = thing.toString()
|
||||||
}
|
.replace('\\/?', '')
|
||||||
}
|
.replace('(?=\\/|$)', '$')
|
||||||
Logging.log("--- Endpoints: ---");
|
.match(/^\/\^((?:\\[.*+?^${}()|[\]\\\/]|[^.*+?^${}()|[\]\\\/])*)\$\//)
|
||||||
web.server._router.stack.forEach(print.bind(null, []))
|
return match
|
||||||
Logging.log("--- Endpoints end ---")
|
? match[1].replace(/\\(.)/g, '$1').split('/')
|
||||||
}).catch(e => {
|
: '<complex:' + thing.toString() + '>'
|
||||||
Logging.error(e)
|
}
|
||||||
process.exit();
|
}
|
||||||
|
// Logging.log("--- Endpoints: ---");
|
||||||
|
// web.server._router.stack.forEach(print.bind(null, []))
|
||||||
|
// Logging.log("--- Endpoints end ---")
|
||||||
|
}).catch(e => {
|
||||||
|
Logging.error(e)
|
||||||
|
process.exit();
|
||||||
})
|
})
|
@ -1,65 +1,67 @@
|
|||||||
import DB from "../database";
|
import DB from "../database";
|
||||||
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
||||||
import { ObjectID } from "mongodb";
|
import { ObjectID } from "mongodb";
|
||||||
import moment = require("moment");
|
import moment = require("moment");
|
||||||
|
|
||||||
export interface ILoginToken extends ModelDataBase {
|
export interface ILoginToken extends ModelDataBase {
|
||||||
token: string;
|
token: string;
|
||||||
special: boolean;
|
special: boolean;
|
||||||
user: ObjectID;
|
user: ObjectID;
|
||||||
validTill: Date;
|
validTill: Date;
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
validated: boolean;
|
validated: boolean;
|
||||||
data: any;
|
data: any;
|
||||||
ip: string;
|
ip: string;
|
||||||
browser: string;
|
browser: string;
|
||||||
}
|
}
|
||||||
const LoginToken = DB.addModel<ILoginToken>({
|
const LoginToken = DB.addModel<ILoginToken>({
|
||||||
name: "login_token",
|
name: "login_token",
|
||||||
versions: [{
|
versions: [{
|
||||||
migration: () => { },
|
migration: () => { },
|
||||||
schema: {
|
schema: {
|
||||||
token: { type: String },
|
token: { type: String },
|
||||||
special: { type: Boolean, default: () => false },
|
special: { type: Boolean, default: () => false },
|
||||||
user: { type: ObjectID },
|
user: { type: ObjectID },
|
||||||
validTill: { type: Date },
|
validTill: { type: Date },
|
||||||
valid: { type: Boolean }
|
valid: { type: Boolean }
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
migration: (doc: ILoginToken) => { doc.validated = true; },
|
migration: (doc: ILoginToken) => { doc.validated = true; },
|
||||||
schema: {
|
schema: {
|
||||||
token: { type: String },
|
token: { type: String },
|
||||||
special: { type: Boolean, default: () => false },
|
special: { type: Boolean, default: () => false },
|
||||||
user: { type: ObjectID },
|
user: { type: ObjectID },
|
||||||
validTill: { type: Date },
|
validTill: { type: Date },
|
||||||
valid: { type: Boolean },
|
valid: { type: Boolean },
|
||||||
validated: { type: Boolean, default: false }
|
validated: { type: Boolean, default: false }
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
migration: (doc: ILoginToken) => { doc.validated = true; },
|
migration: (doc: ILoginToken) => { doc.validated = true; },
|
||||||
schema: {
|
schema: {
|
||||||
token: { type: String },
|
token: { type: String },
|
||||||
special: { type: Boolean, default: () => false },
|
special: { type: Boolean, default: () => false },
|
||||||
user: { type: ObjectID },
|
user: { type: ObjectID },
|
||||||
validTill: { type: Date },
|
validTill: { type: Date },
|
||||||
valid: { type: Boolean },
|
valid: { type: Boolean },
|
||||||
validated: { type: Boolean, default: false },
|
validated: { type: Boolean, default: false },
|
||||||
data: { type: "any", optional: true },
|
data: { type: "any", optional: true },
|
||||||
ip: { type: String, optional: true },
|
ip: { type: String, optional: true },
|
||||||
browser: { type: String, optional: true }
|
browser: { type: String, optional: true }
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function CheckToken(token: ILoginToken, validated: boolean = true): Promise<boolean> {
|
export async function CheckToken(token: ILoginToken, validated: boolean = true): Promise<boolean> {
|
||||||
if (!token || !token.valid) return false;
|
if (!token || !token.valid)
|
||||||
if (validated && !token.validated) return false;
|
return false;
|
||||||
if (moment().isAfter(token.validTill)) {
|
if (validated && !token.validated)
|
||||||
token.valid = false;
|
return false;
|
||||||
await LoginToken.save(token)
|
if (moment().isAfter(token.validTill)) {
|
||||||
return false;
|
token.valid = false;
|
||||||
}
|
await LoginToken.save(token)
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export default LoginToken;
|
export default LoginToken;
|
@ -1,57 +1,67 @@
|
|||||||
import DB from "../database";
|
import DB from "../database";
|
||||||
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
||||||
import { ObjectID } from "bson";
|
import { ObjectID } from "bson";
|
||||||
|
|
||||||
export enum TFATypes {
|
export enum TFATypes {
|
||||||
OTC,
|
OTC,
|
||||||
BACKUP_CODE,
|
BACKUP_CODE,
|
||||||
U2F,
|
U2F,
|
||||||
APP_ALLOW
|
APP_ALLOW
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITwoFactor extends ModelDataBase {
|
export const TFANames = new Map<TFATypes, string>();
|
||||||
user: ObjectID
|
TFANames.set(TFATypes.OTC, "Authenticator");
|
||||||
valid: boolean
|
TFANames.set(TFATypes.BACKUP_CODE, "Backup Codes");
|
||||||
expires?: Date;
|
TFANames.set(TFATypes.U2F, "Security Key (U2F)");
|
||||||
name?: string;
|
TFANames.set(TFATypes.APP_ALLOW, "App Push");
|
||||||
type: TFATypes
|
|
||||||
data: any;
|
export interface ITwoFactor extends ModelDataBase {
|
||||||
}
|
user: ObjectID
|
||||||
|
valid: boolean
|
||||||
export interface IOTP extends ITwoFactor {
|
expires?: Date;
|
||||||
data: string;
|
name?: string;
|
||||||
}
|
type: TFATypes
|
||||||
|
data: any;
|
||||||
export interface IYubiKey extends ITwoFactor {
|
}
|
||||||
data: {
|
|
||||||
registration?: any;
|
export interface IOTC extends ITwoFactor {
|
||||||
publicKey: string;
|
data: string;
|
||||||
keyHandle: string;
|
}
|
||||||
}
|
|
||||||
}
|
export interface IYubiKey extends ITwoFactor {
|
||||||
|
data: {
|
||||||
export interface IU2F extends ITwoFactor {
|
registration?: any;
|
||||||
data: {
|
publicKey: string;
|
||||||
challenge?: string;
|
keyHandle: string;
|
||||||
publicKey: string;
|
}
|
||||||
keyHandle: string;
|
}
|
||||||
registration?: string;
|
|
||||||
}
|
export interface IU2F extends ITwoFactor {
|
||||||
}
|
data: {
|
||||||
|
challenge?: string;
|
||||||
const TwoFactor = DB.addModel<ITwoFactor>({
|
publicKey: string;
|
||||||
name: "twofactor",
|
keyHandle: string;
|
||||||
versions: [{
|
registration?: string;
|
||||||
migration: (e) => { },
|
}
|
||||||
schema: {
|
}
|
||||||
user: { type: ObjectID },
|
|
||||||
valid: { type: Boolean },
|
export interface IBackupCode extends ITwoFactor {
|
||||||
expires: { type: Date, optional: true },
|
data: string[];
|
||||||
name: { type: String, optional: true },
|
}
|
||||||
type: { type: Number },
|
|
||||||
data: { type: "any" },
|
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;
|
export default TwoFactor;
|
201
src/testdata.ts
201
src/testdata.ts
@ -1,80 +1,123 @@
|
|||||||
import User, { Gender } from "./models/user";
|
import User, { Gender } from "./models/user";
|
||||||
import Client from "./models/client";
|
import Client from "./models/client";
|
||||||
import { Logging } from "@hibas123/nodelogging";
|
import { Logging } from "@hibas123/nodelogging";
|
||||||
import RegCode from "./models/regcodes";
|
import RegCode from "./models/regcodes";
|
||||||
import * as moment from "moment";
|
import * as moment from "moment";
|
||||||
import Permission from "./models/permissions";
|
import Permission from "./models/permissions";
|
||||||
import { ObjectID } from "bson";
|
import { ObjectID } from "bson";
|
||||||
import DB from "./database";
|
import DB from "./database";
|
||||||
import TwoFactor from "./models/twofactor";
|
import TwoFactor from "./models/twofactor";
|
||||||
|
|
||||||
export default async function TestData() {
|
|
||||||
await DB.db.dropDatabase();
|
import * as speakeasy from "speakeasy";
|
||||||
let u = await User.findOne({ username: "test" });
|
import LoginToken from "./models/login_token";
|
||||||
if (!u) {
|
import { log } from "handlebars";
|
||||||
Logging.log("Adding test user");
|
|
||||||
u = User.new({
|
export default async function TestData() {
|
||||||
username: "test",
|
await DB.db.dropDatabase();
|
||||||
birthday: new Date(),
|
let u = await User.findOne({ username: "test" });
|
||||||
gender: Gender.male,
|
if (!u) {
|
||||||
name: "Test Test",
|
Logging.log("Adding test user");
|
||||||
password: "125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc",
|
u = User.new({
|
||||||
salt: "test",
|
username: "test",
|
||||||
admin: true
|
birthday: new Date(),
|
||||||
})
|
gender: Gender.male,
|
||||||
await User.save(u);
|
name: "Test Test",
|
||||||
}
|
password: "125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc",
|
||||||
|
salt: "test",
|
||||||
let c = await Client.findOne({ client_id: "test001" });
|
admin: true
|
||||||
if (!c) {
|
})
|
||||||
Logging.log("Adding test client")
|
await User.save(u);
|
||||||
c = Client.new({
|
}
|
||||||
client_id: "test001",
|
|
||||||
client_secret: "test001",
|
let c = await Client.findOne({ client_id: "test001" });
|
||||||
internal: true,
|
if (!c) {
|
||||||
maintainer: u._id,
|
Logging.log("Adding test client")
|
||||||
name: "Test Client",
|
c = Client.new({
|
||||||
website: "http://example.com",
|
client_id: "test001",
|
||||||
redirect_url: "http://example.com"
|
client_secret: "test001",
|
||||||
})
|
internal: true,
|
||||||
await Client.save(c);
|
maintainer: u._id,
|
||||||
}
|
name: "Test Client",
|
||||||
|
website: "http://example.com",
|
||||||
let perm = await Permission.findOne({ id: 0 });
|
redirect_url: "http://example.com"
|
||||||
if (!perm) {
|
})
|
||||||
Logging.log("Adding test permission")
|
await Client.save(c);
|
||||||
perm = Permission.new({
|
}
|
||||||
_id: new ObjectID("507f1f77bcf86cd799439011"),
|
|
||||||
name: "TestPerm",
|
let perm = await Permission.findOne({ id: 0 });
|
||||||
description: "Permission just for testing purposes",
|
if (!perm) {
|
||||||
client: c._id
|
Logging.log("Adding test permission")
|
||||||
})
|
perm = Permission.new({
|
||||||
Permission.save(perm);
|
_id: new ObjectID("507f1f77bcf86cd799439011"),
|
||||||
}
|
name: "TestPerm",
|
||||||
|
description: "Permission just for testing purposes",
|
||||||
let r = await RegCode.findOne({ token: "test" });
|
client: c._id
|
||||||
if (!r) {
|
})
|
||||||
Logging.log("Adding test reg_code")
|
Permission.save(perm);
|
||||||
r = RegCode.new({
|
}
|
||||||
token: "test",
|
|
||||||
valid: true,
|
let r = await RegCode.findOne({ token: "test" });
|
||||||
validTill: moment().add("1", "year").toDate()
|
if (!r) {
|
||||||
})
|
Logging.log("Adding test reg_code")
|
||||||
await RegCode.save(r);
|
r = RegCode.new({
|
||||||
}
|
token: "test",
|
||||||
|
valid: true,
|
||||||
let t = await TwoFactor.findOne({ user: u._id, type: 2 })
|
validTill: moment().add("1", "year").toDate()
|
||||||
if (!t) {
|
})
|
||||||
t = TwoFactor.new({
|
await RegCode.save(r);
|
||||||
user: u._id,
|
}
|
||||||
type: 2,
|
|
||||||
valid: true,
|
let t = await TwoFactor.findOne({ user: u._id, type: 0 })
|
||||||
data: {
|
if (!t) {
|
||||||
keyHandle: "tWSaMoHX2E96CoZOKOi_4aj6WVEh1e46FKXN0oDY2Z-laNOFcATlStNDo52HX7ygupW-v9qZOCX3J4d5nhOzWQ",
|
t = TwoFactor.new({
|
||||||
publicKey: "BPsgBxR8M7MyrknlFuvYZv0Z1lZxiJQJNrLDA1yi3XKD_lrhIpnAh2OY_TsFjASvn3JTtwlCh62QdMvN-ejQL78"
|
user: u._id,
|
||||||
},
|
type: 0,
|
||||||
expires: null
|
valid: true,
|
||||||
})
|
data: "IIRW2P2UJRDDO2LDIRYW4LSREZLWMOKDNBJES2LLHRREK3R6KZJQ",
|
||||||
TwoFactor.save(t);
|
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)
|
||||||
}
|
}
|
200
src/web.ts
200
src/web.ts
@ -1,101 +1,101 @@
|
|||||||
import { WebConfig } from "./config";
|
import { WebConfig } from "./config";
|
||||||
import * as express from "express"
|
import * as express from "express"
|
||||||
import { Express } from "express"
|
import { Express } from "express"
|
||||||
|
|
||||||
import Logging from "@hibas123/nodelogging"
|
import Logging from "@hibas123/nodelogging"
|
||||||
|
|
||||||
import * as bodyparser from "body-parser";
|
import * as bodyparser from "body-parser";
|
||||||
import * as cookieparser from "cookie-parser"
|
import * as cookieparser from "cookie-parser"
|
||||||
|
|
||||||
import * as i18n from "i18n"
|
import * as i18n from "i18n"
|
||||||
import * as compression from "compression";
|
import * as compression from "compression";
|
||||||
import ApiRouter from "./api";
|
import ApiRouter from "./api";
|
||||||
import ViewRouter from "./views/views";
|
import ViewRouter from "./views/views";
|
||||||
import RequestError, { HttpStatusCode } from "./helper/request_error";
|
import RequestError, { HttpStatusCode } from "./helper/request_error";
|
||||||
|
|
||||||
export default class Web {
|
export default class Web {
|
||||||
server: Express
|
server: Express
|
||||||
private port: number
|
private port: number
|
||||||
|
|
||||||
constructor(config: WebConfig) {
|
constructor(config: WebConfig) {
|
||||||
this.server = express()
|
this.server = express()
|
||||||
this.port = Number(config.port);
|
this.port = Number(config.port);
|
||||||
this.registerMiddleware()
|
this.registerMiddleware()
|
||||||
this.registerEndpoints()
|
this.registerEndpoints()
|
||||||
this.registerErrorHandler()
|
this.registerErrorHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
listen() {
|
listen() {
|
||||||
this.server.listen(this.port, () => {
|
this.server.listen(this.port, () => {
|
||||||
Logging.log(`Server listening on port ${this.port}`)
|
Logging.log(`Server listening on port ${this.port}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerMiddleware() {
|
private registerMiddleware() {
|
||||||
this.server.use(cookieparser())
|
this.server.use(cookieparser())
|
||||||
this.server.use(bodyparser.json(), bodyparser.urlencoded({ extended: true }))
|
this.server.use(bodyparser.json(), bodyparser.urlencoded({ extended: true }))
|
||||||
this.server.use(i18n.init)
|
this.server.use(i18n.init)
|
||||||
|
|
||||||
//Logging Middleware
|
//Logging Middleware
|
||||||
this.server.use((req, res, next) => {
|
this.server.use((req, res, next) => {
|
||||||
let start = process.hrtime()
|
let start = process.hrtime()
|
||||||
let finished = false;
|
let finished = false;
|
||||||
let to = false;
|
let to = false;
|
||||||
let listener = () => {
|
let listener = () => {
|
||||||
if (finished) return;
|
if (finished) return;
|
||||||
finished = true;
|
finished = true;
|
||||||
let td = process.hrtime(start);
|
let td = process.hrtime(start);
|
||||||
let time = !to ? (td[0] * 1e3 + td[1] / 1e6).toFixed(2) : "--.--";
|
let time = !to ? (td[0] * 1e3 + td[1] / 1e6).toFixed(2) : "--.--";
|
||||||
let resColor = "";
|
let resColor = "";
|
||||||
if (res.statusCode >= 200 && res.statusCode < 300) resColor = "\x1b[32m" //Green
|
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 === 304 || res.statusCode === 302) resColor = "\x1b[33m"
|
||||||
else if (res.statusCode >= 400 && res.statusCode < 500) resColor = "\x1b[36m" //Cyan
|
else if (res.statusCode >= 400 && res.statusCode < 500) resColor = "\x1b[36m" //Cyan
|
||||||
else if (res.statusCode >= 500 && res.statusCode < 600) resColor = "\x1b[31m" //Red
|
else if (res.statusCode >= 500 && res.statusCode < 600) resColor = "\x1b[31m" //Red
|
||||||
let m = req.method;
|
let m = req.method;
|
||||||
while (m.length < 4) m += " ";
|
while (m.length < 4) m += " ";
|
||||||
Logging.log(`${m} ${req.originalUrl} ${req.language} ${resColor}${res.statusCode}\x1b[0m - ${time}ms`)
|
Logging.log(`${m} ${req.originalUrl} ${req.language} ${resColor}${res.statusCode}\x1b[0m - ${time}ms`)
|
||||||
res.removeListener("finish", listener)
|
res.removeListener("finish", listener)
|
||||||
}
|
}
|
||||||
res.on("finish", listener)
|
res.on("finish", listener)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
to = true;
|
to = true;
|
||||||
listener();
|
listener();
|
||||||
}, 2000)
|
}, 2000)
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.server.use(compression({
|
this.server.use(compression({
|
||||||
filter: (req, res) => {
|
filter: (req, res) => {
|
||||||
if (req.headers['x-no-compression']) {
|
if (req.headers['x-no-compression']) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return compression.filter(req, res)
|
return compression.filter(req, res)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerEndpoints() {
|
private registerEndpoints() {
|
||||||
this.server.use("/api", ApiRouter);
|
this.server.use("/api", ApiRouter);
|
||||||
this.server.use("/", ViewRouter)
|
this.server.use("/", ViewRouter)
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerErrorHandler() {
|
private registerErrorHandler() {
|
||||||
this.server.use((error, req: express.Request, res, next) => {
|
this.server.use((error, req: express.Request, res, next) => {
|
||||||
if (!(error instanceof RequestError)) {
|
if (!(error instanceof RequestError)) {
|
||||||
error = new RequestError(error.message, error.status || HttpStatusCode.INTERNAL_SERVER_ERROR, error.nolog || false);
|
error = new RequestError(error.message, error.status || HttpStatusCode.INTERNAL_SERVER_ERROR, error.nolog || false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.status === 500 && !(<any>error).nolog) {
|
if (error.status === 500 && !(<any>error).nolog) {
|
||||||
Logging.error(error);
|
Logging.error(error);
|
||||||
} else {
|
} else {
|
||||||
Logging.log(typeof error.message === "string" ? error.message.split("\n", 1)[0] : error.message);
|
Logging.log("Responded with Error:", typeof error.message === "string" ? error.message.split("\n", 1)[0] : error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.accepts(["json"])) {
|
if (req.accepts(["json"])) {
|
||||||
res.json_status = error.status || 500;
|
res.json_status = error.status || 500;
|
||||||
res.json({ error: error.message, status: error.status || 500 })
|
res.json({ error: error.message, status: error.status || 500, additional: error.additional })
|
||||||
} else
|
} else
|
||||||
res.status(error.status || 500).send(error.message)
|
res.status(error.status || 500).send(error.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user