@twin.org/api-auth-entity-storage-service 0.0.3-next.2 → 0.0.3-next.21
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 +2 -2
- package/dist/es/entities/authenticationUser.js +9 -1
- package/dist/es/entities/authenticationUser.js.map +1 -1
- package/dist/es/index.js +1 -1
- package/dist/es/index.js.map +1 -1
- package/dist/es/processors/authHeaderProcessor.js +23 -9
- package/dist/es/processors/authHeaderProcessor.js.map +1 -1
- package/dist/es/restEntryPoints.js +7 -0
- package/dist/es/restEntryPoints.js.map +1 -1
- package/dist/es/routes/entityStorageAuthenticationAdminRoutes.js +362 -0
- package/dist/es/routes/entityStorageAuthenticationAdminRoutes.js.map +1 -0
- package/dist/es/routes/entityStorageAuthenticationRoutes.js +12 -12
- package/dist/es/routes/entityStorageAuthenticationRoutes.js.map +1 -1
- package/dist/es/services/entityStorageAuthenticationAdminService.js +110 -41
- package/dist/es/services/entityStorageAuthenticationAdminService.js.map +1 -1
- package/dist/es/services/entityStorageAuthenticationService.js +21 -10
- package/dist/es/services/entityStorageAuthenticationService.js.map +1 -1
- package/dist/es/utils/tokenHelper.js +25 -18
- package/dist/es/utils/tokenHelper.js.map +1 -1
- package/dist/types/entities/authenticationUser.d.ts +4 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/routes/entityStorageAuthenticationAdminRoutes.d.ts +61 -0
- package/dist/types/services/entityStorageAuthenticationAdminService.d.ts +21 -6
- package/dist/types/services/entityStorageAuthenticationService.d.ts +2 -3
- package/dist/types/utils/tokenHelper.d.ts +5 -2
- package/docs/changelog.md +338 -1
- package/docs/examples.md +88 -1
- package/docs/reference/classes/AuthenticationUser.md +8 -0
- package/docs/reference/classes/EntityStorageAuthenticationAdminService.md +73 -13
- package/docs/reference/classes/EntityStorageAuthenticationService.md +3 -9
- package/docs/reference/classes/TokenHelper.md +20 -2
- package/docs/reference/functions/authenticationAdminCreateUser.md +31 -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/generateRestRoutesAuthenticationAdmin.md +25 -0
- package/docs/reference/index.md +8 -1
- package/docs/reference/interfaces/IAuthHeaderProcessorConfig.md +0 -12
- package/docs/reference/interfaces/IAuthHeaderProcessorConstructorOptions.md +0 -6
- package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConfig.md +0 -6
- package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md +0 -6
- package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConfig.md +0 -12
- package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConstructorOptions.md +0 -18
- package/docs/reference/variables/tagsAuthenticationAdmin.md +5 -0
- package/locales/en.json +7 -2
- package/package.json +5 -5
- package/dist/es/utils/passwordHelper.js +0 -29
- package/dist/es/utils/passwordHelper.js.map +0 -1
- package/dist/types/utils/passwordHelper.d.ts +0 -16
- package/docs/reference/classes/PasswordHelper.md +0 -49
|
@@ -51,7 +51,6 @@ export function generateRestRoutesAuthentication(baseRouteName, componentName) {
|
|
|
51
51
|
description: "The response for the login request.",
|
|
52
52
|
response: {
|
|
53
53
|
body: {
|
|
54
|
-
token: "eyJhbGciOiJIU...sw5c",
|
|
55
54
|
expiry: 1722514341067
|
|
56
55
|
}
|
|
57
56
|
}
|
|
@@ -122,7 +121,6 @@ export function generateRestRoutesAuthentication(baseRouteName, componentName) {
|
|
|
122
121
|
description: "The response for the refresh token request.",
|
|
123
122
|
response: {
|
|
124
123
|
body: {
|
|
125
|
-
token: "eyJhbGciOiJIU...sw5c",
|
|
126
124
|
expiry: 1722514341067
|
|
127
125
|
}
|
|
128
126
|
}
|
|
@@ -136,21 +134,18 @@ export function generateRestRoutesAuthentication(baseRouteName, componentName) {
|
|
|
136
134
|
};
|
|
137
135
|
const updatePasswordRoute = {
|
|
138
136
|
operationId: "authenticationUpdatePassword",
|
|
139
|
-
summary: "Update the user's password",
|
|
137
|
+
summary: "Update the current user's password",
|
|
140
138
|
tag: tagsAuthentication[0].name,
|
|
141
139
|
method: "PUT",
|
|
142
|
-
path: `${baseRouteName}
|
|
140
|
+
path: `${baseRouteName}/password`,
|
|
143
141
|
handler: async (httpRequestContext, request) => authenticationUpdatePassword(httpRequestContext, componentName, request),
|
|
144
142
|
requestType: {
|
|
145
143
|
type: "IUpdatePasswordRequest",
|
|
146
144
|
examples: [
|
|
147
145
|
{
|
|
148
146
|
id: "updatePasswordRequestExample",
|
|
149
|
-
description: "The request to update the user's password.",
|
|
147
|
+
description: "The request to update the current user's password.",
|
|
150
148
|
request: {
|
|
151
|
-
pathParams: {
|
|
152
|
-
email: "john:example.com"
|
|
153
|
-
},
|
|
154
149
|
body: {
|
|
155
150
|
currentPassword: "MyNewPassword123!",
|
|
156
151
|
newPassword: "MyNewPassword123!"
|
|
@@ -185,8 +180,11 @@ export async function authenticationLogin(httpRequestContext, componentName, req
|
|
|
185
180
|
// Need to give a hint to any auth processors about the operation
|
|
186
181
|
// in case they need to manipulate the response
|
|
187
182
|
httpRequestContext.processorState.authOperation = "login";
|
|
183
|
+
httpRequestContext.processorState.authToken = result.token;
|
|
188
184
|
return {
|
|
189
|
-
body:
|
|
185
|
+
body: {
|
|
186
|
+
expiry: result.expiry
|
|
187
|
+
}
|
|
190
188
|
};
|
|
191
189
|
}
|
|
192
190
|
/**
|
|
@@ -224,8 +222,11 @@ export async function authenticationRefreshToken(httpRequestContext, componentNa
|
|
|
224
222
|
// Need to give a hint to any auth processors about the operation
|
|
225
223
|
// in case they need to manipulate the response
|
|
226
224
|
httpRequestContext.processorState.authOperation = "refresh";
|
|
225
|
+
httpRequestContext.processorState.authToken = result.token;
|
|
227
226
|
return {
|
|
228
|
-
body:
|
|
227
|
+
body: {
|
|
228
|
+
expiry: result.expiry
|
|
229
|
+
}
|
|
229
230
|
};
|
|
230
231
|
}
|
|
231
232
|
/**
|
|
@@ -237,10 +238,9 @@ export async function authenticationRefreshToken(httpRequestContext, componentNa
|
|
|
237
238
|
*/
|
|
238
239
|
export async function authenticationUpdatePassword(httpRequestContext, componentName, request) {
|
|
239
240
|
Guards.object(ROUTES_SOURCE, "request", request);
|
|
240
|
-
Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
241
241
|
Guards.object(ROUTES_SOURCE, "request.body", request.body);
|
|
242
242
|
const component = ComponentFactory.get(componentName);
|
|
243
|
-
await component.updatePassword(request.
|
|
243
|
+
await component.updatePassword(request.body.currentPassword, request.body.newPassword);
|
|
244
244
|
return {
|
|
245
245
|
statusCode: HttpStatusCode.noContent
|
|
246
246
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entityStorageAuthenticationRoutes.js","sourceRoot":"","sources":["../../../src/routes/entityStorageAuthenticationRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIAuthenticationComponent,\n\tILoginRequest,\n\tILoginResponse,\n\tILogoutRequest,\n\tIRefreshTokenRequest,\n\tIRefreshTokenResponse,\n\tIUpdatePasswordRequest\n} from \"@twin.org/api-auth-entity-storage-models\";\nimport type {\n\tIHttpRequestContext,\n\tINoContentResponse,\n\tIRestRoute,\n\tIRestRouteResponseOptions,\n\tITag,\n\tIUnauthorizedResponse\n} from \"@twin.org/api-models\";\nimport { ComponentFactory, Guards } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HttpStatusCode } from \"@twin.org/web\";\n\n/**\n * The source used when communicating about these routes.\n */\nconst ROUTES_SOURCE = \"authenticationRoutes\";\n\n/**\n * The tag to associate with the routes.\n */\nexport const tagsAuthentication: ITag[] = [\n\t{\n\t\tname: \"Authentication\",\n\t\tdescription: \"Authentication endpoints for the REST server.\"\n\t}\n];\n\n/**\n * The REST routes for authentication.\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated routes.\n */\nexport function generateRestRoutesAuthentication(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst loginRoute: IRestRoute<ILoginRequest, ILoginResponse> = {\n\t\toperationId: \"authenticationLogin\",\n\t\tsummary: \"Login to the server\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/login`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationLogin(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: nameof<ILoginRequest>(),\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"loginRequestExample\",\n\t\t\t\t\tdescription: \"The request to login to the server.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\temail: \"user@example.com\",\n\t\t\t\t\t\t\tpassword: \"MyPassword123!\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<ILoginResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"loginResponseExample\",\n\t\t\t\t\t\tdescription: \"The response for the login request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\ttoken: \"eyJhbGciOiJIU...sw5c\",\n\t\t\t\t\t\t\t\texpiry: 1722514341067\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: nameof<IUnauthorizedResponse>()\n\t\t\t}\n\t\t],\n\t\tskipAuth: true\n\t};\n\n\tconst logoutRoute: IRestRoute<ILogoutRequest, INoContentResponse> = {\n\t\toperationId: \"authenticationLogout\",\n\t\tsummary: \"Logout from the server\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/logout`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationLogout(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: nameof<ILogoutRequest>(),\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"logoutRequestExample\",\n\t\t\t\t\tdescription: \"The request to logout from the server.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\ttoken: \"eyJhbGciOiJIU...sw5c\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<INoContentResponse>()\n\t\t\t}\n\t\t],\n\t\tskipAuth: true\n\t};\n\n\tconst refreshTokenRoute: IRestRoute<IRefreshTokenRequest, IRefreshTokenResponse> = {\n\t\toperationId: \"authenticationRefreshToken\",\n\t\tsummary: \"Refresh an authentication token\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/refresh`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationRefreshToken(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: nameof<IRefreshTokenRequest>(),\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"refreshTokenRequestExample\",\n\t\t\t\t\tdescription: \"The request to refresh an auth token.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\ttoken: \"eyJhbGciOiJIU...sw5c\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IRefreshTokenResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"refreshTokenResponseExample\",\n\t\t\t\t\t\tdescription: \"The response for the refresh token request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\ttoken: \"eyJhbGciOiJIU...sw5c\",\n\t\t\t\t\t\t\t\texpiry: 1722514341067\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: nameof<IUnauthorizedResponse>()\n\t\t\t}\n\t\t]\n\t};\n\n\tconst updatePasswordRoute: IRestRoute<IUpdatePasswordRequest, INoContentResponse> = {\n\t\toperationId: \"authenticationUpdatePassword\",\n\t\tsummary: \"Update the user's password\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"PUT\",\n\t\tpath: `${baseRouteName}/:email/password`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationUpdatePassword(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: nameof<IUpdatePasswordRequest>(),\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"updatePasswordRequestExample\",\n\t\t\t\t\tdescription: \"The request to update the user's password.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tpathParams: {\n\t\t\t\t\t\t\temail: \"john:example.com\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tcurrentPassword: \"MyNewPassword123!\",\n\t\t\t\t\t\t\tnewPassword: \"MyNewPassword123!\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<INoContentResponse>()\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: nameof<IUnauthorizedResponse>()\n\t\t\t}\n\t\t]\n\t};\n\n\treturn [loginRoute, logoutRoute, refreshTokenRoute, updatePasswordRoute];\n}\n\n/**\n * Login to the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationLogin(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ILoginRequest\n): Promise<ILoginResponse & IRestRouteResponseOptions> {\n\tGuards.object<ILoginRequest>(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object<ILoginRequest[\"body\"]>(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\tconst result = await component.login(request.body.email, request.body.password);\n\n\t// Need to give a hint to any auth processors about the operation\n\t// in case they need to manipulate the response\n\thttpRequestContext.processorState.authOperation = \"login\";\n\n\treturn {\n\t\tbody: result\n\t};\n}\n\n/**\n * Logout from the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationLogout(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ILogoutRequest\n): Promise<INoContentResponse & IRestRouteResponseOptions> {\n\tGuards.object<ILogoutRequest>(ROUTES_SOURCE, nameof(request), request);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\tawait component.logout(request.query?.token);\n\n\t// Need to give a hint to any auth processors about the operation\n\t// in case they need to manipulate the response\n\thttpRequestContext.processorState.authOperation = \"logout\";\n\n\treturn {\n\t\tstatusCode: HttpStatusCode.noContent\n\t};\n}\n\n/**\n * Refresh the login token.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationRefreshToken(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IRefreshTokenRequest\n): Promise<IRefreshTokenResponse & IRestRouteResponseOptions> {\n\tGuards.object<IRefreshTokenRequest>(ROUTES_SOURCE, nameof(request), request);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\n\t// If the token is not in the query, then maybe an auth processor has extracted it\n\t// and stored it in the processor state\n\tconst token = request.query?.token ?? (httpRequestContext.processorState.authToken as string);\n\tconst result = await component.refresh(token);\n\n\t// Need to give a hint to any auth processors about the operation\n\t// in case they need to manipulate the response\n\thttpRequestContext.processorState.authOperation = \"refresh\";\n\n\treturn {\n\t\tbody: result\n\t};\n}\n\n/**\n * Update the user's password.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationUpdatePassword(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IUpdatePasswordRequest\n): Promise<INoContentResponse> {\n\tGuards.object<IUpdatePasswordRequest>(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object<IUpdatePasswordRequest[\"pathParams\"]>(\n\t\tROUTES_SOURCE,\n\t\tnameof(request.pathParams),\n\t\trequest.pathParams\n\t);\n\tGuards.object<IUpdatePasswordRequest[\"body\"]>(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\n\tawait component.updatePassword(\n\t\trequest.pathParams.email,\n\t\trequest.body.currentPassword,\n\t\trequest.body.newPassword\n\t);\n\n\treturn {\n\t\tstatusCode: HttpStatusCode.noContent\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"entityStorageAuthenticationRoutes.js","sourceRoot":"","sources":["../../../src/routes/entityStorageAuthenticationRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIAuthenticationComponent,\n\tILoginRequest,\n\tILoginResponse,\n\tILogoutRequest,\n\tIRefreshTokenRequest,\n\tIRefreshTokenResponse,\n\tIUpdatePasswordRequest\n} from \"@twin.org/api-auth-entity-storage-models\";\nimport type {\n\tIHttpRequestContext,\n\tINoContentResponse,\n\tIRestRoute,\n\tIRestRouteResponseOptions,\n\tITag,\n\tIUnauthorizedResponse\n} from \"@twin.org/api-models\";\nimport { ComponentFactory, Guards } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HttpStatusCode } from \"@twin.org/web\";\n\n/**\n * The source used when communicating about these routes.\n */\nconst ROUTES_SOURCE = \"authenticationRoutes\";\n\n/**\n * The tag to associate with the routes.\n */\nexport const tagsAuthentication: ITag[] = [\n\t{\n\t\tname: \"Authentication\",\n\t\tdescription: \"Authentication endpoints for the REST server.\"\n\t}\n];\n\n/**\n * The REST routes for authentication.\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated routes.\n */\nexport function generateRestRoutesAuthentication(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst loginRoute: IRestRoute<ILoginRequest, ILoginResponse> = {\n\t\toperationId: \"authenticationLogin\",\n\t\tsummary: \"Login to the server\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/login`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationLogin(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: nameof<ILoginRequest>(),\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"loginRequestExample\",\n\t\t\t\t\tdescription: \"The request to login to the server.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\temail: \"user@example.com\",\n\t\t\t\t\t\t\tpassword: \"MyPassword123!\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<ILoginResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"loginResponseExample\",\n\t\t\t\t\t\tdescription: \"The response for the login request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\texpiry: 1722514341067\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: nameof<IUnauthorizedResponse>()\n\t\t\t}\n\t\t],\n\t\tskipAuth: true\n\t};\n\n\tconst logoutRoute: IRestRoute<ILogoutRequest, INoContentResponse> = {\n\t\toperationId: \"authenticationLogout\",\n\t\tsummary: \"Logout from the server\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/logout`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationLogout(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: nameof<ILogoutRequest>(),\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"logoutRequestExample\",\n\t\t\t\t\tdescription: \"The request to logout from the server.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\ttoken: \"eyJhbGciOiJIU...sw5c\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<INoContentResponse>()\n\t\t\t}\n\t\t],\n\t\tskipAuth: true\n\t};\n\n\tconst refreshTokenRoute: IRestRoute<IRefreshTokenRequest, IRefreshTokenResponse> = {\n\t\toperationId: \"authenticationRefreshToken\",\n\t\tsummary: \"Refresh an authentication token\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/refresh`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationRefreshToken(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: nameof<IRefreshTokenRequest>(),\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"refreshTokenRequestExample\",\n\t\t\t\t\tdescription: \"The request to refresh an auth token.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\ttoken: \"eyJhbGciOiJIU...sw5c\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IRefreshTokenResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"refreshTokenResponseExample\",\n\t\t\t\t\t\tdescription: \"The response for the refresh token request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\texpiry: 1722514341067\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: nameof<IUnauthorizedResponse>()\n\t\t\t}\n\t\t]\n\t};\n\n\tconst updatePasswordRoute: IRestRoute<IUpdatePasswordRequest, INoContentResponse> = {\n\t\toperationId: \"authenticationUpdatePassword\",\n\t\tsummary: \"Update the current user's password\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"PUT\",\n\t\tpath: `${baseRouteName}/password`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationUpdatePassword(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: nameof<IUpdatePasswordRequest>(),\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"updatePasswordRequestExample\",\n\t\t\t\t\tdescription: \"The request to update the current user's password.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tcurrentPassword: \"MyNewPassword123!\",\n\t\t\t\t\t\t\tnewPassword: \"MyNewPassword123!\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<INoContentResponse>()\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: nameof<IUnauthorizedResponse>()\n\t\t\t}\n\t\t]\n\t};\n\n\treturn [loginRoute, logoutRoute, refreshTokenRoute, updatePasswordRoute];\n}\n\n/**\n * Login to the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationLogin(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ILoginRequest\n): Promise<ILoginResponse & IRestRouteResponseOptions> {\n\tGuards.object<ILoginRequest>(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object<ILoginRequest[\"body\"]>(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\tconst result = await component.login(request.body.email, request.body.password);\n\n\t// Need to give a hint to any auth processors about the operation\n\t// in case they need to manipulate the response\n\thttpRequestContext.processorState.authOperation = \"login\";\n\thttpRequestContext.processorState.authToken = result.token;\n\n\treturn {\n\t\tbody: {\n\t\t\texpiry: result.expiry\n\t\t}\n\t};\n}\n\n/**\n * Logout from the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationLogout(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ILogoutRequest\n): Promise<INoContentResponse & IRestRouteResponseOptions> {\n\tGuards.object<ILogoutRequest>(ROUTES_SOURCE, nameof(request), request);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\tawait component.logout(request.query?.token);\n\n\t// Need to give a hint to any auth processors about the operation\n\t// in case they need to manipulate the response\n\thttpRequestContext.processorState.authOperation = \"logout\";\n\n\treturn {\n\t\tstatusCode: HttpStatusCode.noContent\n\t};\n}\n\n/**\n * Refresh the login token.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationRefreshToken(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IRefreshTokenRequest\n): Promise<IRefreshTokenResponse & IRestRouteResponseOptions> {\n\tGuards.object<IRefreshTokenRequest>(ROUTES_SOURCE, nameof(request), request);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\n\t// If the token is not in the query, then maybe an auth processor has extracted it\n\t// and stored it in the processor state\n\tconst token = request.query?.token ?? (httpRequestContext.processorState.authToken as string);\n\tconst result = await component.refresh(token);\n\n\t// Need to give a hint to any auth processors about the operation\n\t// in case they need to manipulate the response\n\thttpRequestContext.processorState.authOperation = \"refresh\";\n\thttpRequestContext.processorState.authToken = result.token;\n\n\treturn {\n\t\tbody: {\n\t\t\texpiry: result.expiry\n\t\t}\n\t};\n}\n\n/**\n * Update the user's password.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationUpdatePassword(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IUpdatePasswordRequest\n): Promise<INoContentResponse> {\n\tGuards.object<IUpdatePasswordRequest>(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object<IUpdatePasswordRequest[\"body\"]>(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\n\tawait component.updatePassword(request.body.currentPassword, request.body.newPassword);\n\n\treturn {\n\t\tstatusCode: HttpStatusCode.noContent\n\t};\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Converter, GeneralError, Guards, Is, NotFoundError, RandomHelper } from "@twin.org/core";
|
|
2
|
+
import { PasswordGenerator, PasswordValidator } from "@twin.org/crypto";
|
|
2
3
|
import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
|
|
3
|
-
import { PasswordHelper } from "../utils/passwordHelper.js";
|
|
4
4
|
/**
|
|
5
5
|
* Implementation of the authentication component using entity storage.
|
|
6
6
|
*/
|
|
@@ -9,11 +9,6 @@ export class EntityStorageAuthenticationAdminService {
|
|
|
9
9
|
* Runtime name for the class.
|
|
10
10
|
*/
|
|
11
11
|
static CLASS_NAME = "EntityStorageAuthenticationAdminService";
|
|
12
|
-
/**
|
|
13
|
-
* The minimum password length.
|
|
14
|
-
* @internal
|
|
15
|
-
*/
|
|
16
|
-
static _DEFAULT_MIN_PASSWORD_LENGTH = 8;
|
|
17
12
|
/**
|
|
18
13
|
* The entity storage for users.
|
|
19
14
|
* @internal
|
|
@@ -30,9 +25,7 @@ export class EntityStorageAuthenticationAdminService {
|
|
|
30
25
|
*/
|
|
31
26
|
constructor(options) {
|
|
32
27
|
this._userEntityStorage = EntityStorageConnectorFactory.get(options?.userEntityStorageType ?? "authentication-user");
|
|
33
|
-
this._minPasswordLength =
|
|
34
|
-
options?.config?.minPasswordLength ??
|
|
35
|
-
EntityStorageAuthenticationAdminService._DEFAULT_MIN_PASSWORD_LENGTH;
|
|
28
|
+
this._minPasswordLength = options?.config?.minPasswordLength;
|
|
36
29
|
}
|
|
37
30
|
/**
|
|
38
31
|
* Returns the class name of the component.
|
|
@@ -43,36 +36,34 @@ export class EntityStorageAuthenticationAdminService {
|
|
|
43
36
|
}
|
|
44
37
|
/**
|
|
45
38
|
* Create a login for the user.
|
|
46
|
-
* @param
|
|
47
|
-
* @param password The password for the user.
|
|
48
|
-
* @param userIdentity The DID to associate with the account.
|
|
49
|
-
* @param organizationIdentity The organization of the user.
|
|
39
|
+
* @param user The user to create.
|
|
50
40
|
* @returns Nothing.
|
|
51
41
|
*/
|
|
52
|
-
async create(
|
|
53
|
-
Guards.
|
|
54
|
-
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "
|
|
55
|
-
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "
|
|
56
|
-
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "
|
|
42
|
+
async create(user) {
|
|
43
|
+
Guards.object(EntityStorageAuthenticationAdminService.CLASS_NAME, "user", user);
|
|
44
|
+
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.email", user.email);
|
|
45
|
+
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.password", user.password);
|
|
46
|
+
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.userIdentity", user.userIdentity);
|
|
47
|
+
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.organizationIdentity", user.organizationIdentity);
|
|
48
|
+
Guards.array(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.scope", user.scope);
|
|
57
49
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const user = await this._userEntityStorage.get(email);
|
|
64
|
-
if (user) {
|
|
50
|
+
PasswordValidator.validatePassword(user.password, {
|
|
51
|
+
minLength: this._minPasswordLength
|
|
52
|
+
});
|
|
53
|
+
const existingUser = await this._userEntityStorage.get(user.email);
|
|
54
|
+
if (Is.object(existingUser)) {
|
|
65
55
|
throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userExists");
|
|
66
56
|
}
|
|
67
57
|
const saltBytes = RandomHelper.generate(16);
|
|
68
|
-
const passwordBytes = Converter.utf8ToBytes(password);
|
|
69
|
-
const hashedPassword = await
|
|
58
|
+
const passwordBytes = Converter.utf8ToBytes(user.password);
|
|
59
|
+
const hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);
|
|
70
60
|
const newUser = {
|
|
71
|
-
email,
|
|
61
|
+
email: user.email,
|
|
72
62
|
salt: Converter.bytesToBase64(saltBytes),
|
|
73
63
|
password: hashedPassword,
|
|
74
|
-
identity: userIdentity,
|
|
75
|
-
organization: organizationIdentity
|
|
64
|
+
identity: user.userIdentity,
|
|
65
|
+
organization: user.organizationIdentity,
|
|
66
|
+
scope: user.scope.map(s => s.trim().toLocaleLowerCase()).join(",")
|
|
76
67
|
};
|
|
77
68
|
await this._userEntityStorage.set(newUser);
|
|
78
69
|
}
|
|
@@ -80,6 +71,85 @@ export class EntityStorageAuthenticationAdminService {
|
|
|
80
71
|
throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "createUserFailed", undefined, error);
|
|
81
72
|
}
|
|
82
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Update a login for the user.
|
|
76
|
+
* @param user The user to update.
|
|
77
|
+
* @returns Nothing.
|
|
78
|
+
*/
|
|
79
|
+
async update(user) {
|
|
80
|
+
Guards.object(EntityStorageAuthenticationAdminService.CLASS_NAME, "user", user);
|
|
81
|
+
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.email", user.email);
|
|
82
|
+
if (!Is.empty(user.userIdentity)) {
|
|
83
|
+
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.userIdentity", user.userIdentity);
|
|
84
|
+
}
|
|
85
|
+
if (!Is.empty(user.organizationIdentity)) {
|
|
86
|
+
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.organizationIdentity", user.organizationIdentity);
|
|
87
|
+
}
|
|
88
|
+
if (!Is.empty(user.scope)) {
|
|
89
|
+
Guards.array(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.scope", user.scope);
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const existingUser = await this._userEntityStorage.get(user.email);
|
|
93
|
+
if (!Is.object(existingUser)) {
|
|
94
|
+
throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", user.email);
|
|
95
|
+
}
|
|
96
|
+
existingUser.identity = user.userIdentity ?? existingUser.identity;
|
|
97
|
+
existingUser.organization = user.organizationIdentity ?? existingUser.organization;
|
|
98
|
+
existingUser.scope = Is.array(user.scope)
|
|
99
|
+
? user.scope.map(s => s.trim().toLocaleLowerCase()).join(",")
|
|
100
|
+
: user.scope;
|
|
101
|
+
await this._userEntityStorage.set(existingUser);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "updateUserFailed", undefined, error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get a user by email.
|
|
109
|
+
* @param email The email address of the user to get.
|
|
110
|
+
* @returns The user details.
|
|
111
|
+
*/
|
|
112
|
+
async get(email) {
|
|
113
|
+
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "email", email);
|
|
114
|
+
try {
|
|
115
|
+
const user = await this._userEntityStorage.get(email);
|
|
116
|
+
if (!Is.object(user)) {
|
|
117
|
+
throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", email);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
email: user.email,
|
|
121
|
+
userIdentity: user.identity,
|
|
122
|
+
organizationIdentity: user.organization,
|
|
123
|
+
scope: user.scope.split(",")
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "getUserFailed", undefined, error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get a user by identity.
|
|
132
|
+
* @param identity The identity of the user to get.
|
|
133
|
+
* @returns The user details.
|
|
134
|
+
*/
|
|
135
|
+
async getByIdentity(identity) {
|
|
136
|
+
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "identity", identity);
|
|
137
|
+
try {
|
|
138
|
+
const user = await this._userEntityStorage.get(identity, "identity");
|
|
139
|
+
if (!Is.object(user)) {
|
|
140
|
+
throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", identity);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
email: user.email,
|
|
144
|
+
userIdentity: user.identity,
|
|
145
|
+
organizationIdentity: user.organization,
|
|
146
|
+
scope: user.scope.split(",")
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "getUserFailed", undefined, error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
83
153
|
/**
|
|
84
154
|
* Remove the current user.
|
|
85
155
|
* @param email The email address of the user to remove.
|
|
@@ -89,7 +159,7 @@ export class EntityStorageAuthenticationAdminService {
|
|
|
89
159
|
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "email", email);
|
|
90
160
|
try {
|
|
91
161
|
const user = await this._userEntityStorage.get(email);
|
|
92
|
-
if (!user) {
|
|
162
|
+
if (!Is.object(user)) {
|
|
93
163
|
throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", email);
|
|
94
164
|
}
|
|
95
165
|
await this._userEntityStorage.remove(email);
|
|
@@ -109,32 +179,31 @@ export class EntityStorageAuthenticationAdminService {
|
|
|
109
179
|
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "email", email);
|
|
110
180
|
Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "newPassword", newPassword);
|
|
111
181
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
});
|
|
116
|
-
}
|
|
182
|
+
PasswordValidator.validatePassword(newPassword, {
|
|
183
|
+
minLength: this._minPasswordLength
|
|
184
|
+
});
|
|
117
185
|
const user = await this._userEntityStorage.get(email);
|
|
118
|
-
if (!user) {
|
|
186
|
+
if (!Is.object(user)) {
|
|
119
187
|
throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", email);
|
|
120
188
|
}
|
|
121
189
|
if (Is.stringValue(currentPassword)) {
|
|
122
190
|
const saltBytes = Converter.base64ToBytes(user.salt);
|
|
123
191
|
const passwordBytes = Converter.utf8ToBytes(currentPassword);
|
|
124
|
-
const hashedPassword = await
|
|
125
|
-
if (hashedPassword
|
|
192
|
+
const hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);
|
|
193
|
+
if (!PasswordValidator.comparePasswordHashes(hashedPassword, user.password)) {
|
|
126
194
|
throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "currentPasswordMismatch");
|
|
127
195
|
}
|
|
128
196
|
}
|
|
129
197
|
const saltBytes = RandomHelper.generate(16);
|
|
130
198
|
const passwordBytes = Converter.utf8ToBytes(newPassword);
|
|
131
|
-
const hashedPassword = await
|
|
199
|
+
const hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);
|
|
132
200
|
const updatedUser = {
|
|
133
201
|
email,
|
|
134
202
|
salt: Converter.bytesToBase64(saltBytes),
|
|
135
203
|
password: hashedPassword,
|
|
136
204
|
identity: user.identity,
|
|
137
|
-
organization: user.organization
|
|
205
|
+
organization: user.organization,
|
|
206
|
+
scope: user.scope
|
|
138
207
|
};
|
|
139
208
|
await this._userEntityStorage.set(updatedUser);
|
|
140
209
|
}
|
|
@@ -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 { IAuthenticationAdminComponent } from \"@twin.org/api-auth-entity-storage-models\";\nimport { Converter, GeneralError, Guards, Is, NotFoundError, RandomHelper } from \"@twin.org/core\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/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 minimum password length.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_MIN_PASSWORD_LENGTH: number = 8;\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 minimum password length.\n\t * @internal\n\t */\n\tprivate readonly _minPasswordLength: 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._minPasswordLength =\n\t\t\toptions?.config?.minPasswordLength ??\n\t\t\tEntityStorageAuthenticationAdminService._DEFAULT_MIN_PASSWORD_LENGTH;\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 email The email address for the user.\n\t * @param password The password for the user.\n\t * @param userIdentity The DID to associate with the account.\n\t * @param organizationIdentity The organization of the user.\n\t * @returns Nothing.\n\t */\n\tpublic async create(\n\t\temail: string,\n\t\tpassword: string,\n\t\tuserIdentity: string,\n\t\torganizationIdentity: 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(password),\n\t\t\tpassword\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(userIdentity),\n\t\t\tuserIdentity\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\tnameof(organizationIdentity),\n\t\t\torganizationIdentity\n\t\t);\n\n\t\ttry {\n\t\t\tif (password.length < this._minPasswordLength) {\n\t\t\t\tthrow new GeneralError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"passwordTooShort\",\n\t\t\t\t\t{\n\t\t\t\t\t\tminLength: this._minPasswordLength\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (user) {\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(password);\n\n\t\t\tconst hashedPassword = await PasswordHelper.hashPassword(passwordBytes, saltBytes);\n\n\t\t\tconst newUser: AuthenticationUser = {\n\t\t\t\temail,\n\t\t\t\tsalt: Converter.bytesToBase64(saltBytes),\n\t\t\t\tpassword: hashedPassword,\n\t\t\t\tidentity: userIdentity,\n\t\t\t\torganization: organizationIdentity\n\t\t\t};\n\n\t\t\tawait this._userEntityStorage.set(newUser);\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 * Remove the current user.\n\t * @param email The email address of the user to remove.\n\t * @returns Nothing.\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 (!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._userEntityStorage.remove(email);\n\t\t} catch (error) {\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 Nothing.\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\tif (newPassword.length < this._minPasswordLength) {\n\t\t\t\tthrow new GeneralError(\n\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\"passwordTooShort\",\n\t\t\t\t\t{\n\t\t\t\t\t\tminLength: this._minPasswordLength\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!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\tif (Is.stringValue(currentPassword)) {\n\t\t\t\tconst saltBytes = Converter.base64ToBytes(user.salt);\n\t\t\t\tconst passwordBytes = Converter.utf8ToBytes(currentPassword);\n\n\t\t\t\tconst hashedPassword = await PasswordHelper.hashPassword(passwordBytes, saltBytes);\n\n\t\t\t\tif (hashedPassword !== user.password) {\n\t\t\t\t\tthrow new GeneralError(\n\t\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\t\"currentPasswordMismatch\"\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst saltBytes = RandomHelper.generate(16);\n\t\t\tconst passwordBytes = Converter.utf8ToBytes(newPassword);\n\n\t\t\tconst hashedPassword = await PasswordHelper.hashPassword(passwordBytes, saltBytes);\n\n\t\t\tconst updatedUser: AuthenticationUser = {\n\t\t\t\temail,\n\t\t\t\tsalt: Converter.bytesToBase64(saltBytes),\n\t\t\t\tpassword: hashedPassword,\n\t\t\t\tidentity: user.identity,\n\t\t\t\torganization: user.organization\n\t\t\t};\n\n\t\t\tawait this._userEntityStorage.set(updatedUser);\n\t\t} catch (error) {\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"]}
|
|
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\tIAuthenticationUser\n} from \"@twin.org/api-auth-entity-storage-models\";\nimport { Converter, GeneralError, Guards, Is, NotFoundError, RandomHelper } from \"@twin.org/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@twin.org/crypto\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\nimport type { IEntityStorageAuthenticationAdminServiceConstructorOptions } from \"../models/IEntityStorageAuthenticationAdminServiceConstructorOptions.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 entity storage for users.\n\t * @internal\n\t */\n\tprivate readonly _userEntityStorage: IEntityStorageConnector<AuthenticationUser>;\n\n\t/**\n\t * The minimum password length.\n\t * @internal\n\t */\n\tprivate readonly _minPasswordLength?: 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._minPasswordLength = options?.config?.minPasswordLength;\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 Nothing.\n\t */\n\tpublic async create(user: Omit<IAuthenticationUser, \"salt\">): 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\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: user.scope.map(s => s.trim().toLocaleLowerCase()).join(\",\")\n\t\t\t};\n\n\t\t\tawait this._userEntityStorage.set(newUser);\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 Nothing.\n\t */\n\tpublic async update(\n\t\tuser: Partial<Omit<IAuthenticationUser, \"password\" | \"salt\">>\n\t): 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}\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\texistingUser.identity = user.userIdentity ?? existingUser.identity;\n\t\t\texistingUser.organization = user.organizationIdentity ?? existingUser.organization;\n\t\t\texistingUser.scope = Is.array(user.scope)\n\t\t\t\t? user.scope.map(s => s.trim().toLocaleLowerCase()).join(\",\")\n\t\t\t\t: user.scope;\n\n\t\t\tawait this._userEntityStorage.set(existingUser);\n\t\t} catch (error) {\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<Omit<IAuthenticationUser, \"password\" | \"salt\">> {\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: user.scope.split(\",\")\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(\n\t\tidentity: string\n\t): Promise<Omit<IAuthenticationUser, \"password\" | \"salt\">> {\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: user.scope.split(\",\")\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 Nothing.\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._userEntityStorage.remove(email);\n\t\t} catch (error) {\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 Nothing.\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\tPasswordValidator.validatePassword(newPassword, {\n\t\t\t\tminLength: this._minPasswordLength\n\t\t\t});\n\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\tif (Is.stringValue(currentPassword)) {\n\t\t\t\tconst saltBytes = Converter.base64ToBytes(user.salt);\n\t\t\t\tconst passwordBytes = Converter.utf8ToBytes(currentPassword);\n\n\t\t\t\tconst hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\n\t\t\t\tif (!PasswordValidator.comparePasswordHashes(hashedPassword, user.password)) {\n\t\t\t\t\tthrow new GeneralError(\n\t\t\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\t\t\"currentPasswordMismatch\"\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst saltBytes = RandomHelper.generate(16);\n\t\t\tconst passwordBytes = Converter.utf8ToBytes(newPassword);\n\n\t\t\tconst hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\n\t\t\tconst updatedUser: AuthenticationUser = {\n\t\t\t\temail,\n\t\t\t\tsalt: Converter.bytesToBase64(saltBytes),\n\t\t\t\tpassword: hashedPassword,\n\t\t\t\tidentity: user.identity,\n\t\t\t\torganization: user.organization,\n\t\t\t\tscope: user.scope\n\t\t\t};\n\n\t\t\tawait this._userEntityStorage.set(updatedUser);\n\t\t} catch (error) {\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"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@twin.org/context";
|
|
2
|
-
import { ComponentFactory, Converter, GeneralError, Guards, Is, UnauthorizedError } from "@twin.org/core";
|
|
2
|
+
import { Coerce, ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError, UnauthorizedError } from "@twin.org/core";
|
|
3
|
+
import { PasswordGenerator, PasswordValidator } from "@twin.org/crypto";
|
|
3
4
|
import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
|
|
4
5
|
import { VaultConnectorFactory } from "@twin.org/vault-models";
|
|
5
|
-
import { PasswordHelper } from "../utils/passwordHelper.js";
|
|
6
6
|
import { TokenHelper } from "../utils/tokenHelper.js";
|
|
7
7
|
/**
|
|
8
8
|
* Implementation of the authentication component using entity storage.
|
|
@@ -92,11 +92,16 @@ export class EntityStorageAuthenticationService {
|
|
|
92
92
|
}
|
|
93
93
|
const saltBytes = Converter.base64ToBytes(user.salt);
|
|
94
94
|
const passwordBytes = Converter.utf8ToBytes(password);
|
|
95
|
-
const hashedPassword = await
|
|
96
|
-
if (hashedPassword
|
|
95
|
+
const hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);
|
|
96
|
+
if (!PasswordValidator.comparePasswordHashes(hashedPassword, user.password)) {
|
|
97
97
|
throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "passwordMismatch");
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
// This might be undefined if the login is performed in a single tenant context
|
|
100
|
+
// if is verified during the token processing, tenant id will be matched against
|
|
101
|
+
// the context
|
|
102
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
103
|
+
const tenantId = contextIds?.[ContextIdKeys.Tenant];
|
|
104
|
+
const tokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, user.identity, user.organization, tenantId, this._defaultTtlMinutes, user.scope);
|
|
100
105
|
return tokenAndExpiry;
|
|
101
106
|
}
|
|
102
107
|
catch (error) {
|
|
@@ -109,7 +114,8 @@ export class EntityStorageAuthenticationService {
|
|
|
109
114
|
* @returns Nothing.
|
|
110
115
|
*/
|
|
111
116
|
async logout(token) {
|
|
112
|
-
// Nothing to do here.
|
|
117
|
+
// Nothing to do here, as we are stateless.
|
|
118
|
+
// The cookie will be revoked by the REST route handling
|
|
113
119
|
}
|
|
114
120
|
/**
|
|
115
121
|
* Refresh the token.
|
|
@@ -119,18 +125,23 @@ export class EntityStorageAuthenticationService {
|
|
|
119
125
|
async refresh(token) {
|
|
120
126
|
// If the verify fails on the current token then it will throw an exception.
|
|
121
127
|
const headerAndPayload = await TokenHelper.verify(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, token);
|
|
122
|
-
const refreshTokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, headerAndPayload.payload.sub ?? "", Is.stringValue(headerAndPayload.payload.org) ? headerAndPayload.payload.org : "", this._defaultTtlMinutes);
|
|
128
|
+
const refreshTokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, headerAndPayload.payload.sub ?? "", Is.stringValue(headerAndPayload.payload.org) ? headerAndPayload.payload.org : "", Is.stringValue(headerAndPayload.payload.tid) ? headerAndPayload.payload.tid : "", this._defaultTtlMinutes, Coerce.string(headerAndPayload.payload?.scope));
|
|
123
129
|
return refreshTokenAndExpiry;
|
|
124
130
|
}
|
|
125
131
|
/**
|
|
126
132
|
* Update the user's password.
|
|
127
|
-
* @param email The email address of the user to update.
|
|
128
133
|
* @param currentPassword The current password for the user.
|
|
129
134
|
* @param newPassword The new password for the user.
|
|
130
135
|
* @returns Nothing.
|
|
131
136
|
*/
|
|
132
|
-
async updatePassword(
|
|
133
|
-
|
|
137
|
+
async updatePassword(currentPassword, newPassword) {
|
|
138
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
139
|
+
ContextIdHelper.guard(contextIds, ContextIdKeys.User);
|
|
140
|
+
const user = await this._userEntityStorage.get(contextIds[ContextIdKeys.User]);
|
|
141
|
+
if (!Is.object(user)) {
|
|
142
|
+
throw new NotFoundError(EntityStorageAuthenticationService.CLASS_NAME, "userNotFound", contextIds[ContextIdKeys.User]);
|
|
143
|
+
}
|
|
144
|
+
return this._authenticationAdminService.updatePassword(user.email, newPassword, currentPassword);
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
147
|
//# 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\tIAuthenticationAdminComponent,\n\tIAuthenticationComponent\n} from \"@twin.org/api-auth-entity-storage-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tUnauthorizedError\n} from \"@twin.org/core\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { VaultConnectorFactory, type IVaultConnector } from \"@twin.org/vault-models\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\nimport type { IEntityStorageAuthenticationServiceConstructorOptions } from \"../models/IEntityStorageAuthenticationServiceConstructorOptions.js\";\nimport { PasswordHelper } from \"../utils/passwordHelper.js\";\nimport { TokenHelper } from \"../utils/tokenHelper.js\";\n\n/**\n * Implementation of the authentication component using entity storage.\n */\nexport class EntityStorageAuthenticationService implements IAuthenticationComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntityStorageAuthenticationService>();\n\n\t/**\n\t * Default TTL in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TTL_MINUTES: number = 60;\n\n\t/**\n\t * The user admin service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationAdminService: IAuthenticationAdminComponent;\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 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._authenticationAdminService = ComponentFactory.get<IAuthenticationAdminComponent>(\n\t\t\toptions?.authenticationAdminServiceType ?? \"authentication-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}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn EntityStorageAuthenticationService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The service needs to be started when the application is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.Node);\n\t\tthis._nodeId = contextIds[ContextIdKeys.Node];\n\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\ttry {\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 PasswordHelper.hashPassword(passwordBytes, saltBytes);\n\n\t\t\tif (hashedPassword !== user.password) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"passwordMismatch\");\n\t\t\t}\n\n\t\t\tconst tokenAndExpiry = await TokenHelper.createToken(\n\t\t\t\tthis._vaultConnector,\n\t\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\t\tuser.identity,\n\t\t\t\tuser.organization,\n\t\t\t\tthis._defaultTtlMinutes\n\t\t\t);\n\n\t\t\treturn tokenAndExpiry;\n\t\t} catch (error) {\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\t}\n\n\t/**\n\t * Logout the current user.\n\t * @param token The token to logout, if it uses a mechanism with public access.\n\t * @returns Nothing.\n\t */\n\tpublic async logout(token?: string): Promise<void> {\n\t\t// Nothing to do here.\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\t// If the verify fails on the current token then it will throw an exception.\n\t\tconst headerAndPayload = await TokenHelper.verify(\n\t\t\tthis._vaultConnector,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\ttoken\n\t\t);\n\n\t\tconst refreshTokenAndExpiry = await TokenHelper.createToken(\n\t\t\tthis._vaultConnector,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\theaderAndPayload.payload.sub ?? \"\",\n\t\t\tIs.stringValue(headerAndPayload.payload.org) ? headerAndPayload.payload.org : \"\",\n\t\t\tthis._defaultTtlMinutes\n\t\t);\n\n\t\treturn refreshTokenAndExpiry;\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 currentPassword The current password for the user.\n\t * @param newPassword The new password for the user.\n\t * @returns Nothing.\n\t */\n\tpublic async updatePassword(\n\t\temail: string,\n\t\tcurrentPassword: string,\n\t\tnewPassword: string\n\t): Promise<void> {\n\t\treturn this._authenticationAdminService.updatePassword(email, newPassword, currentPassword);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"entityStorageAuthenticationService.js","sourceRoot":"","sources":["../../../src/services/entityStorageAuthenticationService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIAuthenticationAdminComponent,\n\tIAuthenticationComponent\n} from \"@twin.org/api-auth-entity-storage-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tCoerce,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tUnauthorizedError\n} from \"@twin.org/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@twin.org/crypto\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { VaultConnectorFactory, type IVaultConnector } from \"@twin.org/vault-models\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\nimport type { IEntityStorageAuthenticationServiceConstructorOptions } from \"../models/IEntityStorageAuthenticationServiceConstructorOptions.js\";\nimport { 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 * The user admin service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationAdminService: IAuthenticationAdminComponent;\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 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._authenticationAdminService = ComponentFactory.get<IAuthenticationAdminComponent>(\n\t\t\toptions?.authenticationAdminServiceType ?? \"authentication-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}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn EntityStorageAuthenticationService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The service needs to be started when the application is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.Node);\n\t\tthis._nodeId = contextIds[ContextIdKeys.Node];\n\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\ttry {\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// if 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\tconst tenantId = contextIds?.[ContextIdKeys.Tenant];\n\n\t\t\tconst tokenAndExpiry = await TokenHelper.createToken(\n\t\t\t\tthis._vaultConnector,\n\t\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\t\tuser.identity,\n\t\t\t\tuser.organization,\n\t\t\t\ttenantId,\n\t\t\t\tthis._defaultTtlMinutes,\n\t\t\t\tuser.scope\n\t\t\t);\n\n\t\t\treturn tokenAndExpiry;\n\t\t} catch (error) {\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\t}\n\n\t/**\n\t * Logout the current user.\n\t * @param token The token to logout, if it uses a mechanism with public access.\n\t * @returns Nothing.\n\t */\n\tpublic async logout(token?: string): Promise<void> {\n\t\t// Nothing to do here, as we are stateless.\n\t\t// The cookie will be revoked by the REST route handling\n\t}\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\t// If the verify fails on the current token then it will throw an exception.\n\t\tconst headerAndPayload = await TokenHelper.verify(\n\t\t\tthis._vaultConnector,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\ttoken\n\t\t);\n\n\t\tconst refreshTokenAndExpiry = await TokenHelper.createToken(\n\t\t\tthis._vaultConnector,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\theaderAndPayload.payload.sub ?? \"\",\n\t\t\tIs.stringValue(headerAndPayload.payload.org) ? headerAndPayload.payload.org : \"\",\n\t\t\tIs.stringValue(headerAndPayload.payload.tid) ? headerAndPayload.payload.tid : \"\",\n\t\t\tthis._defaultTtlMinutes,\n\t\t\tCoerce.string(headerAndPayload.payload?.scope)\n\t\t);\n\n\t\treturn refreshTokenAndExpiry;\n\t}\n\n\t/**\n\t * Update the user's password.\n\t * @param currentPassword The current password for the user.\n\t * @param newPassword The new password for the user.\n\t * @returns Nothing.\n\t */\n\tpublic async updatePassword(currentPassword: string, newPassword: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.User);\n\n\t\tconst user = await this._userEntityStorage.get(contextIds[ContextIdKeys.User]);\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\tcontextIds[ContextIdKeys.User]\n\t\t\t);\n\t\t}\n\n\t\treturn this._authenticationAdminService.updatePassword(\n\t\t\tuser.email,\n\t\t\tnewPassword,\n\t\t\tcurrentPassword\n\t\t);\n\t}\n}\n"]}
|