Compare commits

..

10 Commits

Author SHA1 Message Date
Sebastian Seedorf
924dfc7def Get Resolvable Sync #2 2020-11-24 12:37:59 +01:00
Sebastian Seedorf
88ca594900 Get Resolvable Sync 2020-11-24 12:30:57 +01:00
Sebastian Seedorf
d6ceb9152e Fixed get session by id 2020-11-23 22:57:38 +01:00
Sebastian Seedorf
8dc481b551 Fixed circular reference #2 2020-11-19 23:04:36 +01:00
Sebastian Seedorf
91ba224f79 Fixed circular reference 2020-11-19 23:03:29 +01:00
Sebastian Seedorf
055d11b9b1 Added reset to resolvable.ts 2020-11-19 22:21:59 +01:00
Sebastian Seedorf
3a58260d12 More strict eslint rules 2020-11-19 22:16:48 +01:00
Sebastian Seedorf
40b148b7c7 Enforce single quotes 2020-11-19 20:40:21 +01:00
Sebastian Seedorf
7f44df42e7 Cache user info until expire 2020-11-19 20:33:07 +01:00
Sebastian Seedorf
e2662b1c3b Added expire header #2 2020-11-19 00:28:52 +01:00
25 changed files with 155 additions and 88 deletions

View File

@@ -9,32 +9,38 @@ module.exports = {
], ],
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
"plugin:@typescript-eslint/eslint-recommended", 'plugin:@typescript-eslint/eslint-recommended',
"plugin:@typescript-eslint/recommended", 'plugin:@typescript-eslint/recommended',
"plugin:promise/recommended", 'plugin:promise/recommended',
], ],
rules: { rules: {
"no-console": "error", 'no-console': 'error',
"max-len": ["error", {"code": 128}], 'max-len': ['error', {'code': 128}],
"no-process-env": "error", 'no-process-env': 'error',
"no-process-exit": "error", 'no-process-exit': 'error',
"no-null/no-null": "error", 'no-null/no-null': 'error',
"no-useless-return": "error", 'no-useless-return': 'error',
"prefer-arrow-callback": "warn", 'prefer-arrow-callback': 'warn',
"object-curly-spacing": "error", 'consistent-return': 'error',
"consistent-return": "error", '@typescript-eslint/explicit-function-return-type': [
"@typescript-eslint/explicit-function-return-type": [ 'error', {
"error", { 'allowExpressions': true,
"allowExpressions": true,
}, },
], ],
"no-void": "error", 'no-void': 'error',
"comma-spacing": "error", 'comma-spacing': 'error',
"comma-dangle": ["error", "always-multiline"], 'comma-dangle': ['error', 'always-multiline'],
"comma-style": "error", 'comma-style': 'error',
"semi": "error", 'semi': 'error',
"no-implicit-coercion": "error", 'no-implicit-coercion': 'error',
'quotes': ['error', 'single', 'avoid-escape'],
"promise/always-return": "off", 'keyword-spacing': 'error',
'semi-spacing': 'error',
'arrow-spacing': 'error',
'object-curly-spacing': 'error',
'array-bracket-spacing': 'error',
'key-spacing': 'error',
'block-spacing': 'error',
'promise/always-return': 'off',
}, },
}; };

View File

@@ -12,19 +12,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthProxy = void 0; exports.AuthProxy = void 0;
const _1 = require("."); const _1 = require(".");
const node_fetch_1 = require("node-fetch"); const node_fetch_1 = require("node-fetch");
const cachedUsers = {};
const router = (req, res, next) => { const router = (req, res, next) => {
const resolvable = new _1.Resolvable(() => __awaiter(void 0, void 0, void 0, function* () { const resolvable = new _1.Resolvable(() => __awaiter(void 0, void 0, void 0, function* () {
if (!_1.DefaultConfig.USERINFO_HEADER) { if (!_1.DefaultConfig.USERINFO_HEADER) {
return undefined; return undefined;
} }
const token = req.header(_1.DefaultConfig.USERINFO_HEADER); const token = req.header(_1.DefaultConfig.USERINFO_HEADER);
if (token && cachedUsers[token])
return cachedUsers[token];
const url = _1.DefaultConfig.AUTH_PROXY_USERINFO_URL; const url = _1.DefaultConfig.AUTH_PROXY_USERINFO_URL;
if (token === undefined || url === undefined) { if (token === undefined || url === undefined) {
return undefined; return undefined;
} }
try { try {
const expireHeader = req.header(_1.DefaultConfig.EXPIRE_HEADER);
const expireSecDate = expireHeader && parseInt(expireHeader, 10) || undefined;
const expiresIn = expireSecDate && expireSecDate * 1000 > Date.now() && expireSecDate * 1000 - Date.now();
const res = yield node_fetch_1.default(url, { headers: [[_1.DefaultConfig.USERINFO_HEADER, token]] }); const res = yield node_fetch_1.default(url, { headers: [[_1.DefaultConfig.USERINFO_HEADER, token]] });
return yield res.json(); const userinfo = yield res.json();
if (expiresIn) {
cachedUsers[token] = userinfo;
setTimeout(() => delete cachedUsers[token], expiresIn);
}
return userinfo;
} }
catch (e) { catch (e) {
_1.Logger.warn(e); _1.Logger.warn(e);

View File

@@ -14,7 +14,7 @@ function getRouter(opts) {
if (!_1.DefaultConfig.isProduction) { if (!_1.DefaultConfig.isProduction) {
let uuid = uuid_1.v4(); let uuid = uuid_1.v4();
let updateTimeout = undefined; let updateTimeout = undefined;
Promise.resolve().then(() => require("node-watch")).then((watch) => { Promise.resolve().then(() => require('node-watch')).then((watch) => {
for (const frontendDir of config.frontendDirs) { for (const frontendDir of config.frontendDirs) {
watch.default(frontendDir, { recursive: true }, () => { watch.default(frontendDir, { recursive: true }, () => {
if (updateTimeout !== undefined) if (updateTimeout !== undefined)
@@ -28,7 +28,7 @@ function getRouter(opts) {
// /auto-reload/client.js // /auto-reload/client.js
router.get(_1.urlJoin(config.subPath, config.jsName), (req, res) => { router.get(_1.urlJoin(config.subPath, config.jsName), (req, res) => {
_1.Logger.debug(req.url, req.originalUrl, req.baseUrl); _1.Logger.debug(req.url, req.originalUrl, req.baseUrl);
res.setHeader('Content-Type', "application/javascript"); res.setHeader('Content-Type', 'application/javascript');
// language=JavaScript // language=JavaScript
res.send(` res.send(`
const loc = window.location; const loc = window.location;
@@ -58,7 +58,7 @@ function getRouter(opts) {
} }
} }
}, 3000); }, 3000);
`.replace(/\t/g, "")); `.replace(/\t/g, ''));
}); });
// /auto-reload // /auto-reload
router.get(_1.urlJoin(config.subPath), (req, res) => { router.get(_1.urlJoin(config.subPath), (req, res) => {

1
out/config.d.ts vendored
View File

@@ -12,6 +12,7 @@ export declare const DefaultConfig: {
REDIS_URL: string | undefined; REDIS_URL: string | undefined;
SESSION_SECRET: string | undefined; SESSION_SECRET: string | undefined;
USERINFO_HEADER: string; USERINFO_HEADER: string;
EXPIRE_HEADER: string;
AUTH_PROXY_URL: string | undefined; AUTH_PROXY_URL: string | undefined;
}; };
export {}; export {};

View File

@@ -2,8 +2,8 @@
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultConfig = void 0; exports.DefaultConfig = void 0;
const env = require("env-var"); const env = require("env-var");
const _1 = require("."); const urlJoin_1 = require("./helpers/urlJoin");
const NODE_ENV = env.get('NODE_ENV').default("development").asString(); const NODE_ENV = env.get('NODE_ENV').default('development').asString();
const isProduction = NODE_ENV === 'production'; const isProduction = NODE_ENV === 'production';
const envs = { const envs = {
NODE_ENV, NODE_ENV,
@@ -20,7 +20,9 @@ const envs = {
// cookie secret for the session id (required in production using Session) // cookie secret for the session id (required in production using Session)
SESSION_SECRET: env.get('SESSION_SECRET').asString() || undefined, SESSION_SECRET: env.get('SESSION_SECRET').asString() || undefined,
// header where user info token is stored to request auth proxy // header where user info token is stored to request auth proxy
USERINFO_HEADER: env.get('USERINFO_HEADER').default("X-Userinfo-Token").asString(), USERINFO_HEADER: env.get('USERINFO_HEADER').default('X-Userinfo-Token').asString(),
// header when user info token expires (seconds after 01/01/1970)
EXPIRE_HEADER: env.get('EXPIRE_HEADER').default('X-Userinfo-Expire').asString(),
// base url to init a logout or request user info // base url to init a logout or request user info
AUTH_PROXY_URL: env.get('AUTH_PROXY_URL').asString() || undefined, AUTH_PROXY_URL: env.get('AUTH_PROXY_URL').asString() || undefined,
// override AUTH_PROXY_URL to request user info // override AUTH_PROXY_URL to request user info
@@ -32,7 +34,7 @@ function requireEnv(name, onlyInProduction = false) {
env.get(name).required(!onlyInProduction || isProduction).asString(); env.get(name).required(!onlyInProduction || isProduction).asString();
} }
exports.DefaultConfig = Object.assign(Object.assign({}, envs), { EXTERNAL_BASE_URL: envs.EXTERNAL_BASE_URL || exports.DefaultConfig = Object.assign(Object.assign({}, envs), { EXTERNAL_BASE_URL: envs.EXTERNAL_BASE_URL ||
_1.urlJoin(`http://${envs.HOSTNAME}${envs.PORT !== 80 ? `:${envs.PORT}` : ""}`, envs.BASE_PATH), isProduction, urlJoin_1.urlJoin(`http://${envs.HOSTNAME}${envs.PORT !== 80 ? `:${envs.PORT}` : ''}`, envs.BASE_PATH), isProduction,
requireEnv, AUTH_PROXY_USERINFO_URL: envs.AUTH_PROXY_USERINFO_URL || requireEnv, AUTH_PROXY_USERINFO_URL: envs.AUTH_PROXY_USERINFO_URL ||
envs.AUTH_PROXY_URL && _1.urlJoin(envs.AUTH_PROXY_URL, "userinfo"), AUTH_PROXY_INIT_LOGOUT_URL: envs.AUTH_PROXY_INIT_LOGOUT_URL || envs.AUTH_PROXY_URL && urlJoin_1.urlJoin(envs.AUTH_PROXY_URL, 'userinfo'), AUTH_PROXY_INIT_LOGOUT_URL: envs.AUTH_PROXY_INIT_LOGOUT_URL ||
envs.AUTH_PROXY_URL && _1.urlJoin(envs.AUTH_PROXY_URL, "init-logout") }); envs.AUTH_PROXY_URL && urlJoin_1.urlJoin(envs.AUTH_PROXY_URL, 'init-logout') });

View File

@@ -11,7 +11,10 @@ declare class FetchOnce<T, U extends Array<unknown>> {
protected state: ResolvableState; protected state: ResolvableState;
protected pendings: [(res: Promise<T> | T) => void, (reason: unknown) => void][]; protected pendings: [(res: Promise<T> | T) => void, (reason: unknown) => void][];
constructor(fetchMethod?: ((...args: U) => Promise<T>) | undefined); constructor(fetchMethod?: ((...args: U) => Promise<T>) | undefined);
reset(): void;
resolve(...args: U): Promise<T>; resolve(...args: U): Promise<T>;
getDataOrUndefined(): T | undefined;
getErrorOrUndefined(): unknown | undefined;
protected isFinished(): boolean; protected isFinished(): boolean;
protected parsePromise(promise: Promise<T>): void; protected parsePromise(promise: Promise<T>): void;
} }
@@ -23,5 +26,6 @@ export declare class WaitForSync<T> extends FetchOnce<T, never> {
constructor(); constructor();
setData(data: T): void; setData(data: T): void;
setError(error: unknown): void; setError(error: unknown): void;
reset(): void;
} }
export {}; export {};

View File

@@ -23,8 +23,12 @@ class FetchOnce {
this.state = ResolvableState.WAITING; this.state = ResolvableState.WAITING;
this.pendings = []; this.pendings = [];
} }
reset() {
this.data = undefined;
this.error = undefined;
this.state = ResolvableState.WAITING;
}
resolve(...args) { resolve(...args) {
// eslint-disable-next-line promise/avoid-new
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
switch (this.state) { switch (this.state) {
case ResolvableState.WAITING: case ResolvableState.WAITING:
@@ -45,6 +49,12 @@ class FetchOnce {
} }
}); });
} }
getDataOrUndefined() {
return this.data;
}
getErrorOrUndefined() {
return this.error;
}
isFinished() { isFinished() {
return this.state === ResolvableState.DONE || this.state === ResolvableState.ERROR; return this.state === ResolvableState.DONE || this.state === ResolvableState.ERROR;
} }
@@ -81,5 +91,9 @@ class WaitForSync extends FetchOnce {
this.parsePromise((() => __awaiter(this, void 0, void 0, function* () { throw error; }))()); this.parsePromise((() => __awaiter(this, void 0, void 0, function* () { throw error; }))());
} }
} }
reset() {
super.reset();
this.state = ResolvableState.PENDING;
}
} }
exports.WaitForSync = WaitForSync; exports.WaitForSync = WaitForSync;

2
out/logging.d.ts vendored
View File

@@ -1,7 +1,7 @@
import * as winston from 'winston'; import * as winston from 'winston';
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
export declare const levels: readonly ["error", "warn", "info", "http", "verbose", "debug", "silly"]; export declare const levels: readonly ["error", "warn", "info", "http", "verbose", "debug", "silly"];
export declare type LogLevels = typeof levels[number] | "log"; export declare type LogLevels = typeof levels[number] | 'log';
export declare const Logger: { export declare const Logger: {
error: (...args: unknown[]) => winston.Logger; error: (...args: unknown[]) => winston.Logger;
http: (...args: unknown[]) => winston.Logger; http: (...args: unknown[]) => winston.Logger;

View File

@@ -6,7 +6,7 @@ const colors = require("colors");
const _1 = require("."); const _1 = require(".");
const prune = require("json-prune"); const prune = require("json-prune");
const logger = winston.createLogger({ const logger = winston.createLogger({
level: _1.DefaultConfig.isProduction ? "info" : "silly", level: _1.DefaultConfig.isProduction ? 'info' : 'silly',
format: winston.format.json(), format: winston.format.json(),
transports: [ transports: [
new winston.transports.Console({ new winston.transports.Console({
@@ -14,17 +14,17 @@ const logger = winston.createLogger({
}), }),
], ],
}); });
exports.levels = ["error", "warn", "info", "http", "verbose", "debug", "silly"]; exports.levels = ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'];
const wrapper = (original) => { const wrapper = (original) => {
return (...args) => { return (...args) => {
return original(args.map((obj) => typeof obj === "string" ? obj : prune(obj)).join(" ")); return original(args.map((obj) => typeof obj === 'string' ? obj : prune(obj)).join(' '));
}; };
}; };
exports.Logger = {}; exports.Logger = {};
for (const level of exports.levels) { for (const level of exports.levels) {
exports.Logger[level] = wrapper(logger[level]); exports.Logger[level] = wrapper(logger[level]);
} }
exports.Logger.log = wrapper(logger["silly"]); exports.Logger.log = wrapper(logger['silly']);
exports.HttpLogger = (req, res, next) => { exports.HttpLogger = (req, res, next) => {
const start = Date.now(); const start = Date.now();
const path = req.path; const path = req.path;
@@ -37,8 +37,8 @@ exports.HttpLogger = (req, res, next) => {
: statusCode >= 200 ? colors.green : statusCode >= 200 ? colors.green
: colors.gray; : colors.gray;
const status = colorFunction(res.statusCode.toString(10)); const status = colorFunction(res.statusCode.toString(10));
const method = req.method.toUpperCase().padEnd(6, " "); const method = req.method.toUpperCase().padEnd(6, ' ');
const responseTime = (Date.now() - start).toString(10).padStart(3, " "); const responseTime = (Date.now() - start).toString(10).padStart(3, ' ');
if (!req.noHttpLogging) if (!req.noHttpLogging)
exports.Logger.http(`${status} ${method} ${responseTime}ms ${path}`); exports.Logger.http(`${status} ${method} ${responseTime}ms ${path}`);
end.apply(res, args); end.apply(res, args);

View File

@@ -88,7 +88,7 @@ class PermQuery extends Query_1.Query {
return this; return this;
} }
sync() { sync() {
throw new role_acl_1.AccessControlError("Sync method is not allowed on PermissionManager!"); throw new role_acl_1.AccessControlError('Sync method is not allowed on PermissionManager!');
} }
} }
exports.PermQuery = PermQuery; exports.PermQuery = PermQuery;

View File

@@ -7,9 +7,9 @@ const polyfill_analyzer_1 = require("@10xjs/polyfill-analyzer");
const polyfills_1 = require("@10xjs/polyfill-analyzer/dist/polyfills"); const polyfills_1 = require("@10xjs/polyfill-analyzer/dist/polyfills");
worker_1.expose((fileToWatch) => { worker_1.expose((fileToWatch) => {
const exclude = [ const exclude = [
"console.markTimeline", 'console.markTimeline',
"console.timeline", 'console.timeline',
"console.timelineEnd", 'console.timelineEnd',
]; ];
const featureList = polyfill_analyzer_1.analyze({ const featureList = polyfill_analyzer_1.analyze({
source: fs.readFileSync(fileToWatch, 'utf-8'), source: fs.readFileSync(fileToWatch, 'utf-8'),

View File

@@ -16,20 +16,20 @@ const threads_1 = require("threads");
function getRouter(fileToWatch, opts) { function getRouter(fileToWatch, opts) {
const features = new _1.WaitForSync(); const features = new _1.WaitForSync();
(() => __awaiter(this, void 0, void 0, function* () { (() => __awaiter(this, void 0, void 0, function* () {
const worker = yield threads_1.spawn(new threads_1.Worker("./polyfill-worker")); const worker = yield threads_1.spawn(new threads_1.Worker('./polyfill-worker'));
const feats = yield worker(fileToWatch); const feats = yield worker(fileToWatch);
yield threads_1.Thread.terminate(worker); yield threads_1.Thread.terminate(worker);
return feats; return feats;
}))() }))()
.then(feats => { .then(feats => {
feats["fetch"] = {}; feats['fetch'] = {};
_1.Logger.debug("Polyfill analysed:", Object.keys(feats)); _1.Logger.debug('Polyfill analysed:', Object.keys(feats));
features.setData(feats); features.setData(feats);
}) })
.catch(err => features.setError(err)); .catch(err => features.setError(err));
const options = Object.assign({ minify: _1.DefaultConfig.isProduction, unknown: "polyfill" }, opts); const options = Object.assign({ minify: _1.DefaultConfig.isProduction, unknown: 'polyfill' }, opts);
return (req, res) => __awaiter(this, void 0, void 0, function* () { return (req, res) => __awaiter(this, void 0, void 0, function* () {
const polyfillBundle = yield polyfillLibrary.getPolyfillString(Object.assign(Object.assign({}, options), { uaString: req.header("user-agent"), features: yield features.resolve(), stream: false })); const polyfillBundle = yield polyfillLibrary.getPolyfillString(Object.assign(Object.assign({}, options), { uaString: req.header('user-agent'), features: yield features.resolve(), stream: false }));
res.setHeader('Content-Type', 'text/javascript'); res.setHeader('Content-Type', 'text/javascript');
res.send(polyfillBundle); res.send(polyfillBundle);
}); });

View File

@@ -30,7 +30,7 @@ function getRouter(options) {
const sessionStore = req.sessionStore; const sessionStore = req.sessionStore;
if (sessionStore) { if (sessionStore) {
const getSession = util_1.promisify(sessionStore.get); const getSession = util_1.promisify(sessionStore.get);
const session = yield getSession(sessionId); const session = yield getSession.call(sessionStore, sessionId);
if (session) { if (session) {
sessionStore.createSession(req, session); sessionStore.createSession(req, session);
return true; // success return true; // success

View File

@@ -2,19 +2,30 @@ import {Request, RequestHandler} from 'express';
import {DefaultConfig, Logger, Resolvable, UserInfo} from '.'; import {DefaultConfig, Logger, Resolvable, UserInfo} from '.';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
const cachedUsers: {[token: string]: UserInfo} = {};
const router: RequestHandler = (req: Request, res, next) => { const router: RequestHandler = (req: Request, res, next) => {
const resolvable = new Resolvable(async () => { const resolvable = new Resolvable(async () => {
if (!DefaultConfig.USERINFO_HEADER) { if (!DefaultConfig.USERINFO_HEADER) {
return undefined; return undefined;
} }
const token = req.header(DefaultConfig.USERINFO_HEADER); const token = req.header(DefaultConfig.USERINFO_HEADER);
if (token && cachedUsers[token]) return cachedUsers[token];
const url = DefaultConfig.AUTH_PROXY_USERINFO_URL; const url = DefaultConfig.AUTH_PROXY_USERINFO_URL;
if (token === undefined || url === undefined) { if (token === undefined || url === undefined) {
return undefined; return undefined;
} }
try { try {
const expireHeader = req.header(DefaultConfig.EXPIRE_HEADER);
const expireSecDate = expireHeader && parseInt(expireHeader, 10) || undefined;
const expiresIn = expireSecDate && expireSecDate * 1000 > Date.now() && expireSecDate * 1000 - Date.now();
const res = await fetch(url, {headers: [[DefaultConfig.USERINFO_HEADER, token]]}); const res = await fetch(url, {headers: [[DefaultConfig.USERINFO_HEADER, token]]});
return await res.json() as UserInfo; const userinfo = await res.json() as UserInfo;
if (expiresIn) {
cachedUsers[token] = userinfo;
setTimeout(() => delete cachedUsers[token], expiresIn);
}
return userinfo;
} catch (e) { } catch (e) {
Logger.warn(e); Logger.warn(e);
return undefined; return undefined;

View File

@@ -32,7 +32,7 @@ function getRouter(opts: ReloaderConfig): Router {
if (!DefaultConfig.isProduction) { if (!DefaultConfig.isProduction) {
let uuid = v4(); let uuid = v4();
let updateTimeout: Timeout|undefined = undefined; let updateTimeout: Timeout|undefined = undefined;
import("node-watch").then((watch) => { import('node-watch').then((watch) => {
for (const frontendDir of config.frontendDirs) { for (const frontendDir of config.frontendDirs) {
watch.default(frontendDir, {recursive: true}, () => { watch.default(frontendDir, {recursive: true}, () => {
if (updateTimeout !== undefined) clearTimeout(updateTimeout); if (updateTimeout !== undefined) clearTimeout(updateTimeout);
@@ -46,7 +46,7 @@ function getRouter(opts: ReloaderConfig): Router {
// /auto-reload/client.js // /auto-reload/client.js
router.get(urlJoin(config.subPath, config.jsName), (req, res) => { router.get(urlJoin(config.subPath, config.jsName), (req, res) => {
Logger.debug(req.url, req.originalUrl, req.baseUrl); Logger.debug(req.url, req.originalUrl, req.baseUrl);
res.setHeader('Content-Type', "application/javascript"); res.setHeader('Content-Type', 'application/javascript');
// language=JavaScript // language=JavaScript
res.send(` res.send(`
const loc = window.location; const loc = window.location;
@@ -76,7 +76,7 @@ function getRouter(opts: ReloaderConfig): Router {
} }
} }
}, 3000); }, 3000);
`.replace(/\t/g, "")); `.replace(/\t/g, ''));
}); });
// /auto-reload // /auto-reload

View File

@@ -1,7 +1,7 @@
import * as env from 'env-var'; import * as env from 'env-var';
import {urlJoin} from '.'; import {urlJoin} from './helpers/urlJoin';
const NODE_ENV = env.get('NODE_ENV').default("development").asString(); const NODE_ENV = env.get('NODE_ENV').default('development').asString();
const isProduction = NODE_ENV === 'production'; const isProduction = NODE_ENV === 'production';
const envs = { const envs = {
@@ -21,9 +21,9 @@ const envs = {
SESSION_SECRET: env.get('SESSION_SECRET').asString() || undefined, SESSION_SECRET: env.get('SESSION_SECRET').asString() || undefined,
// header where user info token is stored to request auth proxy // header where user info token is stored to request auth proxy
USERINFO_HEADER: env.get('USERINFO_HEADER').default("X-Userinfo-Token").asString(), USERINFO_HEADER: env.get('USERINFO_HEADER').default('X-Userinfo-Token').asString(),
// header when user info token expires (seconds after 01/01/1970) // header when user info token expires (seconds after 01/01/1970)
EXPIRE_HEADER: env.get('EXPIRE_HEADER').default("X-Userinfo-Expire").asString(), EXPIRE_HEADER: env.get('EXPIRE_HEADER').default('X-Userinfo-Expire').asString(),
// base url to init a logout or request user info // base url to init a logout or request user info
AUTH_PROXY_URL: env.get('AUTH_PROXY_URL').asString() || undefined, AUTH_PROXY_URL: env.get('AUTH_PROXY_URL').asString() || undefined,
// override AUTH_PROXY_URL to request user info // override AUTH_PROXY_URL to request user info
@@ -40,11 +40,11 @@ export const DefaultConfig = {
...envs, ...envs,
EXTERNAL_BASE_URL: EXTERNAL_BASE_URL:
envs.EXTERNAL_BASE_URL || envs.EXTERNAL_BASE_URL ||
urlJoin(`http://${envs.HOSTNAME}${envs.PORT !== 80 ? `:${envs.PORT}` : ""}`, envs.BASE_PATH), urlJoin(`http://${envs.HOSTNAME}${envs.PORT !== 80 ? `:${envs.PORT}` : ''}`, envs.BASE_PATH),
isProduction, isProduction,
requireEnv, requireEnv,
AUTH_PROXY_USERINFO_URL: envs.AUTH_PROXY_USERINFO_URL || AUTH_PROXY_USERINFO_URL: envs.AUTH_PROXY_USERINFO_URL ||
envs.AUTH_PROXY_URL && urlJoin(envs.AUTH_PROXY_URL, "userinfo"), envs.AUTH_PROXY_URL && urlJoin(envs.AUTH_PROXY_URL, 'userinfo'),
AUTH_PROXY_INIT_LOGOUT_URL: envs.AUTH_PROXY_INIT_LOGOUT_URL || AUTH_PROXY_INIT_LOGOUT_URL: envs.AUTH_PROXY_INIT_LOGOUT_URL ||
envs.AUTH_PROXY_URL && urlJoin(envs.AUTH_PROXY_URL, "init-logout"), envs.AUTH_PROXY_URL && urlJoin(envs.AUTH_PROXY_URL, 'init-logout'),
}; };

View File

@@ -13,8 +13,13 @@ class FetchOnce<T, U extends Array<unknown>> {
constructor(protected fetchMethod?: (...args: U) => Promise<T>) { } constructor(protected fetchMethod?: (...args: U) => Promise<T>) { }
public reset(): void {
this.data = undefined;
this.error = undefined;
this.state = ResolvableState.WAITING;
}
public resolve(...args: U): Promise<T> { public resolve(...args: U): Promise<T> {
// eslint-disable-next-line promise/avoid-new
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
switch (this.state) { switch (this.state) {
case ResolvableState.WAITING: case ResolvableState.WAITING:
@@ -35,6 +40,14 @@ class FetchOnce<T, U extends Array<unknown>> {
}); });
} }
public getDataOrUndefined(): T|undefined {
return this.data;
}
public getErrorOrUndefined(): unknown|undefined {
return this.error;
}
protected isFinished(): boolean { protected isFinished(): boolean {
return this.state === ResolvableState.DONE || this.state === ResolvableState.ERROR; return this.state === ResolvableState.DONE || this.state === ResolvableState.ERROR;
} }
@@ -76,4 +89,9 @@ export class WaitForSync<T> extends FetchOnce<T, never> {
this.parsePromise((async () => { throw error; })()); this.parsePromise((async () => { throw error; })());
} }
} }
public reset(): void {
super.reset();
this.state = ResolvableState.PENDING;
}
} }

View File

@@ -7,7 +7,7 @@ import prune = require('json-prune');
const logger = winston.createLogger({ const logger = winston.createLogger({
level: DefaultConfig.isProduction ? "info" : "silly", level: DefaultConfig.isProduction ? 'info' : 'silly',
format: winston.format.json(), format: winston.format.json(),
transports: [ transports: [
new winston.transports.Console({ new winston.transports.Console({
@@ -16,11 +16,11 @@ const logger = winston.createLogger({
], ],
}); });
export const levels = ["error", "warn", "info", "http", "verbose", "debug", "silly"] as const; export const levels = ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'] as const;
export type LogLevels = typeof levels[number]|"log"; export type LogLevels = typeof levels[number]|'log';
const wrapper = (original: LeveledLogMethod) => { const wrapper = (original: LeveledLogMethod) => {
return (...args: unknown[]) => { return (...args: unknown[]) => {
return original(args.map((obj) => typeof obj === "string" ? obj : prune(obj)).join(" ")); return original(args.map((obj) => typeof obj === 'string' ? obj : prune(obj)).join(' '));
}; };
}; };
@@ -30,7 +30,7 @@ export const Logger = {
for (const level of levels) { for (const level of levels) {
Logger[level] = wrapper(logger[level]); Logger[level] = wrapper(logger[level]);
} }
Logger.log = wrapper(logger["silly"]); Logger.log = wrapper(logger['silly']);
export const HttpLogger: RequestHandler = (req, res, next) => { export const HttpLogger: RequestHandler = (req, res, next) => {
const start = Date.now(); const start = Date.now();
@@ -45,8 +45,8 @@ export const HttpLogger: RequestHandler = (req, res, next) => {
: statusCode >= 200 ? colors.green : statusCode >= 200 ? colors.green
: colors.gray; : colors.gray;
const status = colorFunction(res.statusCode.toString(10)); const status = colorFunction(res.statusCode.toString(10));
const method = req.method.toUpperCase().padEnd(6, " "); const method = req.method.toUpperCase().padEnd(6, ' ');
const responseTime = (Date.now()-start).toString(10).padStart(3, " "); const responseTime = (Date.now()-start).toString(10).padStart(3, ' ');
if (!req.noHttpLogging) if (!req.noHttpLogging)
Logger.http(`${status} ${method} ${responseTime}ms ${path}`); Logger.http(`${status} ${method} ${responseTime}ms ${path}`);
end.apply(res, args); end.apply(res, args);

View File

@@ -87,7 +87,7 @@ export class PermQuery extends Query {
} }
public sync(): PermQuery { public sync(): PermQuery {
throw new AccessControlError("Sync method is not allowed on PermissionManager!"); throw new AccessControlError('Sync method is not allowed on PermissionManager!');
} }
} }

View File

@@ -1,15 +1,15 @@
// workers/add.js // workers/add.js
import {expose} from "threads/worker"; import {expose} from 'threads/worker';
import * as fs from "fs"; import * as fs from 'fs';
import {analyze} from '@10xjs/polyfill-analyzer'; import {analyze} from '@10xjs/polyfill-analyzer';
import allPolyfills from '@10xjs/polyfill-analyzer/dist/polyfills'; import allPolyfills from '@10xjs/polyfill-analyzer/dist/polyfills';
import {PolyfillFeatureList} from 'polyfill-library'; import {PolyfillFeatureList} from 'polyfill-library';
expose((fileToWatch) => { expose((fileToWatch) => {
const exclude = [ const exclude = [
"console.markTimeline", 'console.markTimeline',
"console.timeline", 'console.timeline',
"console.timelineEnd", 'console.timelineEnd',
]; ];
const featureList = analyze({ const featureList = analyze({
source: fs.readFileSync(fileToWatch, 'utf-8'), source: fs.readFileSync(fileToWatch, 'utf-8'),

View File

@@ -22,27 +22,27 @@ export type PolyfillOptions = {
function getRouter(fileToWatch: string, opts?: Partial<PolyfillOptions>): RequestHandler { function getRouter(fileToWatch: string, opts?: Partial<PolyfillOptions>): RequestHandler {
const features = new WaitForSync<PolyfillFeatureList>(); const features = new WaitForSync<PolyfillFeatureList>();
(async () => { (async () => {
const worker = await spawn<WorkerFunction>(new Worker("./polyfill-worker")); const worker = await spawn<WorkerFunction>(new Worker('./polyfill-worker'));
const feats = await worker(fileToWatch) as PolyfillFeatureList; const feats = await worker(fileToWatch) as PolyfillFeatureList;
await Thread.terminate(worker); await Thread.terminate(worker);
return feats; return feats;
})() })()
.then(feats => { .then(feats => {
feats["fetch"] = {}; feats['fetch'] = {};
Logger.debug("Polyfill analysed:", Object.keys(feats)); Logger.debug('Polyfill analysed:', Object.keys(feats));
features.setData(feats); features.setData(feats);
}) })
.catch(err => features.setError(err)); .catch(err => features.setError(err));
const options: Partial<PolyfillOptions> = { const options: Partial<PolyfillOptions> = {
minify: DefaultConfig.isProduction, minify: DefaultConfig.isProduction,
unknown: "polyfill", unknown: 'polyfill',
...opts, ...opts,
}; };
return async (req, res) => { return async (req, res) => {
const polyfillBundle = await polyfillLibrary.getPolyfillString({ const polyfillBundle = await polyfillLibrary.getPolyfillString({
...options, ...options,
uaString: req.header("user-agent"), uaString: req.header('user-agent'),
features: await features.resolve(), features: await features.resolve(),
stream: false, stream: false,
}); });

View File

@@ -12,8 +12,8 @@ interface RedisType {
class RealRedis implements RedisType { class RealRedis implements RedisType {
private _client: redis.RedisClient|undefined; private _client: redis.RedisClient|undefined;
private _promGet: ((key: string) => Promise<string|null>)|undefined; private _promGet: ((key: string) => Promise<string|null>)|undefined;
private _promSet: RedisType["set"]|undefined; private _promSet: RedisType['set']|undefined;
private _promDel: RedisType["del"]|undefined; private _promDel: RedisType['del']|undefined;
get client(): redis.RedisClient { get client(): redis.RedisClient {
if (this._client !== undefined) return this._client; if (this._client !== undefined) return this._client;

View File

@@ -1,9 +1,9 @@
import * as session from 'express-session'; import * as session from 'express-session';
import {Store} from 'express-session'; import {Store} from 'express-session';
import {DefaultConfig, Redis} from '.'; import {DefaultConfig, Redis} from '.';
import * as redisStore from "connect-redis"; import * as redisStore from 'connect-redis';
import {RequestHandler, Router} from 'express'; import {RequestHandler, Router} from 'express';
import {promisify} from "util"; import {promisify} from 'util';
let sessionStore: Store|undefined = undefined; let sessionStore: Store|undefined = undefined;
@@ -29,7 +29,7 @@ function getRouter(options?: Partial<session.SessionOptions>): RequestHandler {
const sessionStore = (req as { sessionStore?: session.Store }).sessionStore; const sessionStore = (req as { sessionStore?: session.Store }).sessionStore;
if (sessionStore) { if (sessionStore) {
const getSession = promisify(sessionStore.get); const getSession = promisify(sessionStore.get);
const session = await getSession(sessionId); const session = await getSession.call(sessionStore, sessionId);
if (session) { if (session) {
sessionStore.createSession(req, session); sessionStore.createSession(req, session);
return true; // success return true; // success

View File

@@ -1,4 +1,4 @@
declare module "json-prune" { declare module 'json-prune' {
function prune(object: unknown): string; function prune(object: unknown): string;
export = prune; export = prune;

View File

@@ -1,4 +1,4 @@
declare module "polyfill-library" { declare module 'polyfill-library' {
import {Readable} from 'stream'; import {Readable} from 'stream';
function listAllPolyfills(): string[]; function listAllPolyfills(): string[];