@3sixty/api-auth-entity-storage-service 0.20.0-next.0 → 0.20.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/es/entities/authenticationUser.js +1 -9
- package/dist/es/entities/authenticationUser.js.map +1 -1
- package/dist/es/entities/authenticationUserV0.js +69 -0
- package/dist/es/entities/authenticationUserV0.js.map +1 -0
- package/dist/es/index.js +1 -9
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IAuthHeaderProcessorConfig.js.map +1 -1
- package/dist/es/models/IAuthHeaderProcessorConstructorOptions.js.map +1 -1
- package/dist/es/models/IAuthTokenContext.js.map +1 -1
- package/dist/es/models/IEntityStorageAuthenticationAdminServiceConfig.js.map +1 -1
- package/dist/es/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js.map +1 -1
- package/dist/es/models/IEntityStorageAuthenticationServiceConfig.js.map +1 -1
- package/dist/es/models/IEntityStorageAuthenticationServiceConstructorOptions.js.map +1 -1
- package/dist/es/processors/authHeaderProcessor.js +9 -14
- package/dist/es/processors/authHeaderProcessor.js.map +1 -1
- package/dist/es/restEntryPoints.js +1 -8
- package/dist/es/restEntryPoints.js.map +1 -1
- package/dist/es/routes/entityStorageAuthenticationAdminRoutes.js +22 -14
- package/dist/es/routes/entityStorageAuthenticationAdminRoutes.js.map +1 -1
- package/dist/es/routes/entityStorageAuthenticationRoutes.js +45 -1
- package/dist/es/routes/entityStorageAuthenticationRoutes.js.map +1 -1
- package/dist/es/schema.js +2 -4
- package/dist/es/schema.js.map +1 -1
- package/dist/es/services/entityStorageAuthenticationAdminService.js +69 -68
- package/dist/es/services/entityStorageAuthenticationAdminService.js.map +1 -1
- package/dist/es/services/entityStorageAuthenticationService.js +29 -17
- package/dist/es/services/entityStorageAuthenticationService.js.map +1 -1
- package/dist/es/utils/passwordHelper.js +7 -5
- package/dist/es/utils/passwordHelper.js.map +1 -1
- package/dist/es/utils/tokenHelper.js +2 -23
- package/dist/es/utils/tokenHelper.js.map +1 -1
- package/dist/types/entities/authenticationUser.d.ts +0 -4
- package/dist/types/entities/authenticationUserV0.d.ts +33 -0
- package/dist/types/index.d.ts +1 -9
- package/dist/types/models/IAuthHeaderProcessorConfig.d.ts +1 -2
- package/dist/types/models/IAuthTokenContext.d.ts +0 -4
- package/dist/types/models/IEntityStorageAuthenticationAdminServiceConfig.d.ts +14 -3
- package/dist/types/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.d.ts +6 -1
- package/dist/types/models/IEntityStorageAuthenticationServiceConfig.d.ts +7 -2
- package/dist/types/restEntryPoints.d.ts +1 -1
- package/dist/types/routes/entityStorageAuthenticationRoutes.d.ts +10 -2
- package/dist/types/services/entityStorageAuthenticationAdminService.d.ts +6 -2
- package/dist/types/services/entityStorageAuthenticationService.d.ts +6 -1
- package/dist/types/utils/passwordHelper.d.ts +6 -3
- package/dist/types/utils/tokenHelper.d.ts +2 -11
- package/docs/changelog.md +19 -0
- package/docs/examples.md +9 -93
- package/docs/open-api/spec.json +178 -956
- package/docs/reference/classes/AuthHeaderProcessor.md +4 -0
- package/docs/reference/classes/AuthenticationUser.md +0 -8
- package/docs/reference/classes/AuthenticationUserV0.md +69 -0
- package/docs/reference/classes/EntityStorageAuthenticationAdminService.md +11 -3
- package/docs/reference/classes/EntityStorageAuthenticationService.md +18 -0
- package/docs/reference/classes/PasswordHelper.md +4 -4
- package/docs/reference/classes/TokenHelper.md +2 -44
- package/docs/reference/functions/authenticationPasswordPolicy.md +31 -0
- package/docs/reference/index.md +2 -15
- package/docs/reference/interfaces/IAuthHeaderProcessorConfig.md +1 -2
- package/docs/reference/interfaces/IAuthTokenContext.md +0 -8
- package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConfig.md +38 -4
- package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md +15 -1
- package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConfig.md +15 -1
- package/docs/reference/variables/restEntryPoints.md +1 -1
- package/locales/en.json +1 -16
- package/package.json +7 -8
- package/dist/es/entities/authenticationAuditEntry.js +0 -121
- package/dist/es/entities/authenticationAuditEntry.js.map +0 -1
- package/dist/es/entities/authenticationRateEntry.js +0 -37
- package/dist/es/entities/authenticationRateEntry.js.map +0 -1
- package/dist/es/models/IEntityStorageAuthenticationAuditServiceConfig.js +0 -4
- package/dist/es/models/IEntityStorageAuthenticationAuditServiceConfig.js.map +0 -1
- package/dist/es/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.js +0 -2
- package/dist/es/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.js.map +0 -1
- package/dist/es/models/IEntityStorageAuthenticationRateServiceConfig.js +0 -2
- package/dist/es/models/IEntityStorageAuthenticationRateServiceConfig.js.map +0 -1
- package/dist/es/models/IEntityStorageAuthenticationRateServiceConstructorOptions.js +0 -2
- package/dist/es/models/IEntityStorageAuthenticationRateServiceConstructorOptions.js.map +0 -1
- package/dist/es/routes/entityStorageAuthenticationAuditRoutes.js +0 -343
- package/dist/es/routes/entityStorageAuthenticationAuditRoutes.js.map +0 -1
- package/dist/es/services/entityStorageAuthenticationAuditService.js +0 -240
- package/dist/es/services/entityStorageAuthenticationAuditService.js.map +0 -1
- package/dist/es/services/entityStorageAuthenticationRateService.js +0 -213
- package/dist/es/services/entityStorageAuthenticationRateService.js.map +0 -1
- package/dist/types/entities/authenticationAuditEntry.d.ts +0 -49
- package/dist/types/entities/authenticationRateEntry.d.ts +0 -17
- package/dist/types/models/IEntityStorageAuthenticationAuditServiceConfig.d.ts +0 -9
- package/dist/types/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.d.ts +0 -15
- package/dist/types/models/IEntityStorageAuthenticationRateServiceConfig.d.ts +0 -10
- package/dist/types/models/IEntityStorageAuthenticationRateServiceConstructorOptions.d.ts +0 -25
- package/dist/types/routes/entityStorageAuthenticationAuditRoutes.d.ts +0 -54
- package/dist/types/services/entityStorageAuthenticationAuditService.d.ts +0 -72
- package/dist/types/services/entityStorageAuthenticationRateService.d.ts +0 -60
- package/docs/reference/classes/AuthenticationAuditEntry.md +0 -101
- package/docs/reference/classes/AuthenticationRateEntry.md +0 -37
- package/docs/reference/classes/EntityStorageAuthenticationAuditService.md +0 -241
- package/docs/reference/classes/EntityStorageAuthenticationRateService.md +0 -227
- package/docs/reference/functions/authenticationAuditCreate.md +0 -37
- package/docs/reference/functions/authenticationAuditGet.md +0 -31
- package/docs/reference/functions/authenticationAuditQuery.md +0 -31
- package/docs/reference/functions/authenticationAuditRemove.md +0 -31
- package/docs/reference/functions/authenticationAuditUpdate.md +0 -31
- package/docs/reference/functions/generateRestRoutesAuthenticationAudit.md +0 -25
- package/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConfig.md +0 -11
- package/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConstructorOptions.md +0 -25
- package/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConfig.md +0 -17
- package/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConstructorOptions.md +0 -53
- package/docs/reference/variables/tagsAuthenticationAudit.md +0 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entityStorageAuthenticationAdminService.js","sourceRoot":"","sources":["../../../src/services/entityStorageAuthenticationAdminService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIAuthenticationAdminComponent,\n\tIAuthenticationAuditComponent,\n\tIAuthenticationUser\n} from \"@3sixty/api-auth-entity-storage-models\";\nimport { AuthAuditEvent } from \"@3sixty/api-auth-entity-storage-models\";\nimport { ForbiddenError, HttpContextIdKeys, ScopeHelper } from \"@3sixty/api-models\";\nimport { ContextIdKeys, ContextIdStore } from \"@3sixty/context\";\nimport {\n\tBaseError,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tRandomHelper\n} from \"@3sixty/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@3sixty/crypto\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@3sixty/entity-storage-models\";\nimport { nameof } from \"@3sixty/nameof\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\nimport type { IEntityStorageAuthenticationAdminServiceConstructorOptions } from \"../models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js\";\nimport { PasswordHelper } from \"../utils/passwordHelper.js\";\n\n/**\n * Implementation of the authentication component using entity storage.\n */\nexport class EntityStorageAuthenticationAdminService implements IAuthenticationAdminComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntityStorageAuthenticationAdminService>();\n\n\t/**\n\t * The scope value that grants permission to assign privileged roles.\n\t */\n\tpublic static readonly DEFAULT_ESCALATED_PRIVILEGE_SCOPE: string = \"global-admin\";\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 audit service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationAuditService?: IAuthenticationAuditComponent;\n\n\t/**\n\t * The minimum password length.\n\t * @internal\n\t */\n\tprivate readonly _minPasswordLength?: number;\n\n\t/**\n\t * The scope value that grants permission to assign privileged roles.\n\t * @internal\n\t */\n\tprivate readonly _escalatedPrivilegeScope: 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?: IEntityStorageAuthenticationAdminServiceConstructorOptions) {\n\t\tthis._userEntityStorage = EntityStorageConnectorFactory.get(\n\t\t\toptions?.userEntityStorageType ?? \"authentication-user\"\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._minPasswordLength = options?.config?.minPasswordLength;\n\t\tthis._escalatedPrivilegeScope = Is.stringValue(options?.config?.escalatedPrivilegeScope)\n\t\t\t? options?.config?.escalatedPrivilegeScope\n\t\t\t: EntityStorageAuthenticationAdminService.DEFAULT_ESCALATED_PRIVILEGE_SCOPE;\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 EntityStorageAuthenticationAdminService.CLASS_NAME;\n\t}\n\n\t/**\n\t * Create a login for the user.\n\t * @param user The user to create.\n\t * @returns A promise that resolves when the user account has been created and the audit entry recorded.\n\t */\n\tpublic async create(user: IAuthenticationUser & { password: string }): Promise<void> {\n\t\tGuards.object<IAuthenticationUser>(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(user),\n\t\t\tuser\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(user.email),\n\t\t\tuser.email\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(user.password),\n\t\t\tuser.password\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(user.userIdentity),\n\t\t\tuser.userIdentity\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(user.organizationIdentity),\n\t\t\tuser.organizationIdentity\n\t\t);\n\t\tGuards.array<string>(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(user.scope),\n\t\t\tuser.scope\n\t\t);\n\n\t\tawait this.guardEscalatedPrivilegeScopeAssignment(user.scope);\n\n\t\ttry {\n\t\t\tPasswordValidator.validatePassword(user.password, {\n\t\t\t\tminLength: this._minPasswordLength\n\t\t\t});\n\n\t\t\tconst existingUser = await this._userEntityStorage.get(user.email);\n\t\t\tif (Is.object<AuthenticationUser>(existingUser)) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, \"userExists\");\n\t\t\t}\n\n\t\t\tconst saltBytes = RandomHelper.generate(16);\n\t\t\tconst passwordBytes = Converter.utf8ToBytes(user.password);\n\n\t\t\tconst hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\n\t\t\tconst newUser: AuthenticationUser = {\n\t\t\t\temail: user.email,\n\t\t\t\tsalt: Converter.bytesToBase64(saltBytes),\n\t\t\t\tpassword: hashedPassword,\n\t\t\t\tidentity: user.userIdentity,\n\t\t\t\torganization: user.organizationIdentity,\n\t\t\t\tscope: ScopeHelper.toString(user.scope),\n\t\t\t\tpasswordVersion: 0\n\t\t\t};\n\n\t\t\tawait this._userEntityStorage.set(newUser);\n\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tconst requestorTenantId = contextIds?.[ContextIdKeys.Tenant];\n\t\t\tconst originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant];\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: user.email,\n\t\t\t\tevent: AuthAuditEvent.AccountCreated,\n\t\t\t\tdata: {\n\t\t\t\t\tuserIdentity: user.userIdentity,\n\t\t\t\t\torganizationIdentity: user.organizationIdentity,\n\t\t\t\t\ttenantId: requestorTenantId,\n\t\t\t\t\toriginalTenantId,\n\t\t\t\t\tscope: user.scope\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"createUserFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Update a login for the user.\n\t * @param user The user to update.\n\t * @returns A promise that resolves when the user account has been updated and the audit entry recorded.\n\t */\n\tpublic async update(user: Partial<IAuthenticationUser>): Promise<void> {\n\t\tGuards.object<IAuthenticationUser>(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(user),\n\t\t\tuser\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(user.email),\n\t\t\tuser.email\n\t\t);\n\n\t\tif (!Is.empty(user.userIdentity)) {\n\t\t\tGuards.stringValue(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\tnameof(user.userIdentity),\n\t\t\t\tuser.userIdentity\n\t\t\t);\n\t\t}\n\t\tif (!Is.empty(user.organizationIdentity)) {\n\t\t\tGuards.stringValue(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\tnameof(user.organizationIdentity),\n\t\t\t\tuser.organizationIdentity\n\t\t\t);\n\t\t}\n\t\tif (!Is.empty(user.scope)) {\n\t\t\tGuards.array<string>(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\tnameof(user.scope),\n\t\t\t\tuser.scope\n\t\t\t);\n\t\t\tawait this.guardEscalatedPrivilegeScopeAssignment(user.scope);\n\t\t}\n\n\t\ttry {\n\t\t\tconst existingUser = await this._userEntityStorage.get(user.email);\n\t\t\tif (!Is.object<AuthenticationUser>(existingUser)) {\n\t\t\t\tthrow new NotFoundError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"userNotFound\",\n\t\t\t\t\tuser.email\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait this.guardEscalatedPrivilegeTarget(existingUser.scope);\n\n\t\t\tconst updatedFields: string[] = [];\n\t\t\tconst updatedScope = Is.array(user.scope)\n\t\t\t\t? ScopeHelper.toString(user.scope)\n\t\t\t\t: existingUser.scope;\n\n\t\t\tif (user.userIdentity !== undefined && user.userIdentity !== existingUser.identity) {\n\t\t\t\tupdatedFields.push(\"userIdentity\");\n\t\t\t}\n\t\t\tif (\n\t\t\t\tuser.organizationIdentity !== undefined &&\n\t\t\t\tuser.organizationIdentity !== existingUser.organization\n\t\t\t) {\n\t\t\t\tupdatedFields.push(\"organizationIdentity\");\n\t\t\t}\n\t\t\tif (Is.array(user.scope) && updatedScope !== existingUser.scope) {\n\t\t\t\tupdatedFields.push(\"scope\");\n\t\t\t}\n\n\t\t\texistingUser.identity = user.userIdentity ?? existingUser.identity;\n\t\t\texistingUser.organization = user.organizationIdentity ?? existingUser.organization;\n\t\t\texistingUser.scope = Is.array(user.scope) ? updatedScope : existingUser.scope;\n\n\t\t\tawait this._userEntityStorage.set(existingUser);\n\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tconst requestorTenantId = contextIds?.[ContextIdKeys.Tenant];\n\t\t\tconst originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant];\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: existingUser.email,\n\t\t\t\tevent: AuthAuditEvent.AccountUpdated,\n\t\t\t\tdata: {\n\t\t\t\t\tupdatedFields,\n\t\t\t\t\tuserIdentity: existingUser.identity,\n\t\t\t\t\torganizationIdentity: existingUser.organization,\n\t\t\t\t\ttenantId: requestorTenantId,\n\t\t\t\t\toriginalTenantId,\n\t\t\t\t\tscope: ScopeHelper.toArray(existingUser.scope)\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tif (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"updateUserFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get a user by email.\n\t * @param email The email address of the user to get.\n\t * @returns The user details.\n\t */\n\tpublic async get(email: string): Promise<IAuthenticationUser> {\n\t\tGuards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, nameof(email), email);\n\n\t\ttry {\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\t\tthrow new NotFoundError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"userNotFound\",\n\t\t\t\t\temail\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\temail: user.email,\n\t\t\t\tuserIdentity: user.identity,\n\t\t\t\torganizationIdentity: user.organization,\n\t\t\t\tscope: ScopeHelper.toArray(user.scope)\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"getUserFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get a user by identity.\n\t * @param identity The identity of the user to get.\n\t * @returns The user details.\n\t */\n\tpublic async getByIdentity(identity: string): Promise<IAuthenticationUser> {\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(identity),\n\t\t\tidentity\n\t\t);\n\n\t\ttry {\n\t\t\tconst user = await this._userEntityStorage.get(identity, \"identity\");\n\t\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\t\tthrow new NotFoundError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"userNotFound\",\n\t\t\t\t\tidentity\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\temail: user.email,\n\t\t\t\tuserIdentity: user.identity,\n\t\t\t\torganizationIdentity: user.organization,\n\t\t\t\tscope: ScopeHelper.toArray(user.scope)\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"getUserFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Remove the current user.\n\t * @param email The email address of the user to remove.\n\t * @returns A promise that resolves when the user account has been removed and the audit entry recorded.\n\t */\n\tpublic async remove(email: string): Promise<void> {\n\t\tGuards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, nameof(email), email);\n\n\t\ttry {\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\t\tthrow new NotFoundError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"userNotFound\",\n\t\t\t\t\temail\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait this.guardEscalatedPrivilegeTarget(user.scope);\n\n\t\t\tawait this._userEntityStorage.remove(email);\n\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tconst requestorTenantId = contextIds?.[ContextIdKeys.Tenant];\n\t\t\tconst originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant];\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: email,\n\t\t\t\tevent: AuthAuditEvent.AccountDeleted,\n\t\t\t\tdata: {\n\t\t\t\t\tuserIdentity: user.identity,\n\t\t\t\t\torganizationIdentity: user.organization,\n\t\t\t\t\ttenantId: requestorTenantId,\n\t\t\t\t\toriginalTenantId,\n\t\t\t\t\tscope: ScopeHelper.toArray(user.scope)\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tif (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"removeUserFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Update the user's password.\n\t * @param email The email address of the user to update.\n\t * @param newPassword The new password for the user.\n\t * @param currentPassword The current password, optional, if supplied will check against existing.\n\t * @returns A promise that resolves when the password has been updated.\n\t */\n\tpublic async updatePassword(\n\t\temail: string,\n\t\tnewPassword: string,\n\t\tcurrentPassword?: string\n\t): Promise<void> {\n\t\tGuards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, nameof(email), email);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(newPassword),\n\t\t\tnewPassword\n\t\t);\n\n\t\ttry {\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\t\tthrow new NotFoundError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"userNotFound\",\n\t\t\t\t\temail\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait this.guardEscalatedPrivilegeTarget(user.scope);\n\n\t\t\tawait PasswordHelper.updatePassword(\n\t\t\t\tthis._userEntityStorage,\n\t\t\t\tthis._authenticationAuditService,\n\t\t\t\tuser,\n\t\t\t\tnewPassword,\n\t\t\t\tcurrentPassword,\n\t\t\t\tthis._minPasswordLength\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"updatePasswordFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Throws ForbiddenError when the given scope list includes the escalated privilege scope but\n\t * the calling request does not hold that scope itself.\n\t * @param scopeList The scopes being assigned.\n\t * @internal\n\t */\n\tprivate async guardEscalatedPrivilegeScopeAssignment(scopeList: string[]): Promise<void> {\n\t\tif (!ScopeHelper.includes(scopeList, this._escalatedPrivilegeScope)) {\n\t\t\treturn;\n\t\t}\n\t\tif (!(await this.callerHasEscalatedPrivilege())) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"insufficientScopeForEscalatedPrivilege\",\n\t\t\t\t{ scopeName: this._escalatedPrivilegeScope }\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Throws ForbiddenError when the account being written to already holds the escalated privilege\n\t * scope but the calling request does not hold that scope itself. Without this an admin with a\n\t * lesser scope could reset the password of, remove, or demote a privileged account.\n\t * @param targetScope The scopes currently held by the account being written to.\n\t * @internal\n\t */\n\tprivate async guardEscalatedPrivilegeTarget(targetScope: string | string[]): Promise<void> {\n\t\tif (!ScopeHelper.includes(targetScope, this._escalatedPrivilegeScope)) {\n\t\t\treturn;\n\t\t}\n\t\tif (!(await this.callerHasEscalatedPrivilege())) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"insufficientScopeForEscalatedTarget\",\n\t\t\t\t{ scopeName: this._escalatedPrivilegeScope }\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Does the calling request hold the escalated privilege scope.\n\t * @returns True when the caller holds the escalated privilege scope.\n\t * @internal\n\t */\n\tprivate async callerHasEscalatedPrivilege(): Promise<boolean> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\treturn ScopeHelper.includes(\n\t\t\tcontextIds?.[HttpContextIdKeys.Scope],\n\t\t\tthis._escalatedPrivilegeScope\n\t\t);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"entityStorageAuthenticationAdminService.js","sources":["../../../src/services/entityStorageAuthenticationAdminService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIAuthenticationAdminComponent,\n\tIAuthenticationUser\n} from \"@3sixty/api-auth-entity-storage-models\";\nimport { type IAuthenticationAuditComponent, AuthAuditEvent } from \"@3sixty/api-auth-models\";\nimport { ForbiddenError, HttpContextIdKeys } from \"@3sixty/api-models\";\nimport type { IAuthorizationComponent } from \"@3sixty/authorization-models\";\nimport { ContextIdKeys, ContextIdStore } from \"@3sixty/context\";\nimport {\n\tBaseError,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tRandomHelper\n} from \"@3sixty/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@3sixty/crypto\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@3sixty/entity-storage-models\";\n\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\nimport type { IEntityStorageAuthenticationAdminServiceConstructorOptions } from \"../models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js\";\nimport { PasswordHelper } from \"../utils/passwordHelper.js\";\n\n/**\n * Implementation of the authentication component using entity storage.\n */\nexport class EntityStorageAuthenticationAdminService implements IAuthenticationAdminComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = \"EntityStorageAuthenticationAdminService\";\n\n\t/**\n\t * The default role that grants escalated privilege.\n\t */\n\tpublic static readonly DEFAULT_ESCALATED_PRIVILEGE_ROLE: string = \"global-admin\";\n\n\t/**\n\t * The default authorization model ID used to look up roles.\n\t */\n\tpublic static readonly DEFAULT_AUTHORIZATION_MODEL_ID: string = \"system\";\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 audit component.\n\t * @internal\n\t */\n\tprivate readonly _authenticationAuditService?: IAuthenticationAuditComponent;\n\n\t/**\n\t * The authorization component used to guard accounts with escalated privilege.\n\t * @internal\n\t */\n\tprivate readonly _authorizationComponent?: IAuthorizationComponent;\n\n\t/**\n\t * The role that grants escalated privilege.\n\t * @internal\n\t */\n\tprivate readonly _escalatedPrivilegeRole: string;\n\n\t/**\n\t * The authorization model ID used to look up roles.\n\t * @internal\n\t */\n\tprivate readonly _authorizationModelId: string;\n\n\t/**\n\t * The minimum password length.\n\t * @internal\n\t */\n\tprivate readonly _minPasswordLength?: number;\n\n\t/**\n\t * The maximum password length.\n\t * @internal\n\t */\n\tprivate readonly _maxPasswordLength?: number;\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?: IEntityStorageAuthenticationAdminServiceConstructorOptions) {\n\t\tthis._userEntityStorage = EntityStorageConnectorFactory.get(\n\t\t\toptions?.userEntityStorageType ?? \"authentication-user\"\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._authorizationComponent = ComponentFactory.getIfExists<IAuthorizationComponent>(\n\t\t\toptions?.authorizationComponentType ?? \"authorization\"\n\t\t);\n\n\t\tthis._minPasswordLength = options?.config?.minPasswordLength;\n\t\tthis._maxPasswordLength = options?.config?.maxPasswordLength;\n\t\tthis._escalatedPrivilegeRole =\n\t\t\toptions?.config?.escalatedPrivilegeRole ??\n\t\t\tEntityStorageAuthenticationAdminService.DEFAULT_ESCALATED_PRIVILEGE_ROLE;\n\t\tthis._authorizationModelId =\n\t\t\toptions?.config?.authorizationModelId ??\n\t\t\tEntityStorageAuthenticationAdminService.DEFAULT_AUTHORIZATION_MODEL_ID;\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 EntityStorageAuthenticationAdminService.CLASS_NAME;\n\t}\n\n\t/**\n\t * Create a login for the user.\n\t * @param user The user to create.\n\t * @returns A promise that resolves when the user account has been created and the audit entry recorded.\n\t */\n\tpublic async create(user: IAuthenticationUser & { password: string }): Promise<void> {\n\t\tGuards.object<IAuthenticationUser>(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\"user\",\n\t\t\tuser\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\"user.email\",\n\t\t\tuser.email\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\"user.password\",\n\t\t\tuser.password\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\"user.userIdentity\",\n\t\t\tuser.userIdentity\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\"user.organizationIdentity\",\n\t\t\tuser.organizationIdentity\n\t\t);\n\n\t\ttry {\n\t\t\tPasswordValidator.validatePassword(user.password, {\n\t\t\t\tminLength: this._minPasswordLength,\n\t\t\t\tmaxLength: this._maxPasswordLength\n\t\t\t});\n\n\t\t\tconst existingUser = await this._userEntityStorage.get(user.email);\n\t\t\tif (Is.object<AuthenticationUser>(existingUser)) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, \"userExists\");\n\t\t\t}\n\n\t\t\t// Roles belong to the identity, so a login mapped to an escalated identity inherits them.\n\t\t\tawait this.guardEscalatedPrivilegeTarget(user.userIdentity);\n\n\t\t\tconst saltBytes = RandomHelper.generate(16);\n\t\t\tconst passwordBytes = Converter.utf8ToBytes(user.password);\n\n\t\t\tconst hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\n\t\t\tconst newUser: AuthenticationUser = {\n\t\t\t\temail: user.email,\n\t\t\t\tsalt: Converter.bytesToBase64(saltBytes),\n\t\t\t\tpassword: hashedPassword,\n\t\t\t\tidentity: user.userIdentity,\n\t\t\t\torganization: user.organizationIdentity,\n\t\t\t\tpasswordVersion: 0\n\t\t\t};\n\n\t\t\tawait this._userEntityStorage.set(newUser);\n\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tconst requestorTenantId = contextIds?.[ContextIdKeys.Tenant];\n\t\t\tconst originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant];\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: user.email,\n\t\t\t\tevent: AuthAuditEvent.AccountCreated,\n\t\t\t\tdata: {\n\t\t\t\t\tuserIdentity: user.userIdentity,\n\t\t\t\t\torganizationIdentity: user.organizationIdentity,\n\t\t\t\t\ttenantId: requestorTenantId,\n\t\t\t\t\toriginalTenantId\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tif (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"createUserFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Update a login for the user.\n\t * @param user The user to update.\n\t * @returns A promise that resolves when the user account has been updated and the audit entry recorded.\n\t */\n\tpublic async update(user: Partial<IAuthenticationUser>): Promise<void> {\n\t\tGuards.object<IAuthenticationUser>(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\"user\",\n\t\t\tuser\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\"user.email\",\n\t\t\tuser.email\n\t\t);\n\n\t\tif (!Is.empty(user.userIdentity)) {\n\t\t\tGuards.stringValue(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"user.userIdentity\",\n\t\t\t\tuser.userIdentity\n\t\t\t);\n\t\t}\n\t\tif (!Is.empty(user.organizationIdentity)) {\n\t\t\tGuards.stringValue(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"user.organizationIdentity\",\n\t\t\t\tuser.organizationIdentity\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tconst existingUser = await this._userEntityStorage.get(user.email);\n\t\t\tif (!Is.object<AuthenticationUser>(existingUser)) {\n\t\t\t\tthrow new NotFoundError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"userNotFound\",\n\t\t\t\t\tuser.email\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait this.guardEscalatedPrivilegeTarget(existingUser.identity);\n\t\t\tif (Is.stringValue(user.userIdentity) && user.userIdentity !== existingUser.identity) {\n\t\t\t\tawait this.guardEscalatedPrivilegeTarget(user.userIdentity);\n\t\t\t}\n\n\t\t\tconst updatedFields: string[] = [];\n\n\t\t\tif (user.userIdentity !== undefined && user.userIdentity !== existingUser.identity) {\n\t\t\t\tupdatedFields.push(\"userIdentity\");\n\t\t\t}\n\t\t\tif (\n\t\t\t\tuser.organizationIdentity !== undefined &&\n\t\t\t\tuser.organizationIdentity !== existingUser.organization\n\t\t\t) {\n\t\t\t\tupdatedFields.push(\"organizationIdentity\");\n\t\t\t}\n\n\t\t\texistingUser.identity = user.userIdentity ?? existingUser.identity;\n\t\t\texistingUser.organization = user.organizationIdentity ?? existingUser.organization;\n\n\t\t\tawait this._userEntityStorage.set(existingUser);\n\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tconst requestorTenantId = contextIds?.[ContextIdKeys.Tenant];\n\t\t\tconst originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant];\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: existingUser.email,\n\t\t\t\tevent: AuthAuditEvent.AccountUpdated,\n\t\t\t\tdata: {\n\t\t\t\t\tupdatedFields,\n\t\t\t\t\tuserIdentity: existingUser.identity,\n\t\t\t\t\torganizationIdentity: existingUser.organization,\n\t\t\t\t\ttenantId: requestorTenantId,\n\t\t\t\t\toriginalTenantId\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tif (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"updateUserFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get a user by email.\n\t * @param email The email address of the user to get.\n\t * @returns The user details.\n\t */\n\tpublic async get(email: string): Promise<IAuthenticationUser> {\n\t\tGuards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, \"email\", email);\n\n\t\ttry {\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\t\tthrow new NotFoundError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"userNotFound\",\n\t\t\t\t\temail\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\temail: user.email,\n\t\t\t\tuserIdentity: user.identity,\n\t\t\t\torganizationIdentity: user.organization\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"getUserFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get a user by identity.\n\t * @param identity The identity of the user to get.\n\t * @returns The user details.\n\t */\n\tpublic async getByIdentity(identity: string): Promise<IAuthenticationUser> {\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\"identity\",\n\t\t\tidentity\n\t\t);\n\n\t\ttry {\n\t\t\tconst user = await this._userEntityStorage.get(identity, \"identity\");\n\t\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\t\tthrow new NotFoundError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"userNotFound\",\n\t\t\t\t\tidentity\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\temail: user.email,\n\t\t\t\tuserIdentity: user.identity,\n\t\t\t\torganizationIdentity: user.organization\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"getUserFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Remove the current user.\n\t * @param email The email address of the user to remove.\n\t * @returns A promise that resolves when the user account has been removed and the audit entry recorded.\n\t */\n\tpublic async remove(email: string): Promise<void> {\n\t\tGuards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, \"email\", email);\n\n\t\ttry {\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\t\tthrow new NotFoundError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"userNotFound\",\n\t\t\t\t\temail\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait this.guardEscalatedPrivilegeTarget(user.identity);\n\n\t\t\tawait this._userEntityStorage.remove(email);\n\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tconst requestorTenantId = contextIds?.[ContextIdKeys.Tenant];\n\t\t\tconst originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant];\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: email,\n\t\t\t\tevent: AuthAuditEvent.AccountDeleted,\n\t\t\t\tdata: {\n\t\t\t\t\tuserIdentity: user.identity,\n\t\t\t\t\torganizationIdentity: user.organization,\n\t\t\t\t\ttenantId: requestorTenantId,\n\t\t\t\t\toriginalTenantId\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tif (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"removeUserFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Update the user's password.\n\t * @param email The email address of the user to update.\n\t * @param newPassword The new password for the user.\n\t * @param currentPassword The current password, optional, if supplied will check against existing.\n\t * @returns A promise that resolves when the password has been updated.\n\t */\n\tpublic async updatePassword(\n\t\temail: string,\n\t\tnewPassword: string,\n\t\tcurrentPassword?: string\n\t): Promise<void> {\n\t\tGuards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, \"email\", email);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\"newPassword\",\n\t\t\tnewPassword\n\t\t);\n\n\t\ttry {\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\t\tthrow new NotFoundError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"userNotFound\",\n\t\t\t\t\temail\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait this.guardEscalatedPrivilegeTarget(user.identity);\n\n\t\t\tawait PasswordHelper.updatePassword(\n\t\t\t\tthis._userEntityStorage,\n\t\t\t\tthis._authenticationAuditService,\n\t\t\t\tuser,\n\t\t\t\tnewPassword,\n\t\t\t\tcurrentPassword,\n\t\t\t\t{\n\t\t\t\t\tminPasswordLength: this._minPasswordLength,\n\t\t\t\t\tmaxPasswordLength: this._maxPasswordLength\n\t\t\t\t}\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"updatePasswordFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Ensure the caller holds escalated privilege when the target identity holds it.\n\t * @param targetIdentity The identity of the account being modified.\n\t * @throws ForbiddenError if the target holds escalated privilege and the caller does not.\n\t * @internal\n\t */\n\tprivate async guardEscalatedPrivilegeTarget(targetIdentity: string): Promise<void> {\n\t\tif (Is.empty(this._authorizationComponent)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst targetIsEscalated = await this._authorizationComponent.hasRoleForSubject(\n\t\t\tthis._authorizationModelId,\n\t\t\ttargetIdentity,\n\t\t\tthis._escalatedPrivilegeRole\n\t\t);\n\t\tif (!targetIsEscalated) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst callerIdentity = contextIds?.[ContextIdKeys.User];\n\t\tconst callerIsEscalated =\n\t\t\tIs.stringValue(callerIdentity) &&\n\t\t\t(await this._authorizationComponent.hasRoleForSubject(\n\t\t\t\tthis._authorizationModelId,\n\t\t\t\tcallerIdentity,\n\t\t\t\tthis._escalatedPrivilegeRole\n\t\t\t));\n\n\t\tif (!callerIsEscalated) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"insufficientRoleForEscalatedTarget\",\n\t\t\t\t{ roleName: this._escalatedPrivilegeRole }\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { AuthAuditEvent } from "@3sixty/api-auth-
|
|
2
|
-
import { ScopeHelper } from "@3sixty/api-models";
|
|
1
|
+
import { AuthAuditEvent } from "@3sixty/api-auth-models";
|
|
3
2
|
import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@3sixty/context";
|
|
4
3
|
import { Coerce, ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError, UnauthorizedError } from "@3sixty/core";
|
|
5
4
|
import { PasswordGenerator, PasswordValidator } from "@3sixty/crypto";
|
|
@@ -85,6 +84,11 @@ export class EntityStorageAuthenticationService {
|
|
|
85
84
|
* @internal
|
|
86
85
|
*/
|
|
87
86
|
_minPasswordLength;
|
|
87
|
+
/**
|
|
88
|
+
* The maximum password length for validation.
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
_maxPasswordLength;
|
|
88
92
|
/**
|
|
89
93
|
* Rate limit configuration for login failures.
|
|
90
94
|
* @internal
|
|
@@ -124,6 +128,7 @@ export class EntityStorageAuthenticationService {
|
|
|
124
128
|
this._defaultTtlMinutes =
|
|
125
129
|
options?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES;
|
|
126
130
|
this._minPasswordLength = options?.config?.minPasswordLength;
|
|
131
|
+
this._maxPasswordLength = options?.config?.maxPasswordLength;
|
|
127
132
|
this._loginRateLimit = {
|
|
128
133
|
maxAttempts: options?.config?.loginRateLimit?.maxAttempts ??
|
|
129
134
|
EntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_MAX_ATTEMPTS,
|
|
@@ -205,7 +210,7 @@ export class EntityStorageAuthenticationService {
|
|
|
205
210
|
// the context
|
|
206
211
|
const contextIds = await ContextIdStore.getContextIds();
|
|
207
212
|
loginTenantId = contextIds?.[ContextIdKeys.Tenant];
|
|
208
|
-
tokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, this._nodeId, this._signingKeyName, user.identity, user.organization, loginTenantId, this._defaultTtlMinutes, user.
|
|
213
|
+
tokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, this._nodeId, this._signingKeyName, user.identity, user.organization, loginTenantId, this._defaultTtlMinutes, user.passwordVersion ?? 0);
|
|
209
214
|
loginUser = user;
|
|
210
215
|
}
|
|
211
216
|
catch (error) {
|
|
@@ -222,8 +227,7 @@ export class EntityStorageAuthenticationService {
|
|
|
222
227
|
data: {
|
|
223
228
|
userIdentity: loginUser.identity,
|
|
224
229
|
organizationIdentity: loginUser.organization,
|
|
225
|
-
tenantId: loginTenantId
|
|
226
|
-
scope: ScopeHelper.toArray(loginUser.scope)
|
|
230
|
+
tenantId: loginTenantId
|
|
227
231
|
}
|
|
228
232
|
});
|
|
229
233
|
return tokenAndExpiry;
|
|
@@ -254,11 +258,10 @@ export class EntityStorageAuthenticationService {
|
|
|
254
258
|
if (!Is.stringValue(this._nodeId)) {
|
|
255
259
|
throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "nodeIdNotSet");
|
|
256
260
|
}
|
|
257
|
-
let refreshPasswordVersion;
|
|
258
|
-
let refreshScope;
|
|
259
261
|
let tenantId;
|
|
262
|
+
let refreshUser;
|
|
260
263
|
// If the verify fails on the current token then it will throw an exception.
|
|
261
|
-
const headerAndPayload = await TokenHelper.verify(this._vaultConnector, this._nodeId, this._signingKeyName, token,
|
|
264
|
+
const headerAndPayload = await TokenHelper.verify(this._vaultConnector, this._nodeId, this._signingKeyName, token, async (sub, org, tid, passwordVersion) => {
|
|
262
265
|
const validParts = [];
|
|
263
266
|
tenantId = tid;
|
|
264
267
|
if (Is.stringValue(tenantId)) {
|
|
@@ -276,11 +279,8 @@ export class EntityStorageAuthenticationService {
|
|
|
276
279
|
: baseContext;
|
|
277
280
|
// Wrap the user lookup in the request context so partitioned storage uses the correct tenant.
|
|
278
281
|
const user = await ContextIdStore.run(contextIdsForUserLookup, async () => this._userEntityStorage.get(sub, "identity"));
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// so a scope change reaches a client that keeps refreshing.
|
|
282
|
-
refreshScope = user?.scope;
|
|
283
|
-
if (user?.identity === sub && (passwordVersion ?? 0) === (refreshPasswordVersion ?? 0)) {
|
|
282
|
+
refreshUser = user;
|
|
283
|
+
if (user?.identity === sub && (passwordVersion ?? 0) === (user?.passwordVersion ?? 0)) {
|
|
284
284
|
validParts.push("user");
|
|
285
285
|
}
|
|
286
286
|
if (user?.organization === org) {
|
|
@@ -291,15 +291,14 @@ export class EntityStorageAuthenticationService {
|
|
|
291
291
|
const refreshSub = headerAndPayload.payload.sub ?? "";
|
|
292
292
|
await this._authenticationRateService.check("token-refresh", refreshSub);
|
|
293
293
|
const payloadOrg = Coerce.string(headerAndPayload.payload.org);
|
|
294
|
-
const refreshTokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, this._nodeId, this._signingKeyName, refreshSub, payloadOrg, tenantId, this._defaultTtlMinutes,
|
|
294
|
+
const refreshTokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, this._nodeId, this._signingKeyName, refreshSub, payloadOrg, tenantId, this._defaultTtlMinutes, refreshUser?.passwordVersion ?? 0);
|
|
295
295
|
await this._authenticationAuditService?.create({
|
|
296
296
|
actorId: refreshSub,
|
|
297
297
|
event: AuthAuditEvent.TokenRefreshed,
|
|
298
298
|
data: {
|
|
299
299
|
organizationIdentity: payloadOrg,
|
|
300
300
|
tenantId,
|
|
301
|
-
|
|
302
|
-
version: refreshPasswordVersion ?? 0
|
|
301
|
+
version: refreshUser?.passwordVersion ?? 0
|
|
303
302
|
}
|
|
304
303
|
});
|
|
305
304
|
return refreshTokenAndExpiry;
|
|
@@ -319,8 +318,21 @@ export class EntityStorageAuthenticationService {
|
|
|
319
318
|
if (!Is.object(user)) {
|
|
320
319
|
throw new NotFoundError(EntityStorageAuthenticationService.CLASS_NAME, "userNotFound", userIdentity);
|
|
321
320
|
}
|
|
322
|
-
await PasswordHelper.updatePassword(this._userEntityStorage, this._authenticationAuditService, user, newPassword, currentPassword,
|
|
321
|
+
await PasswordHelper.updatePassword(this._userEntityStorage, this._authenticationAuditService, user, newPassword, currentPassword, {
|
|
322
|
+
minPasswordLength: this._minPasswordLength,
|
|
323
|
+
maxPasswordLength: this._maxPasswordLength
|
|
324
|
+
});
|
|
323
325
|
await this._authenticationRateService.clear("password-change", userIdentity);
|
|
324
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Get the password policy used for password validation.
|
|
329
|
+
* @returns The password policy options.
|
|
330
|
+
*/
|
|
331
|
+
async passwordPolicy() {
|
|
332
|
+
return {
|
|
333
|
+
minPasswordLength: this._minPasswordLength,
|
|
334
|
+
maxPasswordLength: this._maxPasswordLength
|
|
335
|
+
};
|
|
336
|
+
}
|
|
325
337
|
}
|
|
326
338
|
//# sourceMappingURL=entityStorageAuthenticationService.js.map
|
|
@@ -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 \"@3sixty/api-auth-entity-storage-models\";\nimport { AuthAuditEvent } from \"@3sixty/api-auth-entity-storage-models\";\nimport { type ITenantAdminComponent, ScopeHelper } from \"@3sixty/api-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@3sixty/context\";\nimport {\n\tCoerce,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tUnauthorizedError\n} from \"@3sixty/core\";\nimport { PasswordGenerator, PasswordValidator } 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 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\tif (!Is.stringValue(this._nodeId)) {\n\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"nodeIdNotSet\");\n\t\t}\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._nodeId,\n\t\t\t\tthis._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: ScopeHelper.toArray(loginUser.scope)\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\tif (!Is.stringValue(this._nodeId)) {\n\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"nodeIdNotSet\");\n\t\t}\n\n\t\tlet refreshPasswordVersion: number | undefined;\n\t\tlet refreshScope: string | 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\tthis._nodeId,\n\t\t\tthis._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\t// The scope is read from storage rather than copied from the token being refreshed,\n\t\t\t\t// so a scope change reaches a client that keeps refreshing.\n\t\t\t\trefreshScope = user?.scope;\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\n\t\tconst refreshTokenAndExpiry = await TokenHelper.createToken(\n\t\t\tthis._vaultConnector,\n\t\t\tthis._nodeId,\n\t\t\tthis._signingKeyName,\n\t\t\trefreshSub,\n\t\t\tpayloadOrg,\n\t\t\ttenantId,\n\t\t\tthis._defaultTtlMinutes,\n\t\t\trefreshScope,\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: ScopeHelper.toArray(refreshScope),\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"]}
|
|
1
|
+
{"version":3,"file":"entityStorageAuthenticationService.js","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\tIAuthenticationComponent,\n\tIPasswordOptions\n} from \"@3sixty/api-auth-entity-storage-models\";\nimport type {\n\tIAuthenticationRateActionConfig,\n\tIAuthenticationRateComponent,\n\tIAuthenticationAuditComponent\n} from \"@3sixty/api-auth-models\";\nimport { AuthAuditEvent } from \"@3sixty/api-auth-models\";\nimport type { ITenantAdminComponent } from \"@3sixty/api-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@3sixty/context\";\nimport {\n\tCoerce,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tUnauthorizedError\n} from \"@3sixty/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@3sixty/crypto\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@3sixty/entity-storage-models\";\n\nimport { VaultConnectorFactory, type IVaultConnector } from \"@3sixty/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 = \"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 * The maximum password length for validation.\n\t * @internal\n\t */\n\tprivate readonly _maxPasswordLength?: 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._maxPasswordLength = options?.config?.maxPasswordLength;\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, \"email\", email);\n\t\tGuards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, \"password\", password);\n\n\t\tif (!Is.stringValue(this._nodeId)) {\n\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"nodeIdNotSet\");\n\t\t}\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._nodeId,\n\t\t\t\tthis._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.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}\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\tif (!Is.stringValue(this._nodeId)) {\n\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"nodeIdNotSet\");\n\t\t}\n\n\t\tlet tenantId: string | undefined;\n\t\tlet refreshUser: AuthenticationUser | 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\tthis._nodeId,\n\t\t\tthis._signingKeyName,\n\t\t\ttoken,\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\trefreshUser = user;\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\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\n\t\tconst refreshTokenAndExpiry = await TokenHelper.createToken(\n\t\t\tthis._vaultConnector,\n\t\t\tthis._nodeId,\n\t\t\tthis._signingKeyName,\n\t\t\trefreshSub,\n\t\t\tpayloadOrg,\n\t\t\ttenantId,\n\t\t\tthis._defaultTtlMinutes,\n\t\t\trefreshUser?.passwordVersion ?? 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\tversion: refreshUser?.passwordVersion ?? 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\t{\n\t\t\t\tminPasswordLength: this._minPasswordLength,\n\t\t\t\tmaxPasswordLength: this._maxPasswordLength\n\t\t\t}\n\t\t);\n\n\t\tawait this._authenticationRateService.clear(\"password-change\", userIdentity);\n\t}\n\n\t/**\n\t * Get the password policy used for password validation.\n\t * @returns The password policy options.\n\t */\n\tpublic async passwordPolicy(): Promise<IPasswordOptions> {\n\t\treturn {\n\t\t\tminPasswordLength: this._minPasswordLength,\n\t\t\tmaxPasswordLength: this._maxPasswordLength\n\t\t};\n\t}\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthAuditEvent } from "@3sixty/api-auth-
|
|
1
|
+
import { AuthAuditEvent } from "@3sixty/api-auth-models";
|
|
2
2
|
import { Converter, GeneralError, Is, RandomHelper } from "@3sixty/core";
|
|
3
3
|
import { PasswordGenerator, PasswordValidator } from "@3sixty/crypto";
|
|
4
4
|
/**
|
|
@@ -17,12 +17,15 @@ export class PasswordHelper {
|
|
|
17
17
|
* @param user The user whose password is being updated.
|
|
18
18
|
* @param newPassword The new password to set.
|
|
19
19
|
* @param currentPassword The current password to verify against, if supplied.
|
|
20
|
-
* @param
|
|
20
|
+
* @param options Optional password length validation options.
|
|
21
|
+
* @param options.minPasswordLength Optional minimum password length for validation.
|
|
22
|
+
* @param options.maxPasswordLength Optional maximum password length for validation.
|
|
21
23
|
* @returns A promise that resolves when the new password has been stored and the audit entry recorded.
|
|
22
24
|
*/
|
|
23
|
-
static async updatePassword(userEntityStorage, authenticationAuditService, user, newPassword, currentPassword,
|
|
25
|
+
static async updatePassword(userEntityStorage, authenticationAuditService, user, newPassword, currentPassword, options) {
|
|
24
26
|
PasswordValidator.validatePassword(newPassword, {
|
|
25
|
-
minLength: minPasswordLength
|
|
27
|
+
minLength: options?.minPasswordLength,
|
|
28
|
+
maxLength: options?.maxPasswordLength
|
|
26
29
|
});
|
|
27
30
|
if (Is.stringValue(currentPassword)) {
|
|
28
31
|
const saltBytes = Converter.base64ToBytes(user.salt);
|
|
@@ -41,7 +44,6 @@ export class PasswordHelper {
|
|
|
41
44
|
password: hashedPassword,
|
|
42
45
|
identity: user.identity,
|
|
43
46
|
organization: user.organization,
|
|
44
|
-
scope: user.scope,
|
|
45
47
|
passwordVersion: (user.passwordVersion ?? 0) + 1
|
|
46
48
|
};
|
|
47
49
|
await userEntityStorage.set(updatedUser);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passwordHelper.js","
|
|
1
|
+
{"version":3,"file":"passwordHelper.js","sources":["../../../src/utils/passwordHelper.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAKtE;;GAEG;AACH,MAAM,OAAO,cAAc;IAC1B;;OAEG;IACI,MAAM,CAAU,UAAU,GAAW,gBAAgB,CAAC;IAE7D;;;;;;;;;;;;OAYG;IACI,MAAM,CAAC,KAAK,CAAC,cAAc,CACjC,iBAA8D,EAC9D,0BAAqE,EACrE,IAAwB,EACxB,WAAmB,EACnB,eAAwB,EACxB,OAA0B;QAE1B,iBAAiB,CAAC,gBAAgB,CAAC,WAAW,EAAE;YAC/C,SAAS,EAAE,OAAO,EAAE,iBAAiB;YACrC,SAAS,EAAE,OAAO,EAAE,iBAAiB;SACrC,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,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;CACD","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IPasswordOptions } from \"@3sixty/api-auth-entity-storage-models\";\nimport type { IAuthenticationAuditComponent } from \"@3sixty/api-auth-models\";\nimport { AuthAuditEvent } from \"@3sixty/api-auth-models\";\nimport { Converter, GeneralError, Is, RandomHelper } from \"@3sixty/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@3sixty/crypto\";\nimport type { IEntityStorageConnector } from \"@3sixty/entity-storage-models\";\n\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 = \"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 options Optional password length validation options.\n\t * @param options.minPasswordLength Optional minimum password length for validation.\n\t * @param options.maxPasswordLength Optional maximum 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\toptions?: IPasswordOptions\n\t): Promise<void> {\n\t\tPasswordValidator.validatePassword(newPassword, {\n\t\t\tminLength: options?.minPasswordLength,\n\t\t\tmaxLength: options?.maxPasswordLength\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\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,6 +1,5 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
-
import { ScopeHelper } from "@3sixty/api-models";
|
|
4
3
|
import { Coerce, Is, UnauthorizedError } from "@3sixty/core";
|
|
5
4
|
import { VaultConnectorHelper } from "@3sixty/vault-models";
|
|
6
5
|
import { CookieHelper, HeaderHelper, HeaderTypes, Jwt } from "@3sixty/web";
|
|
@@ -21,11 +20,10 @@ export class TokenHelper {
|
|
|
21
20
|
* @param organizationIdentity The organization for the token.
|
|
22
21
|
* @param tenantId The tenant id for the token.
|
|
23
22
|
* @param ttlMinutes The time to live for the token in minutes.
|
|
24
|
-
* @param scope The scopes for the token.
|
|
25
23
|
* @param passwordVersion The user's current password version counter, embedded in the token so that a password change invalidates existing tokens.
|
|
26
24
|
* @returns The new token and its expiry date.
|
|
27
25
|
*/
|
|
28
|
-
static async createToken(vaultConnector, nodeId, signingKeyName, userIdentity, organizationIdentity, tenantId, ttlMinutes,
|
|
26
|
+
static async createToken(vaultConnector, nodeId, signingKeyName, userIdentity, organizationIdentity, tenantId, ttlMinutes, passwordVersion) {
|
|
29
27
|
const nowSeconds = Math.trunc(Date.now() / 1000);
|
|
30
28
|
const ttlSeconds = ttlMinutes * 60;
|
|
31
29
|
const vaultKeyName = `${nodeId}/${signingKeyName}`;
|
|
@@ -35,7 +33,6 @@ export class TokenHelper {
|
|
|
35
33
|
org: organizationIdentity,
|
|
36
34
|
tid: tenantId,
|
|
37
35
|
exp: nowSeconds + ttlSeconds,
|
|
38
|
-
scope,
|
|
39
36
|
pver: passwordVersion
|
|
40
37
|
}, async (header, payload) => VaultConnectorHelper.jwtSigner(vaultConnector, vaultKeyName, header, payload));
|
|
41
38
|
return {
|
|
@@ -49,12 +46,11 @@ export class TokenHelper {
|
|
|
49
46
|
* @param nodeId The node identifier, expected to match the JWT issuer claim.
|
|
50
47
|
* @param signingKeyName The signing key name, expected to match the JWT key identifier.
|
|
51
48
|
* @param token The token to verify.
|
|
52
|
-
* @param requiredScopes The required scopes.
|
|
53
49
|
* @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.
|
|
54
50
|
* @returns The verified details.
|
|
55
51
|
* @throws UnauthorizedError if the token is missing, invalid or expired.
|
|
56
52
|
*/
|
|
57
|
-
static async verify(vaultConnector, nodeId, signingKeyName, token,
|
|
53
|
+
static async verify(vaultConnector, nodeId, signingKeyName, token, verifyUser) {
|
|
58
54
|
if (!Is.stringValue(token)) {
|
|
59
55
|
throw new UnauthorizedError(TokenHelper.CLASS_NAME, "missing");
|
|
60
56
|
}
|
|
@@ -90,28 +86,11 @@ export class TokenHelper {
|
|
|
90
86
|
throw new UnauthorizedError(TokenHelper.CLASS_NAME, "tenantNotVerified");
|
|
91
87
|
}
|
|
92
88
|
}
|
|
93
|
-
TokenHelper.verifyScopes(Coerce.string(decoded.payload.scope), requiredScopes);
|
|
94
89
|
return {
|
|
95
90
|
header: decoded.header,
|
|
96
91
|
payload: decoded.payload
|
|
97
92
|
};
|
|
98
93
|
}
|
|
99
|
-
/**
|
|
100
|
-
* Verify that a token carries all of the required scopes.
|
|
101
|
-
* @param scope The comma separated scopes from the token.
|
|
102
|
-
* @param requiredScopes The scopes the caller must hold.
|
|
103
|
-
* @throws UnauthorizedError if any of the required scopes is missing.
|
|
104
|
-
*/
|
|
105
|
-
static verifyScopes(scope, requiredScopes) {
|
|
106
|
-
if (Is.arrayValue(requiredScopes)) {
|
|
107
|
-
const tokenScopes = ScopeHelper.toArray(scope);
|
|
108
|
-
for (const requiredScope of ScopeHelper.toArray(requiredScopes)) {
|
|
109
|
-
if (!tokenScopes.includes(requiredScope)) {
|
|
110
|
-
throw new UnauthorizedError(TokenHelper.CLASS_NAME, "insufficientScopes");
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
94
|
/**
|
|
116
95
|
* Extract the auth token from the headers, either from the authorization header or the cookie header.
|
|
117
96
|
* @param headers The headers to extract the token from.
|
|
@@ -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 { ScopeHelper } from \"@3sixty/api-models\";\nimport { Coerce, Is, UnauthorizedError } from \"@3sixty/core\";\nimport { nameof } from \"@3sixty/nameof\";\nimport { type IVaultConnector, VaultConnectorHelper } from \"@3sixty/vault-models\";\nimport {\n\tCookieHelper,\n\tHeaderHelper,\n\tHeaderTypes,\n\ttype IHttpHeaders,\n\ttype IJwtHeader,\n\ttype IJwtPayload,\n\tJwt\n} from \"@3sixty/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 nodeId The node identifier, embedded as the JWT issuer claim.\n\t * @param signingKeyName The signing key name, embedded as the JWT key identifier.\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\tnodeId: string,\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\t\tconst vaultKeyName = `${nodeId}/${signingKeyName}`;\n\n\t\tconst jwt = await Jwt.encodeWithSigner(\n\t\t\t{ alg: \"EdDSA\", kid: signingKeyName },\n\t\t\t{\n\t\t\t\tiss: nodeId,\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, vaultKeyName, 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 nodeId The node identifier, expected to match the JWT issuer claim.\n\t * @param signingKeyName The signing key name, expected to match the JWT key identifier.\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\tnodeId: string,\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 vaultKeyName = `${nodeId}/${signingKeyName}`;\n\t\tconst decoded = await Jwt.verifyWithVerifier(token, async t =>\n\t\t\tVaultConnectorHelper.jwtVerifier(vaultConnector, vaultKeyName, 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 (decoded.header.kid !== signingKeyName) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"headerKeyIdMismatch\");\n\t\t} else if (decoded.payload.iss !== nodeId) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"payloadIssuerMismatch\");\n\t\t} else if (!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\tTokenHelper.verifyScopes(Coerce.string(decoded.payload.scope), requiredScopes);\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 * Verify that a token carries all of the required scopes.\n\t * @param scope The comma separated scopes from the token.\n\t * @param requiredScopes The scopes the caller must hold.\n\t * @throws UnauthorizedError if any of the required scopes is missing.\n\t */\n\tpublic static verifyScopes(scope: string | undefined, requiredScopes?: string[]): void {\n\t\tif (Is.arrayValue(requiredScopes)) {\n\t\t\tconst tokenScopes = ScopeHelper.toArray(scope);\n\n\t\t\tfor (const requiredScope of ScopeHelper.toArray(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\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","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 \"@3sixty/core\";\n\nimport { type IVaultConnector, VaultConnectorHelper } from \"@3sixty/vault-models\";\nimport {\n\tCookieHelper,\n\tHeaderHelper,\n\tHeaderTypes,\n\ttype IHttpHeaders,\n\ttype IJwtHeader,\n\ttype IJwtPayload,\n\tJwt\n} from \"@3sixty/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 = \"TokenHelper\";\n\n\t/**\n\t * Create a new token.\n\t * @param vaultConnector The vault connector.\n\t * @param nodeId The node identifier, embedded as the JWT issuer claim.\n\t * @param signingKeyName The signing key name, embedded as the JWT key identifier.\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 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\tnodeId: string,\n\t\tsigningKeyName: string,\n\t\tuserIdentity: string,\n\t\torganizationIdentity: string | undefined,\n\t\ttenantId: string | undefined,\n\t\tttlMinutes: number,\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\t\tconst vaultKeyName = `${nodeId}/${signingKeyName}`;\n\n\t\tconst jwt = await Jwt.encodeWithSigner(\n\t\t\t{ alg: \"EdDSA\", kid: signingKeyName },\n\t\t\t{\n\t\t\t\tiss: nodeId,\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\tpver: passwordVersion\n\t\t\t},\n\t\t\tasync (header, payload) =>\n\t\t\t\tVaultConnectorHelper.jwtSigner(vaultConnector, vaultKeyName, 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 nodeId The node identifier, expected to match the JWT issuer claim.\n\t * @param signingKeyName The signing key name, expected to match the JWT key identifier.\n\t * @param token The token to verify.\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\tnodeId: string,\n\t\tsigningKeyName: string,\n\t\ttoken: string | undefined,\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 vaultKeyName = `${nodeId}/${signingKeyName}`;\n\t\tconst decoded = await Jwt.verifyWithVerifier(token, async t =>\n\t\t\tVaultConnectorHelper.jwtVerifier(vaultConnector, vaultKeyName, 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 (decoded.header.kid !== signingKeyName) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"headerKeyIdMismatch\");\n\t\t} else if (decoded.payload.iss !== nodeId) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"payloadIssuerMismatch\");\n\t\t} else if (!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\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"]}
|
|
@@ -22,10 +22,6 @@ export declare class AuthenticationUser {
|
|
|
22
22
|
* The users organization.
|
|
23
23
|
*/
|
|
24
24
|
organization: string;
|
|
25
|
-
/**
|
|
26
|
-
* The scope assigned to the user, comma separated.
|
|
27
|
-
*/
|
|
28
|
-
scope: string;
|
|
29
25
|
/**
|
|
30
26
|
* The password version counter, incremented on every password change to invalidate existing tokens.
|
|
31
27
|
*/
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class defining the storage for user login credentials.
|
|
3
|
+
*/
|
|
4
|
+
export declare class AuthenticationUserV0 {
|
|
5
|
+
/**
|
|
6
|
+
* The user e-mail address.
|
|
7
|
+
*/
|
|
8
|
+
email: string;
|
|
9
|
+
/**
|
|
10
|
+
* The encrypted password for the user.
|
|
11
|
+
*/
|
|
12
|
+
password: string;
|
|
13
|
+
/**
|
|
14
|
+
* The salt for the password.
|
|
15
|
+
*/
|
|
16
|
+
salt: string;
|
|
17
|
+
/**
|
|
18
|
+
* The user identity.
|
|
19
|
+
*/
|
|
20
|
+
identity: string;
|
|
21
|
+
/**
|
|
22
|
+
* The users organization.
|
|
23
|
+
*/
|
|
24
|
+
organization: string;
|
|
25
|
+
/**
|
|
26
|
+
* The scope assigned to the user, comma separated.
|
|
27
|
+
*/
|
|
28
|
+
scope: string;
|
|
29
|
+
/**
|
|
30
|
+
* The password version counter, incremented on every password change to invalidate existing tokens.
|
|
31
|
+
*/
|
|
32
|
+
passwordVersion?: number;
|
|
33
|
+
}
|