@twin.org/api-auth-entity-storage-service 0.0.3-next.16 → 0.0.3-next.18

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.
@@ -25,6 +25,10 @@ let AuthenticationUser = class AuthenticationUser {
25
25
  * The users organization.
26
26
  */
27
27
  organization;
28
+ /**
29
+ * The scope assigned to the user, comma separated.
30
+ */
31
+ scope;
28
32
  };
29
33
  __decorate([
30
34
  property({ type: "string", isPrimary: true }),
@@ -39,13 +43,17 @@ __decorate([
39
43
  __metadata("design:type", String)
40
44
  ], AuthenticationUser.prototype, "salt", void 0);
41
45
  __decorate([
42
- property({ type: "string" }),
46
+ property({ type: "string", isSecondary: true }),
43
47
  __metadata("design:type", String)
44
48
  ], AuthenticationUser.prototype, "identity", void 0);
45
49
  __decorate([
46
50
  property({ type: "string" }),
47
51
  __metadata("design:type", String)
48
52
  ], AuthenticationUser.prototype, "organization", void 0);
53
+ __decorate([
54
+ property({ type: "string" }),
55
+ __metadata("design:type", String)
56
+ ], AuthenticationUser.prototype, "scope", void 0);
49
57
  AuthenticationUser = __decorate([
50
58
  entity()
51
59
  ], AuthenticationUser);
@@ -1 +1 @@
1
- {"version":3,"file":"authenticationUser.js","sourceRoot":"","sources":["../../../src/entities/authenticationUser.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;GAEG;AAEI,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAC9B;;OAEG;IAEI,KAAK,CAAU;IAEtB;;OAEG;IAEI,QAAQ,CAAU;IAEzB;;OAEG;IAEI,IAAI,CAAU;IAErB;;OAEG;IAEI,QAAQ,CAAU;IAEzB;;OAEG;IAEI,YAAY,CAAU;CAC7B,CAAA;AAzBO;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;;iDACxB;AAMf;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;;oDACJ;AAMlB;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;;gDACR;AAMd;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;;oDACJ;AAMlB;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;;wDACA;AA7BjB,kBAAkB;IAD9B,MAAM,EAAE;GACI,kBAAkB,CA8B9B","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { entity, property } from \"@twin.org/entity\";\n\n/**\n * Class defining the storage for user login credentials.\n */\n@entity()\nexport class AuthenticationUser {\n\t/**\n\t * The user e-mail address.\n\t */\n\t@property({ type: \"string\", isPrimary: true })\n\tpublic email!: string;\n\n\t/**\n\t * The encrypted password for the user.\n\t */\n\t@property({ type: \"string\" })\n\tpublic password!: string;\n\n\t/**\n\t * The salt for the password.\n\t */\n\t@property({ type: \"string\" })\n\tpublic salt!: string;\n\n\t/**\n\t * The user identity.\n\t */\n\t@property({ type: \"string\" })\n\tpublic identity!: string;\n\n\t/**\n\t * The users organization.\n\t */\n\t@property({ type: \"string\" })\n\tpublic organization!: string;\n}\n"]}
1
+ {"version":3,"file":"authenticationUser.js","sourceRoot":"","sources":["../../../src/entities/authenticationUser.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;GAEG;AAEI,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAC9B;;OAEG;IAEI,KAAK,CAAU;IAEtB;;OAEG;IAEI,QAAQ,CAAU;IAEzB;;OAEG;IAEI,IAAI,CAAU;IAErB;;OAEG;IAEI,QAAQ,CAAU;IAEzB;;OAEG;IAEI,YAAY,CAAU;IAE7B;;OAEG;IAEI,KAAK,CAAU;CACtB,CAAA;AA/BO;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;;iDACxB;AAMf;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;;oDACJ;AAMlB;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;;gDACR;AAMd;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;;oDACvB;AAMlB;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;;wDACA;AAMtB;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;;iDACP;AAnCV,kBAAkB;IAD9B,MAAM,EAAE;GACI,kBAAkB,CAoC9B","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { entity, property } from \"@twin.org/entity\";\n\n/**\n * Class defining the storage for user login credentials.\n */\n@entity()\nexport class AuthenticationUser {\n\t/**\n\t * The user e-mail address.\n\t */\n\t@property({ type: \"string\", isPrimary: true })\n\tpublic email!: string;\n\n\t/**\n\t * The encrypted password for the user.\n\t */\n\t@property({ type: \"string\" })\n\tpublic password!: string;\n\n\t/**\n\t * The salt for the password.\n\t */\n\t@property({ type: \"string\" })\n\tpublic salt!: string;\n\n\t/**\n\t * The user identity.\n\t */\n\t@property({ type: \"string\", isSecondary: true })\n\tpublic identity!: string;\n\n\t/**\n\t * The users organization.\n\t */\n\t@property({ type: \"string\" })\n\tpublic organization!: string;\n\n\t/**\n\t * The scope assigned to the user, comma separated.\n\t */\n\t@property({ type: \"string\" })\n\tpublic scope!: string;\n}\n"]}
@@ -77,7 +77,7 @@ export class AuthHeaderProcessor {
77
77
  if (!Is.empty(route) && !(route.skipAuth ?? false)) {
78
78
  try {
79
79
  const tokenAndLocation = TokenHelper.extractTokenFromHeaders(request.headers, this._cookieName);
80
- const headerAndPayload = await TokenHelper.verify(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, tokenAndLocation?.token);
80
+ const headerAndPayload = await TokenHelper.verify(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, tokenAndLocation?.token, route.requiredScope);
81
81
  // If tenant id is defined in the context, then it must match the one in the token
82
82
  // but both can be undefined in a single tenant context
83
83
  if (contextIds?.[ContextIdKeys.Tenant] !== headerAndPayload?.payload?.tid) {
@@ -1 +1 @@
1
- {"version":3,"file":"authHeaderProcessor.js","sourceRoot":"","sources":["../../../src/processors/authHeaderProcessor.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EACN,eAAe,EAKf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACN,eAAe,EACf,aAAa,EACb,cAAc,EAEd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAErE,OAAO,EAAE,qBAAqB,EAAwB,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE1E,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD;;GAEG;AACH,MAAM,OAAO,mBAAmB;IAC/B;;;OAGG;IACI,MAAM,CAAU,mBAAmB,GAAW,cAAc,CAAC;IAEpE;;OAEG;IACI,MAAM,CAAU,UAAU,yBAAyC;IAE1E;;;OAGG;IACc,eAAe,CAAkB;IAElD;;;OAGG;IACc,eAAe,CAAS;IAEzC;;;OAGG;IACc,WAAW,CAAS;IAErC;;;OAGG;IACK,OAAO,CAAU;IAEzB;;;OAGG;IACH,YAAY,OAAgD;QAC3D,IAAI,CAAC,eAAe,GAAG,qBAAqB,CAAC,GAAG,CAAC,OAAO,EAAE,kBAAkB,IAAI,OAAO,CAAC,CAAC;QACzF,IAAI,CAAC,eAAe,GAAG,OAAO,EAAE,MAAM,EAAE,cAAc,IAAI,cAAc,CAAC;QACzE,IAAI,CAAC,WAAW,GAAG,OAAO,EAAE,MAAM,EAAE,UAAU,IAAI,mBAAmB,CAAC,mBAAmB,CAAC;IAC3F,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,mBAAmB,CAAC,UAAU,CAAC;IACvC,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,KAAK,CAAC,wBAAiC;QACnD,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,eAAe,CAAC,KAAK,CAAC,UAAU,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,GAAG,CACf,OAA2B,EAC3B,QAAuB,EACvB,KAA6B,EAC7B,UAAuB,EACvB,cAAyC;QAEzC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,CAAC;YACpD,IAAI,CAAC;gBACJ,MAAM,gBAAgB,GAAG,WAAW,CAAC,uBAAuB,CAC3D,OAAO,CAAC,OAAO,EACf,IAAI,CAAC,WAAW,CAChB,CAAC;gBAEF,MAAM,gBAAgB,GAAG,MAAM,WAAW,CAAC,MAAM,CAChD,IAAI,CAAC,eAAe,EACpB,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,eAAe,EAAE,EACzC,gBAAgB,EAAE,KAAK,CACvB,CAAC;gBAEF,kFAAkF;gBAClF,uDAAuD;gBACvD,IAAI,UAAU,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,gBAAgB,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;oBAC3E,MAAM,IAAI,YAAY,CAAC,mBAAmB,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;gBAC5E,CAAC;gBAED,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC;gBAC/D,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;gBAEtF,cAAc,CAAC,SAAS,GAAG,gBAAgB,EAAE,KAAK,CAAC;gBACnD,cAAc,CAAC,iBAAiB,GAAG,gBAAgB,EAAE,QAAQ,CAAC;YAC/D,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACvC,eAAe,CAAC,aAAa,CAAC,QAAQ,EAAE,KAAK,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;YAC7E,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,IAAI,CAChB,OAA2B,EAC3B,QAAuB,EACvB,KAA6B,EAC7B,UAAuB,EACvB,cAAyC;QAEzC,MAAM,qBAAqB,GAAG,cAAc,EAAE,aAAa,CAAC;QAC5D,MAAM,iBAAiB,GAAG,cAAc,EAAE,SAAS,CAAC;QAEpD,yFAAyF;QACzF,IACC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC;YAChB,EAAE,CAAC,WAAW,CAAC,qBAAqB,CAAC;YACrC,cAAc,CAAC,iBAAiB,KAAK,eAAe,EACnD,CAAC;YACF,IACC,CAAC,qBAAqB,KAAK,OAAO,IAAI,qBAAqB,KAAK,SAAS,CAAC;gBAC1E,EAAE,CAAC,WAAW,CAAC,iBAAiB,CAAC,EAChC,CAAC;gBACF,QAAQ,CAAC,OAAO,KAAK,EAAE,CAAC;gBACxB,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,GAAG,YAAY,CAAC,YAAY,CAClE,IAAI,CAAC,WAAW,EAChB,iBAAiB,EACjB;oBACC,MAAM,EAAE,IAAI;oBACZ,QAAQ,EAAE,IAAI;oBACd,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,GAAG;iBACT,CACD,CAAC;YACH,CAAC;iBAAM,IAAI,qBAAqB,KAAK,QAAQ,EAAE,CAAC;gBAC/C,QAAQ,CAAC,OAAO,KAAK,EAAE,CAAC;gBACxB,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,GAAG,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE;oBACrF,MAAM,EAAE,IAAI;oBACZ,QAAQ,EAAE,IAAI;oBACd,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,GAAG;iBACT,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;IACF,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHttpErrorHelper,\n\ttype IBaseRoute,\n\ttype IBaseRouteProcessor,\n\ttype IHttpResponse,\n\ttype IHttpServerRequest\n} from \"@twin.org/api-models\";\nimport {\n\tContextIdHelper,\n\tContextIdKeys,\n\tContextIdStore,\n\ttype IContextIds\n} from \"@twin.org/context\";\nimport { BaseError, Coerce, GeneralError, Is } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { VaultConnectorFactory, type IVaultConnector } from \"@twin.org/vault-models\";\nimport { CookieHelper, HeaderTypes, HttpStatusCode } from \"@twin.org/web\";\nimport type { IAuthHeaderProcessorConstructorOptions } from \"../models/IAuthHeaderProcessorConstructorOptions.js\";\nimport { TokenHelper } from \"../utils/tokenHelper.js\";\n\n/**\n * Handle a JWT token in the authorization header or cookies and validate it to populate request context identity.\n */\nexport class AuthHeaderProcessor implements IBaseRouteProcessor {\n\t/**\n\t * The default name for the access token as a cookie.\n\t * @internal\n\t */\n\tpublic static readonly DEFAULT_COOKIE_NAME: string = \"access_token\";\n\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<AuthHeaderProcessor>();\n\n\t/**\n\t * The 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 name of the cookie to use for the token.\n\t * @internal\n\t */\n\tprivate readonly _cookieName: string;\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 AuthCookiePreProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: IAuthHeaderProcessorConstructorOptions) {\n\t\tthis._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? \"vault\");\n\t\tthis._signingKeyName = options?.config?.signingKeyName ?? \"auth-signing\";\n\t\tthis._cookieName = options?.config?.cookieName ?? AuthHeaderProcessor.DEFAULT_COOKIE_NAME;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn AuthHeaderProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * The service needs to be started when the application is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns 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 * Pre process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t */\n\tpublic async pre(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tif (!Is.empty(route) && !(route.skipAuth ?? false)) {\n\t\t\ttry {\n\t\t\t\tconst tokenAndLocation = TokenHelper.extractTokenFromHeaders(\n\t\t\t\t\trequest.headers,\n\t\t\t\t\tthis._cookieName\n\t\t\t\t);\n\n\t\t\t\tconst headerAndPayload = await TokenHelper.verify(\n\t\t\t\t\tthis._vaultConnector,\n\t\t\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\t\t\ttokenAndLocation?.token\n\t\t\t\t);\n\n\t\t\t\t// If tenant id is defined in the context, then it must match the one in the token\n\t\t\t\t// but both can be undefined in a single tenant context\n\t\t\t\tif (contextIds?.[ContextIdKeys.Tenant] !== headerAndPayload?.payload?.tid) {\n\t\t\t\t\tthrow new GeneralError(AuthHeaderProcessor.CLASS_NAME, \"tenantIdMismatch\");\n\t\t\t\t}\n\n\t\t\t\tcontextIds[ContextIdKeys.User] = headerAndPayload.payload?.sub;\n\t\t\t\tcontextIds[ContextIdKeys.Organization] = Coerce.string(headerAndPayload.payload?.org);\n\n\t\t\t\tprocessorState.authToken = tokenAndLocation?.token;\n\t\t\t\tprocessorState.authTokenLocation = tokenAndLocation?.location;\n\t\t\t} catch (err) {\n\t\t\t\tconst error = BaseError.fromError(err);\n\t\t\t\tHttpErrorHelper.buildResponse(response, error, HttpStatusCode.unauthorized);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Post process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t */\n\tpublic async post(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tconst responseAuthOperation = processorState?.authOperation;\n\t\tconst responseAuthToken = processorState?.authToken;\n\n\t\t// We don't populate the cookie if the incoming request was from an authorization header.\n\t\tif (\n\t\t\t!Is.empty(route) &&\n\t\t\tIs.stringValue(responseAuthOperation) &&\n\t\t\tprocessorState.authTokenLocation !== \"authorization\"\n\t\t) {\n\t\t\tif (\n\t\t\t\t(responseAuthOperation === \"login\" || responseAuthOperation === \"refresh\") &&\n\t\t\t\tIs.stringValue(responseAuthToken)\n\t\t\t) {\n\t\t\t\tresponse.headers ??= {};\n\t\t\t\tresponse.headers[HeaderTypes.SetCookie] = CookieHelper.createCookie(\n\t\t\t\t\tthis._cookieName,\n\t\t\t\t\tresponseAuthToken,\n\t\t\t\t\t{\n\t\t\t\t\t\tsecure: true,\n\t\t\t\t\t\thttpOnly: true,\n\t\t\t\t\t\tsameSite: \"None\",\n\t\t\t\t\t\tpath: \"/\"\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t} else if (responseAuthOperation === \"logout\") {\n\t\t\t\tresponse.headers ??= {};\n\t\t\t\tresponse.headers[HeaderTypes.SetCookie] = CookieHelper.deleteCookie(this._cookieName, {\n\t\t\t\t\tsecure: true,\n\t\t\t\t\thttpOnly: true,\n\t\t\t\t\tsameSite: \"None\",\n\t\t\t\t\tpath: \"/\"\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"authHeaderProcessor.js","sourceRoot":"","sources":["../../../src/processors/authHeaderProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHttpErrorHelper,\n\ttype IBaseRoute,\n\ttype IBaseRouteProcessor,\n\ttype IHttpResponse,\n\ttype IHttpServerRequest\n} from \"@twin.org/api-models\";\nimport {\n\tContextIdHelper,\n\tContextIdKeys,\n\tContextIdStore,\n\ttype IContextIds\n} from \"@twin.org/context\";\nimport { BaseError, Coerce, GeneralError, Is } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { VaultConnectorFactory, type IVaultConnector } from \"@twin.org/vault-models\";\nimport { CookieHelper, HeaderTypes, HttpStatusCode } from \"@twin.org/web\";\nimport type { IAuthHeaderProcessorConstructorOptions } from \"../models/IAuthHeaderProcessorConstructorOptions.js\";\nimport { TokenHelper } from \"../utils/tokenHelper.js\";\n\n/**\n * Handle a JWT token in the authorization header or cookies and validate it to populate request context identity.\n */\nexport class AuthHeaderProcessor implements IBaseRouteProcessor {\n\t/**\n\t * The default name for the access token as a cookie.\n\t * @internal\n\t */\n\tpublic static readonly DEFAULT_COOKIE_NAME: string = \"access_token\";\n\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<AuthHeaderProcessor>();\n\n\t/**\n\t * The 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 name of the cookie to use for the token.\n\t * @internal\n\t */\n\tprivate readonly _cookieName: string;\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 AuthCookiePreProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: IAuthHeaderProcessorConstructorOptions) {\n\t\tthis._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? \"vault\");\n\t\tthis._signingKeyName = options?.config?.signingKeyName ?? \"auth-signing\";\n\t\tthis._cookieName = options?.config?.cookieName ?? AuthHeaderProcessor.DEFAULT_COOKIE_NAME;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn AuthHeaderProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * The service needs to be started when the application is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns 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 * Pre process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t */\n\tpublic async pre(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tif (!Is.empty(route) && !(route.skipAuth ?? false)) {\n\t\t\ttry {\n\t\t\t\tconst tokenAndLocation = TokenHelper.extractTokenFromHeaders(\n\t\t\t\t\trequest.headers,\n\t\t\t\t\tthis._cookieName\n\t\t\t\t);\n\n\t\t\t\tconst headerAndPayload = await TokenHelper.verify(\n\t\t\t\t\tthis._vaultConnector,\n\t\t\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\t\t\ttokenAndLocation?.token,\n\t\t\t\t\troute.requiredScope\n\t\t\t\t);\n\n\t\t\t\t// If tenant id is defined in the context, then it must match the one in the token\n\t\t\t\t// but both can be undefined in a single tenant context\n\t\t\t\tif (contextIds?.[ContextIdKeys.Tenant] !== headerAndPayload?.payload?.tid) {\n\t\t\t\t\tthrow new GeneralError(AuthHeaderProcessor.CLASS_NAME, \"tenantIdMismatch\");\n\t\t\t\t}\n\n\t\t\t\tcontextIds[ContextIdKeys.User] = headerAndPayload.payload?.sub;\n\t\t\t\tcontextIds[ContextIdKeys.Organization] = Coerce.string(headerAndPayload.payload?.org);\n\n\t\t\t\tprocessorState.authToken = tokenAndLocation?.token;\n\t\t\t\tprocessorState.authTokenLocation = tokenAndLocation?.location;\n\t\t\t} catch (err) {\n\t\t\t\tconst error = BaseError.fromError(err);\n\t\t\t\tHttpErrorHelper.buildResponse(response, error, HttpStatusCode.unauthorized);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Post process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t */\n\tpublic async post(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tconst responseAuthOperation = processorState?.authOperation;\n\t\tconst responseAuthToken = processorState?.authToken;\n\n\t\t// We don't populate the cookie if the incoming request was from an authorization header.\n\t\tif (\n\t\t\t!Is.empty(route) &&\n\t\t\tIs.stringValue(responseAuthOperation) &&\n\t\t\tprocessorState.authTokenLocation !== \"authorization\"\n\t\t) {\n\t\t\tif (\n\t\t\t\t(responseAuthOperation === \"login\" || responseAuthOperation === \"refresh\") &&\n\t\t\t\tIs.stringValue(responseAuthToken)\n\t\t\t) {\n\t\t\t\tresponse.headers ??= {};\n\t\t\t\tresponse.headers[HeaderTypes.SetCookie] = CookieHelper.createCookie(\n\t\t\t\t\tthis._cookieName,\n\t\t\t\t\tresponseAuthToken,\n\t\t\t\t\t{\n\t\t\t\t\t\tsecure: true,\n\t\t\t\t\t\thttpOnly: true,\n\t\t\t\t\t\tsameSite: \"None\",\n\t\t\t\t\t\tpath: \"/\"\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t} else if (responseAuthOperation === \"logout\") {\n\t\t\t\tresponse.headers ??= {};\n\t\t\t\tresponse.headers[HeaderTypes.SetCookie] = CookieHelper.deleteCookie(this._cookieName, {\n\t\t\t\t\tsecure: true,\n\t\t\t\t\thttpOnly: true,\n\t\t\t\t\tsameSite: \"None\",\n\t\t\t\t\tpath: \"/\"\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -43,36 +43,32 @@ export class EntityStorageAuthenticationAdminService {
43
43
  }
44
44
  /**
45
45
  * Create a login for the user.
46
- * @param email The email address for the user.
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.
46
+ * @param user The user to create.
50
47
  * @returns Nothing.
51
48
  */
52
- async create(email, password, userIdentity, organizationIdentity) {
53
- Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "email", email);
54
- Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "password", password);
55
- Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "userIdentity", userIdentity);
56
- Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "organizationIdentity", organizationIdentity);
49
+ async create(user) {
50
+ Guards.object(EntityStorageAuthenticationAdminService.CLASS_NAME, "user", user);
51
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.email", user.email);
52
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.password", user.password);
53
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.userIdentity", user.userIdentity);
54
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.organizationIdentity", user.organizationIdentity);
55
+ Guards.array(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.scope", user.scope);
57
56
  try {
58
- if (password.length < this._minPasswordLength) {
59
- throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "passwordTooShort", {
60
- minLength: this._minPasswordLength
61
- });
62
- }
63
- const user = await this._userEntityStorage.get(email);
64
- if (user) {
57
+ this.validatePassword(user.password);
58
+ const existingUser = await this._userEntityStorage.get(user.email);
59
+ if (Is.object(existingUser)) {
65
60
  throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userExists");
66
61
  }
67
62
  const saltBytes = RandomHelper.generate(16);
68
- const passwordBytes = Converter.utf8ToBytes(password);
63
+ const passwordBytes = Converter.utf8ToBytes(user.password);
69
64
  const hashedPassword = await PasswordHelper.hashPassword(passwordBytes, saltBytes);
70
65
  const newUser = {
71
- email,
66
+ email: user.email,
72
67
  salt: Converter.bytesToBase64(saltBytes),
73
68
  password: hashedPassword,
74
- identity: userIdentity,
75
- organization: organizationIdentity
69
+ identity: user.userIdentity,
70
+ organization: user.organizationIdentity,
71
+ scope: user.scope.map(s => s.trim().toLocaleLowerCase()).join(",")
76
72
  };
77
73
  await this._userEntityStorage.set(newUser);
78
74
  }
@@ -80,6 +76,85 @@ export class EntityStorageAuthenticationAdminService {
80
76
  throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "createUserFailed", undefined, error);
81
77
  }
82
78
  }
79
+ /**
80
+ * Update a login for the user.
81
+ * @param user The user to update.
82
+ * @returns Nothing.
83
+ */
84
+ async update(user) {
85
+ Guards.object(EntityStorageAuthenticationAdminService.CLASS_NAME, "user", user);
86
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.email", user.email);
87
+ if (!Is.empty(user.userIdentity)) {
88
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.userIdentity", user.userIdentity);
89
+ }
90
+ if (!Is.empty(user.organizationIdentity)) {
91
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.organizationIdentity", user.organizationIdentity);
92
+ }
93
+ if (!Is.empty(user.scope)) {
94
+ Guards.array(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.scope", user.scope);
95
+ }
96
+ try {
97
+ const existingUser = await this._userEntityStorage.get(user.email);
98
+ if (!Is.object(existingUser)) {
99
+ throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", user.email);
100
+ }
101
+ existingUser.identity = user.userIdentity ?? existingUser.identity;
102
+ existingUser.organization = user.organizationIdentity ?? existingUser.organization;
103
+ existingUser.scope = Is.array(user.scope)
104
+ ? user.scope.map(s => s.trim().toLocaleLowerCase()).join(",")
105
+ : user.scope;
106
+ await this._userEntityStorage.set(existingUser);
107
+ }
108
+ catch (error) {
109
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "updateUserFailed", undefined, error);
110
+ }
111
+ }
112
+ /**
113
+ * Get a user by email.
114
+ * @param email The email address of the user to get.
115
+ * @returns The user details.
116
+ */
117
+ async get(email) {
118
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "email", email);
119
+ try {
120
+ const user = await this._userEntityStorage.get(email);
121
+ if (!Is.object(user)) {
122
+ throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", email);
123
+ }
124
+ return {
125
+ email: user.email,
126
+ userIdentity: user.identity,
127
+ organizationIdentity: user.organization,
128
+ scope: user.scope.split(",")
129
+ };
130
+ }
131
+ catch (error) {
132
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "getUserFailed", undefined, error);
133
+ }
134
+ }
135
+ /**
136
+ * Get a user by identity.
137
+ * @param identity The identity of the user to get.
138
+ * @returns The user details.
139
+ */
140
+ async getByIdentity(identity) {
141
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "identity", identity);
142
+ try {
143
+ const user = await this._userEntityStorage.get(identity, "identity");
144
+ if (!Is.object(user)) {
145
+ throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", identity);
146
+ }
147
+ return {
148
+ email: user.email,
149
+ userIdentity: user.identity,
150
+ organizationIdentity: user.organization,
151
+ scope: user.scope.split(",")
152
+ };
153
+ }
154
+ catch (error) {
155
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "getUserFailed", undefined, error);
156
+ }
157
+ }
83
158
  /**
84
159
  * Remove the current user.
85
160
  * @param email The email address of the user to remove.
@@ -89,7 +164,7 @@ export class EntityStorageAuthenticationAdminService {
89
164
  Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "email", email);
90
165
  try {
91
166
  const user = await this._userEntityStorage.get(email);
92
- if (!user) {
167
+ if (!Is.object(user)) {
93
168
  throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", email);
94
169
  }
95
170
  await this._userEntityStorage.remove(email);
@@ -115,7 +190,7 @@ export class EntityStorageAuthenticationAdminService {
115
190
  });
116
191
  }
117
192
  const user = await this._userEntityStorage.get(email);
118
- if (!user) {
193
+ if (!Is.object(user)) {
119
194
  throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", email);
120
195
  }
121
196
  if (Is.stringValue(currentPassword)) {
@@ -134,7 +209,8 @@ export class EntityStorageAuthenticationAdminService {
134
209
  salt: Converter.bytesToBase64(saltBytes),
135
210
  password: hashedPassword,
136
211
  identity: user.identity,
137
- organization: user.organization
212
+ organization: user.organization,
213
+ scope: user.scope
138
214
  };
139
215
  await this._userEntityStorage.set(updatedUser);
140
216
  }
@@ -142,5 +218,17 @@ export class EntityStorageAuthenticationAdminService {
142
218
  throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "updatePasswordFailed", undefined, error);
143
219
  }
144
220
  }
221
+ /**
222
+ * Validate the password against the policy.
223
+ * @param password The password to validate.
224
+ * @internal
225
+ */
226
+ validatePassword(password) {
227
+ if (password.length < this._minPasswordLength) {
228
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "passwordTooShort", {
229
+ minLength: this._minPasswordLength
230
+ });
231
+ }
232
+ }
145
233
  }
146
234
  //# sourceMappingURL=entityStorageAuthenticationAdminService.js.map
@@ -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 {\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 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\tthis.validatePassword(user.password);\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 PasswordHelper.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\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 (!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 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\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\t/**\n\t * Validate the password against the policy.\n\t * @param password The password to validate.\n\t * @internal\n\t */\n\tprivate validatePassword(password: string): void {\n\t\tif (password.length < this._minPasswordLength) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationAdminService.CLASS_NAME,\n\t\t\t\t\"passwordTooShort\",\n\t\t\t\t{\n\t\t\t\t\tminLength: this._minPasswordLength\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
@@ -1,5 +1,5 @@
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, UnauthorizedError } from "@twin.org/core";
3
3
  import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
4
4
  import { VaultConnectorFactory } from "@twin.org/vault-models";
5
5
  import { PasswordHelper } from "../utils/passwordHelper.js";
@@ -101,7 +101,7 @@ export class EntityStorageAuthenticationService {
101
101
  // the context
102
102
  const contextIds = await ContextIdStore.getContextIds();
103
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);
104
+ const tokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, user.identity, user.organization, tenantId, this._defaultTtlMinutes, user.scope);
105
105
  return tokenAndExpiry;
106
106
  }
107
107
  catch (error) {
@@ -124,7 +124,7 @@ export class EntityStorageAuthenticationService {
124
124
  async refresh(token) {
125
125
  // If the verify fails on the current token then it will throw an exception.
126
126
  const headerAndPayload = await TokenHelper.verify(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, token);
127
- 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);
127
+ 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));
128
128
  return refreshTokenAndExpiry;
129
129
  }
130
130
  /**
@@ -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\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);\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\tIs.stringValue(headerAndPayload.payload.tid) ? headerAndPayload.payload.tid : \"\",\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\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\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.\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 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"]}
@@ -19,16 +19,18 @@ export class TokenHelper {
19
19
  * @param organizationIdentity The organization for the token.
20
20
  * @param tenantId The tenant id for the token.
21
21
  * @param ttlMinutes The time to live for the token in minutes.
22
+ * @param scope The scopes for the token.
22
23
  * @returns The new token and its expiry date.
23
24
  */
24
- static async createToken(vaultConnector, signingKeyName, userIdentity, organizationIdentity, tenantId, ttlMinutes) {
25
+ static async createToken(vaultConnector, signingKeyName, userIdentity, organizationIdentity, tenantId, ttlMinutes, scope) {
25
26
  const nowSeconds = Math.trunc(Date.now() / 1000);
26
27
  const ttlSeconds = ttlMinutes * 60;
27
28
  const jwt = await Jwt.encodeWithSigner({ alg: "EdDSA" }, {
28
29
  sub: userIdentity,
29
30
  org: organizationIdentity,
30
31
  tid: tenantId,
31
- exp: nowSeconds + ttlSeconds
32
+ exp: nowSeconds + ttlSeconds,
33
+ scope
32
34
  }, async (header, payload) => VaultConnectorHelper.jwtSigner(vaultConnector, signingKeyName, header, payload));
33
35
  return {
34
36
  token: jwt,
@@ -40,10 +42,11 @@ export class TokenHelper {
40
42
  * @param vaultConnector The vault connector.
41
43
  * @param signingKeyName The signing key name.
42
44
  * @param token The token to verify.
45
+ * @param requiredScopes The required scopes.
43
46
  * @returns The verified details.
44
47
  * @throws UnauthorizedError if the token is missing, invalid or expired.
45
48
  */
46
- static async verify(vaultConnector, signingKeyName, token) {
49
+ static async verify(vaultConnector, signingKeyName, token, requiredScopes) {
47
50
  if (!Is.stringValue(token)) {
48
51
  throw new UnauthorizedError(TokenHelper.CLASS_NAME, "missing");
49
52
  }
@@ -59,6 +62,16 @@ export class TokenHelper {
59
62
  decoded.payload.exp < Math.trunc(Date.now() / 1000)) {
60
63
  throw new UnauthorizedError(TokenHelper.CLASS_NAME, "expired");
61
64
  }
65
+ if (Is.arrayValue(requiredScopes)) {
66
+ const tokenScopes = Is.stringValue(decoded.payload.scope)
67
+ ? decoded.payload.scope.split(" ")
68
+ : [];
69
+ for (const requiredScope of requiredScopes) {
70
+ if (!tokenScopes.includes(requiredScope)) {
71
+ throw new UnauthorizedError(TokenHelper.CLASS_NAME, "insufficientScopes");
72
+ }
73
+ }
74
+ }
62
75
  return {
63
76
  header: decoded.header,
64
77
  payload: decoded.payload
@@ -1 +1 @@
1
- {"version":3,"file":"tokenHelper.js","sourceRoot":"","sources":["../../../src/utils/tokenHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is, UnauthorizedError } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { type IVaultConnector, VaultConnectorHelper } from \"@twin.org/vault-models\";\nimport {\n\tCookieHelper,\n\tHeaderHelper,\n\tHeaderTypes,\n\ttype IHttpHeaders,\n\ttype IJwtHeader,\n\ttype IJwtPayload,\n\tJwt\n} from \"@twin.org/web\";\n\n/**\n * Helper class for token operations.\n */\nexport class TokenHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TokenHelper>();\n\n\t/**\n\t * Create a new token.\n\t * @param vaultConnector The vault connector.\n\t * @param signingKeyName The signing key name.\n\t * @param userIdentity The subject for the token.\n\t * @param organizationIdentity The organization for the token.\n\t * @param tenantId The tenant id for the token.\n\t * @param ttlMinutes The time to live for the token in minutes.\n\t * @returns The new token and its expiry date.\n\t */\n\tpublic static async createToken(\n\t\tvaultConnector: IVaultConnector,\n\t\tsigningKeyName: string,\n\t\tuserIdentity: string,\n\t\torganizationIdentity: string | undefined,\n\t\ttenantId: string | undefined,\n\t\tttlMinutes: number\n\t): Promise<{\n\t\ttoken: string;\n\t\texpiry: number;\n\t}> {\n\t\tconst nowSeconds = Math.trunc(Date.now() / 1000);\n\t\tconst ttlSeconds = ttlMinutes * 60;\n\n\t\tconst jwt = await Jwt.encodeWithSigner(\n\t\t\t{ alg: \"EdDSA\" },\n\t\t\t{\n\t\t\t\tsub: userIdentity,\n\t\t\t\torg: organizationIdentity,\n\t\t\t\ttid: tenantId,\n\t\t\t\texp: nowSeconds + ttlSeconds\n\t\t\t},\n\t\t\tasync (header, payload) =>\n\t\t\t\tVaultConnectorHelper.jwtSigner(vaultConnector, signingKeyName, header, payload)\n\t\t);\n\n\t\treturn {\n\t\t\ttoken: jwt,\n\t\t\texpiry: (nowSeconds + ttlSeconds) * 1000\n\t\t};\n\t}\n\n\t/**\n\t * Verify the token.\n\t * @param vaultConnector The vault connector.\n\t * @param signingKeyName The signing key name.\n\t * @param token The token to verify.\n\t * @returns The verified details.\n\t * @throws UnauthorizedError if the token is missing, invalid or expired.\n\t */\n\tpublic static async verify(\n\t\tvaultConnector: IVaultConnector,\n\t\tsigningKeyName: string,\n\t\ttoken: string | undefined\n\t): Promise<{\n\t\theader: IJwtHeader;\n\t\tpayload: IJwtPayload;\n\t}> {\n\t\tif (!Is.stringValue(token)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"missing\");\n\t\t}\n\n\t\tconst decoded = await Jwt.verifyWithVerifier(token, async t =>\n\t\t\tVaultConnectorHelper.jwtVerifier(vaultConnector, signingKeyName, t)\n\t\t);\n\n\t\t// If some of the header/payload data is not properly populated then it is unauthorized.\n\t\tif (!Is.stringValue(decoded.payload.sub)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"payloadMissingSubject\");\n\t\t} else if (!Is.stringValue(decoded.payload.org)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"payloadMissingOrganization\");\n\t\t} else if (\n\t\t\t!Is.empty(decoded.payload?.exp) &&\n\t\t\tdecoded.payload.exp < Math.trunc(Date.now() / 1000)\n\t\t) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"expired\");\n\t\t}\n\n\t\treturn {\n\t\t\theader: decoded.header,\n\t\t\tpayload: decoded.payload\n\t\t};\n\t}\n\n\t/**\n\t * Extract the auth token from the headers, either from the authorization header or the cookie header.\n\t * @param headers The headers to extract the token from.\n\t * @param cookieName The name of the cookie to extract the token from.\n\t * @returns The token if found.\n\t */\n\tpublic static extractTokenFromHeaders(\n\t\theaders?: IHttpHeaders,\n\t\tcookieName?: string\n\t):\n\t\t| {\n\t\t\t\ttoken: string;\n\t\t\t\tlocation: \"authorization\" | \"cookie\";\n\t\t }\n\t\t| undefined {\n\t\tconst authHeader = headers?.[HeaderTypes.Authorization];\n\t\tconst cookiesHeader = headers?.[HeaderTypes.Cookie];\n\n\t\tconst bearerToken = HeaderHelper.extractBearer(authHeader);\n\t\tif (Is.stringValue(bearerToken)) {\n\t\t\treturn {\n\t\t\t\ttoken: bearerToken,\n\t\t\t\tlocation: \"authorization\"\n\t\t\t};\n\t\t} else if (Is.notEmpty(cookiesHeader) && Is.stringValue(cookieName)) {\n\t\t\tconst value = CookieHelper.getCookieFromHeaders(cookiesHeader, cookieName);\n\t\t\tif (Is.stringValue(value)) {\n\t\t\t\treturn {\n\t\t\t\t\ttoken: value,\n\t\t\t\t\tlocation: \"cookie\"\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"tokenHelper.js","sourceRoot":"","sources":["../../../src/utils/tokenHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is, UnauthorizedError } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { type IVaultConnector, VaultConnectorHelper } from \"@twin.org/vault-models\";\nimport {\n\tCookieHelper,\n\tHeaderHelper,\n\tHeaderTypes,\n\ttype IHttpHeaders,\n\ttype IJwtHeader,\n\ttype IJwtPayload,\n\tJwt\n} from \"@twin.org/web\";\n\n/**\n * Helper class for token operations.\n */\nexport class TokenHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TokenHelper>();\n\n\t/**\n\t * Create a new token.\n\t * @param vaultConnector The vault connector.\n\t * @param signingKeyName The signing key name.\n\t * @param userIdentity The subject for the token.\n\t * @param organizationIdentity The organization for the token.\n\t * @param tenantId The tenant id for the token.\n\t * @param ttlMinutes The time to live for the token in minutes.\n\t * @param scope The scopes for the token.\n\t * @returns The new token and its expiry date.\n\t */\n\tpublic static async createToken(\n\t\tvaultConnector: IVaultConnector,\n\t\tsigningKeyName: string,\n\t\tuserIdentity: string,\n\t\torganizationIdentity: string | undefined,\n\t\ttenantId: string | undefined,\n\t\tttlMinutes: number,\n\t\tscope?: string\n\t): Promise<{\n\t\ttoken: string;\n\t\texpiry: number;\n\t}> {\n\t\tconst nowSeconds = Math.trunc(Date.now() / 1000);\n\t\tconst ttlSeconds = ttlMinutes * 60;\n\n\t\tconst jwt = await Jwt.encodeWithSigner(\n\t\t\t{ alg: \"EdDSA\" },\n\t\t\t{\n\t\t\t\tsub: userIdentity,\n\t\t\t\torg: organizationIdentity,\n\t\t\t\ttid: tenantId,\n\t\t\t\texp: nowSeconds + ttlSeconds,\n\t\t\t\tscope\n\t\t\t},\n\t\t\tasync (header, payload) =>\n\t\t\t\tVaultConnectorHelper.jwtSigner(vaultConnector, signingKeyName, header, payload)\n\t\t);\n\n\t\treturn {\n\t\t\ttoken: jwt,\n\t\t\texpiry: (nowSeconds + ttlSeconds) * 1000\n\t\t};\n\t}\n\n\t/**\n\t * Verify the token.\n\t * @param vaultConnector The vault connector.\n\t * @param signingKeyName The signing key name.\n\t * @param token The token to verify.\n\t * @param requiredScopes The required scopes.\n\t * @returns The verified details.\n\t * @throws UnauthorizedError if the token is missing, invalid or expired.\n\t */\n\tpublic static async verify(\n\t\tvaultConnector: IVaultConnector,\n\t\tsigningKeyName: string,\n\t\ttoken: string | undefined,\n\t\trequiredScopes?: string[]\n\t): Promise<{\n\t\theader: IJwtHeader;\n\t\tpayload: IJwtPayload;\n\t}> {\n\t\tif (!Is.stringValue(token)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"missing\");\n\t\t}\n\n\t\tconst decoded = await Jwt.verifyWithVerifier(token, async t =>\n\t\t\tVaultConnectorHelper.jwtVerifier(vaultConnector, signingKeyName, t)\n\t\t);\n\n\t\t// If some of the header/payload data is not properly populated then it is unauthorized.\n\t\tif (!Is.stringValue(decoded.payload.sub)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"payloadMissingSubject\");\n\t\t} else if (!Is.stringValue(decoded.payload.org)) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"payloadMissingOrganization\");\n\t\t} else if (\n\t\t\t!Is.empty(decoded.payload?.exp) &&\n\t\t\tdecoded.payload.exp < Math.trunc(Date.now() / 1000)\n\t\t) {\n\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"expired\");\n\t\t}\n\n\t\tif (Is.arrayValue(requiredScopes)) {\n\t\t\tconst tokenScopes = Is.stringValue(decoded.payload.scope)\n\t\t\t\t? decoded.payload.scope.split(\" \")\n\t\t\t\t: [];\n\n\t\t\tfor (const requiredScope of requiredScopes) {\n\t\t\t\tif (!tokenScopes.includes(requiredScope)) {\n\t\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"insufficientScopes\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\theader: decoded.header,\n\t\t\tpayload: decoded.payload\n\t\t};\n\t}\n\n\t/**\n\t * Extract the auth token from the headers, either from the authorization header or the cookie header.\n\t * @param headers The headers to extract the token from.\n\t * @param cookieName The name of the cookie to extract the token from.\n\t * @returns The token if found.\n\t */\n\tpublic static extractTokenFromHeaders(\n\t\theaders?: IHttpHeaders,\n\t\tcookieName?: string\n\t):\n\t\t| {\n\t\t\t\ttoken: string;\n\t\t\t\tlocation: \"authorization\" | \"cookie\";\n\t\t }\n\t\t| undefined {\n\t\tconst authHeader = headers?.[HeaderTypes.Authorization];\n\t\tconst cookiesHeader = headers?.[HeaderTypes.Cookie];\n\n\t\tconst bearerToken = HeaderHelper.extractBearer(authHeader);\n\t\tif (Is.stringValue(bearerToken)) {\n\t\t\treturn {\n\t\t\t\ttoken: bearerToken,\n\t\t\t\tlocation: \"authorization\"\n\t\t\t};\n\t\t} else if (Is.notEmpty(cookiesHeader) && Is.stringValue(cookieName)) {\n\t\t\tconst value = CookieHelper.getCookieFromHeaders(cookiesHeader, cookieName);\n\t\t\tif (Is.stringValue(value)) {\n\t\t\t\treturn {\n\t\t\t\t\ttoken: value,\n\t\t\t\t\tlocation: \"cookie\"\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -22,4 +22,8 @@ export declare class AuthenticationUser {
22
22
  * The users organization.
23
23
  */
24
24
  organization: string;
25
+ /**
26
+ * The scope assigned to the user, comma separated.
27
+ */
28
+ scope: string;
25
29
  }
@@ -1,4 +1,4 @@
1
- import type { IAuthenticationAdminComponent } from "@twin.org/api-auth-entity-storage-models";
1
+ import type { IAuthenticationAdminComponent, IAuthenticationUser } from "@twin.org/api-auth-entity-storage-models";
2
2
  import type { IEntityStorageAuthenticationAdminServiceConstructorOptions } from "../models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js";
3
3
  /**
4
4
  * Implementation of the authentication component using entity storage.
@@ -20,13 +20,28 @@ export declare class EntityStorageAuthenticationAdminService implements IAuthent
20
20
  className(): string;
21
21
  /**
22
22
  * Create a login for the user.
23
- * @param email The email address for the user.
24
- * @param password The password for the user.
25
- * @param userIdentity The DID to associate with the account.
26
- * @param organizationIdentity The organization of the user.
23
+ * @param user The user to create.
27
24
  * @returns Nothing.
28
25
  */
29
- create(email: string, password: string, userIdentity: string, organizationIdentity: string): Promise<void>;
26
+ create(user: Omit<IAuthenticationUser, "salt">): Promise<void>;
27
+ /**
28
+ * Update a login for the user.
29
+ * @param user The user to update.
30
+ * @returns Nothing.
31
+ */
32
+ update(user: Partial<Omit<IAuthenticationUser, "password" | "salt">>): Promise<void>;
33
+ /**
34
+ * Get a user by email.
35
+ * @param email The email address of the user to get.
36
+ * @returns The user details.
37
+ */
38
+ get(email: string): Promise<Omit<IAuthenticationUser, "password" | "salt">>;
39
+ /**
40
+ * Get a user by identity.
41
+ * @param identity The identity of the user to get.
42
+ * @returns The user details.
43
+ */
44
+ getByIdentity(identity: string): Promise<Omit<IAuthenticationUser, "password" | "salt">>;
30
45
  /**
31
46
  * Remove the current user.
32
47
  * @param email The email address of the user to remove.
@@ -16,9 +16,10 @@ export declare class TokenHelper {
16
16
  * @param organizationIdentity The organization for the token.
17
17
  * @param tenantId The tenant id for the token.
18
18
  * @param ttlMinutes The time to live for the token in minutes.
19
+ * @param scope The scopes for the token.
19
20
  * @returns The new token and its expiry date.
20
21
  */
21
- static createToken(vaultConnector: IVaultConnector, signingKeyName: string, userIdentity: string, organizationIdentity: string | undefined, tenantId: string | undefined, ttlMinutes: number): Promise<{
22
+ static createToken(vaultConnector: IVaultConnector, signingKeyName: string, userIdentity: string, organizationIdentity: string | undefined, tenantId: string | undefined, ttlMinutes: number, scope?: string): Promise<{
22
23
  token: string;
23
24
  expiry: number;
24
25
  }>;
@@ -27,10 +28,11 @@ export declare class TokenHelper {
27
28
  * @param vaultConnector The vault connector.
28
29
  * @param signingKeyName The signing key name.
29
30
  * @param token The token to verify.
31
+ * @param requiredScopes The required scopes.
30
32
  * @returns The verified details.
31
33
  * @throws UnauthorizedError if the token is missing, invalid or expired.
32
34
  */
33
- static verify(vaultConnector: IVaultConnector, signingKeyName: string, token: string | undefined): Promise<{
35
+ static verify(vaultConnector: IVaultConnector, signingKeyName: string, token: string | undefined, requiredScopes?: string[]): Promise<{
34
36
  header: IJwtHeader;
35
37
  payload: IJwtPayload;
36
38
  }>;
package/docs/changelog.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @twin.org/api-auth-entity-storage-service - Changelog
2
2
 
3
+ ## [0.0.3-next.18](https://github.com/twinfoundation/api/compare/api-auth-entity-storage-service-v0.0.3-next.17...api-auth-entity-storage-service-v0.0.3-next.18) (2026-02-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * tenant api and scopes ([#75](https://github.com/twinfoundation/api/issues/75)) ([c663141](https://github.com/twinfoundation/api/commit/c663141091e8974d769f8f9904ecdab009ebd083))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.17 to 0.0.3-next.18
16
+ * @twin.org/api-core bumped from 0.0.3-next.17 to 0.0.3-next.18
17
+ * @twin.org/api-models bumped from 0.0.3-next.17 to 0.0.3-next.18
18
+
19
+ ## [0.0.3-next.17](https://github.com/twinfoundation/api/compare/api-auth-entity-storage-service-v0.0.3-next.16...api-auth-entity-storage-service-v0.0.3-next.17) (2026-01-26)
20
+
21
+
22
+ ### Miscellaneous Chores
23
+
24
+ * **api-auth-entity-storage-service:** Synchronize repo versions
25
+
26
+
27
+ ### Dependencies
28
+
29
+ * The following workspace dependencies were updated
30
+ * dependencies
31
+ * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.16 to 0.0.3-next.17
32
+ * @twin.org/api-core bumped from 0.0.3-next.16 to 0.0.3-next.17
33
+ * @twin.org/api-models bumped from 0.0.3-next.16 to 0.0.3-next.17
34
+
3
35
  ## [0.0.3-next.16](https://github.com/twinfoundation/api/compare/api-auth-entity-storage-service-v0.0.3-next.15...api-auth-entity-storage-service-v0.0.3-next.16) (2026-01-26)
4
36
 
5
37
 
@@ -51,3 +51,11 @@ The user identity.
51
51
  > **organization**: `string`
52
52
 
53
53
  The users organization.
54
+
55
+ ***
56
+
57
+ ### scope
58
+
59
+ > **scope**: `string`
60
+
61
+ The scope assigned to the user, comma separated.
@@ -56,45 +56,105 @@ The class name of the component.
56
56
 
57
57
  ### create()
58
58
 
59
- > **create**(`email`, `password`, `userIdentity`, `organizationIdentity`): `Promise`\<`void`\>
59
+ > **create**(`user`): `Promise`\<`void`\>
60
60
 
61
61
  Create a login for the user.
62
62
 
63
63
  #### Parameters
64
64
 
65
+ ##### user
66
+
67
+ `Omit`\<`IAuthenticationUser`, `"salt"`\>
68
+
69
+ The user to create.
70
+
71
+ #### Returns
72
+
73
+ `Promise`\<`void`\>
74
+
75
+ Nothing.
76
+
77
+ #### Implementation of
78
+
79
+ `IAuthenticationAdminComponent.create`
80
+
81
+ ***
82
+
83
+ ### update()
84
+
85
+ > **update**(`user`): `Promise`\<`void`\>
86
+
87
+ Update a login for the user.
88
+
89
+ #### Parameters
90
+
91
+ ##### user
92
+
93
+ `Partial`\<`Omit`\<`IAuthenticationUser`, `"password"` \| `"salt"`\>\>
94
+
95
+ The user to update.
96
+
97
+ #### Returns
98
+
99
+ `Promise`\<`void`\>
100
+
101
+ Nothing.
102
+
103
+ #### Implementation of
104
+
105
+ `IAuthenticationAdminComponent.update`
106
+
107
+ ***
108
+
109
+ ### get()
110
+
111
+ > **get**(`email`): `Promise`\<`Omit`\<`IAuthenticationUser`, `"salt"` \| `"password"`\>\>
112
+
113
+ Get a user by email.
114
+
115
+ #### Parameters
116
+
65
117
  ##### email
66
118
 
67
119
  `string`
68
120
 
69
- The email address for the user.
121
+ The email address of the user to get.
70
122
 
71
- ##### password
123
+ #### Returns
72
124
 
73
- `string`
125
+ `Promise`\<`Omit`\<`IAuthenticationUser`, `"salt"` \| `"password"`\>\>
74
126
 
75
- The password for the user.
127
+ The user details.
76
128
 
77
- ##### userIdentity
129
+ #### Implementation of
78
130
 
79
- `string`
131
+ `IAuthenticationAdminComponent.get`
132
+
133
+ ***
134
+
135
+ ### getByIdentity()
80
136
 
81
- The DID to associate with the account.
137
+ > **getByIdentity**(`identity`): `Promise`\<`Omit`\<`IAuthenticationUser`, `"salt"` \| `"password"`\>\>
82
138
 
83
- ##### organizationIdentity
139
+ Get a user by identity.
140
+
141
+ #### Parameters
142
+
143
+ ##### identity
84
144
 
85
145
  `string`
86
146
 
87
- The organization of the user.
147
+ The identity of the user to get.
88
148
 
89
149
  #### Returns
90
150
 
91
- `Promise`\<`void`\>
151
+ `Promise`\<`Omit`\<`IAuthenticationUser`, `"salt"` \| `"password"`\>\>
92
152
 
93
- Nothing.
153
+ The user details.
94
154
 
95
155
  #### Implementation of
96
156
 
97
- `IAuthenticationAdminComponent.create`
157
+ `IAuthenticationAdminComponent.getByIdentity`
98
158
 
99
159
  ***
100
160
 
@@ -24,7 +24,7 @@ Runtime name for the class.
24
24
 
25
25
  ### createToken()
26
26
 
27
- > `static` **createToken**(`vaultConnector`, `signingKeyName`, `userIdentity`, `organizationIdentity`, `tenantId`, `ttlMinutes`): `Promise`\<\{ `token`: `string`; `expiry`: `number`; \}\>
27
+ > `static` **createToken**(`vaultConnector`, `signingKeyName`, `userIdentity`, `organizationIdentity`, `tenantId`, `ttlMinutes`, `scope?`): `Promise`\<\{ `token`: `string`; `expiry`: `number`; \}\>
28
28
 
29
29
  Create a new token.
30
30
 
@@ -66,6 +66,12 @@ The tenant id for the token.
66
66
 
67
67
  The time to live for the token in minutes.
68
68
 
69
+ ##### scope?
70
+
71
+ `string`
72
+
73
+ The scopes for the token.
74
+
69
75
  #### Returns
70
76
 
71
77
  `Promise`\<\{ `token`: `string`; `expiry`: `number`; \}\>
@@ -76,7 +82,7 @@ The new token and its expiry date.
76
82
 
77
83
  ### verify()
78
84
 
79
- > `static` **verify**(`vaultConnector`, `signingKeyName`, `token`): `Promise`\<\{ `header`: `JWTHeaderParameters`; `payload`: `JWTPayload`; \}\>
85
+ > `static` **verify**(`vaultConnector`, `signingKeyName`, `token`, `requiredScopes?`): `Promise`\<\{ `header`: `JWTHeaderParameters`; `payload`: `JWTPayload`; \}\>
80
86
 
81
87
  Verify the token.
82
88
 
@@ -100,6 +106,12 @@ The token to verify.
100
106
 
101
107
  `string` | `undefined`
102
108
 
109
+ ##### requiredScopes?
110
+
111
+ `string`[]
112
+
113
+ The required scopes.
114
+
103
115
  #### Returns
104
116
 
105
117
  `Promise`\<\{ `header`: `JWTHeaderParameters`; `payload`: `JWTPayload`; \}\>
package/locales/en.json CHANGED
@@ -8,6 +8,8 @@
8
8
  "entityStorageAuthenticationAdminService": {
9
9
  "userExists": "The user with the specified e-mail already exists",
10
10
  "createUserFailed": "Creating the user failed",
11
+ "getUserFailed": "Getting the user failed",
12
+ "updateUserFailed": "Updating the user failed",
11
13
  "removeUserFailed": "Removing the user failed",
12
14
  "updatePasswordFailed": "Updating the user's password failed",
13
15
  "passwordTooShort": "The password is too short, it must be at least {minLength} characters long",
@@ -18,7 +20,8 @@
18
20
  "missing": "The JSON Web token could not be found in the authorization header",
19
21
  "payloadMissingSubject": "The JSON Web token payload does not contain a subject",
20
22
  "payloadMissingOrganization": "The JSON Web token payload does not contain an organization",
21
- "expired": "The JSON Web token has expired"
23
+ "expired": "The JSON Web token has expired",
24
+ "insufficientScopes": "The JSON Web token does not have the required scopes to access this resource"
22
25
  },
23
26
  "authHeaderProcessor": {
24
27
  "tenantIdMismatch": "The tenant ID in the token does not match the tenant ID in the context"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/api-auth-entity-storage-service",
3
- "version": "0.0.3-next.16",
3
+ "version": "0.0.3-next.18",
4
4
  "description": "Auth Entity Storage contract implementation and REST endpoint definitions",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,9 +14,9 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/api-auth-entity-storage-models": "0.0.3-next.16",
18
- "@twin.org/api-core": "0.0.3-next.16",
19
- "@twin.org/api-models": "0.0.3-next.16",
17
+ "@twin.org/api-auth-entity-storage-models": "0.0.3-next.18",
18
+ "@twin.org/api-core": "0.0.3-next.18",
19
+ "@twin.org/api-models": "0.0.3-next.18",
20
20
  "@twin.org/context": "next",
21
21
  "@twin.org/core": "next",
22
22
  "@twin.org/crypto": "next",