@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-utils.ts
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import express, { NextFunction } from 'express'
|
|
2
|
-
export function sendErrorResponse(response: express.Response, statusCode: number, message: string | object, error?: any) {
|
|
3
|
-
let msg = message
|
|
4
|
-
if (!msg) {
|
|
5
|
-
console.error('Message was null when calling sendErrorResponse. This should not happen')
|
|
6
|
-
msg = 'An unexpected error occurred'
|
|
7
|
-
statusCode = 500
|
|
8
|
-
} else {
|
|
9
|
-
console.error(`sendErrorResponse (${statusCode}): ${typeof msg === 'string' ? msg : JSON.stringify(msg)}`)
|
|
10
|
-
}
|
|
11
|
-
if (error) {
|
|
12
|
-
if (error instanceof Error) {
|
|
13
|
-
console.error(`error message: ${error.message}`)
|
|
14
|
-
}
|
|
15
|
-
console.error(`error object: ${JSON.stringify(error)}`)
|
|
16
|
-
}
|
|
17
|
-
if (statusCode >= 500) {
|
|
18
|
-
console.error('Original error stack (if any) and REST API error stack:')
|
|
19
|
-
console.error(error?.stack)
|
|
20
|
-
console.error(Error().stack)
|
|
21
|
-
}
|
|
22
|
-
if (response.headersSent) {
|
|
23
|
-
console.error(`sendErrorResponse headers already sent`)
|
|
24
|
-
return response
|
|
25
|
-
}
|
|
26
|
-
response.statusCode = statusCode
|
|
27
|
-
if (typeof msg === 'string' && !msg.startsWith('{')) {
|
|
28
|
-
msg = { error: msg }
|
|
29
|
-
}
|
|
30
|
-
if (typeof msg === 'string' && msg.startsWith('{')) {
|
|
31
|
-
response.header('Content-Type', 'application/json')
|
|
32
|
-
return response.status(statusCode).end(msg)
|
|
33
|
-
}
|
|
34
|
-
return response.status(statusCode).json(msg)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const jsonErrorHandler = (err: any, req: express.Request, res: express.Response, next: NextFunction) => {
|
|
38
|
-
const statusCode: number = 'statusCode' in err ? err.statusCode : 500
|
|
39
|
-
let errorMsg = typeof err === 'string' ? err : (err.message ?? err)
|
|
40
|
-
if (typeof errorMsg !== 'string') {
|
|
41
|
-
errorMsg = JSON.stringify(errorMsg)
|
|
42
|
-
}
|
|
43
|
-
if (res.headersSent) {
|
|
44
|
-
console.log('Headers already sent, when calling error handler. Will defer to next error handler')
|
|
45
|
-
console.log(`Error was: ${JSON.stringify(err)}`)
|
|
46
|
-
return next(err)
|
|
47
|
-
}
|
|
48
|
-
return sendErrorResponse(res, statusCode, errorMsg, err)
|
|
49
|
-
}
|
|
1
|
+
import express, { NextFunction } from 'express'
|
|
2
|
+
export function sendErrorResponse(response: express.Response, statusCode: number, message: string | object, error?: any) {
|
|
3
|
+
let msg = message
|
|
4
|
+
if (!msg) {
|
|
5
|
+
console.error('Message was null when calling sendErrorResponse. This should not happen')
|
|
6
|
+
msg = 'An unexpected error occurred'
|
|
7
|
+
statusCode = 500
|
|
8
|
+
} else {
|
|
9
|
+
console.error(`sendErrorResponse (${statusCode}): ${typeof msg === 'string' ? msg : JSON.stringify(msg)}`)
|
|
10
|
+
}
|
|
11
|
+
if (error) {
|
|
12
|
+
if (error instanceof Error) {
|
|
13
|
+
console.error(`error message: ${error.message}`)
|
|
14
|
+
}
|
|
15
|
+
console.error(`error object: ${JSON.stringify(error)}`)
|
|
16
|
+
}
|
|
17
|
+
if (statusCode >= 500) {
|
|
18
|
+
console.error('Original error stack (if any) and REST API error stack:')
|
|
19
|
+
console.error(error?.stack)
|
|
20
|
+
console.error(Error().stack)
|
|
21
|
+
}
|
|
22
|
+
if (response.headersSent) {
|
|
23
|
+
console.error(`sendErrorResponse headers already sent`)
|
|
24
|
+
return response
|
|
25
|
+
}
|
|
26
|
+
response.statusCode = statusCode
|
|
27
|
+
if (typeof msg === 'string' && !msg.startsWith('{')) {
|
|
28
|
+
msg = { error: msg }
|
|
29
|
+
}
|
|
30
|
+
if (typeof msg === 'string' && msg.startsWith('{')) {
|
|
31
|
+
response.header('Content-Type', 'application/json')
|
|
32
|
+
return response.status(statusCode).end(msg)
|
|
33
|
+
}
|
|
34
|
+
return response.status(statusCode).json(msg)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const jsonErrorHandler = (err: any, req: express.Request, res: express.Response, next: NextFunction) => {
|
|
38
|
+
const statusCode: number = 'statusCode' in err ? err.statusCode : 500
|
|
39
|
+
let errorMsg = typeof err === 'string' ? err : (err.message ?? err)
|
|
40
|
+
if (typeof errorMsg !== 'string') {
|
|
41
|
+
errorMsg = JSON.stringify(errorMsg)
|
|
42
|
+
}
|
|
43
|
+
if (res.headersSent) {
|
|
44
|
+
console.log('Headers already sent, when calling error handler. Will defer to next error handler')
|
|
45
|
+
console.log(`Error was: ${JSON.stringify(err)}`)
|
|
46
|
+
return next(err)
|
|
47
|
+
}
|
|
48
|
+
return sendErrorResponse(res, statusCode, errorMsg, err)
|
|
49
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export * from './entra-id-auth'
|
|
2
|
-
export * from './static-bearer-auth'
|
|
3
|
-
export * from './auth-utils'
|
|
4
|
-
export * from './express-builders'
|
|
5
|
-
export * from './types'
|
|
6
|
-
export { sendErrorResponse, jsonErrorHandler } from './express-utils'
|
|
7
|
-
export * from './functions'
|
|
8
|
-
export * from './openid-connect-rp'
|
|
1
|
+
export * from './entra-id-auth'
|
|
2
|
+
export * from './static-bearer-auth'
|
|
3
|
+
export * from './auth-utils'
|
|
4
|
+
export * from './express-builders'
|
|
5
|
+
export * from './types'
|
|
6
|
+
export { sendErrorResponse, jsonErrorHandler } from './express-utils'
|
|
7
|
+
export * from './functions'
|
|
8
|
+
export * from './openid-connect-rp'
|
package/src/openid-connect-rp.ts
CHANGED
|
@@ -1,228 +1,228 @@
|
|
|
1
|
-
import { TAgent } from '@veramo/core'
|
|
2
|
-
import express, { Express, NextFunction, Router } from 'express'
|
|
3
|
-
import { BaseClient, ClientMetadata, ClientOptions, Issuer } from 'openid-client'
|
|
4
|
-
import passport from 'passport'
|
|
5
|
-
import { copyGlobalAuthToEndpoints, isUserAuthenticated } from './auth-utils'
|
|
6
|
-
import { sendErrorResponse } from './express-utils'
|
|
7
|
-
import { env } from './functions'
|
|
8
|
-
import { ExpressSupport, GenericAuthArgs, ISingleEndpointOpts } from './types'
|
|
9
|
-
|
|
10
|
-
const PREFIX = process.env.PREFIX ?? ''
|
|
11
|
-
export async function oidcDiscoverIssuer(opts?: { issuerUrl?: string }) {
|
|
12
|
-
const issuerUrl = opts?.issuerUrl ?? env('OIDC_ISSUER', PREFIX) ?? 'https://auth01.test.sphereon.com/auth/realms/energy-shr'
|
|
13
|
-
const issuer = await Issuer.discover(issuerUrl)
|
|
14
|
-
console.log('Discovered issuer %s %O', issuer.issuer, issuer.metadata)
|
|
15
|
-
return { issuer, issuerUrl }
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function oidcGetClient(
|
|
19
|
-
issuer: Issuer<BaseClient>,
|
|
20
|
-
metadata: ClientMetadata,
|
|
21
|
-
opts?: {
|
|
22
|
-
jwks?: { keys: JsonWebKey[] }
|
|
23
|
-
options?: ClientOptions
|
|
24
|
-
},
|
|
25
|
-
) {
|
|
26
|
-
// @ts-ignore
|
|
27
|
-
return new issuer.Client(metadata, opts?.jwks, opts?.options)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function getLoginEndpoint(router: Router, opts?: ISingleEndpointOpts & { redirectUrl?: string }) {
|
|
31
|
-
if (opts?.enabled === false) {
|
|
32
|
-
console.log(`Login endpoint is disabled`)
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
const strategy = opts?.endpoint?.authentication?.strategy
|
|
36
|
-
if (!strategy) {
|
|
37
|
-
throw Error('strategy needs to be provided')
|
|
38
|
-
}
|
|
39
|
-
const path = opts?.path ?? '/authentication/login'
|
|
40
|
-
router.get(
|
|
41
|
-
path,
|
|
42
|
-
(req: any, res: any, next: NextFunction) => {
|
|
43
|
-
const redirectPage = req.get('referer') ?? '/'
|
|
44
|
-
req.session.redirectPage = redirectPage
|
|
45
|
-
next()
|
|
46
|
-
},
|
|
47
|
-
passport.authenticate(
|
|
48
|
-
strategy,
|
|
49
|
-
{ ...opts.authentication?.strategyOptions, ...opts.endpoint?.authentication?.strategyOptions, keepSessionInfo: false },
|
|
50
|
-
undefined,
|
|
51
|
-
),
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function getLoginCallbackEndpoint(router: Router, opts?: ISingleEndpointOpts) {
|
|
56
|
-
if (opts?.enabled === false) {
|
|
57
|
-
console.log(`Auth callback endpoint is disabled`)
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
const strategy = opts?.endpoint?.authentication?.strategy
|
|
61
|
-
if (!strategy) {
|
|
62
|
-
throw Error('strategy needs to be provided')
|
|
63
|
-
}
|
|
64
|
-
const path = opts?.path ?? '/authentication/callback'
|
|
65
|
-
router.get(
|
|
66
|
-
path,
|
|
67
|
-
passport.authenticate(
|
|
68
|
-
strategy,
|
|
69
|
-
{ ...opts.authentication?.strategyOptions, ...opts.endpoint?.authentication?.strategyOptions, keepSessionInfo: true },
|
|
70
|
-
undefined,
|
|
71
|
-
),
|
|
72
|
-
(req: any, res: any, next) => {
|
|
73
|
-
if (req.user) {
|
|
74
|
-
console.log('User authenticated', req.user?.name)
|
|
75
|
-
// console.log(req.session)
|
|
76
|
-
const redirectPage = req.session.redirectPage ?? '/search'
|
|
77
|
-
// console.log(`PRE LOGIN PAGE in callback: ${redirectPage}`)
|
|
78
|
-
delete req.session.redirectPage
|
|
79
|
-
return res.redirect(redirectPage)
|
|
80
|
-
} else {
|
|
81
|
-
return res.redirect(env('OIDC_FRONTEND_LOGIN_URL', PREFIX) ?? 'http://localhost:3001/authentication/login')
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function getLogoutEndpoint(router: Router, client: BaseClient, opts?: ISingleEndpointOpts) {
|
|
88
|
-
if (opts?.enabled === false) {
|
|
89
|
-
console.log(`Logout endpoint is disabled`)
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
const path = opts?.path ?? '/authentication/logout'
|
|
93
|
-
router.get(path, (req, res) => {
|
|
94
|
-
try {
|
|
95
|
-
if (client.endSessionUrl()) {
|
|
96
|
-
return res.redirect(client.endSessionUrl())
|
|
97
|
-
} else {
|
|
98
|
-
console.log('IDP does not support end session url')
|
|
99
|
-
return res.redirect('/authentication/logout-callback')
|
|
100
|
-
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.log(error)
|
|
103
|
-
return res.redirect('/authentication/logout-callback')
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function getLogoutCallbackEndpoint(router: Router, opts?: ISingleEndpointOpts) {
|
|
109
|
-
if (opts?.enabled === false) {
|
|
110
|
-
console.log(`Logout callback endpoint is disabled`)
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
const path = opts?.path ?? '/authentication/logout-callback'
|
|
114
|
-
router.get(path, (req, res, next) => {
|
|
115
|
-
try {
|
|
116
|
-
req.logout((err) => {
|
|
117
|
-
if (err) {
|
|
118
|
-
console.log(`Error during calling logout-callback: ${JSON.stringify(err)}`)
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
return res.redirect(env('OIDC_FRONTEND_LOGOUT_REDIRECT_URL', PREFIX) ?? '/')
|
|
122
|
-
} catch (e) {
|
|
123
|
-
return sendErrorResponse(res, 500, 'An unexpected error occurred during logout callback', e)
|
|
124
|
-
}
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function getIdTokenEndpoint(router: Router, client: BaseClient, opts: ISingleEndpointOpts) {
|
|
129
|
-
if (opts?.enabled === false) {
|
|
130
|
-
console.log(`ID Token endpoint is disabled`)
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
const path = opts.path ?? '/authentication/tokens/id'
|
|
134
|
-
router.get(path, isUserAuthenticated, (req: any, res: any) => {
|
|
135
|
-
if (req.session.tokens.id_token) {
|
|
136
|
-
return res.json({ id_token: req.session.tokens.id_token })
|
|
137
|
-
} else {
|
|
138
|
-
return sendErrorResponse(res, 401, 'Authentication required')
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function getAuthenticatedUserEndpoint(router: Router, opts?: ISingleEndpointOpts) {
|
|
144
|
-
if (opts?.enabled === false) {
|
|
145
|
-
console.log(`Authenticated User endpoint is disabled`)
|
|
146
|
-
return
|
|
147
|
-
}
|
|
148
|
-
const path = opts?.path ?? '/authentication/user'
|
|
149
|
-
router.get(path, isUserAuthenticated, (req: any, res: any, next: any) => {
|
|
150
|
-
if (!req.user) {
|
|
151
|
-
return sendErrorResponse(res, 401, 'Authentication required')
|
|
152
|
-
}
|
|
153
|
-
let user = req.user
|
|
154
|
-
return res.json(user)
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export interface IAuthenticationOpts {
|
|
159
|
-
enabledFeatures?: AuthenticationApiFeatures
|
|
160
|
-
endpointOpts?: IAuthenticationEndpointOpts
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export interface IAuthenticationEndpointOpts {
|
|
164
|
-
basePath?: string
|
|
165
|
-
globalAuth?: GenericAuthArgs
|
|
166
|
-
getAuthenticatedUser?: ISingleEndpointOpts
|
|
167
|
-
getLogin?: ISingleEndpointOpts
|
|
168
|
-
getLogout?: ISingleEndpointOpts
|
|
169
|
-
getIdToken?: ISingleEndpointOpts
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export type AuthenticationApiFeatures = 'login' | 'logout' | 'id-token' | 'authenticated-user'
|
|
173
|
-
|
|
174
|
-
export class OpenIDConnectAuthApi {
|
|
175
|
-
get router(): express.Router {
|
|
176
|
-
return this._router
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private readonly _express: Express
|
|
180
|
-
private readonly _agent?: TAgent<any>
|
|
181
|
-
private readonly _opts?: IAuthenticationOpts
|
|
182
|
-
private readonly _router: Router
|
|
183
|
-
|
|
184
|
-
constructor(args: { agent?: TAgent<any>; expressSupport: ExpressSupport; client: BaseClient; opts: IAuthenticationOpts }) {
|
|
185
|
-
const { agent, opts } = args
|
|
186
|
-
this._agent = agent
|
|
187
|
-
copyGlobalAuthToEndpoints({ opts, keys: ['getLogin'] })
|
|
188
|
-
copyGlobalAuthToEndpoints({ opts, keys: ['getIdToken'] })
|
|
189
|
-
copyGlobalAuthToEndpoints({ opts, keys: ['getAuthenticatedUser'] })
|
|
190
|
-
// no need for the logout, as you these are not protected by auth
|
|
191
|
-
this._opts = opts
|
|
192
|
-
this._express = args.expressSupport.express
|
|
193
|
-
this._router = express.Router()
|
|
194
|
-
const features = opts?.enabledFeatures ?? ['login', 'logout', 'id-token', 'authenticated-user']
|
|
195
|
-
console.log(`Authentication API enabled`)
|
|
196
|
-
|
|
197
|
-
if (features.includes('login')) {
|
|
198
|
-
getLoginEndpoint(this.router, opts?.endpointOpts?.getLogin)
|
|
199
|
-
getLoginCallbackEndpoint(this.router, opts?.endpointOpts?.getLogin)
|
|
200
|
-
}
|
|
201
|
-
if (features.includes('logout')) {
|
|
202
|
-
getLogoutEndpoint(this.router, args.client, opts?.endpointOpts?.getLogout)
|
|
203
|
-
getLogoutCallbackEndpoint(this.router, opts?.endpointOpts?.getLogout)
|
|
204
|
-
}
|
|
205
|
-
if (features.includes('id-token')) {
|
|
206
|
-
if (opts.endpointOpts?.getIdToken === undefined) {
|
|
207
|
-
throw Error('Cannot enable id-token endpoint without providing id-token endpoint options')
|
|
208
|
-
}
|
|
209
|
-
getIdTokenEndpoint(this.router, args.client, opts?.endpointOpts?.getIdToken)
|
|
210
|
-
}
|
|
211
|
-
if (features.includes('authenticated-user')) {
|
|
212
|
-
getAuthenticatedUserEndpoint(this.router, opts?.endpointOpts?.getAuthenticatedUser)
|
|
213
|
-
}
|
|
214
|
-
this._express.use(opts?.endpointOpts?.basePath ?? '', this.router)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
get agent(): TAgent<any> | undefined {
|
|
218
|
-
return this._agent
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
get opts(): IAuthenticationOpts | undefined {
|
|
222
|
-
return this._opts
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
get express(): Express {
|
|
226
|
-
return this._express
|
|
227
|
-
}
|
|
228
|
-
}
|
|
1
|
+
import { TAgent } from '@veramo/core'
|
|
2
|
+
import express, { Express, NextFunction, Router } from 'express'
|
|
3
|
+
import { BaseClient, ClientMetadata, ClientOptions, Issuer } from 'openid-client'
|
|
4
|
+
import passport from 'passport'
|
|
5
|
+
import { copyGlobalAuthToEndpoints, isUserAuthenticated } from './auth-utils'
|
|
6
|
+
import { sendErrorResponse } from './express-utils'
|
|
7
|
+
import { env } from './functions'
|
|
8
|
+
import { ExpressSupport, GenericAuthArgs, ISingleEndpointOpts } from './types'
|
|
9
|
+
|
|
10
|
+
const PREFIX = process.env.PREFIX ?? ''
|
|
11
|
+
export async function oidcDiscoverIssuer(opts?: { issuerUrl?: string }) {
|
|
12
|
+
const issuerUrl = opts?.issuerUrl ?? env('OIDC_ISSUER', PREFIX) ?? 'https://auth01.test.sphereon.com/auth/realms/energy-shr'
|
|
13
|
+
const issuer = await Issuer.discover(issuerUrl)
|
|
14
|
+
console.log('Discovered issuer %s %O', issuer.issuer, issuer.metadata)
|
|
15
|
+
return { issuer, issuerUrl }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function oidcGetClient(
|
|
19
|
+
issuer: Issuer<BaseClient>,
|
|
20
|
+
metadata: ClientMetadata,
|
|
21
|
+
opts?: {
|
|
22
|
+
jwks?: { keys: JsonWebKey[] }
|
|
23
|
+
options?: ClientOptions
|
|
24
|
+
},
|
|
25
|
+
) {
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
return new issuer.Client(metadata, opts?.jwks, opts?.options)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getLoginEndpoint(router: Router, opts?: ISingleEndpointOpts & { redirectUrl?: string }) {
|
|
31
|
+
if (opts?.enabled === false) {
|
|
32
|
+
console.log(`Login endpoint is disabled`)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
const strategy = opts?.endpoint?.authentication?.strategy
|
|
36
|
+
if (!strategy) {
|
|
37
|
+
throw Error('strategy needs to be provided')
|
|
38
|
+
}
|
|
39
|
+
const path = opts?.path ?? '/authentication/login'
|
|
40
|
+
router.get(
|
|
41
|
+
path,
|
|
42
|
+
(req: any, res: any, next: NextFunction) => {
|
|
43
|
+
const redirectPage = req.get('referer') ?? '/'
|
|
44
|
+
req.session.redirectPage = redirectPage
|
|
45
|
+
next()
|
|
46
|
+
},
|
|
47
|
+
passport.authenticate(
|
|
48
|
+
strategy,
|
|
49
|
+
{ ...opts.authentication?.strategyOptions, ...opts.endpoint?.authentication?.strategyOptions, keepSessionInfo: false },
|
|
50
|
+
undefined,
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getLoginCallbackEndpoint(router: Router, opts?: ISingleEndpointOpts) {
|
|
56
|
+
if (opts?.enabled === false) {
|
|
57
|
+
console.log(`Auth callback endpoint is disabled`)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
const strategy = opts?.endpoint?.authentication?.strategy
|
|
61
|
+
if (!strategy) {
|
|
62
|
+
throw Error('strategy needs to be provided')
|
|
63
|
+
}
|
|
64
|
+
const path = opts?.path ?? '/authentication/callback'
|
|
65
|
+
router.get(
|
|
66
|
+
path,
|
|
67
|
+
passport.authenticate(
|
|
68
|
+
strategy,
|
|
69
|
+
{ ...opts.authentication?.strategyOptions, ...opts.endpoint?.authentication?.strategyOptions, keepSessionInfo: true },
|
|
70
|
+
undefined,
|
|
71
|
+
),
|
|
72
|
+
(req: any, res: any, next) => {
|
|
73
|
+
if (req.user) {
|
|
74
|
+
console.log('User authenticated', req.user?.name)
|
|
75
|
+
// console.log(req.session)
|
|
76
|
+
const redirectPage = req.session.redirectPage ?? '/search'
|
|
77
|
+
// console.log(`PRE LOGIN PAGE in callback: ${redirectPage}`)
|
|
78
|
+
delete req.session.redirectPage
|
|
79
|
+
return res.redirect(redirectPage)
|
|
80
|
+
} else {
|
|
81
|
+
return res.redirect(env('OIDC_FRONTEND_LOGIN_URL', PREFIX) ?? 'http://localhost:3001/authentication/login')
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getLogoutEndpoint(router: Router, client: BaseClient, opts?: ISingleEndpointOpts) {
|
|
88
|
+
if (opts?.enabled === false) {
|
|
89
|
+
console.log(`Logout endpoint is disabled`)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
const path = opts?.path ?? '/authentication/logout'
|
|
93
|
+
router.get(path, (req, res) => {
|
|
94
|
+
try {
|
|
95
|
+
if (client.endSessionUrl()) {
|
|
96
|
+
return res.redirect(client.endSessionUrl())
|
|
97
|
+
} else {
|
|
98
|
+
console.log('IDP does not support end session url')
|
|
99
|
+
return res.redirect('/authentication/logout-callback')
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.log(error)
|
|
103
|
+
return res.redirect('/authentication/logout-callback')
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function getLogoutCallbackEndpoint(router: Router, opts?: ISingleEndpointOpts) {
|
|
109
|
+
if (opts?.enabled === false) {
|
|
110
|
+
console.log(`Logout callback endpoint is disabled`)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
const path = opts?.path ?? '/authentication/logout-callback'
|
|
114
|
+
router.get(path, (req, res, next) => {
|
|
115
|
+
try {
|
|
116
|
+
req.logout((err) => {
|
|
117
|
+
if (err) {
|
|
118
|
+
console.log(`Error during calling logout-callback: ${JSON.stringify(err)}`)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
return res.redirect(env('OIDC_FRONTEND_LOGOUT_REDIRECT_URL', PREFIX) ?? '/')
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return sendErrorResponse(res, 500, 'An unexpected error occurred during logout callback', e)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function getIdTokenEndpoint(router: Router, client: BaseClient, opts: ISingleEndpointOpts) {
|
|
129
|
+
if (opts?.enabled === false) {
|
|
130
|
+
console.log(`ID Token endpoint is disabled`)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
const path = opts.path ?? '/authentication/tokens/id'
|
|
134
|
+
router.get(path, isUserAuthenticated, (req: any, res: any) => {
|
|
135
|
+
if (req.session.tokens.id_token) {
|
|
136
|
+
return res.json({ id_token: req.session.tokens.id_token })
|
|
137
|
+
} else {
|
|
138
|
+
return sendErrorResponse(res, 401, 'Authentication required')
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function getAuthenticatedUserEndpoint(router: Router, opts?: ISingleEndpointOpts) {
|
|
144
|
+
if (opts?.enabled === false) {
|
|
145
|
+
console.log(`Authenticated User endpoint is disabled`)
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
const path = opts?.path ?? '/authentication/user'
|
|
149
|
+
router.get(path, isUserAuthenticated, (req: any, res: any, next: any) => {
|
|
150
|
+
if (!req.user) {
|
|
151
|
+
return sendErrorResponse(res, 401, 'Authentication required')
|
|
152
|
+
}
|
|
153
|
+
let user = req.user
|
|
154
|
+
return res.json(user)
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface IAuthenticationOpts {
|
|
159
|
+
enabledFeatures?: AuthenticationApiFeatures
|
|
160
|
+
endpointOpts?: IAuthenticationEndpointOpts
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface IAuthenticationEndpointOpts {
|
|
164
|
+
basePath?: string
|
|
165
|
+
globalAuth?: GenericAuthArgs
|
|
166
|
+
getAuthenticatedUser?: ISingleEndpointOpts
|
|
167
|
+
getLogin?: ISingleEndpointOpts
|
|
168
|
+
getLogout?: ISingleEndpointOpts
|
|
169
|
+
getIdToken?: ISingleEndpointOpts
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export type AuthenticationApiFeatures = 'login' | 'logout' | 'id-token' | 'authenticated-user'
|
|
173
|
+
|
|
174
|
+
export class OpenIDConnectAuthApi {
|
|
175
|
+
get router(): express.Router {
|
|
176
|
+
return this._router
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private readonly _express: Express
|
|
180
|
+
private readonly _agent?: TAgent<any>
|
|
181
|
+
private readonly _opts?: IAuthenticationOpts
|
|
182
|
+
private readonly _router: Router
|
|
183
|
+
|
|
184
|
+
constructor(args: { agent?: TAgent<any>; expressSupport: ExpressSupport; client: BaseClient; opts: IAuthenticationOpts }) {
|
|
185
|
+
const { agent, opts } = args
|
|
186
|
+
this._agent = agent
|
|
187
|
+
copyGlobalAuthToEndpoints({ opts, keys: ['getLogin'] })
|
|
188
|
+
copyGlobalAuthToEndpoints({ opts, keys: ['getIdToken'] })
|
|
189
|
+
copyGlobalAuthToEndpoints({ opts, keys: ['getAuthenticatedUser'] })
|
|
190
|
+
// no need for the logout, as you these are not protected by auth
|
|
191
|
+
this._opts = opts
|
|
192
|
+
this._express = args.expressSupport.express
|
|
193
|
+
this._router = express.Router()
|
|
194
|
+
const features = opts?.enabledFeatures ?? ['login', 'logout', 'id-token', 'authenticated-user']
|
|
195
|
+
console.log(`Authentication API enabled`)
|
|
196
|
+
|
|
197
|
+
if (features.includes('login')) {
|
|
198
|
+
getLoginEndpoint(this.router, opts?.endpointOpts?.getLogin)
|
|
199
|
+
getLoginCallbackEndpoint(this.router, opts?.endpointOpts?.getLogin)
|
|
200
|
+
}
|
|
201
|
+
if (features.includes('logout')) {
|
|
202
|
+
getLogoutEndpoint(this.router, args.client, opts?.endpointOpts?.getLogout)
|
|
203
|
+
getLogoutCallbackEndpoint(this.router, opts?.endpointOpts?.getLogout)
|
|
204
|
+
}
|
|
205
|
+
if (features.includes('id-token')) {
|
|
206
|
+
if (opts.endpointOpts?.getIdToken === undefined) {
|
|
207
|
+
throw Error('Cannot enable id-token endpoint without providing id-token endpoint options')
|
|
208
|
+
}
|
|
209
|
+
getIdTokenEndpoint(this.router, args.client, opts?.endpointOpts?.getIdToken)
|
|
210
|
+
}
|
|
211
|
+
if (features.includes('authenticated-user')) {
|
|
212
|
+
getAuthenticatedUserEndpoint(this.router, opts?.endpointOpts?.getAuthenticatedUser)
|
|
213
|
+
}
|
|
214
|
+
this._express.use(opts?.endpointOpts?.basePath ?? '', this.router)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
get agent(): TAgent<any> | undefined {
|
|
218
|
+
return this._agent
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
get opts(): IAuthenticationOpts | undefined {
|
|
222
|
+
return this._opts
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
get express(): Express {
|
|
226
|
+
return this._express
|
|
227
|
+
}
|
|
228
|
+
}
|