@blazedpath/commons 0.0.4
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/README.md +3 -0
- package/blz-base/health/index.js +215 -0
- package/blz-base/index.js +1466 -0
- package/blz-cache/LruCache.js +44 -0
- package/blz-cache/index.js +29 -0
- package/blz-config/index.js +434 -0
- package/blz-core/index.js +364 -0
- package/blz-cryptography/index.js +54 -0
- package/blz-datetimes/index.js +356 -0
- package/blz-file/example.dat +2545 -0
- package/blz-file/fileService.js +205 -0
- package/blz-file/index.js +94 -0
- package/blz-file/index.test.js +31 -0
- package/blz-file/lab.js +33 -0
- package/blz-hazelcast/index.js +189 -0
- package/blz-hazelcast/lib/credentials.js +25 -0
- package/blz-hazelcast/lib/credentialsFactory.js +12 -0
- package/blz-hazelcast/lib/hazelcastCache.js +234 -0
- package/blz-iterable/index.js +446 -0
- package/blz-json-schema/index.js +11 -0
- package/blz-jwt/index.js +121 -0
- package/blz-kafka/index.js +522 -0
- package/blz-math/index.js +131 -0
- package/blz-mongodb/index.js +326 -0
- package/blz-rds/__test__/scape.test.js +58 -0
- package/blz-rds/blz-rds-executor.js +578 -0
- package/blz-rds/blz-rds-helper.js +310 -0
- package/blz-rds/commands/core/add.js +13 -0
- package/blz-rds/commands/core/and.js +18 -0
- package/blz-rds/commands/core/asc.js +10 -0
- package/blz-rds/commands/core/avg.js +10 -0
- package/blz-rds/commands/core/column-ref.js +8 -0
- package/blz-rds/commands/core/count-distinct.js +10 -0
- package/blz-rds/commands/core/count.js +10 -0
- package/blz-rds/commands/core/decimal.js +8 -0
- package/blz-rds/commands/core/desc.js +10 -0
- package/blz-rds/commands/core/distinct.js +10 -0
- package/blz-rds/commands/core/divide.js +11 -0
- package/blz-rds/commands/core/embedded-exists.js +17 -0
- package/blz-rds/commands/core/embedded-select.js +17 -0
- package/blz-rds/commands/core/equals.js +9 -0
- package/blz-rds/commands/core/false.js +8 -0
- package/blz-rds/commands/core/greater-or-equal.js +9 -0
- package/blz-rds/commands/core/greater.js +9 -0
- package/blz-rds/commands/core/in.js +9 -0
- package/blz-rds/commands/core/integer.js +8 -0
- package/blz-rds/commands/core/is-not-null.js +11 -0
- package/blz-rds/commands/core/is-null-or-value.js +10 -0
- package/blz-rds/commands/core/is-null.js +11 -0
- package/blz-rds/commands/core/less-or-equal.js +9 -0
- package/blz-rds/commands/core/less-unary.js +12 -0
- package/blz-rds/commands/core/less.js +9 -0
- package/blz-rds/commands/core/like.js +12 -0
- package/blz-rds/commands/core/max.js +10 -0
- package/blz-rds/commands/core/min.js +10 -0
- package/blz-rds/commands/core/multiply.js +13 -0
- package/blz-rds/commands/core/not-equals.js +9 -0
- package/blz-rds/commands/core/not-in.js +9 -0
- package/blz-rds/commands/core/not.js +13 -0
- package/blz-rds/commands/core/null.js +8 -0
- package/blz-rds/commands/core/nvl.js +11 -0
- package/blz-rds/commands/core/or.js +13 -0
- package/blz-rds/commands/core/parameter.js +34 -0
- package/blz-rds/commands/core/remainder.js +16 -0
- package/blz-rds/commands/core/string.js +8 -0
- package/blz-rds/commands/core/subtract.js +13 -0
- package/blz-rds/commands/core/sum.js +10 -0
- package/blz-rds/commands/core/true.js +8 -0
- package/blz-rds/commands/core/tuple.js +13 -0
- package/blz-rds/commands/datetimes/add-days.js +11 -0
- package/blz-rds/commands/datetimes/add-hours.js +11 -0
- package/blz-rds/commands/datetimes/add-milliseconds.js +11 -0
- package/blz-rds/commands/datetimes/add-minutes.js +11 -0
- package/blz-rds/commands/datetimes/add-months.js +11 -0
- package/blz-rds/commands/datetimes/add-seconds.js +11 -0
- package/blz-rds/commands/datetimes/add-years.js +11 -0
- package/blz-rds/commands/datetimes/date-diff.js +11 -0
- package/blz-rds/commands/datetimes/date.js +12 -0
- package/blz-rds/commands/datetimes/datetime-diff.js +11 -0
- package/blz-rds/commands/datetimes/datetime.js +15 -0
- package/blz-rds/commands/datetimes/day.js +10 -0
- package/blz-rds/commands/datetimes/hour.js +10 -0
- package/blz-rds/commands/datetimes/millisecond.js +10 -0
- package/blz-rds/commands/datetimes/minute.js +10 -0
- package/blz-rds/commands/datetimes/month-text.js +10 -0
- package/blz-rds/commands/datetimes/month.js +10 -0
- package/blz-rds/commands/datetimes/now.js +9 -0
- package/blz-rds/commands/datetimes/second.js +10 -0
- package/blz-rds/commands/datetimes/subtract-days.js +11 -0
- package/blz-rds/commands/datetimes/subtract-hours.js +11 -0
- package/blz-rds/commands/datetimes/subtract-milliseconds.js +11 -0
- package/blz-rds/commands/datetimes/subtract-minutes.js +11 -0
- package/blz-rds/commands/datetimes/subtract-seconds.js +11 -0
- package/blz-rds/commands/datetimes/time-diff.js +11 -0
- package/blz-rds/commands/datetimes/time.js +13 -0
- package/blz-rds/commands/datetimes/today.js +9 -0
- package/blz-rds/commands/datetimes/week-day-text.js +10 -0
- package/blz-rds/commands/datetimes/week-day.js +10 -0
- package/blz-rds/commands/datetimes/week.js +10 -0
- package/blz-rds/commands/datetimes/year.js +10 -0
- package/blz-rds/commands/math/abs.js +10 -0
- package/blz-rds/commands/math/acos.js +10 -0
- package/blz-rds/commands/math/asin.js +10 -0
- package/blz-rds/commands/math/atan.js +10 -0
- package/blz-rds/commands/math/atan2.js +11 -0
- package/blz-rds/commands/math/ceil.js +10 -0
- package/blz-rds/commands/math/cos.js +10 -0
- package/blz-rds/commands/math/cosh.js +10 -0
- package/blz-rds/commands/math/exp.js +10 -0
- package/blz-rds/commands/math/floor.js +10 -0
- package/blz-rds/commands/math/log.js +18 -0
- package/blz-rds/commands/math/log10.js +10 -0
- package/blz-rds/commands/math/pow.js +11 -0
- package/blz-rds/commands/math/random.js +9 -0
- package/blz-rds/commands/math/round.js +18 -0
- package/blz-rds/commands/math/sign.js +10 -0
- package/blz-rds/commands/math/sin.js +10 -0
- package/blz-rds/commands/math/sinh.js +10 -0
- package/blz-rds/commands/math/sqrt.js +10 -0
- package/blz-rds/commands/math/tan.js +10 -0
- package/blz-rds/commands/math/tanh.js +10 -0
- package/blz-rds/commands/math/trunc.js +18 -0
- package/blz-rds/commands/strings/concat.js +20 -0
- package/blz-rds/commands/strings/contains.js +12 -0
- package/blz-rds/commands/strings/ends-with.js +12 -0
- package/blz-rds/commands/strings/index-of.js +11 -0
- package/blz-rds/commands/strings/is-null-or-empty.js +11 -0
- package/blz-rds/commands/strings/is-null-or-white-space.js +11 -0
- package/blz-rds/commands/strings/join.js +22 -0
- package/blz-rds/commands/strings/last-index-of.js +11 -0
- package/blz-rds/commands/strings/length.js +10 -0
- package/blz-rds/commands/strings/pad-left.js +20 -0
- package/blz-rds/commands/strings/pad-right.js +20 -0
- package/blz-rds/commands/strings/replace.js +12 -0
- package/blz-rds/commands/strings/starts-with.js +12 -0
- package/blz-rds/commands/strings/substring.js +12 -0
- package/blz-rds/commands/strings/to-lower.js +10 -0
- package/blz-rds/commands/strings/to-upper.js +10 -0
- package/blz-rds/commands/strings/trim-end.js +10 -0
- package/blz-rds/commands/strings/trim-start.js +10 -0
- package/blz-rds/commands/strings/trim.js +10 -0
- package/blz-rds/index.js +744 -0
- package/blz-rds-mysql/base.js +857 -0
- package/blz-rds-mysql/connection-manager.js +129 -0
- package/blz-rds-mysql/execute-bulk-insert.js +35 -0
- package/blz-rds-mysql/execute-bulk-merge.js +45 -0
- package/blz-rds-mysql/execute-non-query.js +34 -0
- package/blz-rds-mysql/execute-query.js +50 -0
- package/blz-rds-mysql/index.js +41 -0
- package/blz-rds-mysql/stored-procedure.js +207 -0
- package/blz-rds-mysql/syntaxis.json +114 -0
- package/blz-rds-mysqlx/base.js +846 -0
- package/blz-rds-mysqlx/connection-manager.js +141 -0
- package/blz-rds-mysqlx/execute-bulk-insert.js +35 -0
- package/blz-rds-mysqlx/execute-bulk-merge.js +45 -0
- package/blz-rds-mysqlx/execute-non-query.js +29 -0
- package/blz-rds-mysqlx/execute-query.js +39 -0
- package/blz-rds-mysqlx/index.js +41 -0
- package/blz-rds-mysqlx/stored-procedure.js +179 -0
- package/blz-rds-mysqlx/syntaxis.json +105 -0
- package/blz-rds-oracle/index.js +540 -0
- package/blz-rds-oracle/syntaxis.json +112 -0
- package/blz-rds-postgres/base.js +861 -0
- package/blz-rds-postgres/connection-manager.js +225 -0
- package/blz-rds-postgres/execute-bulk-insert.js +81 -0
- package/blz-rds-postgres/execute-bulk-merge.js +93 -0
- package/blz-rds-postgres/execute-non-query.js +23 -0
- package/blz-rds-postgres/execute-query.js +37 -0
- package/blz-rds-postgres/index.js +41 -0
- package/blz-rds-postgres/result-set.js +51 -0
- package/blz-rds-postgres/stored-procedure.js +116 -0
- package/blz-rds-postgres/syntaxis.json +114 -0
- package/blz-redis/index.js +217 -0
- package/blz-redis/lib/redisCache.js +265 -0
- package/blz-regex/index.js +25 -0
- package/blz-security/.eslintrc.js +15 -0
- package/blz-security/__test__/AuthorizationKpn.yaml +1043 -0
- package/blz-security/__test__/FinancingSetting.yaml +177 -0
- package/blz-security/__test__/KpnConfigPortal.yaml +330 -0
- package/blz-security/__test__/OrderManagement.yaml +5190 -0
- package/blz-security/__test__/Security.yaml +128 -0
- package/blz-security/__test__/autorization.test.js +105 -0
- package/blz-security/__test__/orderManagement.test.js +26 -0
- package/blz-security/__test__/secureUrl.test.js +79 -0
- package/blz-security/__test__/solveMergeRule.test.js +109 -0
- package/blz-security/__test__/sqlInjectionGuard.test.js +203 -0
- package/blz-security/__test__/xssGuard.test.js +204 -0
- package/blz-security/authorizationService.js +536 -0
- package/blz-security/config/global.js +8 -0
- package/blz-security/config/welcome +8 -0
- package/blz-security/doc/README.md +75 -0
- package/blz-security/filescanner/index.js +46 -0
- package/blz-security/helpers/consts.js +229 -0
- package/blz-security/helpers/utils.js +267 -0
- package/blz-security/implementations/cache.js +90 -0
- package/blz-security/implementations/oidc.js +404 -0
- package/blz-security/implementations/pkceCacheStore.js +23 -0
- package/blz-security/implementations/saml.js +10 -0
- package/blz-security/implementations/uma.js +63 -0
- package/blz-security/implementations/webAuthn.js +9 -0
- package/blz-security/implementations/wstg.js +72 -0
- package/blz-security/index.js +77 -0
- package/blz-security/lab/index.js +27 -0
- package/blz-security/middleware/HapiServerAzureAd.js +641 -0
- package/blz-security/middleware/HapiServerKeycloak.js +840 -0
- package/blz-security/middleware/HapiServerSimToken.js +247 -0
- package/blz-security/middleware/hapi.js +515 -0
- package/blz-security/middleware/hapiServer.js +974 -0
- package/blz-security/navigationMemoryRepository.js +15 -0
- package/blz-security/navigationMongoDbRepository.js +73 -0
- package/blz-security/secureUrlService.js +47 -0
- package/blz-security/securityService.js +409 -0
- package/blz-security/sqlInjectionGuard.js +162 -0
- package/blz-security/templates/forbidden.html +0 -0
- package/blz-security/templates/session-iframe-azure-ad.html +7 -0
- package/blz-security/templates/session-iframe.html +73 -0
- package/blz-security/templates/unauthorized.html +1 -0
- package/blz-security/xssGuard.js +87 -0
- package/blz-strings/index.js +167 -0
- package/blz-uuid/index.js +7 -0
- package/blz-yaml/index.js +19 -0
- package/index.js +84 -0
- package/package.json +97 -0
- package/process-managers/index.js +422 -0
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Blazedpath Team
|
|
3
|
+
* @implements Protecting all resources through hapi middleware
|
|
4
|
+
* @description Hapi.js (derived from Http-API) is an open-source Node.js
|
|
5
|
+
* framework used to build powerful and scalable web applications.
|
|
6
|
+
* @see https://hapi.dev/api/
|
|
7
|
+
*/
|
|
8
|
+
const Uma = require('../implementations/uma.js')
|
|
9
|
+
const Jsonwebtoken = require('jsonwebtoken') // Implementations of JSON Web Tokens.
|
|
10
|
+
const {
|
|
11
|
+
Exception,
|
|
12
|
+
getFullUrl,
|
|
13
|
+
getHost,
|
|
14
|
+
getProtocol,
|
|
15
|
+
getPathname,
|
|
16
|
+
getTemplate,
|
|
17
|
+
getTokenTolerance,
|
|
18
|
+
trace,
|
|
19
|
+
errorResponse
|
|
20
|
+
} = require('../helpers/utils.js')
|
|
21
|
+
// HapiServer Modules
|
|
22
|
+
const hapiYar = require('@hapi/yar');
|
|
23
|
+
const hapiJwt = require('@hapi/jwt');
|
|
24
|
+
const hapiCookie = require('@hapi/cookie')
|
|
25
|
+
|
|
26
|
+
// Azure AD rotates keys, so we jwk used to routinly fetch them
|
|
27
|
+
const jwksClient = require('jwks-rsa') // Retrieve RSA public keys from a JWKS.
|
|
28
|
+
// MS authenticator library
|
|
29
|
+
const {
|
|
30
|
+
ConfidentialClientApplication
|
|
31
|
+
} = require('@azure/msal-node');
|
|
32
|
+
const crypto = require("crypto"); // FOR PKCE in msal
|
|
33
|
+
const { saveVerifier, getVerifier } = require('../implementations/pkceCacheStore.js');
|
|
34
|
+
|
|
35
|
+
let securityService = null
|
|
36
|
+
|
|
37
|
+
// This class is defined to work with Azure AD.
|
|
38
|
+
class HapiServerAzureAd {
|
|
39
|
+
constructor(openIdConnect, cookiesName, cache) {
|
|
40
|
+
this.openIdConnect = openIdConnect
|
|
41
|
+
this.COOKIE_NAMES = cookiesName
|
|
42
|
+
this.activateTraceApiMethod = false
|
|
43
|
+
this.queryStringLimit = null;
|
|
44
|
+
this.securityLoginTokenExpToleranceSeconds = 3600 * 5; // Default 5 hours
|
|
45
|
+
this.authServerConfig = null;
|
|
46
|
+
this.authServerFullLoginUrl = null;
|
|
47
|
+
// This cache stores locally the jwt token set for refresh and logout.
|
|
48
|
+
this.cache = cache;
|
|
49
|
+
// To terminate sessions
|
|
50
|
+
// This client keeps a refresh of the rotating keys
|
|
51
|
+
this.clientJwk = null;
|
|
52
|
+
this.publicKeyFetch = null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async connect(_securityService, hapiServer, config) {
|
|
56
|
+
this.authServerConfig = config;
|
|
57
|
+
securityService = _securityService
|
|
58
|
+
const {
|
|
59
|
+
authServer,
|
|
60
|
+
activateTraceApiMethod
|
|
61
|
+
} = config
|
|
62
|
+
if (activateTraceApiMethod) {
|
|
63
|
+
this.activateTraceApiMethod = activateTraceApiMethod
|
|
64
|
+
}
|
|
65
|
+
const stateOption = {
|
|
66
|
+
clearInvalid: true,
|
|
67
|
+
encoding: 'base64',
|
|
68
|
+
isSecure: true,
|
|
69
|
+
isHttpOnly: true,
|
|
70
|
+
isSameSite: 'Lax',
|
|
71
|
+
path: '/',
|
|
72
|
+
strictHeader: true
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
if (authServer.sessionCookiesDomain) {
|
|
76
|
+
stateOption.domain = authServer.sessionCookiesDomain
|
|
77
|
+
}
|
|
78
|
+
stateOption.isHttpOnly = authServer.isHttpOnlyForSessionState ?? false;
|
|
79
|
+
hapiServer.state(this.COOKIE_NAMES.SESSION_STATE, stateOption);
|
|
80
|
+
if (!authServer.scope || !authServer.scope.split(' ').some((reg) => reg === 'openid')) {
|
|
81
|
+
authServer.scope = `openid ${authServer.scope || ''}`
|
|
82
|
+
}
|
|
83
|
+
if (authServer.tokenEndpoint && !authServer.tokenEndpoint.match(/https.*/)) {
|
|
84
|
+
hapiServer.states.cookies[this.COOKIE_NAMES.SID].isSecure = false
|
|
85
|
+
hapiServer.states.cookies[this.COOKIE_NAMES.SESSION_STATE].isSecure = false
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error('ERROR', `Exception ${err.message}`, err)
|
|
89
|
+
trace('ERROR', err.stack)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add Plugins
|
|
93
|
+
this.configurePlugins(hapiServer);
|
|
94
|
+
// onPreAuth: Here we check if the jwtToken is stored in the yar, refresh, and recompose the authorization header before hapi jwt module auth.
|
|
95
|
+
// Http protocol does not redirect all headers on a 3XX code.
|
|
96
|
+
hapiServer.ext('onPreAuth', async (request, h) => {
|
|
97
|
+
// Retrieve the token from the yar storage
|
|
98
|
+
let tokenInfo = request.yar.get('jwtToken');
|
|
99
|
+
if (tokenInfo) {
|
|
100
|
+
// check if token is about to be expired, if expired, update
|
|
101
|
+
let aboutToExpire = await me.tokenAboutToExpire(tokenInfo.token, 10);
|
|
102
|
+
if (aboutToExpire) {
|
|
103
|
+
// To refresh the tokens, Azure uses a silent re authentication
|
|
104
|
+
const silentRereshTokenResponse = await me.authServerConfig.authServer.msalClient.acquireTokenSilent({
|
|
105
|
+
account: tokenInfo.account, // If token exists in yar. Account should always exist
|
|
106
|
+
scopes: me.authServerConfig.authServer.scope.split(" ")?? ["user.read"],
|
|
107
|
+
});
|
|
108
|
+
const obtainedTokens = {};
|
|
109
|
+
// Check if the silent refresh token was successful
|
|
110
|
+
if (silentRereshTokenResponse && silentRereshTokenResponse.idToken) {
|
|
111
|
+
obtainedTokens.tokenType = 'Bearer';
|
|
112
|
+
obtainedTokens.token = silentRereshTokenResponse.idToken;
|
|
113
|
+
obtainedTokens.tokenSubType = 'id_token';
|
|
114
|
+
obtainedTokens.account = silentRereshTokenResponse.account;
|
|
115
|
+
request.yar.set('jwtToken', obtainedTokens);
|
|
116
|
+
await request.yar.commit(h);
|
|
117
|
+
} else {
|
|
118
|
+
// no valid set of tokens has returned, clear the yar storage and continue
|
|
119
|
+
request.yar.set('userRelog', true);
|
|
120
|
+
obtainedTokens.account = tokenInfo.account;
|
|
121
|
+
request.yar.set('jwtToken', obtainedTokens);
|
|
122
|
+
await request.yar.commit(h);
|
|
123
|
+
delete request.headers.authorization; // Remove the authorization header
|
|
124
|
+
return h.continue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
switch (tokenInfo.tokenType) {
|
|
128
|
+
case 'Bearer':
|
|
129
|
+
case 'bearer': {
|
|
130
|
+
request.headers.authorization = `Bearer ${tokenInfo.token}`;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return h.continue;
|
|
138
|
+
});
|
|
139
|
+
hapiServer.ext('onPreResponse', async (request, h) => {
|
|
140
|
+
const response = request.response;
|
|
141
|
+
// Seek to redirect to login on unauthorize
|
|
142
|
+
if (response.isBoom && response.output.statusCode === 401 && !request.path.startsWith('/auth/callback')) {
|
|
143
|
+
function base64URLEncode(buffer) {
|
|
144
|
+
return buffer.toString("base64")
|
|
145
|
+
.replace(/=/g, "")
|
|
146
|
+
.replace(/\+/g, "-")
|
|
147
|
+
.replace(/\//g, "_");
|
|
148
|
+
}
|
|
149
|
+
function createPKCECodes() {
|
|
150
|
+
const verifier = base64URLEncode(crypto.randomBytes(32));
|
|
151
|
+
const challenge = base64URLEncode(crypto.createHash("sha256").update(verifier).digest());
|
|
152
|
+
return { verifier, challenge };
|
|
153
|
+
}
|
|
154
|
+
const pkce = createPKCECodes();
|
|
155
|
+
// This state id helps keep yar storage for code verifier across responseMode: form_post requests
|
|
156
|
+
const stateId = crypto.randomBytes(16).toString('hex');
|
|
157
|
+
saveVerifier(stateId, pkce.verifier);
|
|
158
|
+
|
|
159
|
+
const authUrl = await this.authServerConfig.authServer.msalClient.getAuthCodeUrl({
|
|
160
|
+
redirectUri: me.getRedirectUri(request, 'auth/callback'),
|
|
161
|
+
scopes: me.authServerConfig.authServer.scope.split(" ") ?? ["user.read"],
|
|
162
|
+
codeChallenge: pkce.challenge,
|
|
163
|
+
codeChallengeMethod: "S256",
|
|
164
|
+
responseMode: 'form_post',
|
|
165
|
+
state: stateId
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
let userRelog = request.yar.get('userRelog');
|
|
169
|
+
request.yar.set('pkv',pkce.verifier)
|
|
170
|
+
request.yar.commit(h);
|
|
171
|
+
if (userRelog && request.path !== '/') {
|
|
172
|
+
return h.redirect('/');
|
|
173
|
+
} else {
|
|
174
|
+
return h.redirect(authUrl);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return h.continue;
|
|
178
|
+
});
|
|
179
|
+
// GET /auth/callback: Resolves the jwt token on a callback after the login (keycloak/azure)
|
|
180
|
+
hapiServer.route({
|
|
181
|
+
method: 'GET',
|
|
182
|
+
path: '/auth/callback',
|
|
183
|
+
options: {
|
|
184
|
+
auth: false, // Disable authentication for this route
|
|
185
|
+
},
|
|
186
|
+
handler: async (request, h) => {
|
|
187
|
+
const authCode = request.query.code;
|
|
188
|
+
if (!authCode) {
|
|
189
|
+
return h.response('Authorization code missing').code(400);
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
let pkceverifier = request.yar.get('pkv');
|
|
193
|
+
const tokenResponse = await me.authServerConfig.authServer.msalClient.acquireTokenByCode({
|
|
194
|
+
code: authCode,
|
|
195
|
+
redirectUri: me.getRedirectUri(request, 'auth/callback'),
|
|
196
|
+
scopes: me.authServerConfig.authServer.scope.split(" ")?? ["user.read"],
|
|
197
|
+
codeVerifier: pkceverifier,
|
|
198
|
+
});
|
|
199
|
+
let obtainedTokens = {
|
|
200
|
+
tokenType: 'Bearer',
|
|
201
|
+
token: tokenResponse.idToken,
|
|
202
|
+
tokenSubType: 'id_token',
|
|
203
|
+
account: tokenResponse.account
|
|
204
|
+
};
|
|
205
|
+
request.yar.set('jwtToken', obtainedTokens);
|
|
206
|
+
let originalUrlPathName = request.yar.get('originalUrlPathName') ?? '/'
|
|
207
|
+
// Set session state
|
|
208
|
+
const sessionState = request.query.session_state;
|
|
209
|
+
h.state(this.COOKIE_NAMES.SESSION_STATE, sessionState);
|
|
210
|
+
|
|
211
|
+
// already reloged
|
|
212
|
+
request.yar.clear('userRelog');
|
|
213
|
+
// Store the JWT token in the `Authorization` header or a cookie
|
|
214
|
+
switch (obtainedTokens.tokenType) {
|
|
215
|
+
case 'Bearer':
|
|
216
|
+
case 'bearer': {
|
|
217
|
+
request.yar.set('jwtToken', obtainedTokens);
|
|
218
|
+
await request.yar.commit(h);
|
|
219
|
+
return h.redirect(originalUrlPathName);
|
|
220
|
+
}
|
|
221
|
+
default: {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
await request.yar.commit(h);
|
|
226
|
+
return h.redirect('/'); // Continue in case no token_type -> no auth header configured
|
|
227
|
+
} catch (error) {
|
|
228
|
+
request.yar.reset();
|
|
229
|
+
await request.yar.commit(h);
|
|
230
|
+
delete request.headers.authorization; // Remove the authorization header
|
|
231
|
+
console.error('Failed to obtain jwt token: ', error.response?.data ?? error.message);
|
|
232
|
+
return h.response('Failed to authenticate').code(500).takeover();
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
// POST /auth/callback: Resolves the jwt token on a callback after the login (keycloak/azure)
|
|
237
|
+
hapiServer.route({
|
|
238
|
+
method: 'POST',
|
|
239
|
+
path: '/auth/callback',
|
|
240
|
+
options: {
|
|
241
|
+
auth: false, // Disable authentication for this route
|
|
242
|
+
},
|
|
243
|
+
handler: async (request, h) => {
|
|
244
|
+
const authCode = request.payload.code;
|
|
245
|
+
if (!authCode) {
|
|
246
|
+
return h.response('Authorization code missing').code(400);
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
//let pkceverifier = request.yar.get('pkv');
|
|
250
|
+
const stateId = request.payload.state;
|
|
251
|
+
//const pkceverifier = request.yar.get(`pkce:${stateId}`);
|
|
252
|
+
const pkceverifier = getVerifier(stateId)
|
|
253
|
+
const tokenResponse = await me.authServerConfig.authServer.msalClient.acquireTokenByCode({
|
|
254
|
+
code: authCode,
|
|
255
|
+
redirectUri: me.getRedirectUri(request, 'auth/callback'),
|
|
256
|
+
scopes: me.authServerConfig.authServer.scope.split(" ")?? ["user.read"],
|
|
257
|
+
codeVerifier: pkceverifier,
|
|
258
|
+
responseMode: 'form_post'
|
|
259
|
+
});
|
|
260
|
+
let obtainedTokens = {
|
|
261
|
+
tokenType: 'Bearer',
|
|
262
|
+
token: tokenResponse.idToken,
|
|
263
|
+
tokenSubType: 'id_token',
|
|
264
|
+
account: tokenResponse.account
|
|
265
|
+
};
|
|
266
|
+
request.yar.set('jwtToken', obtainedTokens);
|
|
267
|
+
let originalUrlPathName = request.yar.get('originalUrlPathName') ?? '/'
|
|
268
|
+
// Set session state
|
|
269
|
+
const sessionState = request.payload.session_state;
|
|
270
|
+
if (!sessionState) {
|
|
271
|
+
return h.response('Session State missing').code(400);
|
|
272
|
+
}
|
|
273
|
+
h.state(this.COOKIE_NAMES.SESSION_STATE, sessionState);
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
// already reloged
|
|
277
|
+
request.yar.clear('userRelog');
|
|
278
|
+
// Store the JWT token in the `Authorization` header or a cookie
|
|
279
|
+
switch (obtainedTokens.tokenType) {
|
|
280
|
+
case 'Bearer':
|
|
281
|
+
case 'bearer': {
|
|
282
|
+
request.yar.set('jwtToken', obtainedTokens);
|
|
283
|
+
await request.yar.commit(h);
|
|
284
|
+
return h.redirect(originalUrlPathName);
|
|
285
|
+
}
|
|
286
|
+
default: {
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
await request.yar.commit(h);
|
|
291
|
+
return h.redirect('/'); // Continue in case no token_type -> no auth header configured
|
|
292
|
+
} catch (error) {
|
|
293
|
+
request.yar.reset();
|
|
294
|
+
await request.yar.commit(h);
|
|
295
|
+
delete request.headers.authorization; // Remove the authorization header
|
|
296
|
+
console.error('Failed to obtain jwt token: ', error.response?.data ?? error.message);
|
|
297
|
+
return h.response('Failed to authenticate').code(500).takeover();
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
const me = this
|
|
302
|
+
// /get-authorization
|
|
303
|
+
hapiServer.route({
|
|
304
|
+
method: 'GET',
|
|
305
|
+
path: '/get-authorization',
|
|
306
|
+
handler: async (request, h) => {
|
|
307
|
+
try {
|
|
308
|
+
const {
|
|
309
|
+
session_state: ckSessionState
|
|
310
|
+
} = request.state
|
|
311
|
+
if (!ckSessionState) {
|
|
312
|
+
throw new Exception("Azure get-authorization: Session cookie doesn't exist.", 'CookiesError', 404)
|
|
313
|
+
}
|
|
314
|
+
const tokenSet = await me.openIdConnect.tokenSet()
|
|
315
|
+
const tokens = await tokenSet.tokens(ckSessionState)
|
|
316
|
+
const uma = await Uma.permission()
|
|
317
|
+
const token = await uma.ticket({
|
|
318
|
+
tokenUrl: authServer.tokenEndpoint || authServer.tokenUrl,
|
|
319
|
+
token: tokens.access_token,
|
|
320
|
+
audience: authServer.clientId
|
|
321
|
+
})
|
|
322
|
+
const sourceData = Jsonwebtoken.decode(token.access_token)
|
|
323
|
+
return h.response(JSON.stringify(sourceData.authorization)).takeover()
|
|
324
|
+
} catch (err) {
|
|
325
|
+
return errorResponse(h, err, 401)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
})
|
|
329
|
+
// /get-security-rules
|
|
330
|
+
hapiServer.route({
|
|
331
|
+
method: 'GET',
|
|
332
|
+
path: '/get-security-rules',
|
|
333
|
+
handler: async (request, h) => {
|
|
334
|
+
try {
|
|
335
|
+
const securityRules = await securityService.getFrontendSecurityRules(request)
|
|
336
|
+
return h.response(JSON.stringify(securityRules)).takeover()
|
|
337
|
+
} catch (err) {
|
|
338
|
+
return errorResponse(h, err, 401)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
// /get-permissions
|
|
343
|
+
hapiServer.route({
|
|
344
|
+
method: 'GET',
|
|
345
|
+
path: '/get-permissions',
|
|
346
|
+
handler: async (request, h) => {
|
|
347
|
+
try {
|
|
348
|
+
const permissions = await securityService.getPermissions()
|
|
349
|
+
return h.response(JSON.stringify(permissions)).takeover()
|
|
350
|
+
} catch (err) {
|
|
351
|
+
return errorResponse(h, err, 401)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
// /get-user-info
|
|
356
|
+
hapiServer.route({
|
|
357
|
+
method: 'GET',
|
|
358
|
+
path: '/get-user-info',
|
|
359
|
+
handler: async (request, h) => {
|
|
360
|
+
try {
|
|
361
|
+
const userInfo = await securityService.getUserInfo(request)
|
|
362
|
+
return h
|
|
363
|
+
.response(JSON.stringify(userInfo))
|
|
364
|
+
.takeover()
|
|
365
|
+
} catch (err) {
|
|
366
|
+
return errorResponse(h, err, 500)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
// /logout
|
|
371
|
+
hapiServer.route({
|
|
372
|
+
path: '/logout',
|
|
373
|
+
method: 'GET',
|
|
374
|
+
options: {
|
|
375
|
+
auth: false
|
|
376
|
+
}, // No auth required for logout
|
|
377
|
+
handler: async (request, h) => {
|
|
378
|
+
try {
|
|
379
|
+
// Get the post-logout redirect URI (where the user will go after logout)
|
|
380
|
+
const postLogoutRedirectUri = encodeURIComponent(me.getBaseUrl(request));
|
|
381
|
+
// Construct the logout URL for Azure AD
|
|
382
|
+
const logoutUrl = `https://login.microsoftonline.com/${me.authServerConfig.authServer.tenantId}/oauth2/v2.0/logout?post_logout_redirect_uri=${postLogoutRedirectUri}`;
|
|
383
|
+
|
|
384
|
+
// Clear session
|
|
385
|
+
request.yar.clear('jwtToken');
|
|
386
|
+
request.yar.clear('userRelog');
|
|
387
|
+
|
|
388
|
+
return h.redirect(logoutUrl); // Redirect to Azure AD logout
|
|
389
|
+
} catch (error) {
|
|
390
|
+
console.error('Error logging out:', error);
|
|
391
|
+
return h.response('Logout failed').code(500);
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
// /check-session-iframe.html
|
|
396
|
+
// This method returns the htlm code for the iframe
|
|
397
|
+
hapiServer.route({
|
|
398
|
+
path: '/check-session-iframe.html',
|
|
399
|
+
method: 'GET',
|
|
400
|
+
options: {
|
|
401
|
+
auth: false
|
|
402
|
+
},
|
|
403
|
+
handler: async (request, h) => {
|
|
404
|
+
try {
|
|
405
|
+
let content = getTemplate('session-iframe-azure-ad', {
|
|
406
|
+
checkSessionUrl: me.getBaseUrl(request) + 'check-session'
|
|
407
|
+
})
|
|
408
|
+
return h.response(content)
|
|
409
|
+
.header('Content-Type', 'text/html')
|
|
410
|
+
} catch (err) {
|
|
411
|
+
return errorResponse(h, err, 500)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
// /check-session
|
|
416
|
+
hapiServer.route({
|
|
417
|
+
path: '/check-session',
|
|
418
|
+
options: {
|
|
419
|
+
auth: false
|
|
420
|
+
},
|
|
421
|
+
method: 'GET',
|
|
422
|
+
handler: async (request, h) => {
|
|
423
|
+
let tokenInfo = request.yar.get('jwtToken');
|
|
424
|
+
let tokenIsExpired = { expired: false }
|
|
425
|
+
if (tokenInfo) {
|
|
426
|
+
// check if token is about to be expired
|
|
427
|
+
tokenIsExpired.expired = await me.tokenAboutToExpire(tokenInfo.token, 0.5);
|
|
428
|
+
if (tokenIsExpired.expired) {
|
|
429
|
+
let params = {
|
|
430
|
+
redirectUri: me.getRedirectUri(request, 'auth/callback'),
|
|
431
|
+
scopes: me.authServerConfig.authServer.scope.split(" "),
|
|
432
|
+
}
|
|
433
|
+
tokenIsExpired.redirectUrl = await me.authServerConfig.authServer.msalClient.getAuthCodeUrl(params);
|
|
434
|
+
request.yar.clear('jwtToken');
|
|
435
|
+
request.yar.clear('userRelog');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return h.response(tokenIsExpired);
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
}
|
|
442
|
+
getRedirectUri(request, redirectPath) {
|
|
443
|
+
const baseUrl = this.authServerConfig.url ?? this.getBaseUrl(request);
|
|
444
|
+
const path = (redirectPath) ?? this.getPathname(request);
|
|
445
|
+
let url = new URL(path, baseUrl);
|
|
446
|
+
|
|
447
|
+
// If the hostname is not localhost, force HTTPS
|
|
448
|
+
if (url.hostname !== 'localhost' && url.hostname !== '127.0.0.1') {
|
|
449
|
+
url.protocol = 'https:';
|
|
450
|
+
}
|
|
451
|
+
return url.toString();
|
|
452
|
+
}
|
|
453
|
+
getFullUrl(request) {
|
|
454
|
+
return `${getProtocol(request)}://${getHost(request)}${getPathname(request)}`
|
|
455
|
+
}
|
|
456
|
+
getBaseUrl(request) {
|
|
457
|
+
return `${getProtocol(request)}://${getHost(request)}/`
|
|
458
|
+
}
|
|
459
|
+
async authenticate(h, scope) {
|
|
460
|
+
const {
|
|
461
|
+
request
|
|
462
|
+
} = h
|
|
463
|
+
const pkceCode = await this.openIdConnect.pkceCode()
|
|
464
|
+
const requestUrl = getFullUrl(request)
|
|
465
|
+
let oidcMetadata = await this.openIdConnect.oidcMetadata()
|
|
466
|
+
if (!oidcMetadata || !oidcMetadata.openid_configuration) {
|
|
467
|
+
oidcMetadata = await this.configuration(this.authServerConfig.authServer)
|
|
468
|
+
}
|
|
469
|
+
if (requestUrl.match(new RegExp(/^(https?:\/{2}.*):?(\d*)/.source + getHost(request) + /\/?$/.source))) {
|
|
470
|
+
const authorizationUrl = await this.openIdConnect.authorizationUrl({
|
|
471
|
+
scope,
|
|
472
|
+
redirectUri: this.getRedirectUri(request),
|
|
473
|
+
pkceCode
|
|
474
|
+
})
|
|
475
|
+
trace('INFO', `Authenticate redirecting to ${authorizationUrl}`)
|
|
476
|
+
return h
|
|
477
|
+
.response()
|
|
478
|
+
.state(this.COOKIE_NAMES.SID, pkceCode)
|
|
479
|
+
.redirect(authorizationUrl)
|
|
480
|
+
.takeover()
|
|
481
|
+
} else if (getPathname(request) === '/logout') {
|
|
482
|
+
return h.continue
|
|
483
|
+
} else {
|
|
484
|
+
const tokenSet = await this.openIdConnect.tokenSet()
|
|
485
|
+
const {
|
|
486
|
+
state
|
|
487
|
+
} = request
|
|
488
|
+
if (tokenSet && state && state[this.COOKIE_NAMES.SESSION_STATE]) {
|
|
489
|
+
const tokens = await tokenSet.tokens(state[this.COOKIE_NAMES.SESSION_STATE])
|
|
490
|
+
if (!tokens || tokens.refresh_expires_in <= getTokenTolerance(0)) {
|
|
491
|
+
throw new Exception('Error when getting token', 'ExpirationError', 403)
|
|
492
|
+
}
|
|
493
|
+
return h.continue
|
|
494
|
+
} else {
|
|
495
|
+
return h
|
|
496
|
+
.response()
|
|
497
|
+
.code(401)
|
|
498
|
+
.takeover()
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async configurePlugins(server) {
|
|
504
|
+
// Add Yar to save info in the cookies across session calls
|
|
505
|
+
const hapiYarPassword = process.env.blz_hapiYarPassword || 'your-super-secure-yar-atleast-32-bytes-password';
|
|
506
|
+
await server.register({
|
|
507
|
+
plugin: hapiYar,
|
|
508
|
+
options: {
|
|
509
|
+
name: 'yar_state',
|
|
510
|
+
cookieOptions: {
|
|
511
|
+
password: hapiYarPassword,
|
|
512
|
+
isSecure: true, // Use true in production
|
|
513
|
+
isHttpOnly: true,
|
|
514
|
+
isSameSite: 'Lax', // 'Strict', 'Lax', or 'None'
|
|
515
|
+
clearInvalid: true,
|
|
516
|
+
ignoreErrors: true
|
|
517
|
+
},
|
|
518
|
+
storeBlank: false, // Prevent saving blank sessions
|
|
519
|
+
maxCookieSize: 0 // Use server-side storage for larger payloads
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
// Register @hapi/jwt plugin
|
|
523
|
+
await server.register(hapiJwt);
|
|
524
|
+
|
|
525
|
+
// Check for static certificate or rotating.
|
|
526
|
+
let keysFetch = true;
|
|
527
|
+
// Azure rotating certificates, prepare for the hapi jwt module
|
|
528
|
+
this.startupJwksClient();
|
|
529
|
+
// set up the function in this.publickKeyFetch
|
|
530
|
+
this.startupPublickKeyFetch();
|
|
531
|
+
keysFetch = this.publicKeyFetch;
|
|
532
|
+
const tenant_id = this.authServerConfig.authServer.issuer.match(/login\.microsoftonline\.com\/([^/]+)/)?.[1];
|
|
533
|
+
this.authServerConfig.authServer.tenantId = tenant_id;
|
|
534
|
+
this.authServerConfig.authServer.msalConfig = {
|
|
535
|
+
auth: {
|
|
536
|
+
clientId: this.authServerConfig.authServer.clientId,
|
|
537
|
+
authority: `https://login.microsoftonline.com/${tenant_id}`,
|
|
538
|
+
clientSecret: this.authServerConfig.authServer.clientSecret
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
const msalClient = new ConfidentialClientApplication(this.authServerConfig.authServer.msalConfig);
|
|
542
|
+
this.authServerConfig.authServer.msalClient = msalClient;
|
|
543
|
+
|
|
544
|
+
// Define the auth strategy
|
|
545
|
+
server.auth.strategy('jwtAuth', 'jwt', {
|
|
546
|
+
keys: keysFetch,
|
|
547
|
+
verify: {
|
|
548
|
+
aud: this.authServerConfig.authServer.clientId,
|
|
549
|
+
iss: this.authServerConfig.authServer.issuer,
|
|
550
|
+
exp: true, // validate expiration
|
|
551
|
+
sub: false
|
|
552
|
+
},
|
|
553
|
+
validate: false
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// Register the @hapi/cookie plugin
|
|
557
|
+
await server.register(hapiCookie);
|
|
558
|
+
|
|
559
|
+
// Define the cookie-based auth strategy
|
|
560
|
+
server.auth.strategy('cookieAuth', 'cookie', {
|
|
561
|
+
cookie: {
|
|
562
|
+
name: 'sid', // Primary session cookie
|
|
563
|
+
password: 'supersecretpasswordmustbeatleast32characterslong', // Encryption key
|
|
564
|
+
isSecure: true, // Should be true in production
|
|
565
|
+
isHttpOnly: true, // Prevents client-side JavaScript access
|
|
566
|
+
isSameSite: 'Lax', // Protects against CSRF
|
|
567
|
+
},
|
|
568
|
+
keepAlive: true,
|
|
569
|
+
redirectTo: false,
|
|
570
|
+
});
|
|
571
|
+
// Set default auth strategy to try both JWT and cookies
|
|
572
|
+
server.auth.default({
|
|
573
|
+
strategies: ['jwtAuth', 'cookieAuth'], // Try JWT first, then Cookie
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async decodeJwtToken(token) {
|
|
578
|
+
const decodedToken = hapiJwt.token.decode(token);
|
|
579
|
+
return decodedToken;
|
|
580
|
+
}
|
|
581
|
+
async tokenAboutToExpire(token, minutesBeforeExpiration = 0) {
|
|
582
|
+
if (!token)
|
|
583
|
+
return true;
|
|
584
|
+
const decodedToken = hapiJwt.token.decode(token);
|
|
585
|
+
const expirationTime = decodedToken.decoded.payload.exp * 1000; // Convert to milliseconds
|
|
586
|
+
const currentTime = Date.now();
|
|
587
|
+
const expirationThreshold = minutesBeforeExpiration * 60 * 1000; // Convert minutes to milliseconds
|
|
588
|
+
|
|
589
|
+
// Check if the token is expired or about to expire within the specified minutes
|
|
590
|
+
const isAboutToExpire = expirationTime - currentTime <= expirationThreshold;
|
|
591
|
+
return isAboutToExpire;
|
|
592
|
+
}
|
|
593
|
+
async isRefreshTokenExpired(refreshToken) {
|
|
594
|
+
try {
|
|
595
|
+
// Decode the token without verifying its signature.
|
|
596
|
+
const decodedRefreshToken = hapiJwt.token.decode(refreshToken);
|
|
597
|
+
// Get the current timestamp (in seconds).
|
|
598
|
+
const currentTimestamp = Math.floor(Date.now() / 1000);
|
|
599
|
+
|
|
600
|
+
if (decodedRefreshToken && decodedRefreshToken.decoded && decodedRefreshToken.decoded.payload && decodedRefreshToken.decoded.payload.exp) {
|
|
601
|
+
return (decodedRefreshToken.decoded.payload.exp < currentTimestamp)
|
|
602
|
+
} else
|
|
603
|
+
return true;
|
|
604
|
+
} catch (error) {
|
|
605
|
+
// if there is an error treat as if expired, so a re-login is prompted
|
|
606
|
+
console.error('Failed to decode the token: Invalid Refresh token format', error);
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async startupJwksClient() {
|
|
611
|
+
// Azure rotating certificates, prepare for the hapi jwt module
|
|
612
|
+
this.clientJwk = jwksClient({
|
|
613
|
+
jwksUri: this.authServerConfig.authServer.jwksUri,
|
|
614
|
+
cache: true, // Cache signing keys to avoid frequent network calls
|
|
615
|
+
rateLimit: true, // Rate limit the number of requests to the JWKS URI
|
|
616
|
+
jwksRequestsPerMinute: 10, // Limit to 10 requests per minute
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
async startupPublickKeyFetch() {
|
|
620
|
+
// Function to get the signing key
|
|
621
|
+
const getKey = async (kid) => {
|
|
622
|
+
return new Promise((resolve, reject) => {
|
|
623
|
+
this.clientJwk.getSigningKey(kid, (err, key) => {
|
|
624
|
+
if (err) {
|
|
625
|
+
return reject(err);
|
|
626
|
+
}
|
|
627
|
+
const signingKey = key.getPublicKey(); // Public key for signature verification
|
|
628
|
+
resolve(signingKey);
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
};
|
|
632
|
+
this.publicKeyFetch = async (artifacts) => {
|
|
633
|
+
const kid = artifacts.decoded.header.kid; // Extract 'kid' from JWT header
|
|
634
|
+
return getKey(kid); // Fetch the corresponding public key
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
module.exports = {
|
|
640
|
+
HapiServerAzureAd
|
|
641
|
+
}
|