@3sixty/api-auth-entity-storage-service 0.0.0-stage → 0.20.0-next.0

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 (133) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +24 -2
  3. package/dist/es/entities/authenticationAuditEntry.js +121 -0
  4. package/dist/es/entities/authenticationAuditEntry.js.map +1 -0
  5. package/dist/es/entities/authenticationRateEntry.js +37 -0
  6. package/dist/es/entities/authenticationRateEntry.js.map +1 -0
  7. package/dist/es/entities/authenticationUser.js +69 -0
  8. package/dist/es/entities/authenticationUser.js.map +1 -0
  9. package/dist/es/index.js +29 -0
  10. package/dist/es/index.js.map +1 -0
  11. package/dist/es/models/IAuthHeaderProcessorConfig.js +4 -0
  12. package/dist/es/models/IAuthHeaderProcessorConfig.js.map +1 -0
  13. package/dist/es/models/IAuthHeaderProcessorConstructorOptions.js +2 -0
  14. package/dist/es/models/IAuthHeaderProcessorConstructorOptions.js.map +1 -0
  15. package/dist/es/models/IAuthTokenContext.js +4 -0
  16. package/dist/es/models/IAuthTokenContext.js.map +1 -0
  17. package/dist/es/models/IEntityStorageAuthenticationAdminServiceConfig.js +4 -0
  18. package/dist/es/models/IEntityStorageAuthenticationAdminServiceConfig.js.map +1 -0
  19. package/dist/es/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js +2 -0
  20. package/dist/es/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js.map +1 -0
  21. package/dist/es/models/IEntityStorageAuthenticationAuditServiceConfig.js +4 -0
  22. package/dist/es/models/IEntityStorageAuthenticationAuditServiceConfig.js.map +1 -0
  23. package/dist/es/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.js +2 -0
  24. package/dist/es/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.js.map +1 -0
  25. package/dist/es/models/IEntityStorageAuthenticationRateServiceConfig.js +2 -0
  26. package/dist/es/models/IEntityStorageAuthenticationRateServiceConfig.js.map +1 -0
  27. package/dist/es/models/IEntityStorageAuthenticationRateServiceConstructorOptions.js +2 -0
  28. package/dist/es/models/IEntityStorageAuthenticationRateServiceConstructorOptions.js.map +1 -0
  29. package/dist/es/models/IEntityStorageAuthenticationServiceConfig.js +2 -0
  30. package/dist/es/models/IEntityStorageAuthenticationServiceConfig.js.map +1 -0
  31. package/dist/es/models/IEntityStorageAuthenticationServiceConstructorOptions.js +2 -0
  32. package/dist/es/models/IEntityStorageAuthenticationServiceConstructorOptions.js.map +1 -0
  33. package/dist/es/processors/authHeaderProcessor.js +277 -0
  34. package/dist/es/processors/authHeaderProcessor.js.map +1 -0
  35. package/dist/es/restEntryPoints.js +27 -0
  36. package/dist/es/restEntryPoints.js.map +1 -0
  37. package/dist/es/routes/entityStorageAuthenticationAdminRoutes.js +367 -0
  38. package/dist/es/routes/entityStorageAuthenticationAdminRoutes.js.map +1 -0
  39. package/dist/es/routes/entityStorageAuthenticationAuditRoutes.js +343 -0
  40. package/dist/es/routes/entityStorageAuthenticationAuditRoutes.js.map +1 -0
  41. package/dist/es/routes/entityStorageAuthenticationRoutes.js +247 -0
  42. package/dist/es/routes/entityStorageAuthenticationRoutes.js.map +1 -0
  43. package/dist/es/schema.js +15 -0
  44. package/dist/es/schema.js.map +1 -0
  45. package/dist/es/services/entityStorageAuthenticationAdminService.js +323 -0
  46. package/dist/es/services/entityStorageAuthenticationAdminService.js.map +1 -0
  47. package/dist/es/services/entityStorageAuthenticationAuditService.js +240 -0
  48. package/dist/es/services/entityStorageAuthenticationAuditService.js.map +1 -0
  49. package/dist/es/services/entityStorageAuthenticationRateService.js +213 -0
  50. package/dist/es/services/entityStorageAuthenticationRateService.js.map +1 -0
  51. package/dist/es/services/entityStorageAuthenticationService.js +326 -0
  52. package/dist/es/services/entityStorageAuthenticationService.js.map +1 -0
  53. package/dist/es/utils/passwordHelper.js +58 -0
  54. package/dist/es/utils/passwordHelper.js.map +1 -0
  55. package/dist/es/utils/tokenHelper.js +142 -0
  56. package/dist/es/utils/tokenHelper.js.map +1 -0
  57. package/dist/types/entities/authenticationAuditEntry.d.ts +49 -0
  58. package/dist/types/entities/authenticationRateEntry.d.ts +17 -0
  59. package/dist/types/entities/authenticationUser.d.ts +33 -0
  60. package/dist/types/index.d.ts +26 -0
  61. package/dist/types/models/IAuthHeaderProcessorConfig.d.ts +39 -0
  62. package/dist/types/models/IAuthHeaderProcessorConstructorOptions.d.ts +25 -0
  63. package/dist/types/models/IAuthTokenContext.d.ts +34 -0
  64. package/dist/types/models/IEntityStorageAuthenticationAdminServiceConfig.d.ts +14 -0
  65. package/dist/types/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.d.ts +20 -0
  66. package/dist/types/models/IEntityStorageAuthenticationAuditServiceConfig.d.ts +9 -0
  67. package/dist/types/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.d.ts +15 -0
  68. package/dist/types/models/IEntityStorageAuthenticationRateServiceConfig.d.ts +10 -0
  69. package/dist/types/models/IEntityStorageAuthenticationRateServiceConstructorOptions.d.ts +25 -0
  70. package/dist/types/models/IEntityStorageAuthenticationServiceConfig.d.ts +36 -0
  71. package/dist/types/models/IEntityStorageAuthenticationServiceConstructorOptions.d.ts +35 -0
  72. package/dist/types/processors/authHeaderProcessor.d.ts +57 -0
  73. package/dist/types/restEntryPoints.d.ts +5 -0
  74. package/dist/types/routes/entityStorageAuthenticationAdminRoutes.d.ts +62 -0
  75. package/dist/types/routes/entityStorageAuthenticationAuditRoutes.d.ts +54 -0
  76. package/dist/types/routes/entityStorageAuthenticationRoutes.d.ts +45 -0
  77. package/dist/types/schema.d.ts +4 -0
  78. package/dist/types/services/entityStorageAuthenticationAdminService.d.ts +65 -0
  79. package/dist/types/services/entityStorageAuthenticationAuditService.d.ts +72 -0
  80. package/dist/types/services/entityStorageAuthenticationRateService.d.ts +60 -0
  81. package/dist/types/services/entityStorageAuthenticationService.d.ts +65 -0
  82. package/dist/types/utils/passwordHelper.d.ts +24 -0
  83. package/dist/types/utils/tokenHelper.d.ts +60 -0
  84. package/docs/changelog.md +0 -0
  85. package/docs/examples.md +178 -0
  86. package/docs/open-api/spec.json +2122 -0
  87. package/docs/reference/classes/AuthHeaderProcessor.md +193 -0
  88. package/docs/reference/classes/AuthenticationAuditEntry.md +101 -0
  89. package/docs/reference/classes/AuthenticationRateEntry.md +37 -0
  90. package/docs/reference/classes/AuthenticationUser.md +69 -0
  91. package/docs/reference/classes/EntityStorageAuthenticationAdminService.md +229 -0
  92. package/docs/reference/classes/EntityStorageAuthenticationAuditService.md +241 -0
  93. package/docs/reference/classes/EntityStorageAuthenticationRateService.md +227 -0
  94. package/docs/reference/classes/EntityStorageAuthenticationService.md +221 -0
  95. package/docs/reference/classes/PasswordHelper.md +74 -0
  96. package/docs/reference/classes/TokenHelper.md +205 -0
  97. package/docs/reference/functions/authenticationAdminCreateUser.md +37 -0
  98. package/docs/reference/functions/authenticationAdminGetUser.md +31 -0
  99. package/docs/reference/functions/authenticationAdminGetUserByIdentity.md +31 -0
  100. package/docs/reference/functions/authenticationAdminRemoveUser.md +31 -0
  101. package/docs/reference/functions/authenticationAdminUpdateUser.md +31 -0
  102. package/docs/reference/functions/authenticationAdminUpdateUserPassword.md +31 -0
  103. package/docs/reference/functions/authenticationAuditCreate.md +37 -0
  104. package/docs/reference/functions/authenticationAuditGet.md +31 -0
  105. package/docs/reference/functions/authenticationAuditQuery.md +31 -0
  106. package/docs/reference/functions/authenticationAuditRemove.md +31 -0
  107. package/docs/reference/functions/authenticationAuditUpdate.md +31 -0
  108. package/docs/reference/functions/authenticationLogin.md +31 -0
  109. package/docs/reference/functions/authenticationLogout.md +31 -0
  110. package/docs/reference/functions/authenticationRefreshToken.md +31 -0
  111. package/docs/reference/functions/authenticationUpdatePassword.md +31 -0
  112. package/docs/reference/functions/generateRestRoutesAuthentication.md +25 -0
  113. package/docs/reference/functions/generateRestRoutesAuthenticationAdmin.md +25 -0
  114. package/docs/reference/functions/generateRestRoutesAuthenticationAudit.md +25 -0
  115. package/docs/reference/functions/initSchema.md +9 -0
  116. package/docs/reference/index.md +57 -0
  117. package/docs/reference/interfaces/IAuthHeaderProcessorConfig.md +86 -0
  118. package/docs/reference/interfaces/IAuthHeaderProcessorConstructorOptions.md +53 -0
  119. package/docs/reference/interfaces/IAuthTokenContext.md +60 -0
  120. package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConfig.md +25 -0
  121. package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md +39 -0
  122. package/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConfig.md +11 -0
  123. package/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConstructorOptions.md +25 -0
  124. package/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConfig.md +17 -0
  125. package/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConstructorOptions.md +53 -0
  126. package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConfig.md +87 -0
  127. package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConstructorOptions.md +81 -0
  128. package/docs/reference/variables/restEntryPoints.md +5 -0
  129. package/docs/reference/variables/tagsAuthentication.md +5 -0
  130. package/docs/reference/variables/tagsAuthenticationAdmin.md +5 -0
  131. package/docs/reference/variables/tagsAuthenticationAudit.md +5 -0
  132. package/locales/en.json +52 -0
  133. package/package.json +64 -5
@@ -0,0 +1,277 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { HttpContextIdKeys, HttpErrorHelper } from "@3sixty/api-models";
4
+ import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@3sixty/context";
5
+ import { BaseError, Coerce, ComponentFactory, Converter, GeneralError, Is, LfuCache } from "@3sixty/core";
6
+ import { Blake2b } from "@3sixty/crypto";
7
+ import { EntityStorageConnectorFactory } from "@3sixty/entity-storage-models";
8
+ import { VaultConnectorFactory } from "@3sixty/vault-models";
9
+ import { CookieHelper, HeaderTypes, HttpStatusCode } from "@3sixty/web";
10
+ import { TokenHelper } from "../utils/tokenHelper.js";
11
+ /**
12
+ * Handle a JWT token in the authorization header or cookies and validate it to populate request context identity.
13
+ */
14
+ export class AuthHeaderProcessor {
15
+ /**
16
+ * The default name for the access token as a cookie.
17
+ * @internal
18
+ */
19
+ static DEFAULT_COOKIE_NAME = "access_token";
20
+ /**
21
+ * Runtime name for the class.
22
+ */
23
+ static CLASS_NAME = "AuthHeaderProcessor";
24
+ /**
25
+ * The default time to keep a verified token in the cache.
26
+ * @internal
27
+ */
28
+ static _DEFAULT_TOKEN_CACHE_TTL_MS = 30000;
29
+ /**
30
+ * The vault for the keys.
31
+ * @internal
32
+ */
33
+ _vaultConnector;
34
+ /**
35
+ * The component to retrieve tenant information.
36
+ * @internal
37
+ */
38
+ _tenantAdminComponent;
39
+ /**
40
+ * The entity storage for users.
41
+ * @internal
42
+ */
43
+ _userEntityStorage;
44
+ /**
45
+ * The name of the key to retrieve from the vault for signing JWT.
46
+ * @internal
47
+ */
48
+ _signingKeyName;
49
+ /**
50
+ * The name of the cookie to use for the token.
51
+ * @internal
52
+ */
53
+ _cookieName;
54
+ /**
55
+ * Include the stack with errors.
56
+ * @internal
57
+ */
58
+ _includeErrorStack;
59
+ /**
60
+ * The cache of contexts resolved from verified tokens, undefined when caching is disabled.
61
+ * @internal
62
+ */
63
+ _tokenCache;
64
+ /**
65
+ * How long a cached token context may live, counted from when it was resolved.
66
+ * @internal
67
+ */
68
+ _tokenCacheTtlMs;
69
+ /**
70
+ * The node identity.
71
+ * @internal
72
+ */
73
+ _nodeId;
74
+ /**
75
+ * The organization ID for the single-tenant node, cached at startup.
76
+ * @internal
77
+ */
78
+ _nodeOrganizationId;
79
+ /**
80
+ * Create a new instance of AuthHeaderProcessor.
81
+ * @param options Options for the processor.
82
+ */
83
+ constructor(options) {
84
+ this._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
85
+ this._userEntityStorage = EntityStorageConnectorFactory.get(options?.userEntityStorageType ?? "authentication-user");
86
+ this._tenantAdminComponent = ComponentFactory.getIfExists(options?.tenantAdminComponentType ?? "tenant-admin");
87
+ this._signingKeyName = options?.config?.signingKeyName ?? "auth-signing";
88
+ this._cookieName = options?.config?.cookieName ?? AuthHeaderProcessor.DEFAULT_COOKIE_NAME;
89
+ this._includeErrorStack = options?.config?.includeErrorStack ?? false;
90
+ this._tokenCacheTtlMs =
91
+ options?.config?.tokenCacheTtlMs ?? AuthHeaderProcessor._DEFAULT_TOKEN_CACHE_TTL_MS;
92
+ if (this._tokenCacheTtlMs > 0) {
93
+ this._tokenCache = new LfuCache({
94
+ capacity: options?.config?.tokenCacheCapacity,
95
+ ttiMs: this._tokenCacheTtlMs,
96
+ mutexTimeoutMs: options?.config?.tokenCacheMutexTimeoutMs
97
+ });
98
+ }
99
+ }
100
+ /**
101
+ * Returns the class name of the component.
102
+ * @returns The class name of the component.
103
+ */
104
+ className() {
105
+ return AuthHeaderProcessor.CLASS_NAME;
106
+ }
107
+ /**
108
+ * The service needs to be started when the application is initialized.
109
+ * @param nodeLoggingComponentType The node logging component type.
110
+ * @returns A promise that resolves when the node identity and organization ID have been cached.
111
+ */
112
+ async start(nodeLoggingComponentType) {
113
+ const contextIds = await ContextIdStore.getContextIds();
114
+ ContextIdHelper.guard(contextIds, ContextIdKeys.Node);
115
+ this._nodeId = contextIds[ContextIdKeys.Node];
116
+ this._nodeOrganizationId = contextIds[ContextIdKeys.Organization];
117
+ }
118
+ /**
119
+ * Stop the processor and release the token cache.
120
+ * @returns A promise that resolves when the processor has stopped.
121
+ */
122
+ async stop() {
123
+ this._tokenCache?.destroy();
124
+ }
125
+ /**
126
+ * Pre process the REST request for the specified route.
127
+ * @param request The incoming request.
128
+ * @param response The outgoing response.
129
+ * @param route The route to process.
130
+ * @param contextIds The context IDs of the request.
131
+ * @param processorState The state handed through the processors.
132
+ * @returns A promise that resolves when the JWT has been verified and the context populated, or an error response set.
133
+ */
134
+ async pre(request, response, route, contextIds, processorState) {
135
+ if (!Is.empty(route) && !(route.skipAuth ?? false)) {
136
+ try {
137
+ if (!Is.stringValue(this._nodeId)) {
138
+ throw new GeneralError(AuthHeaderProcessor.CLASS_NAME, "nodeIdNotSet");
139
+ }
140
+ const tokenAndLocation = TokenHelper.extractTokenFromHeaders(request.headers, this._cookieName);
141
+ const tokenContext = await this.resolveTokenContext(this._nodeId, tokenAndLocation?.token, contextIds);
142
+ // The scopes a route demands vary from route to route, so this is checked for every
143
+ // request and never forms part of the cached context.
144
+ TokenHelper.verifyScopes(tokenContext.scope, route.requiredScope);
145
+ contextIds[ContextIdKeys.Tenant] = tokenContext.tenantId;
146
+ // In a multi-tenant environment the tenant organization ID is authoritative,
147
+ // in a single-tenant environment we fall back to the node organization ID.
148
+ contextIds[ContextIdKeys.Organization] =
149
+ tokenContext.tenantOrganizationId ?? this._nodeOrganizationId;
150
+ contextIds[ContextIdKeys.User] = tokenContext.userIdentity;
151
+ contextIds[ContextIdKeys.UserOrganization] = tokenContext.userOrganization;
152
+ contextIds[HttpContextIdKeys.Scope] = tokenContext.scope;
153
+ // If the tenant has a custom public origin, we set it in the context for downstream processors to use.
154
+ if (Is.stringValue(tokenContext.tenantPublicOrigin)) {
155
+ contextIds[HttpContextIdKeys.PublicOrigin] = tokenContext.tenantPublicOrigin;
156
+ }
157
+ processorState.authToken = tokenAndLocation?.token;
158
+ processorState.authTokenLocation = tokenAndLocation?.location;
159
+ }
160
+ catch (err) {
161
+ const error = BaseError.fromError(err);
162
+ HttpErrorHelper.buildResponse(response, error, HttpStatusCode.unauthorized, this._includeErrorStack);
163
+ }
164
+ }
165
+ }
166
+ /**
167
+ * Post process the REST request for the specified route.
168
+ * @param request The incoming request.
169
+ * @param response The outgoing response.
170
+ * @param route The route to process.
171
+ * @param contextIds The context IDs of the request.
172
+ * @param processorState The state handed through the processors.
173
+ * @returns A promise that resolves when the Set-Cookie header has been applied to the response if required.
174
+ */
175
+ async post(request, response, route, contextIds, processorState) {
176
+ const responseAuthOperation = processorState?.authOperation;
177
+ const responseAuthToken = processorState?.authToken;
178
+ // We don't populate the cookie if the incoming request was from an authorization header.
179
+ if (!Is.empty(route) &&
180
+ Is.stringValue(responseAuthOperation) &&
181
+ processorState.authTokenLocation !== "authorization") {
182
+ if ((responseAuthOperation === "login" || responseAuthOperation === "refresh") &&
183
+ Is.stringValue(responseAuthToken)) {
184
+ response.headers ??= {};
185
+ response.headers[HeaderTypes.SetCookie] = CookieHelper.createCookie(this._cookieName, responseAuthToken, {
186
+ secure: true,
187
+ httpOnly: true,
188
+ sameSite: "None",
189
+ path: "/"
190
+ });
191
+ }
192
+ else if (responseAuthOperation === "logout") {
193
+ response.headers ??= {};
194
+ response.headers[HeaderTypes.SetCookie] = CookieHelper.deleteCookie(this._cookieName, {
195
+ secure: true,
196
+ httpOnly: true,
197
+ sameSite: "None",
198
+ path: "/"
199
+ });
200
+ }
201
+ }
202
+ }
203
+ /**
204
+ * Resolve the route independent context for a token, through the cache when one is enabled.
205
+ * @param nodeId The node identifier the token must have been issued by.
206
+ * @param token The token from the request, if there was one.
207
+ * @param contextIds The context IDs of the request.
208
+ * @returns The resolved context.
209
+ * @internal
210
+ */
211
+ async resolveTokenContext(nodeId, token, contextIds) {
212
+ if (Is.empty(this._tokenCache) || !Is.stringValue(token)) {
213
+ return this.verifyToken(nodeId, token, contextIds);
214
+ }
215
+ // Use hash of token so we don't store the raw token in memory.
216
+ const cacheKey = Converter.bytesToHex(Blake2b.sum256(Converter.utf8ToBytes(token)));
217
+ const tokenContext = await this._tokenCache.getOrSet(cacheKey, async () => this.verifyToken(nodeId, token, contextIds), Date.now() + this._tokenCacheTtlMs);
218
+ // A token can expire while its context sits in the cache.
219
+ if (Is.integer(tokenContext.expires) && tokenContext.expires <= Date.now()) {
220
+ this._tokenCache.delete(cacheKey);
221
+ return this.verifyToken(nodeId, token, contextIds);
222
+ }
223
+ return tokenContext;
224
+ }
225
+ /**
226
+ * Verify a token against the vault and resolve the tenant and user it refers to.
227
+ * @param nodeId The node identifier the token must have been issued by.
228
+ * @param token The token from the request, if there was one.
229
+ * @param contextIds The context IDs of the request.
230
+ * @returns The resolved context.
231
+ * @internal
232
+ */
233
+ async verifyToken(nodeId, token, contextIds) {
234
+ let user;
235
+ let tenantId;
236
+ let tenantOrganizationId;
237
+ let tenantPublicOrigin;
238
+ const { payload } = await TokenHelper.verify(this._vaultConnector, nodeId, this._signingKeyName, token, undefined, async (sub, org, tid, passwordVersion) => {
239
+ const validParts = [];
240
+ tenantId = tid;
241
+ if (Is.stringValue(tenantId)) {
242
+ const tenant = await this._tenantAdminComponent?.get(tenantId);
243
+ if (tenant?.id === tenantId) {
244
+ validParts.push("tenant");
245
+ tenantOrganizationId = tenant.organizationId;
246
+ tenantPublicOrigin = tenant.publicOrigin;
247
+ }
248
+ }
249
+ // We use the tenant id from the token, if the user is not in that
250
+ // partition then the get will fail
251
+ const contextIdsForUserLookup = {
252
+ ...contextIds,
253
+ [ContextIdKeys.Tenant]: tid
254
+ };
255
+ // Wrap the user lookup in the request context so partitioned storage uses the correct tenant.
256
+ user = await ContextIdStore.run(contextIdsForUserLookup, async () => this._userEntityStorage.get(sub, "identity"));
257
+ if (user?.identity === sub && (passwordVersion ?? 0) === (user.passwordVersion ?? 0)) {
258
+ validParts.push("user");
259
+ }
260
+ if (user?.organization === org) {
261
+ validParts.push("organization");
262
+ }
263
+ return validParts;
264
+ });
265
+ const expiresSeconds = Coerce.integer(payload.exp);
266
+ return {
267
+ tenantId,
268
+ tenantOrganizationId,
269
+ tenantPublicOrigin,
270
+ userIdentity: user?.identity,
271
+ userOrganization: user?.organization,
272
+ scope: Coerce.string(payload.scope),
273
+ expires: Is.integer(expiresSeconds) ? expiresSeconds * 1000 : undefined
274
+ };
275
+ }
276
+ }
277
+ //# sourceMappingURL=authHeaderProcessor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authHeaderProcessor.js","sourceRoot":"","sources":["../../../src/processors/authHeaderProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHttpContextIdKeys,\n\tHttpErrorHelper,\n\ttype IBaseRoute,\n\ttype IBaseRouteProcessor,\n\ttype IHttpResponse,\n\ttype IHttpServerRequest,\n\ttype ITenantAdminComponent\n} from \"@3sixty/api-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore, type IContextIds } from \"@3sixty/context\";\nimport {\n\tBaseError,\n\tCoerce,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tIs,\n\tLfuCache\n} from \"@3sixty/core\";\nimport { Blake2b } from \"@3sixty/crypto\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@3sixty/entity-storage-models\";\nimport { nameof } from \"@3sixty/nameof\";\nimport { VaultConnectorFactory, type IVaultConnector } from \"@3sixty/vault-models\";\nimport { CookieHelper, HeaderTypes, HttpStatusCode } from \"@3sixty/web\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\nimport type { IAuthHeaderProcessorConstructorOptions } from \"../models/IAuthHeaderProcessorConstructorOptions.js\";\nimport type { IAuthTokenContext } from \"../models/IAuthTokenContext.js\";\nimport { TokenHelper } from \"../utils/tokenHelper.js\";\n\n/**\n * Handle a JWT token in the authorization header or cookies and validate it to populate request context identity.\n */\nexport class AuthHeaderProcessor implements IBaseRouteProcessor {\n\t/**\n\t * The default name for the access token as a cookie.\n\t * @internal\n\t */\n\tpublic static readonly DEFAULT_COOKIE_NAME: string = \"access_token\";\n\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<AuthHeaderProcessor>();\n\n\t/**\n\t * The default time to keep a verified token in the cache.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TOKEN_CACHE_TTL_MS: number = 30000;\n\n\t/**\n\t * The vault for the keys.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\n\n\t/**\n\t * The component to retrieve tenant information.\n\t * @internal\n\t */\n\tprivate readonly _tenantAdminComponent?: ITenantAdminComponent;\n\n\t/**\n\t * The entity storage for users.\n\t * @internal\n\t */\n\tprivate readonly _userEntityStorage: IEntityStorageConnector<AuthenticationUser>;\n\n\t/**\n\t * The name of the key to retrieve from the vault for signing JWT.\n\t * @internal\n\t */\n\tprivate readonly _signingKeyName: string;\n\n\t/**\n\t * The name of the cookie to use for the token.\n\t * @internal\n\t */\n\tprivate readonly _cookieName: string;\n\n\t/**\n\t * Include the stack with errors.\n\t * @internal\n\t */\n\tprivate readonly _includeErrorStack: boolean;\n\n\t/**\n\t * The cache of contexts resolved from verified tokens, undefined when caching is disabled.\n\t * @internal\n\t */\n\tprivate readonly _tokenCache?: LfuCache<IAuthTokenContext>;\n\n\t/**\n\t * How long a cached token context may live, counted from when it was resolved.\n\t * @internal\n\t */\n\tprivate readonly _tokenCacheTtlMs: number;\n\n\t/**\n\t * The node identity.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * The organization ID for the single-tenant node, cached at startup.\n\t * @internal\n\t */\n\tprivate _nodeOrganizationId?: string;\n\n\t/**\n\t * Create a new instance of AuthHeaderProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: IAuthHeaderProcessorConstructorOptions) {\n\t\tthis._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? \"vault\");\n\t\tthis._userEntityStorage = EntityStorageConnectorFactory.get(\n\t\t\toptions?.userEntityStorageType ?? \"authentication-user\"\n\t\t);\n\t\tthis._tenantAdminComponent = ComponentFactory.getIfExists<ITenantAdminComponent>(\n\t\t\toptions?.tenantAdminComponentType ?? \"tenant-admin\"\n\t\t);\n\n\t\tthis._signingKeyName = options?.config?.signingKeyName ?? \"auth-signing\";\n\t\tthis._cookieName = options?.config?.cookieName ?? AuthHeaderProcessor.DEFAULT_COOKIE_NAME;\n\t\tthis._includeErrorStack = options?.config?.includeErrorStack ?? false;\n\n\t\tthis._tokenCacheTtlMs =\n\t\t\toptions?.config?.tokenCacheTtlMs ?? AuthHeaderProcessor._DEFAULT_TOKEN_CACHE_TTL_MS;\n\t\tif (this._tokenCacheTtlMs > 0) {\n\t\t\tthis._tokenCache = new LfuCache<IAuthTokenContext>({\n\t\t\t\tcapacity: options?.config?.tokenCacheCapacity,\n\t\t\t\tttiMs: this._tokenCacheTtlMs,\n\t\t\t\tmutexTimeoutMs: options?.config?.tokenCacheMutexTimeoutMs\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn AuthHeaderProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * The service needs to be started when the application is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when the node identity and organization ID have been cached.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.Node);\n\t\tthis._nodeId = contextIds[ContextIdKeys.Node];\n\t\tthis._nodeOrganizationId = contextIds[ContextIdKeys.Organization];\n\t}\n\n\t/**\n\t * Stop the processor and release the token cache.\n\t * @returns A promise that resolves when the processor has stopped.\n\t */\n\tpublic async stop(): Promise<void> {\n\t\tthis._tokenCache?.destroy();\n\t}\n\n\t/**\n\t * Pre process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t * @returns A promise that resolves when the JWT has been verified and the context populated, or an error response set.\n\t */\n\tpublic async pre(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tif (!Is.empty(route) && !(route.skipAuth ?? false)) {\n\t\t\ttry {\n\t\t\t\tif (!Is.stringValue(this._nodeId)) {\n\t\t\t\t\tthrow new GeneralError(AuthHeaderProcessor.CLASS_NAME, \"nodeIdNotSet\");\n\t\t\t\t}\n\n\t\t\t\tconst tokenAndLocation = TokenHelper.extractTokenFromHeaders(\n\t\t\t\t\trequest.headers,\n\t\t\t\t\tthis._cookieName\n\t\t\t\t);\n\n\t\t\t\tconst tokenContext = await this.resolveTokenContext(\n\t\t\t\t\tthis._nodeId,\n\t\t\t\t\ttokenAndLocation?.token,\n\t\t\t\t\tcontextIds\n\t\t\t\t);\n\n\t\t\t\t// The scopes a route demands vary from route to route, so this is checked for every\n\t\t\t\t// request and never forms part of the cached context.\n\t\t\t\tTokenHelper.verifyScopes(tokenContext.scope, route.requiredScope);\n\n\t\t\t\tcontextIds[ContextIdKeys.Tenant] = tokenContext.tenantId;\n\t\t\t\t// In a multi-tenant environment the tenant organization ID is authoritative,\n\t\t\t\t// in a single-tenant environment we fall back to the node organization ID.\n\t\t\t\tcontextIds[ContextIdKeys.Organization] =\n\t\t\t\t\ttokenContext.tenantOrganizationId ?? this._nodeOrganizationId;\n\t\t\t\tcontextIds[ContextIdKeys.User] = tokenContext.userIdentity;\n\t\t\t\tcontextIds[ContextIdKeys.UserOrganization] = tokenContext.userOrganization;\n\t\t\t\tcontextIds[HttpContextIdKeys.Scope] = tokenContext.scope;\n\n\t\t\t\t// If the tenant has a custom public origin, we set it in the context for downstream processors to use.\n\t\t\t\tif (Is.stringValue(tokenContext.tenantPublicOrigin)) {\n\t\t\t\t\tcontextIds[HttpContextIdKeys.PublicOrigin] = tokenContext.tenantPublicOrigin;\n\t\t\t\t}\n\n\t\t\t\tprocessorState.authToken = tokenAndLocation?.token;\n\t\t\t\tprocessorState.authTokenLocation = tokenAndLocation?.location;\n\t\t\t} catch (err) {\n\t\t\t\tconst error = BaseError.fromError(err);\n\t\t\t\tHttpErrorHelper.buildResponse(\n\t\t\t\t\tresponse,\n\t\t\t\t\terror,\n\t\t\t\t\tHttpStatusCode.unauthorized,\n\t\t\t\t\tthis._includeErrorStack\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Post process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t * @returns A promise that resolves when the Set-Cookie header has been applied to the response if required.\n\t */\n\tpublic async post(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tconst responseAuthOperation = processorState?.authOperation;\n\t\tconst responseAuthToken = processorState?.authToken;\n\n\t\t// We don't populate the cookie if the incoming request was from an authorization header.\n\t\tif (\n\t\t\t!Is.empty(route) &&\n\t\t\tIs.stringValue(responseAuthOperation) &&\n\t\t\tprocessorState.authTokenLocation !== \"authorization\"\n\t\t) {\n\t\t\tif (\n\t\t\t\t(responseAuthOperation === \"login\" || responseAuthOperation === \"refresh\") &&\n\t\t\t\tIs.stringValue(responseAuthToken)\n\t\t\t) {\n\t\t\t\tresponse.headers ??= {};\n\t\t\t\tresponse.headers[HeaderTypes.SetCookie] = CookieHelper.createCookie(\n\t\t\t\t\tthis._cookieName,\n\t\t\t\t\tresponseAuthToken,\n\t\t\t\t\t{\n\t\t\t\t\t\tsecure: true,\n\t\t\t\t\t\thttpOnly: true,\n\t\t\t\t\t\tsameSite: \"None\",\n\t\t\t\t\t\tpath: \"/\"\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t} else if (responseAuthOperation === \"logout\") {\n\t\t\t\tresponse.headers ??= {};\n\t\t\t\tresponse.headers[HeaderTypes.SetCookie] = CookieHelper.deleteCookie(this._cookieName, {\n\t\t\t\t\tsecure: true,\n\t\t\t\t\thttpOnly: true,\n\t\t\t\t\tsameSite: \"None\",\n\t\t\t\t\tpath: \"/\"\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Resolve the route independent context for a token, through the cache when one is enabled.\n\t * @param nodeId The node identifier the token must have been issued by.\n\t * @param token The token from the request, if there was one.\n\t * @param contextIds The context IDs of the request.\n\t * @returns The resolved context.\n\t * @internal\n\t */\n\tprivate async resolveTokenContext(\n\t\tnodeId: string,\n\t\ttoken: string | undefined,\n\t\tcontextIds: IContextIds\n\t): Promise<IAuthTokenContext> {\n\t\tif (Is.empty(this._tokenCache) || !Is.stringValue(token)) {\n\t\t\treturn this.verifyToken(nodeId, token, contextIds);\n\t\t}\n\n\t\t// Use hash of token so we don't store the raw token in memory.\n\t\tconst cacheKey = Converter.bytesToHex(Blake2b.sum256(Converter.utf8ToBytes(token)));\n\n\t\tconst tokenContext = await this._tokenCache.getOrSet(\n\t\t\tcacheKey,\n\t\t\tasync () => this.verifyToken(nodeId, token, contextIds),\n\t\t\tDate.now() + this._tokenCacheTtlMs\n\t\t);\n\n\t\t// A token can expire while its context sits in the cache.\n\t\tif (Is.integer(tokenContext.expires) && tokenContext.expires <= Date.now()) {\n\t\t\tthis._tokenCache.delete(cacheKey);\n\t\t\treturn this.verifyToken(nodeId, token, contextIds);\n\t\t}\n\n\t\treturn tokenContext;\n\t}\n\n\t/**\n\t * Verify a token against the vault and resolve the tenant and user it refers to.\n\t * @param nodeId The node identifier the token must have been issued by.\n\t * @param token The token from the request, if there was one.\n\t * @param contextIds The context IDs of the request.\n\t * @returns The resolved context.\n\t * @internal\n\t */\n\tprivate async verifyToken(\n\t\tnodeId: string,\n\t\ttoken: string | undefined,\n\t\tcontextIds: IContextIds\n\t): Promise<IAuthTokenContext> {\n\t\tlet user: AuthenticationUser | undefined;\n\t\tlet tenantId: string | undefined;\n\t\tlet tenantOrganizationId: string | undefined;\n\t\tlet tenantPublicOrigin: string | undefined;\n\n\t\tconst { payload } = await TokenHelper.verify(\n\t\t\tthis._vaultConnector,\n\t\t\tnodeId,\n\t\t\tthis._signingKeyName,\n\t\t\ttoken,\n\t\t\tundefined,\n\t\t\tasync (\n\t\t\t\tsub: string,\n\t\t\t\torg: string,\n\t\t\t\ttid: string | undefined,\n\t\t\t\tpasswordVersion: number | undefined\n\t\t\t) => {\n\t\t\t\tconst validParts = [];\n\n\t\t\t\ttenantId = tid;\n\n\t\t\t\tif (Is.stringValue(tenantId)) {\n\t\t\t\t\tconst tenant = await this._tenantAdminComponent?.get(tenantId);\n\t\t\t\t\tif (tenant?.id === tenantId) {\n\t\t\t\t\t\tvalidParts.push(\"tenant\");\n\t\t\t\t\t\ttenantOrganizationId = tenant.organizationId;\n\t\t\t\t\t\ttenantPublicOrigin = tenant.publicOrigin;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// We use the tenant id from the token, if the user is not in that\n\t\t\t\t// partition then the get will fail\n\t\t\t\tconst contextIdsForUserLookup = {\n\t\t\t\t\t...contextIds,\n\t\t\t\t\t[ContextIdKeys.Tenant]: tid\n\t\t\t\t};\n\n\t\t\t\t// Wrap the user lookup in the request context so partitioned storage uses the correct tenant.\n\t\t\t\tuser = await ContextIdStore.run(contextIdsForUserLookup, async () =>\n\t\t\t\t\tthis._userEntityStorage.get(sub, \"identity\")\n\t\t\t\t);\n\n\t\t\t\tif (user?.identity === sub && (passwordVersion ?? 0) === (user.passwordVersion ?? 0)) {\n\t\t\t\t\tvalidParts.push(\"user\");\n\t\t\t\t}\n\t\t\t\tif (user?.organization === org) {\n\t\t\t\t\tvalidParts.push(\"organization\");\n\t\t\t\t}\n\n\t\t\t\treturn validParts;\n\t\t\t}\n\t\t);\n\n\t\tconst expiresSeconds = Coerce.integer(payload.exp);\n\n\t\treturn {\n\t\t\ttenantId,\n\t\t\ttenantOrganizationId,\n\t\t\ttenantPublicOrigin,\n\t\t\tuserIdentity: user?.identity,\n\t\t\tuserOrganization: user?.organization,\n\t\t\tscope: Coerce.string(payload.scope),\n\t\t\texpires: Is.integer(expiresSeconds) ? expiresSeconds * 1000 : undefined\n\t\t};\n\t}\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import { generateRestRoutesAuthenticationAdmin, tagsAuthenticationAdmin } from "./routes/entityStorageAuthenticationAdminRoutes.js";
2
+ import { generateRestRoutesAuthenticationAudit, tagsAuthenticationAudit } from "./routes/entityStorageAuthenticationAuditRoutes.js";
3
+ import { generateRestRoutesAuthentication, tagsAuthentication } from "./routes/entityStorageAuthenticationRoutes.js";
4
+ /**
5
+ * REST entry points for the authentication, authentication admin, and authentication audit services.
6
+ */
7
+ export const restEntryPoints = [
8
+ {
9
+ name: "authentication",
10
+ defaultBaseRoute: "authentication",
11
+ tags: tagsAuthentication,
12
+ generateRoutes: generateRestRoutesAuthentication
13
+ },
14
+ {
15
+ name: "authenticationAdmin",
16
+ defaultBaseRoute: "authentication/admin",
17
+ tags: tagsAuthenticationAdmin,
18
+ generateRoutes: generateRestRoutesAuthenticationAdmin
19
+ },
20
+ {
21
+ name: "authenticationAudit",
22
+ defaultBaseRoute: "authentication/audit",
23
+ tags: tagsAuthenticationAudit,
24
+ generateRoutes: generateRestRoutesAuthenticationAudit
25
+ }
26
+ ];
27
+ //# sourceMappingURL=restEntryPoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restEntryPoints.js","sourceRoot":"","sources":["../../src/restEntryPoints.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,qCAAqC,EACrC,uBAAuB,EACvB,MAAM,oDAAoD,CAAC;AAC5D,OAAO,EACN,qCAAqC,EACrC,uBAAuB,EACvB,MAAM,oDAAoD,CAAC;AAC5D,OAAO,EACN,gCAAgC,EAChC,kBAAkB,EAClB,MAAM,+CAA+C,CAAC;AAEvD;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAA2B;IACtD;QACC,IAAI,EAAE,gBAAgB;QACtB,gBAAgB,EAAE,gBAAgB;QAClC,IAAI,EAAE,kBAAkB;QACxB,cAAc,EAAE,gCAAgC;KAChD;IACD;QACC,IAAI,EAAE,qBAAqB;QAC3B,gBAAgB,EAAE,sBAAsB;QACxC,IAAI,EAAE,uBAAuB;QAC7B,cAAc,EAAE,qCAAqC;KACrD;IACD;QACC,IAAI,EAAE,qBAAqB;QAC3B,gBAAgB,EAAE,sBAAsB;QACxC,IAAI,EAAE,uBAAuB;QAC7B,cAAc,EAAE,qCAAqC;KACrD;CACD,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IRestRouteEntryPoint } from \"@3sixty/api-models\";\nimport {\n\tgenerateRestRoutesAuthenticationAdmin,\n\ttagsAuthenticationAdmin\n} from \"./routes/entityStorageAuthenticationAdminRoutes.js\";\nimport {\n\tgenerateRestRoutesAuthenticationAudit,\n\ttagsAuthenticationAudit\n} from \"./routes/entityStorageAuthenticationAuditRoutes.js\";\nimport {\n\tgenerateRestRoutesAuthentication,\n\ttagsAuthentication\n} from \"./routes/entityStorageAuthenticationRoutes.js\";\n\n/**\n * REST entry points for the authentication, authentication admin, and authentication audit services.\n */\nexport const restEntryPoints: IRestRouteEntryPoint[] = [\n\t{\n\t\tname: \"authentication\",\n\t\tdefaultBaseRoute: \"authentication\",\n\t\ttags: tagsAuthentication,\n\t\tgenerateRoutes: generateRestRoutesAuthentication\n\t},\n\t{\n\t\tname: \"authenticationAdmin\",\n\t\tdefaultBaseRoute: \"authentication/admin\",\n\t\ttags: tagsAuthenticationAdmin,\n\t\tgenerateRoutes: generateRestRoutesAuthenticationAdmin\n\t},\n\t{\n\t\tname: \"authenticationAudit\",\n\t\tdefaultBaseRoute: \"authentication/audit\",\n\t\ttags: tagsAuthenticationAudit,\n\t\tgenerateRoutes: generateRestRoutesAuthenticationAudit\n\t}\n];\n"]}