Added U2F Support for YubiKey

This commit is contained in:
Fabian Stamm
2019-03-12 21:06:09 -04:00
parent aa47e6c92f
commit c54406564c
41 changed files with 2955 additions and 2005 deletions

View File

@ -1,8 +1,8 @@
import { Request, Router } from "express";
import ClientRoute from "./admin/client";
import UserRoute from "./admin/user";
import RegCodeRoute from "./admin/regcode";
import PermissionRoute from "./admin/permission";
import ClientRoute from "./client";
import UserRoute from "./user";
import RegCodeRoute from "./regcode";
import PermissionRoute from "./permission";
const AdminRoute: Router = Router();
AdminRoute.use("/client", ClientRoute);

View File

@ -14,6 +14,9 @@ ApiRouter.use("/user", UserRoute);
ApiRouter.use("/internal", InternalRoute);
ApiRouter.use("/oauth", OAuthRoute);
ApiRouter.use("/client/user", AuthGetUser);
// Legacy reasons (deprecated)
ApiRouter.use("/user", AuthGetUser);
// Legacy reasons (deprecated)

View File

@ -1,6 +1,6 @@
import { Router } from "express";
import { OAuthInternalApp } from "./internal/oauth";
import PasswordAuth from "./internal/password";
import { OAuthInternalApp } from "./oauth";
import PasswordAuth from "./password";
const InternalRoute: Router = Router();
InternalRoute.get("/oauth", OAuthInternalApp);

View File

@ -1,7 +1,9 @@
import { Request, Response, NextFunction, RequestHandler } from "express";
import promiseMiddleware from "../../helper/promiseMiddleware";
function call(handler: RequestHandler, req: Request, res: Response) {
type RH = (req: Request, res: Response, next?: NextFunction) => any;
function call(handler: RH, req: Request, res: Response) {
return new Promise((yes, no) => {
let p = handler(req, res, (err) => {
if (err) no(err);
@ -11,7 +13,7 @@ function call(handler: RequestHandler, req: Request, res: Response) {
})
}
const Stacker = (...handler: RequestHandler[]) => {
const Stacker = (...handler: RH[]) => {
return promiseMiddleware(async (req: Request, res: Response, next: NextFunction) => {
let hc = handler.concat();
while (hc.length > 0) {

View File

@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from "express";
import LoginToken from "../../models/login_token";
import LoginToken, { CheckToken } from "../../models/login_token";
import Logging from "@hibas123/nodelogging";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
import User from "../../models/user";
@ -14,7 +14,7 @@ class Invalid extends Error { }
* @param json 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
*/
export function GetUserMiddleware(json = false, special_token: boolean = false, redirect_uri?: string) {
export function GetUserMiddleware(json = false, special_required: boolean = false, redirect_uri?: string, validated = true) {
return promiseMiddleware(async function (req: Request, res: Response, next?: NextFunction) {
const invalid = () => {
throw new Invalid();
@ -24,8 +24,7 @@ export function GetUserMiddleware(json = false, special_token: boolean = false,
if (!login) invalid()
let token = await LoginToken.findOne({ token: login, valid: true })
if (!token) invalid()
if (!token.validated) invalid();
if (!await CheckToken(token, validated)) invalid();
let user = await User.findById(token.user);
if (!user) {
@ -34,31 +33,23 @@ export function GetUserMiddleware(json = false, special_token: boolean = false,
invalid();
}
if (token.validTill.getTime() < new Date().getTime()) { //Token expired
token.valid = false;
await LoginToken.save(token);
invalid()
}
let special_token;
if (special) {
Logging.debug("Special found")
let st = await LoginToken.findOne({ token: special, special: true, valid: true })
if (st && st.validated && st.valid && st.user.toHexString() === token.user.toHexString()) {
if (st.validTill.getTime() < new Date().getTime()) { //Token expired
Logging.debug("Special expired")
st.valid = false;
await LoginToken.save(st);
} else {
Logging.debug("Special valid")
req.special = true;
}
}
special_token = await LoginToken.findOne({ token: special, special: true, valid: true, user: token.user })
if (!await CheckToken(special_token, validated))
invalid();
req.special = true;
}
if (special_token && !req.special) invalid();
if (special_required && !req.special) invalid();
req.user = user
req.isAdmin = user.admin;
req.token = {
login: token,
special: special_token
}
if (next)
next()
@ -67,7 +58,7 @@ export function GetUserMiddleware(json = false, special_token: boolean = false,
if (e instanceof Invalid) {
if (req.method === "GET" && !json) {
res.status(HttpStatusCode.UNAUTHORIZED)
res.redirect("/login?base64=true&state=" + new Buffer(redirect_uri ? redirect_uri : req.originalUrl).toString("base64"))
res.redirect("/login?base64=true&state=" + Buffer.from(redirect_uri ? redirect_uri : req.originalUrl).toString("base64"))
} else {
throw new RequestError(req.__("You are not logged in or your login is expired"), HttpStatusCode.UNAUTHORIZED)
}

View File

@ -1,8 +1,8 @@
import { Router } from "express";
import AuthRoute from "./oauth/auth";
import JWTRoute from "./oauth/jwt";
import Public from "./oauth/public";
import RefreshTokenRoute from "./oauth/refresh";
import AuthRoute from "./auth";
import JWTRoute from "./jwt";
import Public from "./public";
import RefreshTokenRoute from "./refresh";
const OAuthRoue: Router = Router();
OAuthRoue.post("/auth", AuthRoute);

View File

@ -1,8 +0,0 @@
import { Request, Router } from "express";
import Register from "./user/register";
import Login from "./user/login";
const UserRoute: Router = Router();
UserRoute.post("/register", Register);
UserRoute.post("/login", Login)
export default UserRoute;

14
src/api/user/index.ts Normal file
View File

@ -0,0 +1,14 @@
import { Router } from "express";
import Register from "./register";
import Login from "./login";
import TwoFactorRoute from "./twofactor";
import { GetToken, DeleteToken } from "./token";
const UserRoute: Router = Router();
UserRoute.post("/register", Register);
UserRoute.post("/login", Login)
UserRoute.use("/twofactor", TwoFactorRoute);
UserRoute.get("/token", GetToken);
UserRoute.delete("/token", DeleteToken);
export default UserRoute;

View File

@ -1,10 +1,12 @@
import { Request, Response } from "express"
import User, { IUser, TokenTypes } from "../../models/user";
import User, { IUser } from "../../models/user";
import { randomBytes } from "crypto";
import moment = require("moment");
import LoginToken from "../../models/login_token";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
import promiseMiddleware from "../../helper/promiseMiddleware";
import * as speakeasy from "speakeasy";
import TwoFactor from "../../models/twofactor";
const Login = promiseMiddleware(async (req: Request, res: Response) => {
let type = req.query.type;
@ -19,7 +21,13 @@ const Login = promiseMiddleware(async (req: Request, res: Response) => {
return;
}
const sendToken = async (user: IUser, tfa?: TokenTypes[]) => {
const sendToken = async (user: IUser, tfa?: any[]) => {
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
let client = {
ip: Array.isArray(ip) ? ip[0] : ip,
browser: req.headers["user-agent"]
}
let token_str = randomBytes(16).toString("hex");
let tfa_exp = moment().add(5, "minutes").toDate()
let token_exp = moment().add(6, "months").toDate()
@ -28,7 +36,8 @@ const Login = promiseMiddleware(async (req: Request, res: Response) => {
valid: true,
validTill: tfa ? tfa_exp : token_exp,
user: user._id,
validated: tfa ? false : true
validated: tfa ? false : true,
...client
});
await LoginToken.save(token);
@ -40,7 +49,8 @@ const Login = promiseMiddleware(async (req: Request, res: Response) => {
validTill: tfa ? tfa_exp : special_exp,
special: true,
user: user._id,
validated: tfa ? false : true
validated: tfa ? false : true,
...client
});
await LoginToken.save(special);
@ -51,7 +61,7 @@ const Login = promiseMiddleware(async (req: Request, res: Response) => {
});
}
if (type === "password" || type === "twofactor") {
if (type === "password") {
let { username, password, uid } = req.body;
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid })
@ -61,20 +71,29 @@ const Login = promiseMiddleware(async (req: Request, res: Response) => {
if (user.password !== password) {
res.json({ error: req.__("Password or username wrong") })
} else {
if (type === "twofactor") {
let twofactor = await TwoFactor.find({ user: user._id, valid: true })
let expired = twofactor.filter(e => e.expires ? moment().isAfter(moment(e.expires)) : false)
await Promise.all(expired.map(e => {
e.valid = false;
return TwoFactor.save(e);
}));
twofactor = twofactor.filter(e => e.valid);
if (twofactor && twofactor.length > 0) {
let tfa = twofactor.map(e => {
return {
id: e._id,
name: e.name,
type: e.type
}
})
await sendToken(user, tfa);
} else {
if (user.twofactor && user.twofactor.length > 0) {
let types = user.twofactor.filter(f => f.valid).map(f => f.type)
await sendToken(user, types);
} else {
await sendToken(user);
}
await sendToken(user);
}
}
}
} else {
throw new RequestError("Invalid type!", HttpStatusCode.BAD_REQUEST);
res.json({ error: req.__("Invalid type!") });
}
});

29
src/api/user/token.ts Normal file
View File

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

View File

@ -0,0 +1,79 @@
import LoginToken, { ILoginToken } from "../../../models/login_token";
import moment = require("moment");
// export async function unlockToken() {
// let { type, code, login, special } = req.body;
// let [login_t, special_t] = await Promise.all([LoginToken.findOne({ token: login }), LoginToken.findOne({ token: special })]);
// if ((login && !login_t) || (special && !special_t)) {
// res.json({ error: req.__("Token not found!") });
// } else {
// let atoken = special_t || login_t;
// 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;
}

View File

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

View File

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

View File

@ -10,6 +10,8 @@ export interface WebConfig {
export interface CoreConfig {
name: string
url: string
dev: string
}
export interface Config {
@ -31,11 +33,12 @@ import * as dotenv from "dotenv";
import { Logging } from "@hibas123/nodelogging";
dotenv.config();
const config: Config = ini.parse(readFileSync("./config.ini").toString())
if (config.dev) config.dev = Boolean(config.dev);
if (process.env.DEV === "true") {
const config = ini.parse(readFileSync("./config.ini").toString()) as Config;
if (config.core.dev) config.dev = Boolean(config.core.dev);
if (process.env.DEV === "true")
config.dev = true;
if (config.dev)
Logging.warning("DEV mode active. This can cause major performance issues, data loss and vulnerabilities! ")
}
export default config;

5
src/express.d.ts vendored
View File

@ -1,5 +1,6 @@
import { IUser } from "./models/user";
import { IClient } from "./models/client";
import { ILoginToken } from "./models/login_token";
declare module "express" {
interface Request {
@ -7,5 +8,9 @@ declare module "express" {
client: IClient;
isAdmin: boolean;
special: boolean;
token: {
login: ILoginToken;
special?: ILoginToken;
}
}
}

View File

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

57
src/models/twofactor.ts Normal file
View File

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

View File

@ -11,11 +11,6 @@ export enum Gender {
other
}
export enum TokenTypes {
OTC,
BACKUP_CODE
}
export interface IUser extends ModelDataBase {
uid: string;
username: string;
@ -28,7 +23,6 @@ export interface IUser extends ModelDataBase {
salt: string;
mails: ObjectID[];
phones: { phone: string, verified: boolean, primary: boolean }[];
twofactor: { token: string, valid: boolean, type: TokenTypes }[];
encryption_key: string;
}
@ -100,6 +94,33 @@ const User = DB.addModel<IUser>({
default: () => randomString(64)
}
}
},
{
migration: (e: any) => { delete e.twofactor },
schema: {
uid: { type: String, default: () => v4() },
username: { type: String },
name: { type: String },
birthday: { type: Date, optional: true },
gender: { type: Number },
admin: { type: Boolean },
password: { type: String },
salt: { type: String },
mails: { type: Array, default: () => [] },
phones: {
array: true,
model: true,
type: {
phone: { type: String },
verified: { type: Boolean },
primary: { type: Boolean }
}
},
encryption_key: {
type: String,
default: () => randomString(64)
}
}
}]
})

View File

@ -6,6 +6,7 @@ import * as moment from "moment";
import Permission from "./models/permissions";
import { ObjectID } from "bson";
import DB from "./database";
import TwoFactor from "./models/twofactor";
export default async function TestData() {
await DB.db.dropDatabase();
@ -61,4 +62,19 @@ export default async function TestData() {
})
await RegCode.save(r);
}
let t = await TwoFactor.findOne({ user: u._id, type: 2 })
if (!t) {
t = TwoFactor.new({
user: u._id,
type: 2,
valid: true,
data: {
keyHandle: "tWSaMoHX2E96CoZOKOi_4aj6WVEh1e46FKXN0oDY2Z-laNOFcATlStNDo52HX7ygupW-v9qZOCX3J4d5nhOzWQ",
publicKey: "BPsgBxR8M7MyrknlFuvYZv0Z1lZxiJQJNrLDA1yi3XKD_lrhIpnAh2OY_TsFjASvn3JTtwlCh62QdMvN-ejQL78"
},
expires: null
})
TwoFactor.save(t);
}
}

View File

@ -8,6 +8,7 @@ function loadStatic() {
let html = readFileSync("./views/out/login/login.html").toString();
template = handlebars.compile(html);
}
/**
* Benchmarks (5000, 500 cuncurrent)
* Plain:
@ -26,7 +27,6 @@ function loadStatic() {
* - prod 6sec
*/
export default function GetLoginPage(__: typeof i__): string {
if (config.dev) {
loadStatic()

View File

@ -13,10 +13,11 @@ import Client from "../models/client";
import { Logging } from "@hibas123/nodelogging";
import Stacker from "../api/middlewares/stacker";
import { UserMiddleware, GetUserMiddleware } from "../api/middlewares/user";
import GetUserPage from "./user";
Handlebars.registerHelper("appname", () => config.core.name);
const cacheTime = moment.duration(1, "month").asSeconds();
const cacheTime = config.dev ? moment.duration(1, "month").asSeconds() : 10;
const ViewRouter: IRouter<void> = Router();
ViewRouter.get("/", UserMiddleware, (req, res) => {
@ -40,6 +41,11 @@ ViewRouter.get("/admin", GetUserMiddleware(false, true), (req: Request, res, nex
res.send(GetAdminPage(req.__))
})
ViewRouter.get("/user", Stacker(GetUserMiddleware(false, true), (req, res) => {
res.setHeader("Cache-Control", "public, max-age=" + cacheTime);
res.send(GetUserPage(req.__));
}));
ViewRouter.get("/auth", Stacker(GetUserMiddleware(false, true), async (req, res) => {
let { scope, redirect_uri, state, client_id }: { [key: string]: string } = req.query;
const sendError = (type) => {
@ -85,7 +91,7 @@ if (config.dev) {
res.send(GetAuthPage(req.__, "Test 05265", [
{
name: "Access Profile",
description: "It allows the application to know who you are. Required for all applications.",
description: "It allows the application to know who you are. Required for all applications. And a lot of more Text, because why not? This will not stop, till it is multiple lines long and maybe kill the layout, so keep reading as long as you like, but I promise it will get boring after some time. So this should be enougth.",
logo: logo
},
{

View File

@ -9,7 +9,7 @@ import * as cookieparser from "cookie-parser"
import * as i18n from "i18n"
import * as compression from "compression";
import ApiRouter from "./api/api";
import ApiRouter from "./api";
import ViewRouter from "./views/views";
import RequestError, { HttpStatusCode } from "./helper/request_error";
@ -64,16 +64,14 @@ export default class Web {
next()
})
function shouldCompress(req, res) {
if (req.headers['x-no-compression']) {
// don't compress responses with this request header
return false
this.server.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false
}
return compression.filter(req, res)
}
// fallback to standard filter function
return compression.filter(req, res)
}
this.server.use(compression({ filter: shouldCompress }))
}));
}
private registerEndpoints() {