@3sixty/api-auth-entity-storage-service 0.0.0-stage → 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/LICENSE +201 -0
- package/README.md +24 -2
- package/dist/es/entities/authenticationUser.js +61 -0
- package/dist/es/entities/authenticationUser.js.map +1 -0
- package/dist/es/entities/authenticationUserV0.js +69 -0
- package/dist/es/entities/authenticationUserV0.js.map +1 -0
- package/dist/es/index.js +21 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/models/IAuthHeaderProcessorConfig.js +4 -0
- package/dist/es/models/IAuthHeaderProcessorConfig.js.map +1 -0
- package/dist/es/models/IAuthHeaderProcessorConstructorOptions.js +2 -0
- package/dist/es/models/IAuthHeaderProcessorConstructorOptions.js.map +1 -0
- package/dist/es/models/IAuthTokenContext.js +4 -0
- package/dist/es/models/IAuthTokenContext.js.map +1 -0
- package/dist/es/models/IEntityStorageAuthenticationAdminServiceConfig.js +4 -0
- package/dist/es/models/IEntityStorageAuthenticationAdminServiceConfig.js.map +1 -0
- package/dist/es/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js +2 -0
- package/dist/es/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js.map +1 -0
- package/dist/es/models/IEntityStorageAuthenticationServiceConfig.js +2 -0
- package/dist/es/models/IEntityStorageAuthenticationServiceConfig.js.map +1 -0
- package/dist/es/models/IEntityStorageAuthenticationServiceConstructorOptions.js +2 -0
- package/dist/es/models/IEntityStorageAuthenticationServiceConstructorOptions.js.map +1 -0
- package/dist/es/processors/authHeaderProcessor.js +272 -0
- package/dist/es/processors/authHeaderProcessor.js.map +1 -0
- package/dist/es/restEntryPoints.js +20 -0
- package/dist/es/restEntryPoints.js.map +1 -0
- package/dist/es/routes/entityStorageAuthenticationAdminRoutes.js +375 -0
- package/dist/es/routes/entityStorageAuthenticationAdminRoutes.js.map +1 -0
- package/dist/es/routes/entityStorageAuthenticationRoutes.js +291 -0
- package/dist/es/routes/entityStorageAuthenticationRoutes.js.map +1 -0
- package/dist/es/schema.js +13 -0
- package/dist/es/schema.js.map +1 -0
- package/dist/es/services/entityStorageAuthenticationAdminService.js +324 -0
- package/dist/es/services/entityStorageAuthenticationAdminService.js.map +1 -0
- package/dist/es/services/entityStorageAuthenticationService.js +338 -0
- package/dist/es/services/entityStorageAuthenticationService.js.map +1 -0
- package/dist/es/utils/passwordHelper.js +60 -0
- package/dist/es/utils/passwordHelper.js.map +1 -0
- package/dist/es/utils/tokenHelper.js +121 -0
- package/dist/es/utils/tokenHelper.js.map +1 -0
- package/dist/types/entities/authenticationUser.d.ts +29 -0
- package/dist/types/entities/authenticationUserV0.d.ts +33 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/models/IAuthHeaderProcessorConfig.d.ts +38 -0
- package/dist/types/models/IAuthHeaderProcessorConstructorOptions.d.ts +25 -0
- package/dist/types/models/IAuthTokenContext.d.ts +30 -0
- package/dist/types/models/IEntityStorageAuthenticationAdminServiceConfig.d.ts +25 -0
- package/dist/types/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.d.ts +25 -0
- package/dist/types/models/IEntityStorageAuthenticationServiceConfig.d.ts +41 -0
- package/dist/types/models/IEntityStorageAuthenticationServiceConstructorOptions.d.ts +35 -0
- package/dist/types/processors/authHeaderProcessor.d.ts +57 -0
- package/dist/types/restEntryPoints.d.ts +5 -0
- package/dist/types/routes/entityStorageAuthenticationAdminRoutes.d.ts +62 -0
- package/dist/types/routes/entityStorageAuthenticationRoutes.d.ts +53 -0
- package/dist/types/schema.d.ts +4 -0
- package/dist/types/services/entityStorageAuthenticationAdminService.d.ts +69 -0
- package/dist/types/services/entityStorageAuthenticationService.d.ts +70 -0
- package/dist/types/utils/passwordHelper.d.ts +27 -0
- package/dist/types/utils/tokenHelper.d.ts +51 -0
- package/docs/changelog.md +19 -0
- package/docs/examples.md +94 -0
- package/docs/open-api/spec.json +1344 -0
- package/docs/reference/classes/AuthHeaderProcessor.md +197 -0
- package/docs/reference/classes/AuthenticationUser.md +61 -0
- package/docs/reference/classes/AuthenticationUserV0.md +69 -0
- package/docs/reference/classes/EntityStorageAuthenticationAdminService.md +237 -0
- package/docs/reference/classes/EntityStorageAuthenticationService.md +239 -0
- package/docs/reference/classes/PasswordHelper.md +74 -0
- package/docs/reference/classes/TokenHelper.md +163 -0
- package/docs/reference/functions/authenticationAdminCreateUser.md +37 -0
- package/docs/reference/functions/authenticationAdminGetUser.md +31 -0
- package/docs/reference/functions/authenticationAdminGetUserByIdentity.md +31 -0
- package/docs/reference/functions/authenticationAdminRemoveUser.md +31 -0
- package/docs/reference/functions/authenticationAdminUpdateUser.md +31 -0
- package/docs/reference/functions/authenticationAdminUpdateUserPassword.md +31 -0
- package/docs/reference/functions/authenticationLogin.md +31 -0
- package/docs/reference/functions/authenticationLogout.md +31 -0
- package/docs/reference/functions/authenticationPasswordPolicy.md +31 -0
- package/docs/reference/functions/authenticationRefreshToken.md +31 -0
- package/docs/reference/functions/authenticationUpdatePassword.md +31 -0
- package/docs/reference/functions/generateRestRoutesAuthentication.md +25 -0
- package/docs/reference/functions/generateRestRoutesAuthenticationAdmin.md +25 -0
- package/docs/reference/functions/initSchema.md +9 -0
- package/docs/reference/index.md +44 -0
- package/docs/reference/interfaces/IAuthHeaderProcessorConfig.md +85 -0
- package/docs/reference/interfaces/IAuthHeaderProcessorConstructorOptions.md +53 -0
- package/docs/reference/interfaces/IAuthTokenContext.md +52 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConfig.md +59 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md +53 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConfig.md +101 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConstructorOptions.md +81 -0
- package/docs/reference/variables/restEntryPoints.md +5 -0
- package/docs/reference/variables/tagsAuthentication.md +5 -0
- package/docs/reference/variables/tagsAuthenticationAdmin.md +5 -0
- package/locales/en.json +37 -0
- package/package.json +63 -5
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { AuthAuditEvent } from "@3sixty/api-auth-models";
|
|
2
|
+
import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@3sixty/context";
|
|
3
|
+
import { Coerce, ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError, UnauthorizedError } from "@3sixty/core";
|
|
4
|
+
import { PasswordGenerator, PasswordValidator } from "@3sixty/crypto";
|
|
5
|
+
import { EntityStorageConnectorFactory } from "@3sixty/entity-storage-models";
|
|
6
|
+
import { VaultConnectorFactory } from "@3sixty/vault-models";
|
|
7
|
+
import { PasswordHelper } from "../utils/passwordHelper.js";
|
|
8
|
+
import { TokenHelper } from "../utils/tokenHelper.js";
|
|
9
|
+
/**
|
|
10
|
+
* Implementation of the authentication component using entity storage.
|
|
11
|
+
*/
|
|
12
|
+
export class EntityStorageAuthenticationService {
|
|
13
|
+
/**
|
|
14
|
+
* Runtime name for the class.
|
|
15
|
+
*/
|
|
16
|
+
static CLASS_NAME = "EntityStorageAuthenticationService";
|
|
17
|
+
/**
|
|
18
|
+
* Default TTL in minutes.
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
static _DEFAULT_TTL_MINUTES = 60;
|
|
22
|
+
/**
|
|
23
|
+
* Default maximum login attempts in a rate window.
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
static _DEFAULT_LOGIN_RATE_MAX_ATTEMPTS = 5;
|
|
27
|
+
/**
|
|
28
|
+
* Default login rate window in minutes.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
static _DEFAULT_LOGIN_RATE_WINDOW_MINUTES = 15;
|
|
32
|
+
/**
|
|
33
|
+
* Default maximum password change attempts in a rate window.
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
static _DEFAULT_PASSWORD_CHANGE_RATE_MAX_ATTEMPTS = 5;
|
|
37
|
+
/**
|
|
38
|
+
* Default password change rate window in minutes.
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
static _DEFAULT_PASSWORD_CHANGE_RATE_WINDOW_MINUTES = 15;
|
|
42
|
+
/**
|
|
43
|
+
* Default maximum token refresh attempts in a rate window.
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
static _DEFAULT_TOKEN_REFRESH_RATE_MAX_ATTEMPTS = 30;
|
|
47
|
+
/**
|
|
48
|
+
* Default token refresh rate window in minutes.
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
static _DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES = 60;
|
|
52
|
+
/**
|
|
53
|
+
* The audit service.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
_authenticationAuditService;
|
|
57
|
+
/**
|
|
58
|
+
* The rate service.
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
_authenticationRateService;
|
|
62
|
+
/**
|
|
63
|
+
* The entity storage for users.
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
_userEntityStorage;
|
|
67
|
+
/**
|
|
68
|
+
* The vault for the keys.
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
_vaultConnector;
|
|
72
|
+
/**
|
|
73
|
+
* The name of the key to retrieve from the vault for signing JWT.
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
_signingKeyName;
|
|
77
|
+
/**
|
|
78
|
+
* The default TTL for the token.
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
_defaultTtlMinutes;
|
|
82
|
+
/**
|
|
83
|
+
* The minimum password length for validation.
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
_minPasswordLength;
|
|
87
|
+
/**
|
|
88
|
+
* The maximum password length for validation.
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
_maxPasswordLength;
|
|
92
|
+
/**
|
|
93
|
+
* Rate limit configuration for login failures.
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
96
|
+
_loginRateLimit;
|
|
97
|
+
/**
|
|
98
|
+
* Rate limit configuration for password changes.
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
_passwordChangeRateLimit;
|
|
102
|
+
/**
|
|
103
|
+
* Rate limit configuration for token refresh.
|
|
104
|
+
* @internal
|
|
105
|
+
*/
|
|
106
|
+
_tokenRefreshRateLimit;
|
|
107
|
+
/**
|
|
108
|
+
* The component to retrieve tenant information.
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
_tenantAdminComponent;
|
|
112
|
+
/**
|
|
113
|
+
* The node identity.
|
|
114
|
+
* @internal
|
|
115
|
+
*/
|
|
116
|
+
_nodeId;
|
|
117
|
+
/**
|
|
118
|
+
* Create a new instance of EntityStorageAuthentication.
|
|
119
|
+
* @param options The dependencies for the identity connector.
|
|
120
|
+
*/
|
|
121
|
+
constructor(options) {
|
|
122
|
+
this._userEntityStorage = EntityStorageConnectorFactory.get(options?.userEntityStorageType ?? "authentication-user");
|
|
123
|
+
this._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
|
|
124
|
+
this._authenticationAuditService = ComponentFactory.getIfExists(options?.authenticationAuditServiceType ?? "authentication-audit");
|
|
125
|
+
this._authenticationRateService = ComponentFactory.get(options?.authenticationRateServiceType ?? "authentication-rate");
|
|
126
|
+
this._tenantAdminComponent = ComponentFactory.getIfExists(options?.tenantAdminComponentType ?? "tenant-admin");
|
|
127
|
+
this._signingKeyName = options?.config?.signingKeyName ?? "auth-signing";
|
|
128
|
+
this._defaultTtlMinutes =
|
|
129
|
+
options?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES;
|
|
130
|
+
this._minPasswordLength = options?.config?.minPasswordLength;
|
|
131
|
+
this._maxPasswordLength = options?.config?.maxPasswordLength;
|
|
132
|
+
this._loginRateLimit = {
|
|
133
|
+
maxAttempts: options?.config?.loginRateLimit?.maxAttempts ??
|
|
134
|
+
EntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_MAX_ATTEMPTS,
|
|
135
|
+
windowMinutes: options?.config?.loginRateLimit?.windowMinutes ??
|
|
136
|
+
EntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_WINDOW_MINUTES
|
|
137
|
+
};
|
|
138
|
+
this._passwordChangeRateLimit = {
|
|
139
|
+
maxAttempts: options?.config?.passwordChangeRateLimit?.maxAttempts ??
|
|
140
|
+
EntityStorageAuthenticationService._DEFAULT_PASSWORD_CHANGE_RATE_MAX_ATTEMPTS,
|
|
141
|
+
windowMinutes: options?.config?.passwordChangeRateLimit?.windowMinutes ??
|
|
142
|
+
EntityStorageAuthenticationService._DEFAULT_PASSWORD_CHANGE_RATE_WINDOW_MINUTES
|
|
143
|
+
};
|
|
144
|
+
this._tokenRefreshRateLimit = {
|
|
145
|
+
maxAttempts: options?.config?.tokenRefreshRateLimit?.maxAttempts ??
|
|
146
|
+
EntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_MAX_ATTEMPTS,
|
|
147
|
+
windowMinutes: options?.config?.tokenRefreshRateLimit?.windowMinutes ??
|
|
148
|
+
EntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Returns the class name of the component.
|
|
153
|
+
* @returns The class name of the component.
|
|
154
|
+
*/
|
|
155
|
+
className() {
|
|
156
|
+
return EntityStorageAuthenticationService.CLASS_NAME;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The service needs to be started when the application is initialized.
|
|
160
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
161
|
+
* @returns A promise that resolves when rate-limit actions have been registered and the node identity cached.
|
|
162
|
+
*/
|
|
163
|
+
async start(nodeLoggingComponentType) {
|
|
164
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
165
|
+
ContextIdHelper.guard(contextIds, ContextIdKeys.Node);
|
|
166
|
+
this._nodeId = contextIds[ContextIdKeys.Node];
|
|
167
|
+
await this._authenticationRateService.registerAction("login", this._loginRateLimit);
|
|
168
|
+
await this._authenticationRateService.registerAction("password-change", this._passwordChangeRateLimit);
|
|
169
|
+
await this._authenticationRateService.registerAction("token-refresh", this._tokenRefreshRateLimit);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The component needs to be stopped when the node is closed.
|
|
173
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
174
|
+
* @returns A promise that resolves when all rate-limit actions have been unregistered.
|
|
175
|
+
*/
|
|
176
|
+
async stop(nodeLoggingComponentType) {
|
|
177
|
+
await this._authenticationRateService.unregisterAction("login");
|
|
178
|
+
await this._authenticationRateService.unregisterAction("password-change");
|
|
179
|
+
await this._authenticationRateService.unregisterAction("token-refresh");
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Perform a login for the user.
|
|
183
|
+
* @param email The email address for the user.
|
|
184
|
+
* @param password The password for the user.
|
|
185
|
+
* @returns The authentication token for the user, if it uses a mechanism with public access.
|
|
186
|
+
*/
|
|
187
|
+
async login(email, password) {
|
|
188
|
+
Guards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, "email", email);
|
|
189
|
+
Guards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, "password", password);
|
|
190
|
+
if (!Is.stringValue(this._nodeId)) {
|
|
191
|
+
throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "nodeIdNotSet");
|
|
192
|
+
}
|
|
193
|
+
let loginUser;
|
|
194
|
+
let loginTenantId;
|
|
195
|
+
let tokenAndExpiry;
|
|
196
|
+
try {
|
|
197
|
+
await this._authenticationRateService.check("login", email);
|
|
198
|
+
const user = await this._userEntityStorage.get(email);
|
|
199
|
+
if (!user) {
|
|
200
|
+
throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "userNotFound");
|
|
201
|
+
}
|
|
202
|
+
const saltBytes = Converter.base64ToBytes(user.salt);
|
|
203
|
+
const passwordBytes = Converter.utf8ToBytes(password);
|
|
204
|
+
const hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);
|
|
205
|
+
if (!PasswordValidator.comparePasswordHashes(hashedPassword, user.password)) {
|
|
206
|
+
throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "passwordMismatch");
|
|
207
|
+
}
|
|
208
|
+
// This might be undefined if the login is performed in a single tenant context
|
|
209
|
+
// it is verified during the token processing, tenant id will be matched against
|
|
210
|
+
// the context
|
|
211
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
212
|
+
loginTenantId = contextIds?.[ContextIdKeys.Tenant];
|
|
213
|
+
tokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, this._nodeId, this._signingKeyName, user.identity, user.organization, loginTenantId, this._defaultTtlMinutes, user.passwordVersion ?? 0);
|
|
214
|
+
loginUser = user;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
await this._authenticationAuditService?.create({
|
|
218
|
+
actorId: email,
|
|
219
|
+
event: AuthAuditEvent.LoginFailure
|
|
220
|
+
});
|
|
221
|
+
throw new UnauthorizedError(EntityStorageAuthenticationService.CLASS_NAME, "loginFailed", undefined, error);
|
|
222
|
+
}
|
|
223
|
+
await this._authenticationRateService.clear("login", email);
|
|
224
|
+
await this._authenticationAuditService?.create({
|
|
225
|
+
actorId: email,
|
|
226
|
+
event: AuthAuditEvent.LoginSuccess,
|
|
227
|
+
data: {
|
|
228
|
+
userIdentity: loginUser.identity,
|
|
229
|
+
organizationIdentity: loginUser.organization,
|
|
230
|
+
tenantId: loginTenantId
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
return tokenAndExpiry;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Logout the current user.
|
|
237
|
+
* @param token The token to logout, if it uses a mechanism with public access.
|
|
238
|
+
* @returns A promise that resolves when the logout audit entry has been recorded.
|
|
239
|
+
*/
|
|
240
|
+
async logout(token) {
|
|
241
|
+
// Nothing to do here, as we are stateless.
|
|
242
|
+
// The cookie will be revoked by the REST route handling
|
|
243
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
244
|
+
const identifier = contextIds?.[ContextIdKeys.User];
|
|
245
|
+
if (Is.stringValue(identifier)) {
|
|
246
|
+
await this._authenticationAuditService?.create({
|
|
247
|
+
actorId: identifier,
|
|
248
|
+
event: AuthAuditEvent.Logout
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Refresh the token.
|
|
254
|
+
* @param token The token to refresh, if it uses a mechanism with public access.
|
|
255
|
+
* @returns The refreshed token, if it uses a mechanism with public access.
|
|
256
|
+
*/
|
|
257
|
+
async refresh(token) {
|
|
258
|
+
if (!Is.stringValue(this._nodeId)) {
|
|
259
|
+
throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "nodeIdNotSet");
|
|
260
|
+
}
|
|
261
|
+
let tenantId;
|
|
262
|
+
let refreshUser;
|
|
263
|
+
// If the verify fails on the current token then it will throw an exception.
|
|
264
|
+
const headerAndPayload = await TokenHelper.verify(this._vaultConnector, this._nodeId, this._signingKeyName, token, async (sub, org, tid, passwordVersion) => {
|
|
265
|
+
const validParts = [];
|
|
266
|
+
tenantId = tid;
|
|
267
|
+
if (Is.stringValue(tenantId)) {
|
|
268
|
+
const tenant = await this._tenantAdminComponent?.get(tenantId);
|
|
269
|
+
if (tenant?.id === tenantId) {
|
|
270
|
+
validParts.push("tenant");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// We use the tenant id from the token, if the user is not in that
|
|
274
|
+
// partition then the get will fail. Only override the tenant key when
|
|
275
|
+
// tid is present - setting it to undefined would drop the active partition.
|
|
276
|
+
const baseContext = (await ContextIdStore.getContextIds()) ?? {};
|
|
277
|
+
const contextIdsForUserLookup = Is.stringValue(tid)
|
|
278
|
+
? { ...baseContext, [ContextIdKeys.Tenant]: tid }
|
|
279
|
+
: baseContext;
|
|
280
|
+
// Wrap the user lookup in the request context so partitioned storage uses the correct tenant.
|
|
281
|
+
const user = await ContextIdStore.run(contextIdsForUserLookup, async () => this._userEntityStorage.get(sub, "identity"));
|
|
282
|
+
refreshUser = user;
|
|
283
|
+
if (user?.identity === sub && (passwordVersion ?? 0) === (user?.passwordVersion ?? 0)) {
|
|
284
|
+
validParts.push("user");
|
|
285
|
+
}
|
|
286
|
+
if (user?.organization === org) {
|
|
287
|
+
validParts.push("organization");
|
|
288
|
+
}
|
|
289
|
+
return validParts;
|
|
290
|
+
});
|
|
291
|
+
const refreshSub = headerAndPayload.payload.sub ?? "";
|
|
292
|
+
await this._authenticationRateService.check("token-refresh", refreshSub);
|
|
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, refreshUser?.passwordVersion ?? 0);
|
|
295
|
+
await this._authenticationAuditService?.create({
|
|
296
|
+
actorId: refreshSub,
|
|
297
|
+
event: AuthAuditEvent.TokenRefreshed,
|
|
298
|
+
data: {
|
|
299
|
+
organizationIdentity: payloadOrg,
|
|
300
|
+
tenantId,
|
|
301
|
+
version: refreshUser?.passwordVersion ?? 0
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return refreshTokenAndExpiry;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Update the user's password.
|
|
308
|
+
* @param currentPassword The current password for the user.
|
|
309
|
+
* @param newPassword The new password for the user.
|
|
310
|
+
* @returns A promise that resolves when the password has been updated and the rate limit cleared.
|
|
311
|
+
*/
|
|
312
|
+
async updatePassword(currentPassword, newPassword) {
|
|
313
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
314
|
+
ContextIdHelper.guard(contextIds, ContextIdKeys.User);
|
|
315
|
+
const userIdentity = contextIds[ContextIdKeys.User];
|
|
316
|
+
await this._authenticationRateService.check("password-change", userIdentity);
|
|
317
|
+
const user = await this._userEntityStorage.get(userIdentity, "identity");
|
|
318
|
+
if (!Is.object(user)) {
|
|
319
|
+
throw new NotFoundError(EntityStorageAuthenticationService.CLASS_NAME, "userNotFound", userIdentity);
|
|
320
|
+
}
|
|
321
|
+
await PasswordHelper.updatePassword(this._userEntityStorage, this._authenticationAuditService, user, newPassword, currentPassword, {
|
|
322
|
+
minPasswordLength: this._minPasswordLength,
|
|
323
|
+
maxPasswordLength: this._maxPasswordLength
|
|
324
|
+
});
|
|
325
|
+
await this._authenticationRateService.clear("password-change", userIdentity);
|
|
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
|
+
}
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=entityStorageAuthenticationService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { AuthAuditEvent } from "@3sixty/api-auth-models";
|
|
2
|
+
import { Converter, GeneralError, Is, RandomHelper } from "@3sixty/core";
|
|
3
|
+
import { PasswordGenerator, PasswordValidator } from "@3sixty/crypto";
|
|
4
|
+
/**
|
|
5
|
+
* Helper class for password operations.
|
|
6
|
+
*/
|
|
7
|
+
export class PasswordHelper {
|
|
8
|
+
/**
|
|
9
|
+
* Runtime name for the class.
|
|
10
|
+
*/
|
|
11
|
+
static CLASS_NAME = "PasswordHelper";
|
|
12
|
+
/**
|
|
13
|
+
* Update the password for a user.
|
|
14
|
+
* Validates password strength, verifies the current password if provided, then hashes and stores the new password and raises an audit event.
|
|
15
|
+
* @param userEntityStorage The entity storage for users.
|
|
16
|
+
* @param authenticationAuditService The optional audit service.
|
|
17
|
+
* @param user The user whose password is being updated.
|
|
18
|
+
* @param newPassword The new password to set.
|
|
19
|
+
* @param currentPassword The current password to verify against, if supplied.
|
|
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.
|
|
23
|
+
* @returns A promise that resolves when the new password has been stored and the audit entry recorded.
|
|
24
|
+
*/
|
|
25
|
+
static async updatePassword(userEntityStorage, authenticationAuditService, user, newPassword, currentPassword, options) {
|
|
26
|
+
PasswordValidator.validatePassword(newPassword, {
|
|
27
|
+
minLength: options?.minPasswordLength,
|
|
28
|
+
maxLength: options?.maxPasswordLength
|
|
29
|
+
});
|
|
30
|
+
if (Is.stringValue(currentPassword)) {
|
|
31
|
+
const saltBytes = Converter.base64ToBytes(user.salt);
|
|
32
|
+
const passwordBytes = Converter.utf8ToBytes(currentPassword);
|
|
33
|
+
const hashedCurrentPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);
|
|
34
|
+
if (!PasswordValidator.comparePasswordHashes(hashedCurrentPassword, user.password)) {
|
|
35
|
+
throw new GeneralError(PasswordHelper.CLASS_NAME, "currentPasswordMismatch");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const saltBytes = RandomHelper.generate(16);
|
|
39
|
+
const passwordBytes = Converter.utf8ToBytes(newPassword);
|
|
40
|
+
const hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);
|
|
41
|
+
const updatedUser = {
|
|
42
|
+
email: user.email,
|
|
43
|
+
salt: Converter.bytesToBase64(saltBytes),
|
|
44
|
+
password: hashedPassword,
|
|
45
|
+
identity: user.identity,
|
|
46
|
+
organization: user.organization,
|
|
47
|
+
passwordVersion: (user.passwordVersion ?? 0) + 1
|
|
48
|
+
};
|
|
49
|
+
await userEntityStorage.set(updatedUser);
|
|
50
|
+
await authenticationAuditService?.create({
|
|
51
|
+
actorId: user.email,
|
|
52
|
+
event: AuthAuditEvent.PasswordChanged,
|
|
53
|
+
data: {
|
|
54
|
+
userIdentity: updatedUser.identity,
|
|
55
|
+
organizationIdentity: updatedUser.organization
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=passwordHelper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|