@twin.org/api-auth-entity-storage-service 0.0.3-next.43 → 0.0.3-next.45

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 (36) hide show
  1. package/dist/es/models/IAuthHeaderProcessorConstructorOptions.js.map +1 -1
  2. package/dist/es/models/IEntityStorageAuthenticationServiceConstructorOptions.js.map +1 -1
  3. package/dist/es/processors/authHeaderProcessor.js +35 -26
  4. package/dist/es/processors/authHeaderProcessor.js.map +1 -1
  5. package/dist/es/restEntryPoints.js +3 -0
  6. package/dist/es/restEntryPoints.js.map +1 -1
  7. package/dist/es/services/entityStorageAuthenticationAdminService.js +4 -4
  8. package/dist/es/services/entityStorageAuthenticationAdminService.js.map +1 -1
  9. package/dist/es/services/entityStorageAuthenticationRateService.js +6 -6
  10. package/dist/es/services/entityStorageAuthenticationRateService.js.map +1 -1
  11. package/dist/es/services/entityStorageAuthenticationService.js +27 -33
  12. package/dist/es/services/entityStorageAuthenticationService.js.map +1 -1
  13. package/dist/es/utils/passwordHelper.js +1 -1
  14. package/dist/es/utils/passwordHelper.js.map +1 -1
  15. package/dist/es/utils/tokenHelper.js +7 -8
  16. package/dist/es/utils/tokenHelper.js.map +1 -1
  17. package/dist/types/models/IAuthHeaderProcessorConstructorOptions.d.ts +0 -4
  18. package/dist/types/models/IEntityStorageAuthenticationServiceConstructorOptions.d.ts +0 -4
  19. package/dist/types/processors/authHeaderProcessor.d.ts +3 -1
  20. package/dist/types/restEntryPoints.d.ts +3 -0
  21. package/dist/types/services/entityStorageAuthenticationAdminService.d.ts +4 -4
  22. package/dist/types/services/entityStorageAuthenticationRateService.d.ts +5 -5
  23. package/dist/types/services/entityStorageAuthenticationService.d.ts +4 -4
  24. package/dist/types/utils/passwordHelper.d.ts +1 -1
  25. package/dist/types/utils/tokenHelper.d.ts +2 -4
  26. package/docs/changelog.md +32 -0
  27. package/docs/reference/classes/AuthHeaderProcessor.md +5 -1
  28. package/docs/reference/classes/EntityStorageAuthenticationAdminService.md +4 -4
  29. package/docs/reference/classes/EntityStorageAuthenticationRateService.md +5 -5
  30. package/docs/reference/classes/EntityStorageAuthenticationService.md +4 -4
  31. package/docs/reference/classes/PasswordHelper.md +1 -1
  32. package/docs/reference/classes/TokenHelper.md +2 -8
  33. package/docs/reference/interfaces/IAuthHeaderProcessorConstructorOptions.md +0 -8
  34. package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConstructorOptions.md +0 -8
  35. package/docs/reference/variables/restEntryPoints.md +2 -0
  36. package/package.json +4 -4
@@ -69,11 +69,6 @@ export class EntityStorageAuthenticationService {
69
69
  * @internal
70
70
  */
71
71
  _vaultConnector;
72
- /**
73
- * The transformer component, used to resolve public origins for tenants and encrypt/decrypt tenant tokens.
74
- * @internal
75
- */
76
- _urlTransformerService;
77
72
  /**
78
73
  * The name of the key to retrieve from the vault for signing JWT.
79
74
  * @internal
@@ -121,7 +116,6 @@ export class EntityStorageAuthenticationService {
121
116
  constructor(options) {
122
117
  this._userEntityStorage = EntityStorageConnectorFactory.get(options?.userEntityStorageType ?? "authentication-user");
123
118
  this._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
124
- this._urlTransformerService = ComponentFactory.get(options?.urlTransformerComponentType ?? "url-transformer");
125
119
  this._authenticationAuditService = ComponentFactory.getIfExists(options?.authenticationAuditServiceType ?? "authentication-audit");
126
120
  this._authenticationRateService = ComponentFactory.get(options?.authenticationRateServiceType ?? "authentication-rate");
127
121
  this._tenantAdminComponent = ComponentFactory.getIfExists(options?.tenantAdminComponentType ?? "tenant-admin");
@@ -158,7 +152,7 @@ export class EntityStorageAuthenticationService {
158
152
  /**
159
153
  * The service needs to be started when the application is initialized.
160
154
  * @param nodeLoggingComponentType The node logging component type.
161
- * @returns Nothing.
155
+ * @returns A promise that resolves when rate-limit actions have been registered and the node identity cached.
162
156
  */
163
157
  async start(nodeLoggingComponentType) {
164
158
  const contextIds = await ContextIdStore.getContextIds();
@@ -171,7 +165,7 @@ export class EntityStorageAuthenticationService {
171
165
  /**
172
166
  * The component needs to be stopped when the node is closed.
173
167
  * @param nodeLoggingComponentType The node logging component type.
174
- * @returns Nothing.
168
+ * @returns A promise that resolves when all rate-limit actions have been unregistered.
175
169
  */
176
170
  async stop(nodeLoggingComponentType) {
177
171
  await this._authenticationRateService.unregisterAction("login");
@@ -207,7 +201,7 @@ export class EntityStorageAuthenticationService {
207
201
  // the context
208
202
  const contextIds = await ContextIdStore.getContextIds();
209
203
  loginTenantId = contextIds?.[ContextIdKeys.Tenant];
210
- tokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, this._urlTransformerService, `${this._nodeId}/${this._signingKeyName}`, user.identity, user.organization, loginTenantId, this._defaultTtlMinutes, user.scope, user.passwordVersion ?? 0);
204
+ tokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, user.identity, user.organization, loginTenantId, this._defaultTtlMinutes, user.scope, user.passwordVersion ?? 0);
211
205
  loginUser = user;
212
206
  }
213
207
  catch (error) {
@@ -233,7 +227,7 @@ export class EntityStorageAuthenticationService {
233
227
  /**
234
228
  * Logout the current user.
235
229
  * @param token The token to logout, if it uses a mechanism with public access.
236
- * @returns Nothing.
230
+ * @returns A promise that resolves when the logout audit entry has been recorded.
237
231
  */
238
232
  async logout(token) {
239
233
  // Nothing to do here, as we are stateless.
@@ -256,46 +250,46 @@ export class EntityStorageAuthenticationService {
256
250
  let refreshPasswordVersion;
257
251
  let tenantId;
258
252
  // If the verify fails on the current token then it will throw an exception.
259
- const headerAndPayload = await TokenHelper.verify(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, token, undefined, async (userIdentity, organizationIdentity, encryptedTenantId, passwordVersion) => {
253
+ const headerAndPayload = await TokenHelper.verify(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, token, undefined, async (sub, org, tid, passwordVersion) => {
260
254
  const validParts = [];
261
- const contextIds = (await ContextIdStore.getContextIds()) ?? {};
262
- // If the token carries an encrypted tenant ID and the admin component is available,
263
- // decrypt and resolve the tenant first so the user lookup runs in the correct partition.
264
- if (Is.stringValue(encryptedTenantId)) {
265
- tenantId = await this._urlTransformerService.decryptParam(encryptedTenantId);
266
- if (Is.stringValue(tenantId)) {
267
- const tenant = await this._tenantAdminComponent?.get(tenantId);
268
- if (!Is.empty(tenant)) {
269
- validParts.push("tenant");
270
- contextIds[ContextIdKeys.Tenant] = tenantId;
271
- }
255
+ tenantId = tid;
256
+ if (Is.stringValue(tenantId)) {
257
+ const tenant = await this._tenantAdminComponent?.get(tenantId);
258
+ if (tenant?.id === tenantId) {
259
+ validParts.push("tenant");
272
260
  }
273
261
  }
262
+ // We use the tenant id from the token, if the user is not in that
263
+ // partition then the get will fail. Only override the tenant key when
264
+ // tid is present — setting it to undefined would drop the active partition.
265
+ const baseContext = (await ContextIdStore.getContextIds()) ?? {};
266
+ const contextIdsForUserLookup = Is.stringValue(tid)
267
+ ? { ...baseContext, [ContextIdKeys.Tenant]: tid }
268
+ : baseContext;
274
269
  // Wrap the user lookup in the request context so partitioned storage uses the correct tenant.
275
- const user = await ContextIdStore.run(contextIds, async () => this._userEntityStorage.get(userIdentity, "identity"));
270
+ const user = await ContextIdStore.run(contextIdsForUserLookup, async () => this._userEntityStorage.get(sub, "identity"));
276
271
  refreshPasswordVersion = user?.passwordVersion;
277
- if (user?.identity === userIdentity &&
278
- (passwordVersion ?? 0) === (refreshPasswordVersion ?? 0)) {
272
+ if (user?.identity === sub && (passwordVersion ?? 0) === (refreshPasswordVersion ?? 0)) {
279
273
  validParts.push("user");
280
274
  }
281
- if (user?.organization === organizationIdentity) {
275
+ if (user?.organization === org) {
282
276
  validParts.push("organization");
283
277
  }
284
278
  return validParts;
285
279
  });
286
280
  const refreshSub = headerAndPayload.payload.sub ?? "";
287
281
  await this._authenticationRateService.check("token-refresh", refreshSub);
288
- const refreshTokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, this._urlTransformerService, `${this._nodeId}/${this._signingKeyName}`, refreshSub, Is.stringValue(headerAndPayload.payload.org) ? headerAndPayload.payload.org : "", tenantId, this._defaultTtlMinutes, Coerce.string(headerAndPayload.payload?.scope), refreshPasswordVersion ?? 0);
289
- const refreshScope = Coerce.string(headerAndPayload.payload?.scope) ?? "";
282
+ const payloadOrg = Coerce.string(headerAndPayload.payload.org);
283
+ const payloadScope = Coerce.string(headerAndPayload.payload?.scope);
284
+ const refreshTokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, refreshSub, payloadOrg, tenantId, this._defaultTtlMinutes, payloadScope, refreshPasswordVersion ?? 0);
290
285
  await this._authenticationAuditService?.create({
291
286
  actorId: refreshSub,
292
287
  event: AuthAuditEvent.TokenRefreshed,
293
288
  data: {
294
- organizationIdentity: Is.stringValue(headerAndPayload.payload.org)
295
- ? headerAndPayload.payload.org
296
- : "",
289
+ organizationIdentity: payloadOrg,
297
290
  tenantId,
298
- scope: refreshScope.split(",").filter(scope => scope.length > 0)
291
+ scope: payloadScope?.split(",").filter(scope => scope.length > 0),
292
+ version: refreshPasswordVersion ?? 0
299
293
  }
300
294
  });
301
295
  return refreshTokenAndExpiry;
@@ -304,7 +298,7 @@ export class EntityStorageAuthenticationService {
304
298
  * Update the user's password.
305
299
  * @param currentPassword The current password for the user.
306
300
  * @param newPassword The new password for the user.
307
- * @returns Nothing.
301
+ * @returns A promise that resolves when the password has been updated and the rate limit cleared.
308
302
  */
309
303
  async updatePassword(currentPassword, newPassword) {
310
304
  const contextIds = await ContextIdStore.getContextIds();
@@ -1 +1 @@
1
- {"version":3,"file":"entityStorageAuthenticationService.js","sourceRoot":"","sources":["../../../src/services/entityStorageAuthenticationService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIAuthenticationRateActionConfig,\n\tIAuthenticationRateComponent,\n\tIAuthenticationAuditComponent,\n\tIAuthenticationComponent\n} from \"@twin.org/api-auth-entity-storage-models\";\nimport { AuthAuditEvent } from \"@twin.org/api-auth-entity-storage-models\";\nimport type { ITenantAdminComponent, IUrlTransformerComponent } from \"@twin.org/api-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tCoerce,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tUnauthorizedError\n} from \"@twin.org/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@twin.org/crypto\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { VaultConnectorFactory, type IVaultConnector } from \"@twin.org/vault-models\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\nimport type { IEntityStorageAuthenticationServiceConstructorOptions } from \"../models/IEntityStorageAuthenticationServiceConstructorOptions.js\";\nimport { PasswordHelper } from \"../utils/passwordHelper.js\";\nimport { TokenHelper } from \"../utils/tokenHelper.js\";\n\n/**\n * Implementation of the authentication component using entity storage.\n */\nexport class EntityStorageAuthenticationService implements IAuthenticationComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntityStorageAuthenticationService>();\n\n\t/**\n\t * Default TTL in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TTL_MINUTES: number = 60;\n\n\t/**\n\t * Default maximum login attempts in a rate window.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_LOGIN_RATE_MAX_ATTEMPTS: number = 5;\n\n\t/**\n\t * Default login rate window in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_LOGIN_RATE_WINDOW_MINUTES: number = 15;\n\n\t/**\n\t * Default maximum password change attempts in a rate window.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_PASSWORD_CHANGE_RATE_MAX_ATTEMPTS: number = 5;\n\n\t/**\n\t * Default password change rate window in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_PASSWORD_CHANGE_RATE_WINDOW_MINUTES: number = 15;\n\n\t/**\n\t * Default maximum token refresh attempts in a rate window.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TOKEN_REFRESH_RATE_MAX_ATTEMPTS: number = 30;\n\n\t/**\n\t * Default token refresh rate window in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES: number = 60;\n\n\t/**\n\t * The audit service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationAuditService?: IAuthenticationAuditComponent;\n\n\t/**\n\t * The rate service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationRateService: IAuthenticationRateComponent;\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 vault for the keys.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\n\n\t/**\n\t * The transformer component, used to resolve public origins for tenants and encrypt/decrypt tenant tokens.\n\t * @internal\n\t */\n\tprivate readonly _urlTransformerService: IUrlTransformerComponent;\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 default TTL for the token.\n\t * @internal\n\t */\n\tprivate readonly _defaultTtlMinutes: number;\n\n\t/**\n\t * The minimum password length for validation.\n\t * @internal\n\t */\n\tprivate readonly _minPasswordLength?: number;\n\n\t/**\n\t * Rate limit configuration for login failures.\n\t * @internal\n\t */\n\tprivate readonly _loginRateLimit: IAuthenticationRateActionConfig;\n\n\t/**\n\t * Rate limit configuration for password changes.\n\t * @internal\n\t */\n\tprivate readonly _passwordChangeRateLimit: IAuthenticationRateActionConfig;\n\n\t/**\n\t * Rate limit configuration for token refresh.\n\t * @internal\n\t */\n\tprivate readonly _tokenRefreshRateLimit: IAuthenticationRateActionConfig;\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 node identity.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of EntityStorageAuthentication.\n\t * @param options The dependencies for the identity connector.\n\t */\n\tconstructor(options?: IEntityStorageAuthenticationServiceConstructorOptions) {\n\t\tthis._userEntityStorage = EntityStorageConnectorFactory.get(\n\t\t\toptions?.userEntityStorageType ?? \"authentication-user\"\n\t\t);\n\n\t\tthis._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? \"vault\");\n\n\t\tthis._urlTransformerService = ComponentFactory.get(\n\t\t\toptions?.urlTransformerComponentType ?? \"url-transformer\"\n\t\t);\n\n\t\tthis._authenticationAuditService = ComponentFactory.getIfExists<IAuthenticationAuditComponent>(\n\t\t\toptions?.authenticationAuditServiceType ?? \"authentication-audit\"\n\t\t);\n\n\t\tthis._authenticationRateService = ComponentFactory.get<IAuthenticationRateComponent>(\n\t\t\toptions?.authenticationRateServiceType ?? \"authentication-rate\"\n\t\t);\n\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._defaultTtlMinutes =\n\t\t\toptions?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES;\n\t\tthis._minPasswordLength = options?.config?.minPasswordLength;\n\t\tthis._loginRateLimit = {\n\t\t\tmaxAttempts:\n\t\t\t\toptions?.config?.loginRateLimit?.maxAttempts ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_MAX_ATTEMPTS,\n\t\t\twindowMinutes:\n\t\t\t\toptions?.config?.loginRateLimit?.windowMinutes ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_WINDOW_MINUTES\n\t\t};\n\t\tthis._passwordChangeRateLimit = {\n\t\t\tmaxAttempts:\n\t\t\t\toptions?.config?.passwordChangeRateLimit?.maxAttempts ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_PASSWORD_CHANGE_RATE_MAX_ATTEMPTS,\n\t\t\twindowMinutes:\n\t\t\t\toptions?.config?.passwordChangeRateLimit?.windowMinutes ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_PASSWORD_CHANGE_RATE_WINDOW_MINUTES\n\t\t};\n\t\tthis._tokenRefreshRateLimit = {\n\t\t\tmaxAttempts:\n\t\t\t\toptions?.config?.tokenRefreshRateLimit?.maxAttempts ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_MAX_ATTEMPTS,\n\t\t\twindowMinutes:\n\t\t\t\toptions?.config?.tokenRefreshRateLimit?.windowMinutes ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES\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 EntityStorageAuthenticationService.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 Nothing.\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\n\t\tawait this._authenticationRateService.registerAction(\"login\", this._loginRateLimit);\n\t\tawait this._authenticationRateService.registerAction(\n\t\t\t\"password-change\",\n\t\t\tthis._passwordChangeRateLimit\n\t\t);\n\t\tawait this._authenticationRateService.registerAction(\n\t\t\t\"token-refresh\",\n\t\t\tthis._tokenRefreshRateLimit\n\t\t);\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tawait this._authenticationRateService.unregisterAction(\"login\");\n\t\tawait this._authenticationRateService.unregisterAction(\"password-change\");\n\t\tawait this._authenticationRateService.unregisterAction(\"token-refresh\");\n\t}\n\n\t/**\n\t * Perform a login for the user.\n\t * @param email The email address for the user.\n\t * @param password The password for the user.\n\t * @returns The authentication token for the user, if it uses a mechanism with public access.\n\t */\n\tpublic async login(\n\t\temail: string,\n\t\tpassword: string\n\t): Promise<{\n\t\ttoken?: string;\n\t\texpiry: number;\n\t}> {\n\t\tGuards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, nameof(email), email);\n\t\tGuards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, nameof(password), password);\n\n\t\tlet loginUser: AuthenticationUser | undefined;\n\t\tlet loginTenantId: string | undefined;\n\t\tlet tokenAndExpiry: { token?: string; expiry: number } | undefined;\n\n\t\ttry {\n\t\t\tawait this._authenticationRateService.check(\"login\", email);\n\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!user) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"userNotFound\");\n\t\t\t}\n\n\t\t\tconst saltBytes = Converter.base64ToBytes(user.salt);\n\t\t\tconst passwordBytes = Converter.utf8ToBytes(password);\n\n\t\t\tconst hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\n\t\t\tif (!PasswordValidator.comparePasswordHashes(hashedPassword, user.password)) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"passwordMismatch\");\n\t\t\t}\n\n\t\t\t// This might be undefined if the login is performed in a single tenant context\n\t\t\t// it is verified during the token processing, tenant id will be matched against\n\t\t\t// the context\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tloginTenantId = contextIds?.[ContextIdKeys.Tenant];\n\n\t\t\ttokenAndExpiry = await TokenHelper.createToken(\n\t\t\t\tthis._vaultConnector,\n\t\t\t\tthis._urlTransformerService,\n\t\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\t\tuser.identity,\n\t\t\t\tuser.organization,\n\t\t\t\tloginTenantId,\n\t\t\t\tthis._defaultTtlMinutes,\n\t\t\t\tuser.scope,\n\t\t\t\tuser.passwordVersion ?? 0\n\t\t\t);\n\t\t\tloginUser = user;\n\t\t} catch (error) {\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: email,\n\t\t\t\tevent: AuthAuditEvent.LoginFailure\n\t\t\t});\n\n\t\t\tthrow new UnauthorizedError(\n\t\t\t\tEntityStorageAuthenticationService.CLASS_NAME,\n\t\t\t\t\"loginFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\n\t\tawait this._authenticationRateService.clear(\"login\", email);\n\n\t\tawait this._authenticationAuditService?.create({\n\t\t\tactorId: email,\n\t\t\tevent: AuthAuditEvent.LoginSuccess,\n\t\t\tdata: {\n\t\t\t\tuserIdentity: loginUser.identity,\n\t\t\t\torganizationIdentity: loginUser.organization,\n\t\t\t\ttenantId: loginTenantId,\n\t\t\t\tscope: loginUser.scope.split(\",\")\n\t\t\t}\n\t\t});\n\n\t\treturn tokenAndExpiry;\n\t}\n\n\t/**\n\t * Logout the current user.\n\t * @param token The token to logout, if it uses a mechanism with public access.\n\t * @returns Nothing.\n\t */\n\tpublic async logout(token?: string): Promise<void> {\n\t\t// Nothing to do here, as we are stateless.\n\t\t// The cookie will be revoked by the REST route handling\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst identifier = contextIds?.[ContextIdKeys.User];\n\t\tif (Is.stringValue(identifier)) {\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: identifier,\n\t\t\t\tevent: AuthAuditEvent.Logout\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Refresh the token.\n\t * @param token The token to refresh, if it uses a mechanism with public access.\n\t * @returns The refreshed token, if it uses a mechanism with public access.\n\t */\n\tpublic async refresh(token?: string): Promise<{\n\t\ttoken?: string;\n\t\texpiry: number;\n\t}> {\n\t\tlet refreshPasswordVersion: number | undefined;\n\t\tlet tenantId: string | undefined;\n\n\t\t// If the verify fails on the current token then it will throw an exception.\n\t\tconst headerAndPayload = await TokenHelper.verify(\n\t\t\tthis._vaultConnector,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\ttoken,\n\t\t\tundefined,\n\t\t\tasync (userIdentity, organizationIdentity, encryptedTenantId, passwordVersion) => {\n\t\t\t\tconst validParts = [];\n\n\t\t\t\tconst contextIds = (await ContextIdStore.getContextIds()) ?? {};\n\n\t\t\t\t// If the token carries an encrypted tenant ID and the admin component is available,\n\t\t\t\t// decrypt and resolve the tenant first so the user lookup runs in the correct partition.\n\t\t\t\tif (Is.stringValue(encryptedTenantId)) {\n\t\t\t\t\ttenantId = await this._urlTransformerService.decryptParam(encryptedTenantId);\n\t\t\t\t\tif (Is.stringValue(tenantId)) {\n\t\t\t\t\t\tconst tenant = await this._tenantAdminComponent?.get(tenantId);\n\t\t\t\t\t\tif (!Is.empty(tenant)) {\n\t\t\t\t\t\t\tvalidParts.push(\"tenant\");\n\t\t\t\t\t\t\tcontextIds[ContextIdKeys.Tenant] = tenantId;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\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\tconst user = await ContextIdStore.run(contextIds, async () =>\n\t\t\t\t\tthis._userEntityStorage.get(userIdentity, \"identity\")\n\t\t\t\t);\n\n\t\t\t\trefreshPasswordVersion = user?.passwordVersion;\n\t\t\t\tif (\n\t\t\t\t\tuser?.identity === userIdentity &&\n\t\t\t\t\t(passwordVersion ?? 0) === (refreshPasswordVersion ?? 0)\n\t\t\t\t) {\n\t\t\t\t\tvalidParts.push(\"user\");\n\t\t\t\t}\n\t\t\t\tif (user?.organization === organizationIdentity) {\n\t\t\t\t\tvalidParts.push(\"organization\");\n\t\t\t\t}\n\t\t\t\treturn validParts;\n\t\t\t}\n\t\t);\n\n\t\tconst refreshSub = headerAndPayload.payload.sub ?? \"\";\n\t\tawait this._authenticationRateService.check(\"token-refresh\", refreshSub);\n\n\t\tconst refreshTokenAndExpiry = await TokenHelper.createToken(\n\t\t\tthis._vaultConnector,\n\t\t\tthis._urlTransformerService,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\trefreshSub,\n\t\t\tIs.stringValue(headerAndPayload.payload.org) ? headerAndPayload.payload.org : \"\",\n\t\t\ttenantId,\n\t\t\tthis._defaultTtlMinutes,\n\t\t\tCoerce.string(headerAndPayload.payload?.scope),\n\t\t\trefreshPasswordVersion ?? 0\n\t\t);\n\t\tconst refreshScope = Coerce.string(headerAndPayload.payload?.scope) ?? \"\";\n\n\t\tawait this._authenticationAuditService?.create({\n\t\t\tactorId: refreshSub,\n\t\t\tevent: AuthAuditEvent.TokenRefreshed,\n\t\t\tdata: {\n\t\t\t\torganizationIdentity: Is.stringValue(headerAndPayload.payload.org)\n\t\t\t\t\t? headerAndPayload.payload.org\n\t\t\t\t\t: \"\",\n\t\t\t\ttenantId,\n\t\t\t\tscope: refreshScope.split(\",\").filter(scope => scope.length > 0)\n\t\t\t}\n\t\t});\n\n\t\treturn refreshTokenAndExpiry;\n\t}\n\n\t/**\n\t * Update the user's password.\n\t * @param currentPassword The current password for the user.\n\t * @param newPassword The new password for the user.\n\t * @returns Nothing.\n\t */\n\tpublic async updatePassword(currentPassword: string, newPassword: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.User);\n\n\t\tconst userIdentity = contextIds[ContextIdKeys.User];\n\t\tawait this._authenticationRateService.check(\"password-change\", userIdentity);\n\n\t\tconst user = await this._userEntityStorage.get(userIdentity, \"identity\");\n\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\tthrow new NotFoundError(\n\t\t\t\tEntityStorageAuthenticationService.CLASS_NAME,\n\t\t\t\t\"userNotFound\",\n\t\t\t\tuserIdentity\n\t\t\t);\n\t\t}\n\n\t\tawait PasswordHelper.updatePassword(\n\t\t\tthis._userEntityStorage,\n\t\t\tthis._authenticationAuditService,\n\t\t\tuser,\n\t\t\tnewPassword,\n\t\t\tcurrentPassword,\n\t\t\tthis._minPasswordLength\n\t\t);\n\n\t\tawait this._authenticationRateService.clear(\"password-change\", userIdentity);\n\t}\n}\n"]}
1
+ {"version":3,"file":"entityStorageAuthenticationService.js","sourceRoot":"","sources":["../../../src/services/entityStorageAuthenticationService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIAuthenticationRateActionConfig,\n\tIAuthenticationRateComponent,\n\tIAuthenticationAuditComponent,\n\tIAuthenticationComponent\n} from \"@twin.org/api-auth-entity-storage-models\";\nimport { AuthAuditEvent } from \"@twin.org/api-auth-entity-storage-models\";\nimport type { ITenantAdminComponent } from \"@twin.org/api-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tCoerce,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tUnauthorizedError\n} from \"@twin.org/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@twin.org/crypto\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { VaultConnectorFactory, type IVaultConnector } from \"@twin.org/vault-models\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\nimport type { IEntityStorageAuthenticationServiceConstructorOptions } from \"../models/IEntityStorageAuthenticationServiceConstructorOptions.js\";\nimport { PasswordHelper } from \"../utils/passwordHelper.js\";\nimport { TokenHelper } from \"../utils/tokenHelper.js\";\n\n/**\n * Implementation of the authentication component using entity storage.\n */\nexport class EntityStorageAuthenticationService implements IAuthenticationComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntityStorageAuthenticationService>();\n\n\t/**\n\t * Default TTL in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TTL_MINUTES: number = 60;\n\n\t/**\n\t * Default maximum login attempts in a rate window.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_LOGIN_RATE_MAX_ATTEMPTS: number = 5;\n\n\t/**\n\t * Default login rate window in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_LOGIN_RATE_WINDOW_MINUTES: number = 15;\n\n\t/**\n\t * Default maximum password change attempts in a rate window.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_PASSWORD_CHANGE_RATE_MAX_ATTEMPTS: number = 5;\n\n\t/**\n\t * Default password change rate window in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_PASSWORD_CHANGE_RATE_WINDOW_MINUTES: number = 15;\n\n\t/**\n\t * Default maximum token refresh attempts in a rate window.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TOKEN_REFRESH_RATE_MAX_ATTEMPTS: number = 30;\n\n\t/**\n\t * Default token refresh rate window in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES: number = 60;\n\n\t/**\n\t * The audit service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationAuditService?: IAuthenticationAuditComponent;\n\n\t/**\n\t * The rate service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationRateService: IAuthenticationRateComponent;\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 vault for the keys.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\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 default TTL for the token.\n\t * @internal\n\t */\n\tprivate readonly _defaultTtlMinutes: number;\n\n\t/**\n\t * The minimum password length for validation.\n\t * @internal\n\t */\n\tprivate readonly _minPasswordLength?: number;\n\n\t/**\n\t * Rate limit configuration for login failures.\n\t * @internal\n\t */\n\tprivate readonly _loginRateLimit: IAuthenticationRateActionConfig;\n\n\t/**\n\t * Rate limit configuration for password changes.\n\t * @internal\n\t */\n\tprivate readonly _passwordChangeRateLimit: IAuthenticationRateActionConfig;\n\n\t/**\n\t * Rate limit configuration for token refresh.\n\t * @internal\n\t */\n\tprivate readonly _tokenRefreshRateLimit: IAuthenticationRateActionConfig;\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 node identity.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of EntityStorageAuthentication.\n\t * @param options The dependencies for the identity connector.\n\t */\n\tconstructor(options?: IEntityStorageAuthenticationServiceConstructorOptions) {\n\t\tthis._userEntityStorage = EntityStorageConnectorFactory.get(\n\t\t\toptions?.userEntityStorageType ?? \"authentication-user\"\n\t\t);\n\n\t\tthis._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? \"vault\");\n\n\t\tthis._authenticationAuditService = ComponentFactory.getIfExists<IAuthenticationAuditComponent>(\n\t\t\toptions?.authenticationAuditServiceType ?? \"authentication-audit\"\n\t\t);\n\n\t\tthis._authenticationRateService = ComponentFactory.get<IAuthenticationRateComponent>(\n\t\t\toptions?.authenticationRateServiceType ?? \"authentication-rate\"\n\t\t);\n\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._defaultTtlMinutes =\n\t\t\toptions?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES;\n\t\tthis._minPasswordLength = options?.config?.minPasswordLength;\n\t\tthis._loginRateLimit = {\n\t\t\tmaxAttempts:\n\t\t\t\toptions?.config?.loginRateLimit?.maxAttempts ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_MAX_ATTEMPTS,\n\t\t\twindowMinutes:\n\t\t\t\toptions?.config?.loginRateLimit?.windowMinutes ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_WINDOW_MINUTES\n\t\t};\n\t\tthis._passwordChangeRateLimit = {\n\t\t\tmaxAttempts:\n\t\t\t\toptions?.config?.passwordChangeRateLimit?.maxAttempts ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_PASSWORD_CHANGE_RATE_MAX_ATTEMPTS,\n\t\t\twindowMinutes:\n\t\t\t\toptions?.config?.passwordChangeRateLimit?.windowMinutes ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_PASSWORD_CHANGE_RATE_WINDOW_MINUTES\n\t\t};\n\t\tthis._tokenRefreshRateLimit = {\n\t\t\tmaxAttempts:\n\t\t\t\toptions?.config?.tokenRefreshRateLimit?.maxAttempts ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_MAX_ATTEMPTS,\n\t\t\twindowMinutes:\n\t\t\t\toptions?.config?.tokenRefreshRateLimit?.windowMinutes ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES\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 EntityStorageAuthenticationService.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 rate-limit actions have been registered and the node identity 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\n\t\tawait this._authenticationRateService.registerAction(\"login\", this._loginRateLimit);\n\t\tawait this._authenticationRateService.registerAction(\n\t\t\t\"password-change\",\n\t\t\tthis._passwordChangeRateLimit\n\t\t);\n\t\tawait this._authenticationRateService.registerAction(\n\t\t\t\"token-refresh\",\n\t\t\tthis._tokenRefreshRateLimit\n\t\t);\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when all rate-limit actions have been unregistered.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tawait this._authenticationRateService.unregisterAction(\"login\");\n\t\tawait this._authenticationRateService.unregisterAction(\"password-change\");\n\t\tawait this._authenticationRateService.unregisterAction(\"token-refresh\");\n\t}\n\n\t/**\n\t * Perform a login for the user.\n\t * @param email The email address for the user.\n\t * @param password The password for the user.\n\t * @returns The authentication token for the user, if it uses a mechanism with public access.\n\t */\n\tpublic async login(\n\t\temail: string,\n\t\tpassword: string\n\t): Promise<{\n\t\ttoken?: string;\n\t\texpiry: number;\n\t}> {\n\t\tGuards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, nameof(email), email);\n\t\tGuards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, nameof(password), password);\n\n\t\tlet loginUser: AuthenticationUser | undefined;\n\t\tlet loginTenantId: string | undefined;\n\t\tlet tokenAndExpiry: { token?: string; expiry: number } | undefined;\n\n\t\ttry {\n\t\t\tawait this._authenticationRateService.check(\"login\", email);\n\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!user) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"userNotFound\");\n\t\t\t}\n\n\t\t\tconst saltBytes = Converter.base64ToBytes(user.salt);\n\t\t\tconst passwordBytes = Converter.utf8ToBytes(password);\n\n\t\t\tconst hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\n\t\t\tif (!PasswordValidator.comparePasswordHashes(hashedPassword, user.password)) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"passwordMismatch\");\n\t\t\t}\n\n\t\t\t// This might be undefined if the login is performed in a single tenant context\n\t\t\t// it is verified during the token processing, tenant id will be matched against\n\t\t\t// the context\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tloginTenantId = contextIds?.[ContextIdKeys.Tenant];\n\n\t\t\ttokenAndExpiry = await TokenHelper.createToken(\n\t\t\t\tthis._vaultConnector,\n\t\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\t\tuser.identity,\n\t\t\t\tuser.organization,\n\t\t\t\tloginTenantId,\n\t\t\t\tthis._defaultTtlMinutes,\n\t\t\t\tuser.scope,\n\t\t\t\tuser.passwordVersion ?? 0\n\t\t\t);\n\t\t\tloginUser = user;\n\t\t} catch (error) {\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: email,\n\t\t\t\tevent: AuthAuditEvent.LoginFailure\n\t\t\t});\n\n\t\t\tthrow new UnauthorizedError(\n\t\t\t\tEntityStorageAuthenticationService.CLASS_NAME,\n\t\t\t\t\"loginFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\n\t\tawait this._authenticationRateService.clear(\"login\", email);\n\n\t\tawait this._authenticationAuditService?.create({\n\t\t\tactorId: email,\n\t\t\tevent: AuthAuditEvent.LoginSuccess,\n\t\t\tdata: {\n\t\t\t\tuserIdentity: loginUser.identity,\n\t\t\t\torganizationIdentity: loginUser.organization,\n\t\t\t\ttenantId: loginTenantId,\n\t\t\t\tscope: loginUser.scope.split(\",\")\n\t\t\t}\n\t\t});\n\n\t\treturn tokenAndExpiry;\n\t}\n\n\t/**\n\t * Logout the current user.\n\t * @param token The token to logout, if it uses a mechanism with public access.\n\t * @returns A promise that resolves when the logout audit entry has been recorded.\n\t */\n\tpublic async logout(token?: string): Promise<void> {\n\t\t// Nothing to do here, as we are stateless.\n\t\t// The cookie will be revoked by the REST route handling\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst identifier = contextIds?.[ContextIdKeys.User];\n\t\tif (Is.stringValue(identifier)) {\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: identifier,\n\t\t\t\tevent: AuthAuditEvent.Logout\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Refresh the token.\n\t * @param token The token to refresh, if it uses a mechanism with public access.\n\t * @returns The refreshed token, if it uses a mechanism with public access.\n\t */\n\tpublic async refresh(token?: string): Promise<{\n\t\ttoken?: string;\n\t\texpiry: number;\n\t}> {\n\t\tlet refreshPasswordVersion: number | undefined;\n\t\tlet tenantId: string | undefined;\n\n\t\t// If the verify fails on the current token then it will throw an exception.\n\t\tconst headerAndPayload = await TokenHelper.verify(\n\t\t\tthis._vaultConnector,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\ttoken,\n\t\t\tundefined,\n\t\t\tasync (sub, org, tid, passwordVersion) => {\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}\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. Only override the tenant key when\n\t\t\t\t// tid is present — setting it to undefined would drop the active partition.\n\t\t\t\tconst baseContext = (await ContextIdStore.getContextIds()) ?? {};\n\t\t\t\tconst contextIdsForUserLookup = Is.stringValue(tid)\n\t\t\t\t\t? { ...baseContext, [ContextIdKeys.Tenant]: tid }\n\t\t\t\t\t: baseContext;\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\tconst user = 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\trefreshPasswordVersion = user?.passwordVersion;\n\t\t\t\tif (user?.identity === sub && (passwordVersion ?? 0) === (refreshPasswordVersion ?? 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\t\t\t\treturn validParts;\n\t\t\t}\n\t\t);\n\n\t\tconst refreshSub = headerAndPayload.payload.sub ?? \"\";\n\t\tawait this._authenticationRateService.check(\"token-refresh\", refreshSub);\n\n\t\tconst payloadOrg = Coerce.string(headerAndPayload.payload.org);\n\t\tconst payloadScope = Coerce.string(headerAndPayload.payload?.scope);\n\n\t\tconst refreshTokenAndExpiry = await TokenHelper.createToken(\n\t\t\tthis._vaultConnector,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\trefreshSub,\n\t\t\tpayloadOrg,\n\t\t\ttenantId,\n\t\t\tthis._defaultTtlMinutes,\n\t\t\tpayloadScope,\n\t\t\trefreshPasswordVersion ?? 0\n\t\t);\n\n\t\tawait this._authenticationAuditService?.create({\n\t\t\tactorId: refreshSub,\n\t\t\tevent: AuthAuditEvent.TokenRefreshed,\n\t\t\tdata: {\n\t\t\t\torganizationIdentity: payloadOrg,\n\t\t\t\ttenantId,\n\t\t\t\tscope: payloadScope?.split(\",\").filter(scope => scope.length > 0),\n\t\t\t\tversion: refreshPasswordVersion ?? 0\n\t\t\t}\n\t\t});\n\n\t\treturn refreshTokenAndExpiry;\n\t}\n\n\t/**\n\t * Update the user's password.\n\t * @param currentPassword The current password for the user.\n\t * @param newPassword The new password for the user.\n\t * @returns A promise that resolves when the password has been updated and the rate limit cleared.\n\t */\n\tpublic async updatePassword(currentPassword: string, newPassword: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.User);\n\n\t\tconst userIdentity = contextIds[ContextIdKeys.User];\n\t\tawait this._authenticationRateService.check(\"password-change\", userIdentity);\n\n\t\tconst user = await this._userEntityStorage.get(userIdentity, \"identity\");\n\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\tthrow new NotFoundError(\n\t\t\t\tEntityStorageAuthenticationService.CLASS_NAME,\n\t\t\t\t\"userNotFound\",\n\t\t\t\tuserIdentity\n\t\t\t);\n\t\t}\n\n\t\tawait PasswordHelper.updatePassword(\n\t\t\tthis._userEntityStorage,\n\t\t\tthis._authenticationAuditService,\n\t\t\tuser,\n\t\t\tnewPassword,\n\t\t\tcurrentPassword,\n\t\t\tthis._minPasswordLength\n\t\t);\n\n\t\tawait this._authenticationRateService.clear(\"password-change\", userIdentity);\n\t}\n}\n"]}
@@ -18,7 +18,7 @@ export class PasswordHelper {
18
18
  * @param newPassword The new password to set.
19
19
  * @param currentPassword The current password to verify against, if supplied.
20
20
  * @param minPasswordLength Optional minimum password length for validation.
21
- * @returns Nothing.
21
+ * @returns A promise that resolves when the new password has been stored and the audit entry recorded.
22
22
  */
23
23
  static async updatePassword(userEntityStorage, authenticationAuditService, user, newPassword, currentPassword, minPasswordLength) {
24
24
  PasswordValidator.validatePassword(newPassword, {
@@ -1 +1 @@
1
- {"version":3,"file":"passwordHelper.js","sourceRoot":"","sources":["../../../src/utils/passwordHelper.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAKxE;;GAEG;AACH,MAAM,OAAO,cAAc;IAC1B;;OAEG;IACI,MAAM,CAAU,UAAU,oBAAoC;IAErE;;;;;;;;;;OAUG;IACI,MAAM,CAAC,KAAK,CAAC,cAAc,CACjC,iBAA8D,EAC9D,0BAAqE,EACrE,IAAwB,EACxB,WAAmB,EACnB,eAAwB,EACxB,iBAA0B;QAE1B,iBAAiB,CAAC,gBAAgB,CAAC,WAAW,EAAE;YAC/C,SAAS,EAAE,iBAAiB;SAC5B,CAAC,CAAC;QAEH,IAAI,EAAE,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC;YACrC,MAAM,SAAS,GAAG,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrD,MAAM,aAAa,GAAG,SAAS,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;YAC7D,MAAM,qBAAqB,GAAG,MAAM,iBAAiB,CAAC,YAAY,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;YAC7F,IAAI,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,qBAAqB,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpF,MAAM,IAAI,YAAY,CAAC,cAAc,CAAC,UAAU,EAAE,yBAAyB,CAAC,CAAC;YAC9E,CAAC;QACF,CAAC;QAED,MAAM,SAAS,GAAG,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC5C,MAAM,aAAa,GAAG,SAAS,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACzD,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAAC,YAAY,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QAEtF,MAAM,WAAW,GAAuB;YACvC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI,EAAE,SAAS,CAAC,aAAa,CAAC,SAAS,CAAC;YACxC,QAAQ,EAAE,cAAc;YACxB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,eAAe,EAAE,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC,GAAG,CAAC;SAChD,CAAC;QAEF,MAAM,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACzC,MAAM,0BAA0B,EAAE,MAAM,CAAC;YACxC,OAAO,EAAE,IAAI,CAAC,KAAK;YACnB,KAAK,EAAE,cAAc,CAAC,eAAe;YACrC,IAAI,EAAE;gBACL,YAAY,EAAE,WAAW,CAAC,QAAQ;gBAClC,oBAAoB,EAAE,WAAW,CAAC,YAAY;aAC9C;SACD,CAAC,CAAC;IACJ,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IAuthenticationAuditComponent } from \"@twin.org/api-auth-entity-storage-models\";\nimport { AuthAuditEvent } from \"@twin.org/api-auth-entity-storage-models\";\nimport { Converter, GeneralError, Is, RandomHelper } from \"@twin.org/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@twin.org/crypto\";\nimport type { IEntityStorageConnector } from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\n\n/**\n * Helper class for password operations.\n */\nexport class PasswordHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<PasswordHelper>();\n\n\t/**\n\t * Update the password for a user.\n\t * Validates password strength, verifies the current password if provided, then hashes and stores the new password and raises an audit event.\n\t * @param userEntityStorage The entity storage for users.\n\t * @param authenticationAuditService The optional audit service.\n\t * @param user The user whose password is being updated.\n\t * @param newPassword The new password to set.\n\t * @param currentPassword The current password to verify against, if supplied.\n\t * @param minPasswordLength Optional minimum password length for validation.\n\t * @returns Nothing.\n\t */\n\tpublic static async updatePassword(\n\t\tuserEntityStorage: IEntityStorageConnector<AuthenticationUser>,\n\t\tauthenticationAuditService: IAuthenticationAuditComponent | undefined,\n\t\tuser: AuthenticationUser,\n\t\tnewPassword: string,\n\t\tcurrentPassword?: string,\n\t\tminPasswordLength?: number\n\t): Promise<void> {\n\t\tPasswordValidator.validatePassword(newPassword, {\n\t\t\tminLength: minPasswordLength\n\t\t});\n\n\t\tif (Is.stringValue(currentPassword)) {\n\t\t\tconst saltBytes = Converter.base64ToBytes(user.salt);\n\t\t\tconst passwordBytes = Converter.utf8ToBytes(currentPassword);\n\t\t\tconst hashedCurrentPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\t\t\tif (!PasswordValidator.comparePasswordHashes(hashedCurrentPassword, user.password)) {\n\t\t\t\tthrow new GeneralError(PasswordHelper.CLASS_NAME, \"currentPasswordMismatch\");\n\t\t\t}\n\t\t}\n\n\t\tconst saltBytes = RandomHelper.generate(16);\n\t\tconst passwordBytes = Converter.utf8ToBytes(newPassword);\n\t\tconst hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\n\t\tconst updatedUser: AuthenticationUser = {\n\t\t\temail: user.email,\n\t\t\tsalt: Converter.bytesToBase64(saltBytes),\n\t\t\tpassword: hashedPassword,\n\t\t\tidentity: user.identity,\n\t\t\torganization: user.organization,\n\t\t\tscope: user.scope,\n\t\t\tpasswordVersion: (user.passwordVersion ?? 0) + 1\n\t\t};\n\n\t\tawait userEntityStorage.set(updatedUser);\n\t\tawait authenticationAuditService?.create({\n\t\t\tactorId: user.email,\n\t\t\tevent: AuthAuditEvent.PasswordChanged,\n\t\t\tdata: {\n\t\t\t\tuserIdentity: updatedUser.identity,\n\t\t\t\torganizationIdentity: updatedUser.organization\n\t\t\t}\n\t\t});\n\t}\n}\n"]}
1
+ {"version":3,"file":"passwordHelper.js","sourceRoot":"","sources":["../../../src/utils/passwordHelper.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAKxE;;GAEG;AACH,MAAM,OAAO,cAAc;IAC1B;;OAEG;IACI,MAAM,CAAU,UAAU,oBAAoC;IAErE;;;;;;;;;;OAUG;IACI,MAAM,CAAC,KAAK,CAAC,cAAc,CACjC,iBAA8D,EAC9D,0BAAqE,EACrE,IAAwB,EACxB,WAAmB,EACnB,eAAwB,EACxB,iBAA0B;QAE1B,iBAAiB,CAAC,gBAAgB,CAAC,WAAW,EAAE;YAC/C,SAAS,EAAE,iBAAiB;SAC5B,CAAC,CAAC;QAEH,IAAI,EAAE,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC;YACrC,MAAM,SAAS,GAAG,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrD,MAAM,aAAa,GAAG,SAAS,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;YAC7D,MAAM,qBAAqB,GAAG,MAAM,iBAAiB,CAAC,YAAY,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;YAC7F,IAAI,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,qBAAqB,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpF,MAAM,IAAI,YAAY,CAAC,cAAc,CAAC,UAAU,EAAE,yBAAyB,CAAC,CAAC;YAC9E,CAAC;QACF,CAAC;QAED,MAAM,SAAS,GAAG,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC5C,MAAM,aAAa,GAAG,SAAS,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACzD,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAAC,YAAY,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QAEtF,MAAM,WAAW,GAAuB;YACvC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI,EAAE,SAAS,CAAC,aAAa,CAAC,SAAS,CAAC;YACxC,QAAQ,EAAE,cAAc;YACxB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,eAAe,EAAE,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC,GAAG,CAAC;SAChD,CAAC;QAEF,MAAM,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACzC,MAAM,0BAA0B,EAAE,MAAM,CAAC;YACxC,OAAO,EAAE,IAAI,CAAC,KAAK;YACnB,KAAK,EAAE,cAAc,CAAC,eAAe;YACrC,IAAI,EAAE;gBACL,YAAY,EAAE,WAAW,CAAC,QAAQ;gBAClC,oBAAoB,EAAE,WAAW,CAAC,YAAY;aAC9C;SACD,CAAC,CAAC;IACJ,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IAuthenticationAuditComponent } from \"@twin.org/api-auth-entity-storage-models\";\nimport { AuthAuditEvent } from \"@twin.org/api-auth-entity-storage-models\";\nimport { Converter, GeneralError, Is, RandomHelper } from \"@twin.org/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@twin.org/crypto\";\nimport type { IEntityStorageConnector } from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\n\n/**\n * Helper class for password operations.\n */\nexport class PasswordHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<PasswordHelper>();\n\n\t/**\n\t * Update the password for a user.\n\t * Validates password strength, verifies the current password if provided, then hashes and stores the new password and raises an audit event.\n\t * @param userEntityStorage The entity storage for users.\n\t * @param authenticationAuditService The optional audit service.\n\t * @param user The user whose password is being updated.\n\t * @param newPassword The new password to set.\n\t * @param currentPassword The current password to verify against, if supplied.\n\t * @param minPasswordLength Optional minimum password length for validation.\n\t * @returns A promise that resolves when the new password has been stored and the audit entry recorded.\n\t */\n\tpublic static async updatePassword(\n\t\tuserEntityStorage: IEntityStorageConnector<AuthenticationUser>,\n\t\tauthenticationAuditService: IAuthenticationAuditComponent | undefined,\n\t\tuser: AuthenticationUser,\n\t\tnewPassword: string,\n\t\tcurrentPassword?: string,\n\t\tminPasswordLength?: number\n\t): Promise<void> {\n\t\tPasswordValidator.validatePassword(newPassword, {\n\t\t\tminLength: minPasswordLength\n\t\t});\n\n\t\tif (Is.stringValue(currentPassword)) {\n\t\t\tconst saltBytes = Converter.base64ToBytes(user.salt);\n\t\t\tconst passwordBytes = Converter.utf8ToBytes(currentPassword);\n\t\t\tconst hashedCurrentPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\t\t\tif (!PasswordValidator.comparePasswordHashes(hashedCurrentPassword, user.password)) {\n\t\t\t\tthrow new GeneralError(PasswordHelper.CLASS_NAME, \"currentPasswordMismatch\");\n\t\t\t}\n\t\t}\n\n\t\tconst saltBytes = RandomHelper.generate(16);\n\t\tconst passwordBytes = Converter.utf8ToBytes(newPassword);\n\t\tconst hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\n\t\tconst updatedUser: AuthenticationUser = {\n\t\t\temail: user.email,\n\t\t\tsalt: Converter.bytesToBase64(saltBytes),\n\t\t\tpassword: hashedPassword,\n\t\t\tidentity: user.identity,\n\t\t\torganization: user.organization,\n\t\t\tscope: user.scope,\n\t\t\tpasswordVersion: (user.passwordVersion ?? 0) + 1\n\t\t};\n\n\t\tawait userEntityStorage.set(updatedUser);\n\t\tawait authenticationAuditService?.create({\n\t\t\tactorId: user.email,\n\t\t\tevent: AuthAuditEvent.PasswordChanged,\n\t\t\tdata: {\n\t\t\t\tuserIdentity: updatedUser.identity,\n\t\t\t\torganizationIdentity: updatedUser.organization\n\t\t\t}\n\t\t});\n\t}\n}\n"]}
@@ -1,3 +1,5 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
1
3
  import { Coerce, Is, UnauthorizedError } from "@twin.org/core";
2
4
  import { VaultConnectorHelper } from "@twin.org/vault-models";
3
5
  import { CookieHelper, HeaderHelper, HeaderTypes, Jwt } from "@twin.org/web";
@@ -12,7 +14,6 @@ export class TokenHelper {
12
14
  /**
13
15
  * Create a new token.
14
16
  * @param vaultConnector The vault connector.
15
- * @param urlTransformerComponent The URL transformer component, used to encrypt the tenant ID for inclusion in the token.
16
17
  * @param signingKeyName The signing key name.
17
18
  * @param userIdentity The subject for the token.
18
19
  * @param organizationIdentity The organization for the token.
@@ -22,15 +23,13 @@ export class TokenHelper {
22
23
  * @param passwordVersion The user's current password version counter, embedded in the token so that a password change invalidates existing tokens.
23
24
  * @returns The new token and its expiry date.
24
25
  */
25
- static async createToken(vaultConnector, urlTransformerComponent, signingKeyName, userIdentity, organizationIdentity, tenantId, ttlMinutes, scope, passwordVersion) {
26
+ static async createToken(vaultConnector, signingKeyName, userIdentity, organizationIdentity, tenantId, ttlMinutes, scope, passwordVersion) {
26
27
  const nowSeconds = Math.trunc(Date.now() / 1000);
27
28
  const ttlSeconds = ttlMinutes * 60;
28
29
  const jwt = await Jwt.encodeWithSigner({ alg: "EdDSA" }, {
29
30
  sub: userIdentity,
30
31
  org: organizationIdentity,
31
- tid: Is.stringValue(tenantId)
32
- ? await urlTransformerComponent.encryptParam(tenantId)
33
- : undefined,
32
+ tid: tenantId,
34
33
  exp: nowSeconds + ttlSeconds,
35
34
  scope,
36
35
  pver: passwordVersion
@@ -67,15 +66,15 @@ export class TokenHelper {
67
66
  throw new UnauthorizedError(TokenHelper.CLASS_NAME, "expired");
68
67
  }
69
68
  if (Is.function(verifyUser)) {
70
- const encryptedTenantId = Coerce.string(decoded.payload.tid);
71
- const userVerified = await verifyUser(decoded.payload.sub, decoded.payload.org, encryptedTenantId, Coerce.integer(decoded.payload.pver));
69
+ const tid = Coerce.string(decoded.payload.tid);
70
+ const userVerified = await verifyUser(decoded.payload.sub, decoded.payload.org, tid, Coerce.integer(decoded.payload.pver));
72
71
  if (!userVerified.includes("user")) {
73
72
  throw new UnauthorizedError(TokenHelper.CLASS_NAME, "userNotVerified");
74
73
  }
75
74
  else if (!userVerified.includes("organization")) {
76
75
  throw new UnauthorizedError(TokenHelper.CLASS_NAME, "organizationNotVerified");
77
76
  }
78
- else if (Is.stringValue(encryptedTenantId) && !userVerified.includes("tenant")) {
77
+ else if (Is.stringValue(tid) && !userVerified.includes("tenant")) {
79
78
  throw new UnauthorizedError(TokenHelper.CLASS_NAME, "tenantNotVerified");
80
79
  }
81
80
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tokenHelper.js","sourceRoot":"","sources":["../../../src/utils/tokenHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IUrlTransformerComponent } from \"@twin.org/api-models\";\nimport { Coerce, Is, UnauthorizedError } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { type IVaultConnector, VaultConnectorHelper } from \"@twin.org/vault-models\";\nimport {\n\tCookieHelper,\n\tHeaderHelper,\n\tHeaderTypes,\n\ttype IHttpHeaders,\n\ttype IJwtHeader,\n\ttype IJwtPayload,\n\tJwt\n} from \"@twin.org/web\";\n\n/**\n * Helper class for token operations.\n */\nexport class TokenHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TokenHelper>();\n\n\t/**\n\t * Create a new token.\n\t * @param vaultConnector The vault connector.\n\t * @param urlTransformerComponent The URL transformer component, used to encrypt the tenant ID for inclusion in the token.\n\t * @param signingKeyName The signing key name.\n\t * @param userIdentity The subject for the token.\n\t * @param organizationIdentity The organization for the token.\n\t * @param tenantId The tenant id for the token.\n\t * @param ttlMinutes The time to live for the token in minutes.\n\t * @param scope The scopes for the token.\n\t * @param passwordVersion The user's current password version counter, embedded in the token so that a password change invalidates existing tokens.\n\t * @returns The new token and its expiry date.\n\t */\n\tpublic static async createToken(\n\t\tvaultConnector: IVaultConnector,\n\t\turlTransformerComponent: IUrlTransformerComponent,\n\t\tsigningKeyName: string,\n\t\tuserIdentity: string,\n\t\torganizationIdentity: string | undefined,\n\t\ttenantId: string | undefined,\n\t\tttlMinutes: number,\n\t\tscope?: string,\n\t\tpasswordVersion?: number\n\t): Promise<{\n\t\ttoken: string;\n\t\texpiry: number;\n\t}> {\n\t\tconst nowSeconds = Math.trunc(Date.now() / 1000);\n\t\tconst ttlSeconds = ttlMinutes * 60;\n\n\t\tconst jwt = await Jwt.encodeWithSigner(\n\t\t\t{ alg: \"EdDSA\" },\n\t\t\t{\n\t\t\t\tsub: userIdentity,\n\t\t\t\torg: organizationIdentity,\n\t\t\t\ttid: Is.stringValue(tenantId)\n\t\t\t\t\t? await urlTransformerComponent.encryptParam(tenantId)\n\t\t\t\t\t: undefined,\n\t\t\t\texp: nowSeconds + ttlSeconds,\n\t\t\t\tscope,\n\t\t\t\tpver: passwordVersion\n\t\t\t},\n\t\t\tasync (header, payload) =>\n\t\t\t\tVaultConnectorHelper.jwtSigner(vaultConnector, signingKeyName, header, payload)\n\t\t);\n\n\t\treturn {\n\t\t\ttoken: jwt,\n\t\t\texpiry: (nowSeconds + ttlSeconds) * 1000\n\t\t};\n\t}\n\n\t/**\n\t * Verify the token.\n\t * @param vaultConnector The vault connector.\n\t * @param signingKeyName The signing key name.\n\t * @param token The token to verify.\n\t * @param requiredScopes The required scopes.\n\t * @param verifyUser A function to verify the user identity and organization. The password version counter embedded in the token (pver claim) is passed so callers can detect if the password has changed since the token was issued.\n\t * @returns The verified details.\n\t * @throws UnauthorizedError if the token is missing, invalid or expired.\n\t */\n\tpublic static async verify(\n\t\tvaultConnector: IVaultConnector,\n\t\tsigningKeyName: string,\n\t\ttoken: string | undefined,\n\t\trequiredScopes?: string[],\n\t\tverifyUser?: (\n\t\t\tuserIdentity: string,\n\t\t\torganizationIdentity: string,\n\t\t\tencryptedTenantId: string | undefined,\n\t\t\tpasswordVersion: number | undefined\n\t\t) => Promise<string[]>\n\t): Promise<{\n\t\theader: IJwtHeader;\n\t\tpayload: IJwtPayload;\n\t}> {\n\t\tif (!Is.stringValue(token)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"missing\");\n\t\t}\n\n\t\tconst decoded = await Jwt.verifyWithVerifier(token, async t =>\n\t\t\tVaultConnectorHelper.jwtVerifier(vaultConnector, signingKeyName, t)\n\t\t);\n\n\t\t// If some of the header/payload data is not properly populated then it is unauthorized.\n\t\tif (!Is.stringValue(decoded.payload.sub)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"payloadMissingSubject\");\n\t\t} else if (!Is.stringValue(decoded.payload.org)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"payloadMissingOrganization\");\n\t\t} else if (\n\t\t\t!Is.empty(decoded.payload?.exp) &&\n\t\t\tdecoded.payload.exp < Math.trunc(Date.now() / 1000)\n\t\t) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"expired\");\n\t\t}\n\n\t\tif (Is.function(verifyUser)) {\n\t\t\tconst encryptedTenantId = Coerce.string(decoded.payload.tid);\n\t\t\tconst userVerified = await verifyUser(\n\t\t\t\tdecoded.payload.sub,\n\t\t\t\tdecoded.payload.org,\n\t\t\t\tencryptedTenantId,\n\t\t\t\tCoerce.integer(decoded.payload.pver)\n\t\t\t);\n\t\t\tif (!userVerified.includes(\"user\")) {\n\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"userNotVerified\");\n\t\t\t} else if (!userVerified.includes(\"organization\")) {\n\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"organizationNotVerified\");\n\t\t\t} else if (Is.stringValue(encryptedTenantId) && !userVerified.includes(\"tenant\")) {\n\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"tenantNotVerified\");\n\t\t\t}\n\t\t}\n\n\t\tif (Is.arrayValue(requiredScopes)) {\n\t\t\tconst tokenScopes = Is.stringValue(decoded.payload.scope)\n\t\t\t\t? decoded.payload.scope.split(\",\")\n\t\t\t\t: [];\n\n\t\t\tfor (const requiredScope of requiredScopes) {\n\t\t\t\tif (!tokenScopes.includes(requiredScope)) {\n\t\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"insufficientScopes\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\theader: decoded.header,\n\t\t\tpayload: decoded.payload\n\t\t};\n\t}\n\n\t/**\n\t * Extract the auth token from the headers, either from the authorization header or the cookie header.\n\t * @param headers The headers to extract the token from.\n\t * @param cookieName The name of the cookie to extract the token from.\n\t * @returns The token if found.\n\t */\n\tpublic static extractTokenFromHeaders(\n\t\theaders?: IHttpHeaders,\n\t\tcookieName?: string\n\t):\n\t\t| {\n\t\t\t\ttoken: string;\n\t\t\t\tlocation: \"authorization\" | \"cookie\";\n\t\t }\n\t\t| undefined {\n\t\tconst authHeader = headers?.[HeaderTypes.Authorization];\n\t\tconst cookiesHeader = headers?.[HeaderTypes.Cookie];\n\n\t\tconst bearerToken = HeaderHelper.extractBearer(authHeader);\n\t\tif (Is.stringValue(bearerToken)) {\n\t\t\treturn {\n\t\t\t\ttoken: bearerToken,\n\t\t\t\tlocation: \"authorization\"\n\t\t\t};\n\t\t} else if (Is.notEmpty(cookiesHeader) && Is.stringValue(cookieName)) {\n\t\t\tconst value = CookieHelper.getCookieFromHeaders(cookiesHeader, cookieName);\n\t\t\tif (Is.stringValue(value)) {\n\t\t\t\treturn {\n\t\t\t\t\ttoken: value,\n\t\t\t\t\tlocation: \"cookie\"\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"tokenHelper.js","sourceRoot":"","sources":["../../../src/utils/tokenHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Coerce, Is, UnauthorizedError } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { type IVaultConnector, VaultConnectorHelper } from \"@twin.org/vault-models\";\nimport {\n\tCookieHelper,\n\tHeaderHelper,\n\tHeaderTypes,\n\ttype IHttpHeaders,\n\ttype IJwtHeader,\n\ttype IJwtPayload,\n\tJwt\n} from \"@twin.org/web\";\n\n/**\n * Helper class for token operations.\n */\nexport class TokenHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TokenHelper>();\n\n\t/**\n\t * Create a new token.\n\t * @param vaultConnector The vault connector.\n\t * @param signingKeyName The signing key name.\n\t * @param userIdentity The subject for the token.\n\t * @param organizationIdentity The organization for the token.\n\t * @param tenantId The tenant id for the token.\n\t * @param ttlMinutes The time to live for the token in minutes.\n\t * @param scope The scopes for the token.\n\t * @param passwordVersion The user's current password version counter, embedded in the token so that a password change invalidates existing tokens.\n\t * @returns The new token and its expiry date.\n\t */\n\tpublic static async createToken(\n\t\tvaultConnector: IVaultConnector,\n\t\tsigningKeyName: string,\n\t\tuserIdentity: string,\n\t\torganizationIdentity: string | undefined,\n\t\ttenantId: string | undefined,\n\t\tttlMinutes: number,\n\t\tscope?: string,\n\t\tpasswordVersion?: number\n\t): Promise<{\n\t\ttoken: string;\n\t\texpiry: number;\n\t}> {\n\t\tconst nowSeconds = Math.trunc(Date.now() / 1000);\n\t\tconst ttlSeconds = ttlMinutes * 60;\n\n\t\tconst jwt = await Jwt.encodeWithSigner(\n\t\t\t{ alg: \"EdDSA\" },\n\t\t\t{\n\t\t\t\tsub: userIdentity,\n\t\t\t\torg: organizationIdentity,\n\t\t\t\ttid: tenantId,\n\t\t\t\texp: nowSeconds + ttlSeconds,\n\t\t\t\tscope,\n\t\t\t\tpver: passwordVersion\n\t\t\t},\n\t\t\tasync (header, payload) =>\n\t\t\t\tVaultConnectorHelper.jwtSigner(vaultConnector, signingKeyName, header, payload)\n\t\t);\n\n\t\treturn {\n\t\t\ttoken: jwt,\n\t\t\texpiry: (nowSeconds + ttlSeconds) * 1000\n\t\t};\n\t}\n\n\t/**\n\t * Verify the token.\n\t * @param vaultConnector The vault connector.\n\t * @param signingKeyName The signing key name.\n\t * @param token The token to verify.\n\t * @param requiredScopes The required scopes.\n\t * @param verifyUser A function to verify the user identity and organization. The password version counter embedded in the token (pver claim) is passed so callers can detect if the password has changed since the token was issued.\n\t * @returns The verified details.\n\t * @throws UnauthorizedError if the token is missing, invalid or expired.\n\t */\n\tpublic static async verify(\n\t\tvaultConnector: IVaultConnector,\n\t\tsigningKeyName: string,\n\t\ttoken: string | undefined,\n\t\trequiredScopes?: string[],\n\t\tverifyUser?: (\n\t\t\tsub: string,\n\t\t\torg: string,\n\t\t\ttid: string | undefined,\n\t\t\tpasswordVersion: number | undefined\n\t\t) => Promise<string[]>\n\t): Promise<{\n\t\theader: IJwtHeader;\n\t\tpayload: IJwtPayload;\n\t}> {\n\t\tif (!Is.stringValue(token)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"missing\");\n\t\t}\n\n\t\tconst decoded = await Jwt.verifyWithVerifier(token, async t =>\n\t\t\tVaultConnectorHelper.jwtVerifier(vaultConnector, signingKeyName, t)\n\t\t);\n\n\t\t// If some of the header/payload data is not properly populated then it is unauthorized.\n\t\tif (!Is.stringValue(decoded.payload.sub)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"payloadMissingSubject\");\n\t\t} else if (!Is.stringValue(decoded.payload.org)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"payloadMissingOrganization\");\n\t\t} else if (\n\t\t\t!Is.empty(decoded.payload?.exp) &&\n\t\t\tdecoded.payload.exp < Math.trunc(Date.now() / 1000)\n\t\t) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"expired\");\n\t\t}\n\n\t\tif (Is.function(verifyUser)) {\n\t\t\tconst tid = Coerce.string(decoded.payload.tid);\n\t\t\tconst userVerified = await verifyUser(\n\t\t\t\tdecoded.payload.sub,\n\t\t\t\tdecoded.payload.org,\n\t\t\t\ttid,\n\t\t\t\tCoerce.integer(decoded.payload.pver)\n\t\t\t);\n\t\t\tif (!userVerified.includes(\"user\")) {\n\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"userNotVerified\");\n\t\t\t} else if (!userVerified.includes(\"organization\")) {\n\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"organizationNotVerified\");\n\t\t\t} else if (Is.stringValue(tid) && !userVerified.includes(\"tenant\")) {\n\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"tenantNotVerified\");\n\t\t\t}\n\t\t}\n\n\t\tif (Is.arrayValue(requiredScopes)) {\n\t\t\tconst tokenScopes = Is.stringValue(decoded.payload.scope)\n\t\t\t\t? decoded.payload.scope.split(\",\")\n\t\t\t\t: [];\n\n\t\t\tfor (const requiredScope of requiredScopes) {\n\t\t\t\tif (!tokenScopes.includes(requiredScope)) {\n\t\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"insufficientScopes\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\theader: decoded.header,\n\t\t\tpayload: decoded.payload\n\t\t};\n\t}\n\n\t/**\n\t * Extract the auth token from the headers, either from the authorization header or the cookie header.\n\t * @param headers The headers to extract the token from.\n\t * @param cookieName The name of the cookie to extract the token from.\n\t * @returns The token if found.\n\t */\n\tpublic static extractTokenFromHeaders(\n\t\theaders?: IHttpHeaders,\n\t\tcookieName?: string\n\t):\n\t\t| {\n\t\t\t\ttoken: string;\n\t\t\t\tlocation: \"authorization\" | \"cookie\";\n\t\t }\n\t\t| undefined {\n\t\tconst authHeader = headers?.[HeaderTypes.Authorization];\n\t\tconst cookiesHeader = headers?.[HeaderTypes.Cookie];\n\n\t\tconst bearerToken = HeaderHelper.extractBearer(authHeader);\n\t\tif (Is.stringValue(bearerToken)) {\n\t\t\treturn {\n\t\t\t\ttoken: bearerToken,\n\t\t\t\tlocation: \"authorization\"\n\t\t\t};\n\t\t} else if (Is.notEmpty(cookiesHeader) && Is.stringValue(cookieName)) {\n\t\t\tconst value = CookieHelper.getCookieFromHeaders(cookiesHeader, cookieName);\n\t\t\tif (Is.stringValue(value)) {\n\t\t\t\treturn {\n\t\t\t\t\ttoken: value,\n\t\t\t\t\tlocation: \"cookie\"\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -13,10 +13,6 @@ export interface IAuthHeaderProcessorConstructorOptions {
13
13
  * @default vault
14
14
  */
15
15
  vaultConnectorType?: string;
16
- /**
17
- * The URL transformer component for the tenants.
18
- */
19
- urlTransformerComponentType?: string;
20
16
  /**
21
17
  * The component to retrieve tenant information.
22
18
  * @default tenant-admin
@@ -13,10 +13,6 @@ export interface IEntityStorageAuthenticationServiceConstructorOptions {
13
13
  * @default vault
14
14
  */
15
15
  vaultConnectorType?: string;
16
- /**
17
- * The URL transformer component for the tenants.
18
- */
19
- urlTransformerComponentType?: string;
20
16
  /**
21
17
  * The audit service.
22
18
  * @default authentication-audit
@@ -22,7 +22,7 @@ export declare class AuthHeaderProcessor implements IBaseRouteProcessor {
22
22
  /**
23
23
  * The service needs to be started when the application is initialized.
24
24
  * @param nodeLoggingComponentType The node logging component type.
25
- * @returns Nothing.
25
+ * @returns A promise that resolves when the node identity and organization ID have been cached.
26
26
  */
27
27
  start(nodeLoggingComponentType?: string): Promise<void>;
28
28
  /**
@@ -32,6 +32,7 @@ export declare class AuthHeaderProcessor implements IBaseRouteProcessor {
32
32
  * @param route The route to process.
33
33
  * @param contextIds The context IDs of the request.
34
34
  * @param processorState The state handed through the processors.
35
+ * @returns A promise that resolves when the JWT has been verified and the context populated, or an error response set.
35
36
  */
36
37
  pre(request: IHttpServerRequest, response: IHttpResponse, route: IBaseRoute | undefined, contextIds: IContextIds, processorState: {
37
38
  [id: string]: unknown;
@@ -43,6 +44,7 @@ export declare class AuthHeaderProcessor implements IBaseRouteProcessor {
43
44
  * @param route The route to process.
44
45
  * @param contextIds The context IDs of the request.
45
46
  * @param processorState The state handed through the processors.
47
+ * @returns A promise that resolves when the Set-Cookie header has been applied to the response if required.
46
48
  */
47
49
  post(request: IHttpServerRequest, response: IHttpResponse, route: IBaseRoute | undefined, contextIds: IContextIds, processorState: {
48
50
  [id: string]: unknown;
@@ -1,2 +1,5 @@
1
1
  import type { IRestRouteEntryPoint } from "@twin.org/api-models";
2
+ /**
3
+ * REST entry points for the authentication, authentication admin, and authentication audit services.
4
+ */
2
5
  export declare const restEntryPoints: IRestRouteEntryPoint[];
@@ -21,7 +21,7 @@ export declare class EntityStorageAuthenticationAdminService implements IAuthent
21
21
  /**
22
22
  * Create a login for the user.
23
23
  * @param user The user to create.
24
- * @returns Nothing.
24
+ * @returns A promise that resolves when the user account has been created and the audit entry recorded.
25
25
  */
26
26
  create(user: IAuthenticationUser & {
27
27
  password: string;
@@ -29,7 +29,7 @@ export declare class EntityStorageAuthenticationAdminService implements IAuthent
29
29
  /**
30
30
  * Update a login for the user.
31
31
  * @param user The user to update.
32
- * @returns Nothing.
32
+ * @returns A promise that resolves when the user account has been updated and the audit entry recorded.
33
33
  */
34
34
  update(user: Partial<IAuthenticationUser>): Promise<void>;
35
35
  /**
@@ -47,7 +47,7 @@ export declare class EntityStorageAuthenticationAdminService implements IAuthent
47
47
  /**
48
48
  * Remove the current user.
49
49
  * @param email The email address of the user to remove.
50
- * @returns Nothing.
50
+ * @returns A promise that resolves when the user account has been removed and the audit entry recorded.
51
51
  */
52
52
  remove(email: string): Promise<void>;
53
53
  /**
@@ -55,7 +55,7 @@ export declare class EntityStorageAuthenticationAdminService implements IAuthent
55
55
  * @param email The email address of the user to update.
56
56
  * @param newPassword The new password for the user.
57
57
  * @param currentPassword The current password, optional, if supplied will check against existing.
58
- * @returns Nothing.
58
+ * @returns A promise that resolves when the password has been updated.
59
59
  */
60
60
  updatePassword(email: string, newPassword: string, currentPassword?: string): Promise<void>;
61
61
  }
@@ -17,13 +17,13 @@ export declare class EntityStorageAuthenticationRateService implements IAuthenti
17
17
  * Register or update rate-limit configuration for an action.
18
18
  * @param action The action name.
19
19
  * @param config The action configuration.
20
- * @returns Nothing.
20
+ * @returns A promise that resolves when the action configuration has been stored.
21
21
  */
22
22
  registerAction(action: string, config: IAuthenticationRateActionConfig): Promise<void>;
23
23
  /**
24
24
  * Unregister rate-limit configuration for an action.
25
25
  * @param action The action name.
26
- * @returns Nothing.
26
+ * @returns A promise that resolves when the action configuration has been removed.
27
27
  */
28
28
  unregisterAction(action: string): Promise<void>;
29
29
  /**
@@ -34,13 +34,13 @@ export declare class EntityStorageAuthenticationRateService implements IAuthenti
34
34
  /**
35
35
  * The service needs to be started when the application is initialized.
36
36
  * @param nodeLoggingComponentType The node logging component type.
37
- * @returns Nothing.
37
+ * @returns A promise that resolves when the periodic cleanup task has been registered.
38
38
  */
39
39
  start(nodeLoggingComponentType?: string): Promise<void>;
40
40
  /**
41
41
  * The component needs to be stopped when the node is closed.
42
42
  * @param nodeLoggingComponentType The node logging component type.
43
- * @returns Nothing.
43
+ * @returns A promise that resolves when the periodic cleanup task has been removed.
44
44
  */
45
45
  stop(nodeLoggingComponentType?: string): Promise<void>;
46
46
  /**
@@ -54,7 +54,7 @@ export declare class EntityStorageAuthenticationRateService implements IAuthenti
54
54
  * Clear the authentication rate entry for the given action and identifier.
55
55
  * @param action The action to clear.
56
56
  * @param identifier The identifier to clear.
57
- * @returns Nothing.
57
+ * @returns A promise that resolves when the rate entry has been removed.
58
58
  */
59
59
  clear(action: string, identifier: string): Promise<void>;
60
60
  }
@@ -21,13 +21,13 @@ export declare class EntityStorageAuthenticationService implements IAuthenticati
21
21
  /**
22
22
  * The service needs to be started when the application is initialized.
23
23
  * @param nodeLoggingComponentType The node logging component type.
24
- * @returns Nothing.
24
+ * @returns A promise that resolves when rate-limit actions have been registered and the node identity cached.
25
25
  */
26
26
  start(nodeLoggingComponentType?: string): Promise<void>;
27
27
  /**
28
28
  * The component needs to be stopped when the node is closed.
29
29
  * @param nodeLoggingComponentType The node logging component type.
30
- * @returns Nothing.
30
+ * @returns A promise that resolves when all rate-limit actions have been unregistered.
31
31
  */
32
32
  stop(nodeLoggingComponentType?: string): Promise<void>;
33
33
  /**
@@ -43,7 +43,7 @@ export declare class EntityStorageAuthenticationService implements IAuthenticati
43
43
  /**
44
44
  * Logout the current user.
45
45
  * @param token The token to logout, if it uses a mechanism with public access.
46
- * @returns Nothing.
46
+ * @returns A promise that resolves when the logout audit entry has been recorded.
47
47
  */
48
48
  logout(token?: string): Promise<void>;
49
49
  /**
@@ -59,7 +59,7 @@ export declare class EntityStorageAuthenticationService implements IAuthenticati
59
59
  * Update the user's password.
60
60
  * @param currentPassword The current password for the user.
61
61
  * @param newPassword The new password for the user.
62
- * @returns Nothing.
62
+ * @returns A promise that resolves when the password has been updated and the rate limit cleared.
63
63
  */
64
64
  updatePassword(currentPassword: string, newPassword: string): Promise<void>;
65
65
  }
@@ -18,7 +18,7 @@ export declare class PasswordHelper {
18
18
  * @param newPassword The new password to set.
19
19
  * @param currentPassword The current password to verify against, if supplied.
20
20
  * @param minPasswordLength Optional minimum password length for validation.
21
- * @returns Nothing.
21
+ * @returns A promise that resolves when the new password has been stored and the audit entry recorded.
22
22
  */
23
23
  static updatePassword(userEntityStorage: IEntityStorageConnector<AuthenticationUser>, authenticationAuditService: IAuthenticationAuditComponent | undefined, user: AuthenticationUser, newPassword: string, currentPassword?: string, minPasswordLength?: number): Promise<void>;
24
24
  }
@@ -1,4 +1,3 @@
1
- import type { IUrlTransformerComponent } from "@twin.org/api-models";
2
1
  import { type IVaultConnector } from "@twin.org/vault-models";
3
2
  import { type IHttpHeaders, type IJwtHeader, type IJwtPayload } from "@twin.org/web";
4
3
  /**
@@ -12,7 +11,6 @@ export declare class TokenHelper {
12
11
  /**
13
12
  * Create a new token.
14
13
  * @param vaultConnector The vault connector.
15
- * @param urlTransformerComponent The URL transformer component, used to encrypt the tenant ID for inclusion in the token.
16
14
  * @param signingKeyName The signing key name.
17
15
  * @param userIdentity The subject for the token.
18
16
  * @param organizationIdentity The organization for the token.
@@ -22,7 +20,7 @@ export declare class TokenHelper {
22
20
  * @param passwordVersion The user's current password version counter, embedded in the token so that a password change invalidates existing tokens.
23
21
  * @returns The new token and its expiry date.
24
22
  */
25
- static createToken(vaultConnector: IVaultConnector, urlTransformerComponent: IUrlTransformerComponent, signingKeyName: string, userIdentity: string, organizationIdentity: string | undefined, tenantId: string | undefined, ttlMinutes: number, scope?: string, passwordVersion?: number): Promise<{
23
+ static createToken(vaultConnector: IVaultConnector, signingKeyName: string, userIdentity: string, organizationIdentity: string | undefined, tenantId: string | undefined, ttlMinutes: number, scope?: string, passwordVersion?: number): Promise<{
26
24
  token: string;
27
25
  expiry: number;
28
26
  }>;
@@ -36,7 +34,7 @@ export declare class TokenHelper {
36
34
  * @returns The verified details.
37
35
  * @throws UnauthorizedError if the token is missing, invalid or expired.
38
36
  */
39
- static verify(vaultConnector: IVaultConnector, signingKeyName: string, token: string | undefined, requiredScopes?: string[], verifyUser?: (userIdentity: string, organizationIdentity: string, encryptedTenantId: string | undefined, passwordVersion: number | undefined) => Promise<string[]>): Promise<{
37
+ static verify(vaultConnector: IVaultConnector, signingKeyName: string, token: string | undefined, requiredScopes?: string[], verifyUser?: (sub: string, org: string, tid: string | undefined, passwordVersion: number | undefined) => Promise<string[]>): Promise<{
40
38
  header: IJwtHeader;
41
39
  payload: IJwtPayload;
42
40
  }>;