@sphereon/ssi-express-support 0.30.1-unstable.4 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/dist/auth-utils.d.ts.map +1 -1
- package/dist/auth-utils.js +3 -3
- package/dist/auth-utils.js.map +1 -1
- package/dist/express-builders.d.ts +1 -0
- package/dist/express-builders.d.ts.map +1 -1
- package/dist/express-utils.d.ts.map +1 -1
- package/dist/express-utils.js +2 -2
- package/dist/express-utils.js.map +1 -1
- package/dist/functions.js +2 -1
- package/dist/functions.js.map +1 -1
- package/dist/openid-connect-rp.js +9 -9
- package/dist/openid-connect-rp.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/auth-utils.ts +155 -155
- package/src/entra-id-auth.ts +47 -47
- package/src/express-builders.ts +348 -348
- package/src/express-utils.ts +49 -49
- package/src/index.ts +8 -8
- package/src/openid-connect-rp.ts +228 -228
- package/src/static-bearer-auth.ts +151 -151
package/src/express-builders.ts
CHANGED
|
@@ -1,348 +1,348 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @public
|
|
3
|
-
*/
|
|
4
|
-
import bodyParser from 'body-parser'
|
|
5
|
-
import { Enforcer } from 'casbin'
|
|
6
|
-
import cors, { CorsOptions } from 'cors'
|
|
7
|
-
|
|
8
|
-
import express, { Express } from 'express'
|
|
9
|
-
import { Application, ApplicationRequestHandler } from 'express-serve-static-core'
|
|
10
|
-
import expressSession from 'express-session'
|
|
11
|
-
import session from 'express-session'
|
|
12
|
-
import http from 'http'
|
|
13
|
-
import { createHttpTerminator, HttpTerminator } from 'http-terminator'
|
|
14
|
-
import morgan from 'morgan'
|
|
15
|
-
import passport, { InitializeOptions } from 'passport'
|
|
16
|
-
import { checkUserIsInRole } from './auth-utils'
|
|
17
|
-
import { jsonErrorHandler } from './express-utils'
|
|
18
|
-
import { env } from './functions'
|
|
19
|
-
import { ExpressSupport, IExpressServerOpts } from './types'
|
|
20
|
-
|
|
21
|
-
type Handler<Request extends http.IncomingMessage, Response extends http.ServerResponse> = (
|
|
22
|
-
req: Request,
|
|
23
|
-
res: Response,
|
|
24
|
-
callback: (err?: Error) => void,
|
|
25
|
-
) => void
|
|
26
|
-
|
|
27
|
-
export class ExpressBuilder {
|
|
28
|
-
private existingExpress?: Express
|
|
29
|
-
private hostnameOrIP?: string
|
|
30
|
-
private port?: number
|
|
31
|
-
private _handlers?: ApplicationRequestHandler<Application>[] = []
|
|
32
|
-
private listenCallback?: () => void
|
|
33
|
-
private _startListen?: boolean | undefined = undefined
|
|
34
|
-
private readonly envVarPrefix?: string
|
|
35
|
-
private _corsConfigurer?: ExpressCorsConfigurer
|
|
36
|
-
private _sessionOpts?: session.SessionOptions
|
|
37
|
-
private _usePassportAuth?: boolean = false
|
|
38
|
-
private _passportInitOpts?: InitializeOptions
|
|
39
|
-
private _userIsInRole?: string | string[]
|
|
40
|
-
private _enforcer?: Enforcer
|
|
41
|
-
private _server?: http.Server | undefined
|
|
42
|
-
private _terminator?: HttpTerminator
|
|
43
|
-
private _morgan?: Handler<any, any> | undefined
|
|
44
|
-
|
|
45
|
-
private constructor(opts?: { existingExpress?: Express; envVarPrefix?: string }) {
|
|
46
|
-
const { existingExpress, envVarPrefix } = opts ?? {}
|
|
47
|
-
if (existingExpress) {
|
|
48
|
-
this.withExpress(existingExpress)
|
|
49
|
-
}
|
|
50
|
-
this.envVarPrefix = envVarPrefix ?? ''
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public static fromExistingExpress(opts?: { existingExpress?: Express; envVarPrefix?: string }) {
|
|
54
|
-
return new ExpressBuilder(opts ?? {})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
public static fromServerOpts(opts: IExpressServerOpts & { envVarPrefix?: string }) {
|
|
58
|
-
const builder = new ExpressBuilder({ existingExpress: opts?.existingExpress, envVarPrefix: opts?.envVarPrefix })
|
|
59
|
-
return builder.withEnableListenOpts({ ...opts, hostnameOrIP: opts.hostname, startOnBuild: opts.startListening ?? false })
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
public enableListen(startOnBuild?: boolean): this {
|
|
63
|
-
if (startOnBuild !== undefined) {
|
|
64
|
-
this._startListen = startOnBuild
|
|
65
|
-
}
|
|
66
|
-
return this
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
public withMorganLogging(opts?: { existingMorgan?: Handler<any, any>; format?: string; options?: morgan.Options<any, any> }): this {
|
|
70
|
-
if (opts?.existingMorgan && (opts.format || opts.options)) {
|
|
71
|
-
throw Error('Cannot using an existing morgan with either a format or options')
|
|
72
|
-
}
|
|
73
|
-
this._morgan = opts?.existingMorgan ?? morgan(opts?.format ?? 'dev', opts?.options)
|
|
74
|
-
return this
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
public withEnableListenOpts({
|
|
78
|
-
port,
|
|
79
|
-
hostnameOrIP,
|
|
80
|
-
callback,
|
|
81
|
-
startOnBuild,
|
|
82
|
-
}: {
|
|
83
|
-
port?: number
|
|
84
|
-
hostnameOrIP?: string
|
|
85
|
-
startOnBuild?: boolean
|
|
86
|
-
callback?: () => void
|
|
87
|
-
}): this {
|
|
88
|
-
port && this.withPort(port)
|
|
89
|
-
hostnameOrIP && this.withHostname(hostnameOrIP)
|
|
90
|
-
if (typeof callback === 'function') {
|
|
91
|
-
this.withListenCallback(callback)
|
|
92
|
-
}
|
|
93
|
-
this._startListen = startOnBuild === true
|
|
94
|
-
return this
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
public withPort(port: number): this {
|
|
98
|
-
this.port = port
|
|
99
|
-
return this
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public withHostname(hostnameOrIP: string): this {
|
|
103
|
-
this.hostnameOrIP = hostnameOrIP
|
|
104
|
-
return this
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
public withListenCallback(callback: () => void): this {
|
|
108
|
-
this.listenCallback = callback
|
|
109
|
-
return this
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public withExpress(existingExpress: Express): this {
|
|
113
|
-
this.existingExpress = existingExpress
|
|
114
|
-
this._startListen = false
|
|
115
|
-
return this
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
public withCorsConfigurer(configurer: ExpressCorsConfigurer): this {
|
|
119
|
-
this._corsConfigurer = configurer
|
|
120
|
-
return this
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
public withPassportAuth(usePassport: boolean, initializeOptions?: InitializeOptions): this {
|
|
124
|
-
this._usePassportAuth = usePassport
|
|
125
|
-
this._passportInitOpts = initializeOptions
|
|
126
|
-
return this
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
public withGlobalUserIsInRole(userIsInRole: string | string[]): this {
|
|
130
|
-
this._userIsInRole = userIsInRole
|
|
131
|
-
return this
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public withEnforcer(enforcer: Enforcer): this {
|
|
135
|
-
this._enforcer = enforcer
|
|
136
|
-
return this
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
public startListening(express: Express) {
|
|
140
|
-
this._server = express.listen(this.getPort(), this.getHostname(), this.listenCallback)
|
|
141
|
-
this._terminator = createHttpTerminator({
|
|
142
|
-
server: this._server,
|
|
143
|
-
// gracefulTerminationTimeout: 10
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
return { server: this._server, terminator: this._terminator }
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
public getHostname(): string {
|
|
150
|
-
return this.hostnameOrIP ?? env('HOSTNAME', this.envVarPrefix) ?? '0.0.0.0'
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
public getPort(): number {
|
|
154
|
-
return (this.port ?? env('PORT', this.envVarPrefix) ?? 5000) as number
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
public setHandlers(handlers: ApplicationRequestHandler<any> | ApplicationRequestHandler<any>[]): this {
|
|
158
|
-
if (Array.isArray(handlers)) {
|
|
159
|
-
this._handlers = handlers
|
|
160
|
-
} else if (handlers) {
|
|
161
|
-
if (!this._handlers) {
|
|
162
|
-
this._handlers = []
|
|
163
|
-
}
|
|
164
|
-
this._handlers.push(handlers)
|
|
165
|
-
} else {
|
|
166
|
-
this._handlers = []
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return this
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
public addHandler(handler: ApplicationRequestHandler<any>): this {
|
|
173
|
-
if (!this._handlers) {
|
|
174
|
-
this._handlers = []
|
|
175
|
-
}
|
|
176
|
-
this._handlers.push(handler)
|
|
177
|
-
return this
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
public withSessionOptions(sessionOpts: session.SessionOptions): this {
|
|
181
|
-
this._sessionOpts = sessionOpts
|
|
182
|
-
return this
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
public build<T extends Application>(opts?: {
|
|
186
|
-
express?: Express
|
|
187
|
-
startListening?: boolean
|
|
188
|
-
handlers?: ApplicationRequestHandler<T> | ApplicationRequestHandler<T>[]
|
|
189
|
-
}): ExpressSupport {
|
|
190
|
-
const express = this.buildExpress(opts)
|
|
191
|
-
const startListening = opts?.startListening === undefined ? this._startListen !== true : opts.startListening
|
|
192
|
-
let started = this._server !== undefined
|
|
193
|
-
if (startListening && !started) {
|
|
194
|
-
this.startListening(express)
|
|
195
|
-
started = true
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
express,
|
|
200
|
-
port: this.getPort(),
|
|
201
|
-
hostname: this.getHostname(),
|
|
202
|
-
userIsInRole: this._userIsInRole,
|
|
203
|
-
startListening,
|
|
204
|
-
enforcer: this._enforcer,
|
|
205
|
-
start: (opts) => {
|
|
206
|
-
if (opts?.doNotStartListening) {
|
|
207
|
-
console.log('Express will not start listening. You will have to start it yourself')
|
|
208
|
-
} else {
|
|
209
|
-
if (!started) {
|
|
210
|
-
this.startListening(express)
|
|
211
|
-
started = true
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (opts?.disableErrorHandler !== true) {
|
|
216
|
-
express.use(jsonErrorHandler)
|
|
217
|
-
}
|
|
218
|
-
return { server: this._server!, terminator: this._terminator! }
|
|
219
|
-
},
|
|
220
|
-
stop: async (terminator?: HttpTerminator) => {
|
|
221
|
-
const term = terminator ?? this._terminator
|
|
222
|
-
if (!term) {
|
|
223
|
-
return false
|
|
224
|
-
}
|
|
225
|
-
return await term.terminate().then(() => true)
|
|
226
|
-
},
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
protected buildExpress<T extends Application>(opts?: {
|
|
231
|
-
express?: Express
|
|
232
|
-
startListening?: boolean
|
|
233
|
-
handlers?: ApplicationRequestHandler<T> | ApplicationRequestHandler<T>[]
|
|
234
|
-
}): express.Express {
|
|
235
|
-
const app: express.Express = opts?.express ?? this.existingExpress ?? express()
|
|
236
|
-
if (this._morgan) {
|
|
237
|
-
app.use(this._morgan)
|
|
238
|
-
}
|
|
239
|
-
if (this._sessionOpts) {
|
|
240
|
-
const store = this._sessionOpts.store ?? new expressSession.MemoryStore()
|
|
241
|
-
this._sessionOpts.store = store
|
|
242
|
-
app.use(expressSession(this._sessionOpts))
|
|
243
|
-
}
|
|
244
|
-
if (this._usePassportAuth) {
|
|
245
|
-
app.use(passport.initialize(this._passportInitOpts))
|
|
246
|
-
if (this._sessionOpts) {
|
|
247
|
-
// app.use(passport.authenticate('session'))
|
|
248
|
-
//_sessionOpts are not for passport session, they are for express above
|
|
249
|
-
app.use(passport.session())
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
if (this._userIsInRole) {
|
|
253
|
-
app.use(checkUserIsInRole({ roles: this._userIsInRole }))
|
|
254
|
-
}
|
|
255
|
-
if (this._corsConfigurer) {
|
|
256
|
-
this._corsConfigurer.configure({ existingExpress: app })
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// @ts-ignore
|
|
260
|
-
this._handlers && this._handlers.length > 0 && app.use(this._handlers)
|
|
261
|
-
// @ts-ignore
|
|
262
|
-
opts?.handlers && app.use(opts.handlers)
|
|
263
|
-
//fixme: this should come from the config
|
|
264
|
-
app.use(bodyParser.urlencoded({ extended: true }))
|
|
265
|
-
app.use(bodyParser.json({ limit: '5mb' }))
|
|
266
|
-
return app
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export class ExpressCorsConfigurer {
|
|
271
|
-
private _disableCors?: boolean
|
|
272
|
-
private _enablePreflightOptions?: boolean
|
|
273
|
-
private _allowOrigin?: boolean | string | RegExp | Array<boolean | string | RegExp>
|
|
274
|
-
private _allowMethods?: string | string[]
|
|
275
|
-
private _allowedHeaders?: string | string[]
|
|
276
|
-
private _allowCredentials?: boolean
|
|
277
|
-
private readonly _express?: Express
|
|
278
|
-
private readonly _envVarPrefix?: string
|
|
279
|
-
|
|
280
|
-
constructor(args?: { existingExpress?: Express; envVarPrefix?: string }) {
|
|
281
|
-
const { existingExpress, envVarPrefix } = args ?? {}
|
|
282
|
-
this._express = existingExpress
|
|
283
|
-
this._envVarPrefix = envVarPrefix
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
public allowOrigin(value: string | boolean | RegExp | Array<string | boolean | RegExp>): this {
|
|
287
|
-
this._allowOrigin = value
|
|
288
|
-
return this
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
public disableCors(value: boolean): this {
|
|
292
|
-
this._disableCors = value
|
|
293
|
-
return this
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
public allowMethods(value: string | string[]): this {
|
|
297
|
-
this._allowMethods = value
|
|
298
|
-
return this
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
public allowedHeaders(value: string | string[]): this {
|
|
302
|
-
this._allowedHeaders = value
|
|
303
|
-
return this
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
public allowCredentials(value: boolean): this {
|
|
307
|
-
this._allowCredentials = value
|
|
308
|
-
return this
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
public configure({ existingExpress }: { existingExpress?: Express }) {
|
|
312
|
-
const express = existingExpress ?? this._express
|
|
313
|
-
if (!express) {
|
|
314
|
-
throw Error('No express passed in during construction or configure')
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const disableCorsEnv = env('CORS_DISABLE', this._envVarPrefix)
|
|
318
|
-
const corsDisabled = this._disableCors ?? (disableCorsEnv ? /true/.test(disableCorsEnv) : false)
|
|
319
|
-
if (corsDisabled) {
|
|
320
|
-
return
|
|
321
|
-
}
|
|
322
|
-
const envAllowOriginStr = env('CORS_ALLOW_ORIGIN', this._envVarPrefix) ?? '*'
|
|
323
|
-
let envAllowOrigin: string[] | string
|
|
324
|
-
if (envAllowOriginStr.includes(',')) {
|
|
325
|
-
envAllowOrigin = envAllowOriginStr.split(',')
|
|
326
|
-
} else if (envAllowOriginStr.includes(' ')) {
|
|
327
|
-
envAllowOrigin = envAllowOriginStr.split(' ')
|
|
328
|
-
} else {
|
|
329
|
-
envAllowOrigin = envAllowOriginStr
|
|
330
|
-
}
|
|
331
|
-
if (Array.isArray(envAllowOrigin) && envAllowOrigin.length === 1) {
|
|
332
|
-
envAllowOrigin = envAllowOrigin[0]
|
|
333
|
-
}
|
|
334
|
-
const corsOptions: CorsOptions = {
|
|
335
|
-
origin: this._allowOrigin ?? envAllowOrigin,
|
|
336
|
-
// todo: env vars
|
|
337
|
-
...(this._allowMethods && { methods: this._allowMethods }),
|
|
338
|
-
...(this._allowedHeaders && { allowedHeaders: this._allowedHeaders }),
|
|
339
|
-
...(this._allowCredentials !== undefined && { credentials: this._allowCredentials }),
|
|
340
|
-
optionsSuccessStatus: 204,
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (this._enablePreflightOptions) {
|
|
344
|
-
express.options('*', cors(corsOptions))
|
|
345
|
-
}
|
|
346
|
-
express.use(cors(corsOptions))
|
|
347
|
-
}
|
|
348
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @public
|
|
3
|
+
*/
|
|
4
|
+
import bodyParser from 'body-parser'
|
|
5
|
+
import { Enforcer } from 'casbin'
|
|
6
|
+
import cors, { CorsOptions } from 'cors'
|
|
7
|
+
|
|
8
|
+
import express, { Express } from 'express'
|
|
9
|
+
import { Application, ApplicationRequestHandler } from 'express-serve-static-core'
|
|
10
|
+
import expressSession from 'express-session'
|
|
11
|
+
import session from 'express-session'
|
|
12
|
+
import http from 'http'
|
|
13
|
+
import { createHttpTerminator, HttpTerminator } from 'http-terminator'
|
|
14
|
+
import morgan from 'morgan'
|
|
15
|
+
import passport, { InitializeOptions } from 'passport'
|
|
16
|
+
import { checkUserIsInRole } from './auth-utils'
|
|
17
|
+
import { jsonErrorHandler } from './express-utils'
|
|
18
|
+
import { env } from './functions'
|
|
19
|
+
import { ExpressSupport, IExpressServerOpts } from './types'
|
|
20
|
+
|
|
21
|
+
type Handler<Request extends http.IncomingMessage, Response extends http.ServerResponse> = (
|
|
22
|
+
req: Request,
|
|
23
|
+
res: Response,
|
|
24
|
+
callback: (err?: Error) => void,
|
|
25
|
+
) => void
|
|
26
|
+
|
|
27
|
+
export class ExpressBuilder {
|
|
28
|
+
private existingExpress?: Express
|
|
29
|
+
private hostnameOrIP?: string
|
|
30
|
+
private port?: number
|
|
31
|
+
private _handlers?: ApplicationRequestHandler<Application>[] = []
|
|
32
|
+
private listenCallback?: () => void
|
|
33
|
+
private _startListen?: boolean | undefined = undefined
|
|
34
|
+
private readonly envVarPrefix?: string
|
|
35
|
+
private _corsConfigurer?: ExpressCorsConfigurer
|
|
36
|
+
private _sessionOpts?: session.SessionOptions
|
|
37
|
+
private _usePassportAuth?: boolean = false
|
|
38
|
+
private _passportInitOpts?: InitializeOptions
|
|
39
|
+
private _userIsInRole?: string | string[]
|
|
40
|
+
private _enforcer?: Enforcer
|
|
41
|
+
private _server?: http.Server | undefined
|
|
42
|
+
private _terminator?: HttpTerminator
|
|
43
|
+
private _morgan?: Handler<any, any> | undefined
|
|
44
|
+
|
|
45
|
+
private constructor(opts?: { existingExpress?: Express; envVarPrefix?: string }) {
|
|
46
|
+
const { existingExpress, envVarPrefix } = opts ?? {}
|
|
47
|
+
if (existingExpress) {
|
|
48
|
+
this.withExpress(existingExpress)
|
|
49
|
+
}
|
|
50
|
+
this.envVarPrefix = envVarPrefix ?? ''
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public static fromExistingExpress(opts?: { existingExpress?: Express; envVarPrefix?: string }) {
|
|
54
|
+
return new ExpressBuilder(opts ?? {})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public static fromServerOpts(opts: IExpressServerOpts & { envVarPrefix?: string }) {
|
|
58
|
+
const builder = new ExpressBuilder({ existingExpress: opts?.existingExpress, envVarPrefix: opts?.envVarPrefix })
|
|
59
|
+
return builder.withEnableListenOpts({ ...opts, hostnameOrIP: opts.hostname, startOnBuild: opts.startListening ?? false })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public enableListen(startOnBuild?: boolean): this {
|
|
63
|
+
if (startOnBuild !== undefined) {
|
|
64
|
+
this._startListen = startOnBuild
|
|
65
|
+
}
|
|
66
|
+
return this
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public withMorganLogging(opts?: { existingMorgan?: Handler<any, any>; format?: string; options?: morgan.Options<any, any> }): this {
|
|
70
|
+
if (opts?.existingMorgan && (opts.format || opts.options)) {
|
|
71
|
+
throw Error('Cannot using an existing morgan with either a format or options')
|
|
72
|
+
}
|
|
73
|
+
this._morgan = opts?.existingMorgan ?? morgan(opts?.format ?? 'dev', opts?.options)
|
|
74
|
+
return this
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public withEnableListenOpts({
|
|
78
|
+
port,
|
|
79
|
+
hostnameOrIP,
|
|
80
|
+
callback,
|
|
81
|
+
startOnBuild,
|
|
82
|
+
}: {
|
|
83
|
+
port?: number
|
|
84
|
+
hostnameOrIP?: string
|
|
85
|
+
startOnBuild?: boolean
|
|
86
|
+
callback?: () => void
|
|
87
|
+
}): this {
|
|
88
|
+
port && this.withPort(port)
|
|
89
|
+
hostnameOrIP && this.withHostname(hostnameOrIP)
|
|
90
|
+
if (typeof callback === 'function') {
|
|
91
|
+
this.withListenCallback(callback)
|
|
92
|
+
}
|
|
93
|
+
this._startListen = startOnBuild === true
|
|
94
|
+
return this
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public withPort(port: number): this {
|
|
98
|
+
this.port = port
|
|
99
|
+
return this
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public withHostname(hostnameOrIP: string): this {
|
|
103
|
+
this.hostnameOrIP = hostnameOrIP
|
|
104
|
+
return this
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public withListenCallback(callback: () => void): this {
|
|
108
|
+
this.listenCallback = callback
|
|
109
|
+
return this
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public withExpress(existingExpress: Express): this {
|
|
113
|
+
this.existingExpress = existingExpress
|
|
114
|
+
this._startListen = false
|
|
115
|
+
return this
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public withCorsConfigurer(configurer: ExpressCorsConfigurer): this {
|
|
119
|
+
this._corsConfigurer = configurer
|
|
120
|
+
return this
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public withPassportAuth(usePassport: boolean, initializeOptions?: InitializeOptions): this {
|
|
124
|
+
this._usePassportAuth = usePassport
|
|
125
|
+
this._passportInitOpts = initializeOptions
|
|
126
|
+
return this
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public withGlobalUserIsInRole(userIsInRole: string | string[]): this {
|
|
130
|
+
this._userIsInRole = userIsInRole
|
|
131
|
+
return this
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public withEnforcer(enforcer: Enforcer): this {
|
|
135
|
+
this._enforcer = enforcer
|
|
136
|
+
return this
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public startListening(express: Express) {
|
|
140
|
+
this._server = express.listen(this.getPort(), this.getHostname(), this.listenCallback)
|
|
141
|
+
this._terminator = createHttpTerminator({
|
|
142
|
+
server: this._server,
|
|
143
|
+
// gracefulTerminationTimeout: 10
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
return { server: this._server, terminator: this._terminator }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public getHostname(): string {
|
|
150
|
+
return this.hostnameOrIP ?? env('HOSTNAME', this.envVarPrefix) ?? '0.0.0.0'
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public getPort(): number {
|
|
154
|
+
return (this.port ?? env('PORT', this.envVarPrefix) ?? 5000) as number
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public setHandlers(handlers: ApplicationRequestHandler<any> | ApplicationRequestHandler<any>[]): this {
|
|
158
|
+
if (Array.isArray(handlers)) {
|
|
159
|
+
this._handlers = handlers
|
|
160
|
+
} else if (handlers) {
|
|
161
|
+
if (!this._handlers) {
|
|
162
|
+
this._handlers = []
|
|
163
|
+
}
|
|
164
|
+
this._handlers.push(handlers)
|
|
165
|
+
} else {
|
|
166
|
+
this._handlers = []
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return this
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public addHandler(handler: ApplicationRequestHandler<any>): this {
|
|
173
|
+
if (!this._handlers) {
|
|
174
|
+
this._handlers = []
|
|
175
|
+
}
|
|
176
|
+
this._handlers.push(handler)
|
|
177
|
+
return this
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public withSessionOptions(sessionOpts: session.SessionOptions): this {
|
|
181
|
+
this._sessionOpts = sessionOpts
|
|
182
|
+
return this
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public build<T extends Application>(opts?: {
|
|
186
|
+
express?: Express
|
|
187
|
+
startListening?: boolean
|
|
188
|
+
handlers?: ApplicationRequestHandler<T> | ApplicationRequestHandler<T>[]
|
|
189
|
+
}): ExpressSupport {
|
|
190
|
+
const express = this.buildExpress(opts)
|
|
191
|
+
const startListening = opts?.startListening === undefined ? this._startListen !== true : opts.startListening
|
|
192
|
+
let started = this._server !== undefined
|
|
193
|
+
if (startListening && !started) {
|
|
194
|
+
this.startListening(express)
|
|
195
|
+
started = true
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
express,
|
|
200
|
+
port: this.getPort(),
|
|
201
|
+
hostname: this.getHostname(),
|
|
202
|
+
userIsInRole: this._userIsInRole,
|
|
203
|
+
startListening,
|
|
204
|
+
enforcer: this._enforcer,
|
|
205
|
+
start: (opts) => {
|
|
206
|
+
if (opts?.doNotStartListening) {
|
|
207
|
+
console.log('Express will not start listening. You will have to start it yourself')
|
|
208
|
+
} else {
|
|
209
|
+
if (!started) {
|
|
210
|
+
this.startListening(express)
|
|
211
|
+
started = true
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (opts?.disableErrorHandler !== true) {
|
|
216
|
+
express.use(jsonErrorHandler)
|
|
217
|
+
}
|
|
218
|
+
return { server: this._server!, terminator: this._terminator! }
|
|
219
|
+
},
|
|
220
|
+
stop: async (terminator?: HttpTerminator) => {
|
|
221
|
+
const term = terminator ?? this._terminator
|
|
222
|
+
if (!term) {
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
225
|
+
return await term.terminate().then(() => true)
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
protected buildExpress<T extends Application>(opts?: {
|
|
231
|
+
express?: Express
|
|
232
|
+
startListening?: boolean
|
|
233
|
+
handlers?: ApplicationRequestHandler<T> | ApplicationRequestHandler<T>[]
|
|
234
|
+
}): express.Express {
|
|
235
|
+
const app: express.Express = opts?.express ?? this.existingExpress ?? express()
|
|
236
|
+
if (this._morgan) {
|
|
237
|
+
app.use(this._morgan)
|
|
238
|
+
}
|
|
239
|
+
if (this._sessionOpts) {
|
|
240
|
+
const store = this._sessionOpts.store ?? new expressSession.MemoryStore()
|
|
241
|
+
this._sessionOpts.store = store
|
|
242
|
+
app.use(expressSession(this._sessionOpts))
|
|
243
|
+
}
|
|
244
|
+
if (this._usePassportAuth) {
|
|
245
|
+
app.use(passport.initialize(this._passportInitOpts))
|
|
246
|
+
if (this._sessionOpts) {
|
|
247
|
+
// app.use(passport.authenticate('session'))
|
|
248
|
+
//_sessionOpts are not for passport session, they are for express above
|
|
249
|
+
app.use(passport.session())
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (this._userIsInRole) {
|
|
253
|
+
app.use(checkUserIsInRole({ roles: this._userIsInRole }))
|
|
254
|
+
}
|
|
255
|
+
if (this._corsConfigurer) {
|
|
256
|
+
this._corsConfigurer.configure({ existingExpress: app })
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// @ts-ignore
|
|
260
|
+
this._handlers && this._handlers.length > 0 && app.use(this._handlers)
|
|
261
|
+
// @ts-ignore
|
|
262
|
+
opts?.handlers && app.use(opts.handlers)
|
|
263
|
+
//fixme: this should come from the config
|
|
264
|
+
app.use(bodyParser.urlencoded({ extended: true }))
|
|
265
|
+
app.use(bodyParser.json({ limit: '5mb' }))
|
|
266
|
+
return app
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export class ExpressCorsConfigurer {
|
|
271
|
+
private _disableCors?: boolean
|
|
272
|
+
private _enablePreflightOptions?: boolean
|
|
273
|
+
private _allowOrigin?: boolean | string | RegExp | Array<boolean | string | RegExp>
|
|
274
|
+
private _allowMethods?: string | string[]
|
|
275
|
+
private _allowedHeaders?: string | string[]
|
|
276
|
+
private _allowCredentials?: boolean
|
|
277
|
+
private readonly _express?: Express
|
|
278
|
+
private readonly _envVarPrefix?: string
|
|
279
|
+
|
|
280
|
+
constructor(args?: { existingExpress?: Express; envVarPrefix?: string }) {
|
|
281
|
+
const { existingExpress, envVarPrefix } = args ?? {}
|
|
282
|
+
this._express = existingExpress
|
|
283
|
+
this._envVarPrefix = envVarPrefix
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public allowOrigin(value: string | boolean | RegExp | Array<string | boolean | RegExp>): this {
|
|
287
|
+
this._allowOrigin = value
|
|
288
|
+
return this
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public disableCors(value: boolean): this {
|
|
292
|
+
this._disableCors = value
|
|
293
|
+
return this
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
public allowMethods(value: string | string[]): this {
|
|
297
|
+
this._allowMethods = value
|
|
298
|
+
return this
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
public allowedHeaders(value: string | string[]): this {
|
|
302
|
+
this._allowedHeaders = value
|
|
303
|
+
return this
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public allowCredentials(value: boolean): this {
|
|
307
|
+
this._allowCredentials = value
|
|
308
|
+
return this
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
public configure({ existingExpress }: { existingExpress?: Express }) {
|
|
312
|
+
const express = existingExpress ?? this._express
|
|
313
|
+
if (!express) {
|
|
314
|
+
throw Error('No express passed in during construction or configure')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const disableCorsEnv = env('CORS_DISABLE', this._envVarPrefix)
|
|
318
|
+
const corsDisabled = this._disableCors ?? (disableCorsEnv ? /true/.test(disableCorsEnv) : false)
|
|
319
|
+
if (corsDisabled) {
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
const envAllowOriginStr = env('CORS_ALLOW_ORIGIN', this._envVarPrefix) ?? '*'
|
|
323
|
+
let envAllowOrigin: string[] | string
|
|
324
|
+
if (envAllowOriginStr.includes(',')) {
|
|
325
|
+
envAllowOrigin = envAllowOriginStr.split(',')
|
|
326
|
+
} else if (envAllowOriginStr.includes(' ')) {
|
|
327
|
+
envAllowOrigin = envAllowOriginStr.split(' ')
|
|
328
|
+
} else {
|
|
329
|
+
envAllowOrigin = envAllowOriginStr
|
|
330
|
+
}
|
|
331
|
+
if (Array.isArray(envAllowOrigin) && envAllowOrigin.length === 1) {
|
|
332
|
+
envAllowOrigin = envAllowOrigin[0]
|
|
333
|
+
}
|
|
334
|
+
const corsOptions: CorsOptions = {
|
|
335
|
+
origin: this._allowOrigin ?? envAllowOrigin,
|
|
336
|
+
// todo: env vars
|
|
337
|
+
...(this._allowMethods && { methods: this._allowMethods }),
|
|
338
|
+
...(this._allowedHeaders && { allowedHeaders: this._allowedHeaders }),
|
|
339
|
+
...(this._allowCredentials !== undefined && { credentials: this._allowCredentials }),
|
|
340
|
+
optionsSuccessStatus: 204,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (this._enablePreflightOptions) {
|
|
344
|
+
express.options('*', cors(corsOptions))
|
|
345
|
+
}
|
|
346
|
+
express.use(cors(corsOptions))
|
|
347
|
+
}
|
|
348
|
+
}
|