Moved utils to another repository

This commit is contained in:
Sebastian Seedorf
2020-11-16 13:14:05 +01:00
parent 689e087aef
commit 0597b36ef3
25 changed files with 64 additions and 571 deletions

View File

@@ -25,7 +25,7 @@ services:
- "CLIENT_SCOPE=openid email profile roles groups" - "CLIENT_SCOPE=openid email profile roles groups"
- "NODE_ENV=debug" - "NODE_ENV=debug"
- "SSL_VERIFY=false" - "SSL_VERIFY=false"
- "EXT_RESOURCE_URI=http://localhost" - "EXT_RESOURCE_URI=http://localhost:3001"
- "REDIS_URL=redis://redis:6379" - "REDIS_URL=redis://redis:6379"
- "NO_PROXY=redis:6379" - "NO_PROXY=redis:6379"
ports: ports:

34
package-lock.json generated
View File

@@ -3173,6 +3173,29 @@
"pinkie": "^2.0.0" "pinkie": "^2.0.0"
} }
}, },
"pkg-express-utils": {
"version": "git+https://git.biotronik.int/scm/coe-bs-website/node-pkg-express-utils.git#99527fdb494c464c9351626548bf446b39c155f4",
"from": "git+https://git.biotronik.int/scm/coe-bs-website/node-pkg-express-utils.git",
"requires": {
"@10xjs/polyfill-analyzer": "^0.1.0",
"connect-redis": "^5.0.0",
"cookie-parser": "~1.4.4",
"env-var": "^6.3.0",
"express": "~4.16.1",
"express-session": "^1.17.1",
"json-prune": "^1.1.0",
"node-fetch": "^2.6.1",
"polyfill-library": "^3.97.0",
"proper-url-join": "^2.1.1",
"redis": "^3.0.2",
"role-acl": "^4.5.4",
"run-script-os": "^1.1.3",
"threads": "^1.6.3",
"tiny-worker": "^2.3.0",
"uuid": "^8.3.1",
"winston": "^3.3.3"
}
},
"polyfill-library": { "polyfill-library": {
"version": "3.97.0", "version": "3.97.0",
"resolved": "https://registry.npmjs.org/polyfill-library/-/polyfill-library-3.97.0.tgz", "resolved": "https://registry.npmjs.org/polyfill-library/-/polyfill-library-3.97.0.tgz",
@@ -3643,6 +3666,11 @@
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz",
"integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==" "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw=="
}, },
"run-script-os": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.3.tgz",
"integrity": "sha512-xPlzE6533nvWVea5z7e5J7+JAIepfpxTu/HLGxcjJYlemVukOCWJBaRCod/DWXJFRIWEFOgSGbjd2m1QWTJi5w=="
},
"rxjs": { "rxjs": {
"version": "6.6.3", "version": "6.6.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz",
@@ -4606,12 +4634,6 @@
"mkdirp": "^0.5.1" "mkdirp": "^0.5.1"
} }
}, },
"ws": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz",
"integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==",
"dev": true
},
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",

View File

@@ -5,7 +5,6 @@
"scripts": { "scripts": {
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint-fix": "eslint . --ext .ts --fix", "lint-fix": "eslint . --ext .ts --fix",
"update-client-hash": "node -e \"require('fs').writeFileSync('public/misc/hash.txt', require('randomstring').generate())\"",
"debug-client": "tsc-watch --project ./public/js-source", "debug-client": "tsc-watch --project ./public/js-source",
"debug-server": "tsc-watch --project . --onSuccess \"node --enable-source-maps --use-openssl-ca --unhandled-rejections=strict ./out/index\"", "debug-server": "tsc-watch --project . --onSuccess \"node --enable-source-maps --use-openssl-ca --unhandled-rejections=strict ./out/index\"",
"debug": "concurrently npm:debug-*", "debug": "concurrently npm:debug-*",
@@ -27,6 +26,7 @@
"json-prune": "^1.1.0", "json-prune": "^1.1.0",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"node-sass-middleware": "0.11.0", "node-sass-middleware": "0.11.0",
"pkg-express-utils": "git+https://git.biotronik.int/scm/coe-bs-website/node-pkg-express-utils.git",
"polyfill-library": "^3.97.0", "polyfill-library": "^3.97.0",
"proper-url-join": "^2.1.1", "proper-url-join": "^2.1.1",
"pug": "^3.0.0", "pug": "^3.0.0",
@@ -57,7 +57,6 @@
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"node-watch": "^0.7.0", "node-watch": "^0.7.0",
"tsc-watch": "^4.2.9", "tsc-watch": "^4.2.9",
"typescript": "^4.0.5", "typescript": "^4.0.5"
"ws": "^7.4.0"
} }
} }

View File

@@ -5,7 +5,7 @@ import * as path from 'path';
import * as sassMiddleware from 'node-sass-middleware'; import * as sassMiddleware from 'node-sass-middleware';
import * as compression from 'compression'; import * as compression from 'compression';
import indexRouter from './routes'; import indexRouter from './routes';
import {AuthProxy, AutoReloader, DefaultConfig, HttpLogger, Polyfill} from './utils'; import {AuthProxy, AutoReloader, DefaultConfig, HttpLogger, Polyfill, Permissions} from 'pkg-express-utils';
export const app = express(); export const app = express();
@@ -36,7 +36,7 @@ router.use(AutoReloader.router);
//router.use(Session.getRouter()); //router.use(Session.getRouter());
// static config // static config
router.use("/js/polyfill.js", Polyfill.router); router.use("/js/polyfill.js", Polyfill.getRouter('./public/js/bundle.js'));
router.use(sassMiddleware({ router.use(sassMiddleware({
src: path.join(__dirname, '../public'), src: path.join(__dirname, '../public'),
dest: path.join(__dirname, '../public'), dest: path.join(__dirname, '../public'),

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-process-exit,no-console */ /* eslint-disable no-process-exit,no-console */
import * as http from "http"; import * as http from "http";
import urlJoin from 'proper-url-join'; import urlJoin from 'proper-url-join';
import {DefaultConfig} from './utils'; import {DefaultConfig} from 'pkg-express-utils';
const options = { const options = {
host: DefaultConfig.HOSTNAME, host: DefaultConfig.HOSTNAME,

View File

@@ -4,7 +4,7 @@
import * as http from 'http'; import * as http from 'http';
import {app} from './app'; import {app} from './app';
import {HttpError} from 'http-errors'; import {HttpError} from 'http-errors';
import {DefaultConfig, Logger} from './utils'; import {DefaultConfig, Logger} from 'pkg-express-utils';
app.set('port', DefaultConfig.PORT); app.set('port', DefaultConfig.PORT);
const server = http.createServer(app); const server = http.createServer(app);

28
src/types/extend-request.d.ts vendored Normal file
View File

@@ -0,0 +1,28 @@
// extend-request.d.ts
declare global {
namespace Express {
interface UserInfo {
email: string,
email_verified: boolean,
family_name: string,
given_name: string,
groups: string[],
name: string,
preferred_username: string,
sub: string,
}
interface Request {
getUserInfo(): Promise<UserInfo|undefined>;
noLogging: boolean|undefined;
permissionDetails?: import('role-acl').Permission;
}
interface Response {
initLogout(): boolean;
}
}
}
export {};

View File

@@ -1,40 +0,0 @@
import {Request, RequestHandler} from 'express';
import {DefaultConfig, Logger, Resolvable, urlJoin} from '.';
import fetch from 'node-fetch';
const router: RequestHandler = (req: Request, res, next) => {
const resolvable = new Resolvable(async () => {
if (!DefaultConfig.USERINFO_HEADER) {
return undefined;
}
const token = req.header(DefaultConfig.USERINFO_HEADER);
const url = DefaultConfig.AUTH_PROXY_USERINFO_URL ||
DefaultConfig.AUTH_PROXY_URL && urlJoin(DefaultConfig.AUTH_PROXY_URL, "userinfo");
if (token === undefined || url === undefined) {
return undefined;
}
try {
const res = await fetch(url, {headers: [[DefaultConfig.USERINFO_HEADER, token]]});
return await res.json() as UserInfo;
} catch (e) {
Logger.warn(e);
return undefined;
}
});
req.getUserInfo = () => resolvable.resolve();
res.initLogout = function() {
const url = DefaultConfig.AUTH_PROXY_INIT_LOGOUT_URL ||
DefaultConfig.AUTH_PROXY_URL && urlJoin(DefaultConfig.AUTH_PROXY_URL, "init-logout");
if (url === undefined) {
return false;
}
this.redirect(307, url);
return true;
};
next();
};
export const AuthProxy = {
router,
};

View File

@@ -1,62 +0,0 @@
import {Router} from 'express';
import {DefaultConfig, Logger, urlJoin} from '.';
import {v4} from 'uuid';
import Timeout = NodeJS.Timeout;
const router = Router();
if (!DefaultConfig.isProduction) {
let uuid = v4();
let updateTimeout: Timeout|undefined = undefined;
import("node-watch").then((watch) => {
watch.default('public', {recursive: true}, () => {
if (updateTimeout !== undefined) clearTimeout(updateTimeout);
updateTimeout = setTimeout(() => {
uuid = v4();
}, 200);
});
}).catch((err) => { Logger.error(err); });
router.get("/auto-reload/client.js", (req, res) => {
Logger.debug(req.url, req.originalUrl, req.baseUrl);
res.setHeader('Content-Type', "application/javascript");
// language=JavaScript
res.send(`
const loc = window.location;
const url = loc.protocol+'//'+loc.host+'${urlJoin(req.baseUrl, "/auto-reload")}';
// const parse = async res => (await res.json()).uuid;
const parse = function(res) {
return res.json()
.then(function(json) {return json.uuid;}) }
let hash = undefined;
let hadError = false;
setInterval(function() {
try {
fetch(url)
.then(function(res) { return parse(res) })
.then(function(data) {
if (data) {
hash = hash === undefined ? data : hash;
if (hash !== data) {
window.location.reload();
}
}
});
} catch (e) {
if (hadError === false) {
console.log(e);
hadError = true;
}
}
}, 3000);
`.replace(/\t/g, ""));
});
router.get("/auto-reload", (req, res) => {
req.noLogging = true;
res.json({uuid});
});
}
export const AutoReloader = {
router,
};

View File

@@ -1,44 +0,0 @@
import * as env from 'env-var';
import {urlJoin} from '.';
const NODE_ENV = env.get('NODE_ENV').default("development").asString();
const isProduction = NODE_ENV === 'production';
const envs = {
NODE_ENV,
// port of the server
PORT: env.get('PORT').default('3000').asPortNumber(),
// hostname of the server ('0.0.0.0' listens on all network interfaces)
HOSTNAME: env.get('HOSTNAME').default('0.0.0.0').asString(),
// base path
BASE_PATH: env.get('BASE_PATH').default('/').asString(),
// external base url
EXTERNAL_BASE_URL: env.get('EXTERNAL_BASE_URL').asString(),
// url of redis session store (required in production using InMemory)
REDIS_URL: env.get('REDIS_URL').asString() || undefined,
// cookie secret for the session id (required in production using Session)
SESSION_SECRET: env.get('SESSION_SECRET').asString() || undefined,
// header where user info token is stored to request auth proxy
USERINFO_HEADER: env.get('USERINFO_HEADER').asString() || undefined,
// base url to init a logout or request user info
AUTH_PROXY_URL: env.get('AUTH_PROXY_URL').asString() || undefined,
// override base url to request user info
AUTH_PROXY_USERINFO_URL: env.get('AUTH_PROXY_USERINFO_URL').asString() || undefined,
// override base url to init a logout
AUTH_PROXY_INIT_LOGOUT_URL: env.get('AUTH_PROXY_INIT_LOGOUT_URL').asString() || undefined,
};
function requireEnv(name: string, onlyInProduction = false): void {
env.get(name).required(!onlyInProduction || isProduction).asString();
}
export const DefaultConfig = {
...envs,
EXTERNAL_BASE_URL:
envs.EXTERNAL_BASE_URL ||
urlJoin(`http://${envs.HOSTNAME}${envs.PORT !== 80 ? `:${envs.PORT}` : ""}`, envs.BASE_PATH),
isProduction,
requireEnv,
};

View File

@@ -1,79 +0,0 @@
enum ResolvableState {
WAITING,
PENDING,
ERROR,
DONE
}
class FetchOnce<T, U extends Array<unknown>> {
protected data: T|undefined;
protected error: unknown|undefined;
protected state: ResolvableState = ResolvableState.WAITING;
protected pendings: [(res: Promise<T>|T) => void, (reason: unknown) => void][] = [];
constructor(protected fetchMethod?: (...args: U) => Promise<T>) { }
public resolve(...args: U): Promise<T> {
// eslint-disable-next-line promise/avoid-new
return new Promise((resolve, reject) => {
switch (this.state) {
case ResolvableState.WAITING:
this.state = ResolvableState.PENDING;
this.pendings.push([resolve, reject]);
if (this.fetchMethod) this.parsePromise(this.fetchMethod(...args));
break;
case ResolvableState.PENDING:
this.pendings.push([resolve, reject]);
break;
case ResolvableState.DONE:
resolve(this.data);
break;
case ResolvableState.ERROR:
reject(this.error);
break;
}
});
}
protected isFinished(): boolean {
return this.state === ResolvableState.DONE || this.state === ResolvableState.ERROR;
}
protected parsePromise(promise: Promise<T>): void {
promise.then((data) => {
this.data = data;
this.state = ResolvableState.DONE;
this.pendings.forEach(pending => pending[0](data));
}).catch(err => {
this.error = err;
this.state = ResolvableState.ERROR;
this.pendings.forEach(pending => pending[1](err));
});
}
}
export class Resolvable<T, U extends Array<unknown>> extends FetchOnce<T, U> {
constructor(fetchMethod: (...args: U) => Promise<T>) {
super(fetchMethod);
}
}
export class WaitForSync<T> extends FetchOnce<T, never> {
protected state: ResolvableState = ResolvableState.PENDING;
constructor() {
super(undefined);
}
public setData(data: T): void {
if (!this.isFinished()) {
this.parsePromise((async () => data)());
}
}
public setError(error: unknown): void {
if (!this.isFinished()) {
this.parsePromise((async () => { throw error; })());
}
}
}

View File

@@ -1,3 +0,0 @@
import * as properUrlJoin from 'proper-url-join';
export const urlJoin = properUrlJoin as unknown as properUrlJoin.default;

View File

@@ -1,10 +0,0 @@
export {DefaultConfig} from './config';
export {Redis} from './redis';
export {Logger, HttpLogger} from './logging';
export {AuthProxy} from './auth-proxy';
export {Resolvable, WaitForSync} from './helpers/resolvable';
export {urlJoin} from './helpers/urlJoin';
export {AutoReloader} from './auto-reload';
export {Polyfill} from './polyfill';
export {Session} from './session';
export {Permissions} from './permissions';

View File

@@ -1,55 +0,0 @@
import * as winston from 'winston';
import {LeveledLogMethod} from 'winston';
import {RequestHandler} from 'express';
import * as colors from 'colors';
import {DefaultConfig} from '.';
import prune = require('json-prune');
const logger = winston.createLogger({
level: DefaultConfig.isProduction ? "info" : "silly",
format: winston.format.json(),
transports: [
new winston.transports.Console({
format: winston.format.simple(),
}),
],
});
const levels = ["error", "warn", "info", "http", "verbose", "debug", "silly"] as const;
type LogLevels = typeof levels[number]|"log";
const wrapper = (original: LeveledLogMethod) => {
return (...args: unknown[]) => {
return original(args.map((obj) => typeof obj === "string" ? obj : prune(obj)).join(" "));
};
};
export const Logger = {
} as {[level in LogLevels]: ReturnType<typeof wrapper>};
for (const level of levels) {
Logger[level] = wrapper(logger[level]);
}
Logger.log = wrapper(logger["silly"]);
export const HttpLogger: RequestHandler = (req, res, next) => {
const start = Date.now();
const path = req.path;
type Callback = (() => void)|undefined;
const end = res.end;
res.end = function(...args: [Callback] & [unknown, Callback] & [unknown, BufferEncoding, Callback]) {
const statusCode = res.statusCode;
const colorFunction = statusCode >= 500 ? colors.red
: statusCode >= 400 ? colors.yellow
: statusCode >= 300 ? colors.cyan
: statusCode >= 200 ? colors.green
: colors.gray;
const status = colorFunction(res.statusCode.toString(10));
const method = req.method.toUpperCase().padEnd(6, " ");
const responseTime = (Date.now()-start).toString(10).padStart(3, " ");
if (!req.noLogging)
Logger.http(`${status} ${method} ${responseTime}ms ${path}`);
end.apply(res, args);
};
next();
};

View File

@@ -1,92 +0,0 @@
import {AccessControl, AccessControlError, IQueryInfo, Permission} from 'role-acl';
import {Query} from 'role-acl/lib/src/core/Query';
import {Request, RequestHandler} from 'express';
// see https://www.npmjs.com/package/role-acl
class PermissionManager extends AccessControl {
public can(roleOrRequest: Request|string|string[]|IQueryInfo): PermQuery {
return new PermQuery(this.getGrants(), roleOrRequest);
}
public getRouter(resource: string, opts: Partial<RermRouterOpts>): RequestHandler {
return async (req: Request, res, next) => {
let query = this.can(req);
if (opts.context)
query = query.context(opts.context);
if (opts.action)
query = query.execute(opts.action);
if (opts.skipConditions)
query = query.skipConditions(opts.skipConditions);
const permission = await query.on(resource);
if (permission.granted) {
req.permissionDetails = permission;
next();
} else {
res.sendStatus(403);
}
};
}
}
export type RermRouterOpts = {
context: unknown,
action: string,
skipConditions: boolean
}
export class PermQuery extends Query {
protected resolveRequest: Request|undefined;
constructor(grants: unknown, roleOrRequest: Request|string|string[]|IQueryInfo) {
function isRequest(obj: unknown): obj is Request {
// eslint-disable-next-line no-prototype-builtins
return typeof obj === 'object' && obj && obj.hasOwnProperty('path') || false;
}
if (isRequest(roleOrRequest)) {
super(grants, []);
this.resolveRequest = roleOrRequest;
} else {
super(grants, roleOrRequest);
}
}
public async on(resource: string, skipConditions?: boolean): Promise<Permission> {
if (this.resolveRequest) {
const userInfo = await this.resolveRequest.getUserInfo();
this.role(userInfo?.groups ?? []);
}
if (
typeof this._.role === 'object' && this._.role.includes('noaccess') ||
typeof this._.role === 'string' && this._.role === 'noaccess'
) {
this.role([]);
}
return super.on(resource, skipConditions);
}
public context(context: unknown): PermQuery {
super.context(context);
return this;
}
public skipConditions(value: boolean): PermQuery {
super.skipConditions(value);
return this;
}
public with(context: unknown): PermQuery {
super.with(context);
return this;
}
public execute(action: string): PermQuery {
super.execute(action);
return this;
}
public sync(): PermQuery {
throw new AccessControlError("Sync method is not allowed on PermissionManager!");
}
}
export const Permissions = new PermissionManager();

View File

@@ -1,25 +0,0 @@
// workers/add.js
import {expose} from "threads/worker";
import * as fs from "fs";
import {analyze} from '@10xjs/polyfill-analyzer';
import allPolyfills from '@10xjs/polyfill-analyzer/dist/polyfills';
import {PolyfillFeatureList} from "polyfill-library";
expose(() => {
const exclude = [
"console.markTimeline",
"console.timeline",
"console.timelineEnd",
];
const featureList = analyze({
source: fs.readFileSync('./public/js/bundle.js', 'utf-8'),
include: allPolyfills.filter(x => !exclude.includes(x)),
// Not all features listed by polyfillLibrary.listAllPolyfills()` can be detected.
unsupportedPolyfill: 'ignore',
});
const feats: PolyfillFeatureList = {};
for (const feature of featureList) {
feats[feature] = {};
}
return feats;
});

View File

@@ -1,38 +0,0 @@
import {RequestHandler} from 'express';
import * as polyfillLibrary from 'polyfill-library';
import {PolyfillFeatureList} from 'polyfill-library';
import {DefaultConfig, Logger, WaitForSync} from '.';
import {spawn, Thread, Worker} from 'threads';
import {WorkerFunction} from 'threads/dist/types/worker';
const features = new WaitForSync<PolyfillFeatureList>();
(async () => {
const worker = await spawn<WorkerFunction>(new Worker("./polyfill-worker"));
const feats = await worker() as PolyfillFeatureList;
await Thread.terminate(worker);
return feats;
})()
.then(feats => {
feats["fetch"] = {};
Logger.debug("Polyfill analysed:", Object.keys(feats));
features.setData(feats);
})
.catch(err => features.setError(err));
const router: RequestHandler = async (req, res) => {
const polyfillBundle = await polyfillLibrary.getPolyfillString({
uaString: req.header("user-agent"),
features: await features.resolve(),
minify: DefaultConfig.isProduction,
unknown: "polyfill",
stream: false,
});
res.setHeader('Content-Type', 'text/javascript');
res.send(polyfillBundle);
};
export const Polyfill = {
router: router,
};

View File

@@ -1,70 +0,0 @@
import * as redis from 'redis';
import {DefaultConfig} from '.';
import {promisify} from 'util';
interface RedisType {
client: redis.RedisClient | undefined;
get(key: string): Promise<string | undefined>;
set(key: string, value: string): Promise<void>;
del(...keys: string[]): Promise<number>;
}
class RealRedis implements RedisType {
private _client: redis.RedisClient|undefined;
private _promGet: ((key: string) => Promise<string|null>)|undefined;
private _promSet: RedisType["set"]|undefined;
private _promDel: RedisType["del"]|undefined;
get client(): redis.RedisClient {
if (this._client !== undefined) return this._client;
DefaultConfig.requireEnv('REDIS_URL');
this._client = redis.createClient({url: DefaultConfig.REDIS_URL});
return this._client;
}
async get(key: string): Promise<string | undefined> {
if (!this._promGet) this._promGet = promisify(this.client.get);
const value = await this._promGet(key);
// eslint-disable-next-line no-null/no-null
return value === null ? undefined : value;
}
async set(key: string, value: string): Promise<void> {
if (!this._promSet) this._promSet = promisify(this.client.set);
await this._promSet(key, value);
}
async del(...keys: string[]): Promise<number> {
if (!this._promDel) this._promDel = promisify(this.client.del);
return await this._promDel(...keys);
}
}
class MockRedis implements RedisType {
private inMemory: {[key: string]: string} = {};
get client(): undefined {
return undefined;
}
async get(key: string): Promise<string | undefined> {
return this.inMemory[key] || undefined;
}
async set(key: string, value: string): Promise<void> {
this.inMemory[key] = value;
}
async del(...keys: string[]): Promise<number> {
for (const key of keys) {
delete this.inMemory[key];
}
return keys.length;
}
}
export const Redis: RedisType =
DefaultConfig.REDIS_URL || DefaultConfig.isProduction
? new RealRedis()
: new MockRedis();

View File

@@ -1,28 +0,0 @@
import * as session from 'express-session';
import {Store} from 'express-session';
import {DefaultConfig, Redis} from '.';
import * as redisStore from "connect-redis";
import {RequestHandler} from 'express';
let sessionStore: Store|undefined = undefined;
function getRouter(options?: Partial<session.SessionOptions>): RequestHandler {
DefaultConfig.requireEnv('SESSION_SECRET', true);
if (Redis.client && sessionStore !== undefined) {
const RedisStore = redisStore(session);
sessionStore = new RedisStore({client: Redis.client});
}
return session({
store: sessionStore,
secret: DefaultConfig.SESSION_SECRET || 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: {secure: false},
...options,
});
}
export const Session = {
getRouter,
};

View File

@@ -1,10 +0,0 @@
type UserInfo = {
email: string,
email_verified: boolean,
family_name: string,
given_name: string,
groups: string[],
name: string,
preferred_username: string,
sub: string,
};

View File

@@ -1,6 +1,6 @@
declare namespace Express { declare namespace Express {
interface Request { interface Request {
getUserInfo(): Promise<UserInfo|undefined>; getUserInfo(): Promise<import('pkg-express-utils').UserInfo|undefined>;
} }
interface Response { interface Response {