@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.
Files changed (224) hide show
  1. package/README.md +3 -0
  2. package/blz-base/health/index.js +215 -0
  3. package/blz-base/index.js +1466 -0
  4. package/blz-cache/LruCache.js +44 -0
  5. package/blz-cache/index.js +29 -0
  6. package/blz-config/index.js +434 -0
  7. package/blz-core/index.js +364 -0
  8. package/blz-cryptography/index.js +54 -0
  9. package/blz-datetimes/index.js +356 -0
  10. package/blz-file/example.dat +2545 -0
  11. package/blz-file/fileService.js +205 -0
  12. package/blz-file/index.js +94 -0
  13. package/blz-file/index.test.js +31 -0
  14. package/blz-file/lab.js +33 -0
  15. package/blz-hazelcast/index.js +189 -0
  16. package/blz-hazelcast/lib/credentials.js +25 -0
  17. package/blz-hazelcast/lib/credentialsFactory.js +12 -0
  18. package/blz-hazelcast/lib/hazelcastCache.js +234 -0
  19. package/blz-iterable/index.js +446 -0
  20. package/blz-json-schema/index.js +11 -0
  21. package/blz-jwt/index.js +121 -0
  22. package/blz-kafka/index.js +522 -0
  23. package/blz-math/index.js +131 -0
  24. package/blz-mongodb/index.js +326 -0
  25. package/blz-rds/__test__/scape.test.js +58 -0
  26. package/blz-rds/blz-rds-executor.js +578 -0
  27. package/blz-rds/blz-rds-helper.js +310 -0
  28. package/blz-rds/commands/core/add.js +13 -0
  29. package/blz-rds/commands/core/and.js +18 -0
  30. package/blz-rds/commands/core/asc.js +10 -0
  31. package/blz-rds/commands/core/avg.js +10 -0
  32. package/blz-rds/commands/core/column-ref.js +8 -0
  33. package/blz-rds/commands/core/count-distinct.js +10 -0
  34. package/blz-rds/commands/core/count.js +10 -0
  35. package/blz-rds/commands/core/decimal.js +8 -0
  36. package/blz-rds/commands/core/desc.js +10 -0
  37. package/blz-rds/commands/core/distinct.js +10 -0
  38. package/blz-rds/commands/core/divide.js +11 -0
  39. package/blz-rds/commands/core/embedded-exists.js +17 -0
  40. package/blz-rds/commands/core/embedded-select.js +17 -0
  41. package/blz-rds/commands/core/equals.js +9 -0
  42. package/blz-rds/commands/core/false.js +8 -0
  43. package/blz-rds/commands/core/greater-or-equal.js +9 -0
  44. package/blz-rds/commands/core/greater.js +9 -0
  45. package/blz-rds/commands/core/in.js +9 -0
  46. package/blz-rds/commands/core/integer.js +8 -0
  47. package/blz-rds/commands/core/is-not-null.js +11 -0
  48. package/blz-rds/commands/core/is-null-or-value.js +10 -0
  49. package/blz-rds/commands/core/is-null.js +11 -0
  50. package/blz-rds/commands/core/less-or-equal.js +9 -0
  51. package/blz-rds/commands/core/less-unary.js +12 -0
  52. package/blz-rds/commands/core/less.js +9 -0
  53. package/blz-rds/commands/core/like.js +12 -0
  54. package/blz-rds/commands/core/max.js +10 -0
  55. package/blz-rds/commands/core/min.js +10 -0
  56. package/blz-rds/commands/core/multiply.js +13 -0
  57. package/blz-rds/commands/core/not-equals.js +9 -0
  58. package/blz-rds/commands/core/not-in.js +9 -0
  59. package/blz-rds/commands/core/not.js +13 -0
  60. package/blz-rds/commands/core/null.js +8 -0
  61. package/blz-rds/commands/core/nvl.js +11 -0
  62. package/blz-rds/commands/core/or.js +13 -0
  63. package/blz-rds/commands/core/parameter.js +34 -0
  64. package/blz-rds/commands/core/remainder.js +16 -0
  65. package/blz-rds/commands/core/string.js +8 -0
  66. package/blz-rds/commands/core/subtract.js +13 -0
  67. package/blz-rds/commands/core/sum.js +10 -0
  68. package/blz-rds/commands/core/true.js +8 -0
  69. package/blz-rds/commands/core/tuple.js +13 -0
  70. package/blz-rds/commands/datetimes/add-days.js +11 -0
  71. package/blz-rds/commands/datetimes/add-hours.js +11 -0
  72. package/blz-rds/commands/datetimes/add-milliseconds.js +11 -0
  73. package/blz-rds/commands/datetimes/add-minutes.js +11 -0
  74. package/blz-rds/commands/datetimes/add-months.js +11 -0
  75. package/blz-rds/commands/datetimes/add-seconds.js +11 -0
  76. package/blz-rds/commands/datetimes/add-years.js +11 -0
  77. package/blz-rds/commands/datetimes/date-diff.js +11 -0
  78. package/blz-rds/commands/datetimes/date.js +12 -0
  79. package/blz-rds/commands/datetimes/datetime-diff.js +11 -0
  80. package/blz-rds/commands/datetimes/datetime.js +15 -0
  81. package/blz-rds/commands/datetimes/day.js +10 -0
  82. package/blz-rds/commands/datetimes/hour.js +10 -0
  83. package/blz-rds/commands/datetimes/millisecond.js +10 -0
  84. package/blz-rds/commands/datetimes/minute.js +10 -0
  85. package/blz-rds/commands/datetimes/month-text.js +10 -0
  86. package/blz-rds/commands/datetimes/month.js +10 -0
  87. package/blz-rds/commands/datetimes/now.js +9 -0
  88. package/blz-rds/commands/datetimes/second.js +10 -0
  89. package/blz-rds/commands/datetimes/subtract-days.js +11 -0
  90. package/blz-rds/commands/datetimes/subtract-hours.js +11 -0
  91. package/blz-rds/commands/datetimes/subtract-milliseconds.js +11 -0
  92. package/blz-rds/commands/datetimes/subtract-minutes.js +11 -0
  93. package/blz-rds/commands/datetimes/subtract-seconds.js +11 -0
  94. package/blz-rds/commands/datetimes/time-diff.js +11 -0
  95. package/blz-rds/commands/datetimes/time.js +13 -0
  96. package/blz-rds/commands/datetimes/today.js +9 -0
  97. package/blz-rds/commands/datetimes/week-day-text.js +10 -0
  98. package/blz-rds/commands/datetimes/week-day.js +10 -0
  99. package/blz-rds/commands/datetimes/week.js +10 -0
  100. package/blz-rds/commands/datetimes/year.js +10 -0
  101. package/blz-rds/commands/math/abs.js +10 -0
  102. package/blz-rds/commands/math/acos.js +10 -0
  103. package/blz-rds/commands/math/asin.js +10 -0
  104. package/blz-rds/commands/math/atan.js +10 -0
  105. package/blz-rds/commands/math/atan2.js +11 -0
  106. package/blz-rds/commands/math/ceil.js +10 -0
  107. package/blz-rds/commands/math/cos.js +10 -0
  108. package/blz-rds/commands/math/cosh.js +10 -0
  109. package/blz-rds/commands/math/exp.js +10 -0
  110. package/blz-rds/commands/math/floor.js +10 -0
  111. package/blz-rds/commands/math/log.js +18 -0
  112. package/blz-rds/commands/math/log10.js +10 -0
  113. package/blz-rds/commands/math/pow.js +11 -0
  114. package/blz-rds/commands/math/random.js +9 -0
  115. package/blz-rds/commands/math/round.js +18 -0
  116. package/blz-rds/commands/math/sign.js +10 -0
  117. package/blz-rds/commands/math/sin.js +10 -0
  118. package/blz-rds/commands/math/sinh.js +10 -0
  119. package/blz-rds/commands/math/sqrt.js +10 -0
  120. package/blz-rds/commands/math/tan.js +10 -0
  121. package/blz-rds/commands/math/tanh.js +10 -0
  122. package/blz-rds/commands/math/trunc.js +18 -0
  123. package/blz-rds/commands/strings/concat.js +20 -0
  124. package/blz-rds/commands/strings/contains.js +12 -0
  125. package/blz-rds/commands/strings/ends-with.js +12 -0
  126. package/blz-rds/commands/strings/index-of.js +11 -0
  127. package/blz-rds/commands/strings/is-null-or-empty.js +11 -0
  128. package/blz-rds/commands/strings/is-null-or-white-space.js +11 -0
  129. package/blz-rds/commands/strings/join.js +22 -0
  130. package/blz-rds/commands/strings/last-index-of.js +11 -0
  131. package/blz-rds/commands/strings/length.js +10 -0
  132. package/blz-rds/commands/strings/pad-left.js +20 -0
  133. package/blz-rds/commands/strings/pad-right.js +20 -0
  134. package/blz-rds/commands/strings/replace.js +12 -0
  135. package/blz-rds/commands/strings/starts-with.js +12 -0
  136. package/blz-rds/commands/strings/substring.js +12 -0
  137. package/blz-rds/commands/strings/to-lower.js +10 -0
  138. package/blz-rds/commands/strings/to-upper.js +10 -0
  139. package/blz-rds/commands/strings/trim-end.js +10 -0
  140. package/blz-rds/commands/strings/trim-start.js +10 -0
  141. package/blz-rds/commands/strings/trim.js +10 -0
  142. package/blz-rds/index.js +744 -0
  143. package/blz-rds-mysql/base.js +857 -0
  144. package/blz-rds-mysql/connection-manager.js +129 -0
  145. package/blz-rds-mysql/execute-bulk-insert.js +35 -0
  146. package/blz-rds-mysql/execute-bulk-merge.js +45 -0
  147. package/blz-rds-mysql/execute-non-query.js +34 -0
  148. package/blz-rds-mysql/execute-query.js +50 -0
  149. package/blz-rds-mysql/index.js +41 -0
  150. package/blz-rds-mysql/stored-procedure.js +207 -0
  151. package/blz-rds-mysql/syntaxis.json +114 -0
  152. package/blz-rds-mysqlx/base.js +846 -0
  153. package/blz-rds-mysqlx/connection-manager.js +141 -0
  154. package/blz-rds-mysqlx/execute-bulk-insert.js +35 -0
  155. package/blz-rds-mysqlx/execute-bulk-merge.js +45 -0
  156. package/blz-rds-mysqlx/execute-non-query.js +29 -0
  157. package/blz-rds-mysqlx/execute-query.js +39 -0
  158. package/blz-rds-mysqlx/index.js +41 -0
  159. package/blz-rds-mysqlx/stored-procedure.js +179 -0
  160. package/blz-rds-mysqlx/syntaxis.json +105 -0
  161. package/blz-rds-oracle/index.js +540 -0
  162. package/blz-rds-oracle/syntaxis.json +112 -0
  163. package/blz-rds-postgres/base.js +861 -0
  164. package/blz-rds-postgres/connection-manager.js +225 -0
  165. package/blz-rds-postgres/execute-bulk-insert.js +81 -0
  166. package/blz-rds-postgres/execute-bulk-merge.js +93 -0
  167. package/blz-rds-postgres/execute-non-query.js +23 -0
  168. package/blz-rds-postgres/execute-query.js +37 -0
  169. package/blz-rds-postgres/index.js +41 -0
  170. package/blz-rds-postgres/result-set.js +51 -0
  171. package/blz-rds-postgres/stored-procedure.js +116 -0
  172. package/blz-rds-postgres/syntaxis.json +114 -0
  173. package/blz-redis/index.js +217 -0
  174. package/blz-redis/lib/redisCache.js +265 -0
  175. package/blz-regex/index.js +25 -0
  176. package/blz-security/.eslintrc.js +15 -0
  177. package/blz-security/__test__/AuthorizationKpn.yaml +1043 -0
  178. package/blz-security/__test__/FinancingSetting.yaml +177 -0
  179. package/blz-security/__test__/KpnConfigPortal.yaml +330 -0
  180. package/blz-security/__test__/OrderManagement.yaml +5190 -0
  181. package/blz-security/__test__/Security.yaml +128 -0
  182. package/blz-security/__test__/autorization.test.js +105 -0
  183. package/blz-security/__test__/orderManagement.test.js +26 -0
  184. package/blz-security/__test__/secureUrl.test.js +79 -0
  185. package/blz-security/__test__/solveMergeRule.test.js +109 -0
  186. package/blz-security/__test__/sqlInjectionGuard.test.js +203 -0
  187. package/blz-security/__test__/xssGuard.test.js +204 -0
  188. package/blz-security/authorizationService.js +536 -0
  189. package/blz-security/config/global.js +8 -0
  190. package/blz-security/config/welcome +8 -0
  191. package/blz-security/doc/README.md +75 -0
  192. package/blz-security/filescanner/index.js +46 -0
  193. package/blz-security/helpers/consts.js +229 -0
  194. package/blz-security/helpers/utils.js +267 -0
  195. package/blz-security/implementations/cache.js +90 -0
  196. package/blz-security/implementations/oidc.js +404 -0
  197. package/blz-security/implementations/pkceCacheStore.js +23 -0
  198. package/blz-security/implementations/saml.js +10 -0
  199. package/blz-security/implementations/uma.js +63 -0
  200. package/blz-security/implementations/webAuthn.js +9 -0
  201. package/blz-security/implementations/wstg.js +72 -0
  202. package/blz-security/index.js +77 -0
  203. package/blz-security/lab/index.js +27 -0
  204. package/blz-security/middleware/HapiServerAzureAd.js +641 -0
  205. package/blz-security/middleware/HapiServerKeycloak.js +840 -0
  206. package/blz-security/middleware/HapiServerSimToken.js +247 -0
  207. package/blz-security/middleware/hapi.js +515 -0
  208. package/blz-security/middleware/hapiServer.js +974 -0
  209. package/blz-security/navigationMemoryRepository.js +15 -0
  210. package/blz-security/navigationMongoDbRepository.js +73 -0
  211. package/blz-security/secureUrlService.js +47 -0
  212. package/blz-security/securityService.js +409 -0
  213. package/blz-security/sqlInjectionGuard.js +162 -0
  214. package/blz-security/templates/forbidden.html +0 -0
  215. package/blz-security/templates/session-iframe-azure-ad.html +7 -0
  216. package/blz-security/templates/session-iframe.html +73 -0
  217. package/blz-security/templates/unauthorized.html +1 -0
  218. package/blz-security/xssGuard.js +87 -0
  219. package/blz-strings/index.js +167 -0
  220. package/blz-uuid/index.js +7 -0
  221. package/blz-yaml/index.js +19 -0
  222. package/index.js +84 -0
  223. package/package.json +97 -0
  224. 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
+ }