@twin.org/api-auth-entity-storage-service 0.0.1 → 0.0.2-next.10

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.
@@ -117,9 +117,10 @@ class TokenHelper {
117
117
  static extractTokenFromHeaders(headers, cookieName) {
118
118
  const authHeader = headers?.[web.HeaderTypes.Authorization];
119
119
  const cookiesHeader = headers?.[web.HeaderTypes.Cookie];
120
- if (core.Is.string(authHeader) && authHeader.startsWith("Bearer ")) {
120
+ const bearerToken = web.HeaderHelper.extractBearer(authHeader);
121
+ if (core.Is.stringValue(bearerToken)) {
121
122
  return {
122
- token: authHeader.slice(7).trim(),
123
+ token: bearerToken,
123
124
  location: "authorization"
124
125
  };
125
126
  }
@@ -149,10 +150,6 @@ class TokenHelper {
149
150
  * Handle a JWT token in the authorization header or cookies and validate it to populate request context identity.
150
151
  */
151
152
  class AuthHeaderProcessor {
152
- /**
153
- * The namespace supported by the processor.
154
- */
155
- static NAMESPACE = "auth-header";
156
153
  /**
157
154
  * The default name for the access token as a cookie.
158
155
  * @internal
@@ -194,10 +191,10 @@ class AuthHeaderProcessor {
194
191
  /**
195
192
  * The service needs to be started when the application is initialized.
196
193
  * @param nodeIdentity The identity of the node.
197
- * @param nodeLoggingConnectorType The node logging connector type, defaults to "node-logging".
194
+ * @param nodeLoggingComponentType The node logging component type.
198
195
  * @returns Nothing.
199
196
  */
200
- async start(nodeIdentity, nodeLoggingConnectorType) {
197
+ async start(nodeIdentity, nodeLoggingComponentType) {
201
198
  core.Guards.string(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
202
199
  this._nodeIdentity = nodeIdentity;
203
200
  }
@@ -388,7 +385,41 @@ function generateRestRoutesAuthentication(baseRouteName, componentName) {
388
385
  }
389
386
  ]
390
387
  };
391
- return [loginRoute, logoutRoute, refreshTokenRoute];
388
+ const updatePasswordRoute = {
389
+ operationId: "authenticationUpdatePassword",
390
+ summary: "Update the user's password",
391
+ tag: tagsAuthentication[0].name,
392
+ method: "PUT",
393
+ path: `${baseRouteName}/:email/password`,
394
+ handler: async (httpRequestContext, request) => authenticationUpdatePassword(httpRequestContext, componentName, request),
395
+ requestType: {
396
+ type: "IUpdatePasswordRequest",
397
+ examples: [
398
+ {
399
+ id: "updatePasswordRequestExample",
400
+ description: "The request to update the user's password.",
401
+ request: {
402
+ pathParams: {
403
+ email: "john:example.com"
404
+ },
405
+ body: {
406
+ currentPassword: "MyNewPassword123!",
407
+ newPassword: "MyNewPassword123!"
408
+ }
409
+ }
410
+ }
411
+ ]
412
+ },
413
+ responseType: [
414
+ {
415
+ type: "INoContentResponse"
416
+ },
417
+ {
418
+ type: "IUnauthorizedResponse"
419
+ }
420
+ ]
421
+ };
422
+ return [loginRoute, logoutRoute, refreshTokenRoute, updatePasswordRoute];
392
423
  }
393
424
  /**
394
425
  * Login to the server.
@@ -448,6 +479,23 @@ async function authenticationRefreshToken(httpRequestContext, componentName, req
448
479
  body: result
449
480
  };
450
481
  }
482
+ /**
483
+ * Update the user's password.
484
+ * @param httpRequestContext The request context for the API.
485
+ * @param componentName The name of the component to use in the routes.
486
+ * @param request The request.
487
+ * @returns The response object with additional http response properties.
488
+ */
489
+ async function authenticationUpdatePassword(httpRequestContext, componentName, request) {
490
+ core.Guards.object(ROUTES_SOURCE, "request", request);
491
+ core.Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
492
+ core.Guards.object(ROUTES_SOURCE, "request.body", request.body);
493
+ const component = core.ComponentFactory.get(componentName);
494
+ await component.updatePassword(request.pathParams.email, request.body.currentPassword, request.body.newPassword);
495
+ return {
496
+ statusCode: web.HttpStatusCode.noContent
497
+ };
498
+ }
451
499
 
452
500
  const restEntryPoints = [
453
501
  {
@@ -498,11 +546,138 @@ class PasswordHelper {
498
546
  /**
499
547
  * Implementation of the authentication component using entity storage.
500
548
  */
501
- class EntityStorageAuthenticationService {
549
+ class EntityStorageAuthenticationAdminService {
502
550
  /**
503
- * The namespace supported by the authentication service.
551
+ * The minimum password length.
552
+ * @internal
504
553
  */
505
- static NAMESPACE = "authentication-entity-storage";
554
+ static _DEFAULT_MIN_PASSWORD_LENGTH = 8;
555
+ /**
556
+ * Runtime name for the class.
557
+ */
558
+ CLASS_NAME = "EntityStorageAuthenticationAdminService";
559
+ /**
560
+ * The entity storage for users.
561
+ * @internal
562
+ */
563
+ _userEntityStorage;
564
+ /**
565
+ * The minimum password length.
566
+ * @internal
567
+ */
568
+ _minPasswordLength;
569
+ /**
570
+ * Create a new instance of EntityStorageAuthentication.
571
+ * @param options The dependencies for the identity connector.
572
+ */
573
+ constructor(options) {
574
+ this._userEntityStorage = entityStorageModels.EntityStorageConnectorFactory.get(options?.userEntityStorageType ?? "authentication-user");
575
+ this._minPasswordLength =
576
+ options?.config?.minPasswordLength ??
577
+ EntityStorageAuthenticationAdminService._DEFAULT_MIN_PASSWORD_LENGTH;
578
+ }
579
+ /**
580
+ * Create a login for the user.
581
+ * @param email The email address for the user.
582
+ * @param password The password for the user.
583
+ * @param identity The DID to associate with the account.
584
+ * @returns Nothing.
585
+ */
586
+ async create(email, password, identity) {
587
+ core.Guards.stringValue(this.CLASS_NAME, "email", email);
588
+ core.Guards.stringValue(this.CLASS_NAME, "password", password);
589
+ try {
590
+ if (password.length < this._minPasswordLength) {
591
+ throw new core.GeneralError(this.CLASS_NAME, "passwordTooShort", {
592
+ minLength: this._minPasswordLength
593
+ });
594
+ }
595
+ const user = await this._userEntityStorage.get(email);
596
+ if (user) {
597
+ throw new core.GeneralError(this.CLASS_NAME, "userExists");
598
+ }
599
+ const saltBytes = core.RandomHelper.generate(16);
600
+ const passwordBytes = core.Converter.utf8ToBytes(password);
601
+ const hashedPassword = await PasswordHelper.hashPassword(passwordBytes, saltBytes);
602
+ const newUser = {
603
+ email,
604
+ salt: core.Converter.bytesToBase64(saltBytes),
605
+ password: hashedPassword,
606
+ identity
607
+ };
608
+ await this._userEntityStorage.set(newUser);
609
+ }
610
+ catch (error) {
611
+ throw new core.GeneralError(this.CLASS_NAME, "createUserFailed", undefined, error);
612
+ }
613
+ }
614
+ /**
615
+ * Remove the current user.
616
+ * @param email The email address of the user to remove.
617
+ * @returns Nothing.
618
+ */
619
+ async remove(email) {
620
+ core.Guards.stringValue(this.CLASS_NAME, "email", email);
621
+ try {
622
+ const user = await this._userEntityStorage.get(email);
623
+ if (!user) {
624
+ throw new core.NotFoundError(this.CLASS_NAME, "userNotFound", email);
625
+ }
626
+ await this._userEntityStorage.remove(email);
627
+ }
628
+ catch (error) {
629
+ throw new core.GeneralError(this.CLASS_NAME, "removeUserFailed", undefined, error);
630
+ }
631
+ }
632
+ /**
633
+ * Update the user's password.
634
+ * @param email The email address of the user to update.
635
+ * @param newPassword The new password for the user.
636
+ * @param currentPassword The current password, optional, if supplied will check against existing.
637
+ * @returns Nothing.
638
+ */
639
+ async updatePassword(email, newPassword, currentPassword) {
640
+ core.Guards.stringValue(this.CLASS_NAME, "email", email);
641
+ core.Guards.stringValue(this.CLASS_NAME, "newPassword", newPassword);
642
+ try {
643
+ if (newPassword.length < this._minPasswordLength) {
644
+ throw new core.GeneralError(this.CLASS_NAME, "passwordTooShort", {
645
+ minLength: this._minPasswordLength
646
+ });
647
+ }
648
+ const user = await this._userEntityStorage.get(email);
649
+ if (!user) {
650
+ throw new core.NotFoundError(this.CLASS_NAME, "userNotFound", email);
651
+ }
652
+ if (core.Is.stringValue(currentPassword)) {
653
+ const saltBytes = core.Converter.base64ToBytes(user.salt);
654
+ const passwordBytes = core.Converter.utf8ToBytes(currentPassword);
655
+ const hashedPassword = await PasswordHelper.hashPassword(passwordBytes, saltBytes);
656
+ if (hashedPassword !== user.password) {
657
+ throw new core.GeneralError(this.CLASS_NAME, "currentPasswordMismatch");
658
+ }
659
+ }
660
+ const saltBytes = core.RandomHelper.generate(16);
661
+ const passwordBytes = core.Converter.utf8ToBytes(newPassword);
662
+ const hashedPassword = await PasswordHelper.hashPassword(passwordBytes, saltBytes);
663
+ const updatedUser = {
664
+ email,
665
+ salt: core.Converter.bytesToBase64(saltBytes),
666
+ password: hashedPassword,
667
+ identity: user.identity
668
+ };
669
+ await this._userEntityStorage.set(updatedUser);
670
+ }
671
+ catch (error) {
672
+ throw new core.GeneralError(this.CLASS_NAME, "updatePasswordFailed", undefined, error);
673
+ }
674
+ }
675
+ }
676
+
677
+ /**
678
+ * Implementation of the authentication component using entity storage.
679
+ */
680
+ class EntityStorageAuthenticationService {
506
681
  /**
507
682
  * Default TTL in minutes.
508
683
  * @internal
@@ -512,6 +687,11 @@ class EntityStorageAuthenticationService {
512
687
  * Runtime name for the class.
513
688
  */
514
689
  CLASS_NAME = "EntityStorageAuthenticationService";
690
+ /**
691
+ * The user admin service.
692
+ * @internal
693
+ */
694
+ _authenticationAdminService;
515
695
  /**
516
696
  * The entity storage for users.
517
697
  * @internal
@@ -544,6 +724,7 @@ class EntityStorageAuthenticationService {
544
724
  constructor(options) {
545
725
  this._userEntityStorage = entityStorageModels.EntityStorageConnectorFactory.get(options?.userEntityStorageType ?? "authentication-user");
546
726
  this._vaultConnector = vaultModels.VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
727
+ this._authenticationAdminService = core.ComponentFactory.get(options?.authenticationAdminServiceType ?? "authentication-admin");
547
728
  this._signingKeyName = options?.config?.signingKeyName ?? "auth-signing";
548
729
  this._defaultTtlMinutes =
549
730
  options?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES;
@@ -551,10 +732,10 @@ class EntityStorageAuthenticationService {
551
732
  /**
552
733
  * The service needs to be started when the application is initialized.
553
734
  * @param nodeIdentity The identity of the node.
554
- * @param nodeLoggingConnectorType The node logging connector type, defaults to "node-logging".
735
+ * @param nodeLoggingComponentType The node logging component type.
555
736
  * @returns Nothing.
556
737
  */
557
- async start(nodeIdentity, nodeLoggingConnectorType) {
738
+ async start(nodeIdentity, nodeLoggingComponentType) {
558
739
  core.Guards.string(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
559
740
  this._nodeIdentity = nodeIdentity;
560
741
  }
@@ -582,7 +763,7 @@ class EntityStorageAuthenticationService {
582
763
  return tokenAndExpiry;
583
764
  }
584
765
  catch (error) {
585
- throw new core.UnauthorizedError(this.CLASS_NAME, "loginFailed", error);
766
+ throw new core.UnauthorizedError(this.CLASS_NAME, "loginFailed", undefined, error);
586
767
  }
587
768
  }
588
769
  /**
@@ -604,15 +785,27 @@ class EntityStorageAuthenticationService {
604
785
  const refreshTokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, `${this._nodeIdentity}/${this._signingKeyName}`, headerAndPayload.payload.sub ?? "", this._defaultTtlMinutes);
605
786
  return refreshTokenAndExpiry;
606
787
  }
788
+ /**
789
+ * Update the user's password.
790
+ * @param email The email address of the user to update.
791
+ * @param currentPassword The current password for the user.
792
+ * @param newPassword The new password for the user.
793
+ * @returns Nothing.
794
+ */
795
+ async updatePassword(email, currentPassword, newPassword) {
796
+ return this._authenticationAdminService.updatePassword(email, newPassword, currentPassword);
797
+ }
607
798
  }
608
799
 
609
800
  exports.AuthHeaderProcessor = AuthHeaderProcessor;
801
+ exports.EntityStorageAuthenticationAdminService = EntityStorageAuthenticationAdminService;
610
802
  exports.EntityStorageAuthenticationService = EntityStorageAuthenticationService;
611
803
  exports.PasswordHelper = PasswordHelper;
612
804
  exports.TokenHelper = TokenHelper;
613
805
  exports.authenticationLogin = authenticationLogin;
614
806
  exports.authenticationLogout = authenticationLogout;
615
807
  exports.authenticationRefreshToken = authenticationRefreshToken;
808
+ exports.authenticationUpdatePassword = authenticationUpdatePassword;
616
809
  exports.generateRestRoutesAuthentication = generateRestRoutesAuthentication;
617
810
  exports.initSchema = initSchema;
618
811
  exports.restEntryPoints = restEntryPoints;
@@ -1,8 +1,8 @@
1
1
  import { property, entity, EntitySchemaFactory, EntitySchemaHelper } from '@twin.org/entity';
2
2
  import { HttpErrorHelper } from '@twin.org/api-models';
3
- import { Is, UnauthorizedError, Guards, BaseError, ComponentFactory, Converter, GeneralError } from '@twin.org/core';
3
+ import { Is, UnauthorizedError, Guards, BaseError, ComponentFactory, Converter, GeneralError, RandomHelper, NotFoundError } from '@twin.org/core';
4
4
  import { VaultConnectorHelper, VaultConnectorFactory } from '@twin.org/vault-models';
5
- import { Jwt, HeaderTypes, HttpStatusCode } from '@twin.org/web';
5
+ import { Jwt, HeaderTypes, HeaderHelper, HttpStatusCode } from '@twin.org/web';
6
6
  import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
7
7
  import { Blake2b } from '@twin.org/crypto';
8
8
 
@@ -115,9 +115,10 @@ class TokenHelper {
115
115
  static extractTokenFromHeaders(headers, cookieName) {
116
116
  const authHeader = headers?.[HeaderTypes.Authorization];
117
117
  const cookiesHeader = headers?.[HeaderTypes.Cookie];
118
- if (Is.string(authHeader) && authHeader.startsWith("Bearer ")) {
118
+ const bearerToken = HeaderHelper.extractBearer(authHeader);
119
+ if (Is.stringValue(bearerToken)) {
119
120
  return {
120
- token: authHeader.slice(7).trim(),
121
+ token: bearerToken,
121
122
  location: "authorization"
122
123
  };
123
124
  }
@@ -147,10 +148,6 @@ class TokenHelper {
147
148
  * Handle a JWT token in the authorization header or cookies and validate it to populate request context identity.
148
149
  */
149
150
  class AuthHeaderProcessor {
150
- /**
151
- * The namespace supported by the processor.
152
- */
153
- static NAMESPACE = "auth-header";
154
151
  /**
155
152
  * The default name for the access token as a cookie.
156
153
  * @internal
@@ -192,10 +189,10 @@ class AuthHeaderProcessor {
192
189
  /**
193
190
  * The service needs to be started when the application is initialized.
194
191
  * @param nodeIdentity The identity of the node.
195
- * @param nodeLoggingConnectorType The node logging connector type, defaults to "node-logging".
192
+ * @param nodeLoggingComponentType The node logging component type.
196
193
  * @returns Nothing.
197
194
  */
198
- async start(nodeIdentity, nodeLoggingConnectorType) {
195
+ async start(nodeIdentity, nodeLoggingComponentType) {
199
196
  Guards.string(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
200
197
  this._nodeIdentity = nodeIdentity;
201
198
  }
@@ -386,7 +383,41 @@ function generateRestRoutesAuthentication(baseRouteName, componentName) {
386
383
  }
387
384
  ]
388
385
  };
389
- return [loginRoute, logoutRoute, refreshTokenRoute];
386
+ const updatePasswordRoute = {
387
+ operationId: "authenticationUpdatePassword",
388
+ summary: "Update the user's password",
389
+ tag: tagsAuthentication[0].name,
390
+ method: "PUT",
391
+ path: `${baseRouteName}/:email/password`,
392
+ handler: async (httpRequestContext, request) => authenticationUpdatePassword(httpRequestContext, componentName, request),
393
+ requestType: {
394
+ type: "IUpdatePasswordRequest",
395
+ examples: [
396
+ {
397
+ id: "updatePasswordRequestExample",
398
+ description: "The request to update the user's password.",
399
+ request: {
400
+ pathParams: {
401
+ email: "john:example.com"
402
+ },
403
+ body: {
404
+ currentPassword: "MyNewPassword123!",
405
+ newPassword: "MyNewPassword123!"
406
+ }
407
+ }
408
+ }
409
+ ]
410
+ },
411
+ responseType: [
412
+ {
413
+ type: "INoContentResponse"
414
+ },
415
+ {
416
+ type: "IUnauthorizedResponse"
417
+ }
418
+ ]
419
+ };
420
+ return [loginRoute, logoutRoute, refreshTokenRoute, updatePasswordRoute];
390
421
  }
391
422
  /**
392
423
  * Login to the server.
@@ -446,6 +477,23 @@ async function authenticationRefreshToken(httpRequestContext, componentName, req
446
477
  body: result
447
478
  };
448
479
  }
480
+ /**
481
+ * Update the user's password.
482
+ * @param httpRequestContext The request context for the API.
483
+ * @param componentName The name of the component to use in the routes.
484
+ * @param request The request.
485
+ * @returns The response object with additional http response properties.
486
+ */
487
+ async function authenticationUpdatePassword(httpRequestContext, componentName, request) {
488
+ Guards.object(ROUTES_SOURCE, "request", request);
489
+ Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
490
+ Guards.object(ROUTES_SOURCE, "request.body", request.body);
491
+ const component = ComponentFactory.get(componentName);
492
+ await component.updatePassword(request.pathParams.email, request.body.currentPassword, request.body.newPassword);
493
+ return {
494
+ statusCode: HttpStatusCode.noContent
495
+ };
496
+ }
449
497
 
450
498
  const restEntryPoints = [
451
499
  {
@@ -496,11 +544,138 @@ class PasswordHelper {
496
544
  /**
497
545
  * Implementation of the authentication component using entity storage.
498
546
  */
499
- class EntityStorageAuthenticationService {
547
+ class EntityStorageAuthenticationAdminService {
500
548
  /**
501
- * The namespace supported by the authentication service.
549
+ * The minimum password length.
550
+ * @internal
502
551
  */
503
- static NAMESPACE = "authentication-entity-storage";
552
+ static _DEFAULT_MIN_PASSWORD_LENGTH = 8;
553
+ /**
554
+ * Runtime name for the class.
555
+ */
556
+ CLASS_NAME = "EntityStorageAuthenticationAdminService";
557
+ /**
558
+ * The entity storage for users.
559
+ * @internal
560
+ */
561
+ _userEntityStorage;
562
+ /**
563
+ * The minimum password length.
564
+ * @internal
565
+ */
566
+ _minPasswordLength;
567
+ /**
568
+ * Create a new instance of EntityStorageAuthentication.
569
+ * @param options The dependencies for the identity connector.
570
+ */
571
+ constructor(options) {
572
+ this._userEntityStorage = EntityStorageConnectorFactory.get(options?.userEntityStorageType ?? "authentication-user");
573
+ this._minPasswordLength =
574
+ options?.config?.minPasswordLength ??
575
+ EntityStorageAuthenticationAdminService._DEFAULT_MIN_PASSWORD_LENGTH;
576
+ }
577
+ /**
578
+ * Create a login for the user.
579
+ * @param email The email address for the user.
580
+ * @param password The password for the user.
581
+ * @param identity The DID to associate with the account.
582
+ * @returns Nothing.
583
+ */
584
+ async create(email, password, identity) {
585
+ Guards.stringValue(this.CLASS_NAME, "email", email);
586
+ Guards.stringValue(this.CLASS_NAME, "password", password);
587
+ try {
588
+ if (password.length < this._minPasswordLength) {
589
+ throw new GeneralError(this.CLASS_NAME, "passwordTooShort", {
590
+ minLength: this._minPasswordLength
591
+ });
592
+ }
593
+ const user = await this._userEntityStorage.get(email);
594
+ if (user) {
595
+ throw new GeneralError(this.CLASS_NAME, "userExists");
596
+ }
597
+ const saltBytes = RandomHelper.generate(16);
598
+ const passwordBytes = Converter.utf8ToBytes(password);
599
+ const hashedPassword = await PasswordHelper.hashPassword(passwordBytes, saltBytes);
600
+ const newUser = {
601
+ email,
602
+ salt: Converter.bytesToBase64(saltBytes),
603
+ password: hashedPassword,
604
+ identity
605
+ };
606
+ await this._userEntityStorage.set(newUser);
607
+ }
608
+ catch (error) {
609
+ throw new GeneralError(this.CLASS_NAME, "createUserFailed", undefined, error);
610
+ }
611
+ }
612
+ /**
613
+ * Remove the current user.
614
+ * @param email The email address of the user to remove.
615
+ * @returns Nothing.
616
+ */
617
+ async remove(email) {
618
+ Guards.stringValue(this.CLASS_NAME, "email", email);
619
+ try {
620
+ const user = await this._userEntityStorage.get(email);
621
+ if (!user) {
622
+ throw new NotFoundError(this.CLASS_NAME, "userNotFound", email);
623
+ }
624
+ await this._userEntityStorage.remove(email);
625
+ }
626
+ catch (error) {
627
+ throw new GeneralError(this.CLASS_NAME, "removeUserFailed", undefined, error);
628
+ }
629
+ }
630
+ /**
631
+ * Update the user's password.
632
+ * @param email The email address of the user to update.
633
+ * @param newPassword The new password for the user.
634
+ * @param currentPassword The current password, optional, if supplied will check against existing.
635
+ * @returns Nothing.
636
+ */
637
+ async updatePassword(email, newPassword, currentPassword) {
638
+ Guards.stringValue(this.CLASS_NAME, "email", email);
639
+ Guards.stringValue(this.CLASS_NAME, "newPassword", newPassword);
640
+ try {
641
+ if (newPassword.length < this._minPasswordLength) {
642
+ throw new GeneralError(this.CLASS_NAME, "passwordTooShort", {
643
+ minLength: this._minPasswordLength
644
+ });
645
+ }
646
+ const user = await this._userEntityStorage.get(email);
647
+ if (!user) {
648
+ throw new NotFoundError(this.CLASS_NAME, "userNotFound", email);
649
+ }
650
+ if (Is.stringValue(currentPassword)) {
651
+ const saltBytes = Converter.base64ToBytes(user.salt);
652
+ const passwordBytes = Converter.utf8ToBytes(currentPassword);
653
+ const hashedPassword = await PasswordHelper.hashPassword(passwordBytes, saltBytes);
654
+ if (hashedPassword !== user.password) {
655
+ throw new GeneralError(this.CLASS_NAME, "currentPasswordMismatch");
656
+ }
657
+ }
658
+ const saltBytes = RandomHelper.generate(16);
659
+ const passwordBytes = Converter.utf8ToBytes(newPassword);
660
+ const hashedPassword = await PasswordHelper.hashPassword(passwordBytes, saltBytes);
661
+ const updatedUser = {
662
+ email,
663
+ salt: Converter.bytesToBase64(saltBytes),
664
+ password: hashedPassword,
665
+ identity: user.identity
666
+ };
667
+ await this._userEntityStorage.set(updatedUser);
668
+ }
669
+ catch (error) {
670
+ throw new GeneralError(this.CLASS_NAME, "updatePasswordFailed", undefined, error);
671
+ }
672
+ }
673
+ }
674
+
675
+ /**
676
+ * Implementation of the authentication component using entity storage.
677
+ */
678
+ class EntityStorageAuthenticationService {
504
679
  /**
505
680
  * Default TTL in minutes.
506
681
  * @internal
@@ -510,6 +685,11 @@ class EntityStorageAuthenticationService {
510
685
  * Runtime name for the class.
511
686
  */
512
687
  CLASS_NAME = "EntityStorageAuthenticationService";
688
+ /**
689
+ * The user admin service.
690
+ * @internal
691
+ */
692
+ _authenticationAdminService;
513
693
  /**
514
694
  * The entity storage for users.
515
695
  * @internal
@@ -542,6 +722,7 @@ class EntityStorageAuthenticationService {
542
722
  constructor(options) {
543
723
  this._userEntityStorage = EntityStorageConnectorFactory.get(options?.userEntityStorageType ?? "authentication-user");
544
724
  this._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
725
+ this._authenticationAdminService = ComponentFactory.get(options?.authenticationAdminServiceType ?? "authentication-admin");
545
726
  this._signingKeyName = options?.config?.signingKeyName ?? "auth-signing";
546
727
  this._defaultTtlMinutes =
547
728
  options?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES;
@@ -549,10 +730,10 @@ class EntityStorageAuthenticationService {
549
730
  /**
550
731
  * The service needs to be started when the application is initialized.
551
732
  * @param nodeIdentity The identity of the node.
552
- * @param nodeLoggingConnectorType The node logging connector type, defaults to "node-logging".
733
+ * @param nodeLoggingComponentType The node logging component type.
553
734
  * @returns Nothing.
554
735
  */
555
- async start(nodeIdentity, nodeLoggingConnectorType) {
736
+ async start(nodeIdentity, nodeLoggingComponentType) {
556
737
  Guards.string(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
557
738
  this._nodeIdentity = nodeIdentity;
558
739
  }
@@ -580,7 +761,7 @@ class EntityStorageAuthenticationService {
580
761
  return tokenAndExpiry;
581
762
  }
582
763
  catch (error) {
583
- throw new UnauthorizedError(this.CLASS_NAME, "loginFailed", error);
764
+ throw new UnauthorizedError(this.CLASS_NAME, "loginFailed", undefined, error);
584
765
  }
585
766
  }
586
767
  /**
@@ -602,6 +783,16 @@ class EntityStorageAuthenticationService {
602
783
  const refreshTokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, `${this._nodeIdentity}/${this._signingKeyName}`, headerAndPayload.payload.sub ?? "", this._defaultTtlMinutes);
603
784
  return refreshTokenAndExpiry;
604
785
  }
786
+ /**
787
+ * Update the user's password.
788
+ * @param email The email address of the user to update.
789
+ * @param currentPassword The current password for the user.
790
+ * @param newPassword The new password for the user.
791
+ * @returns Nothing.
792
+ */
793
+ async updatePassword(email, currentPassword, newPassword) {
794
+ return this._authenticationAdminService.updatePassword(email, newPassword, currentPassword);
795
+ }
605
796
  }
606
797
 
607
- export { AuthHeaderProcessor, AuthenticationUser, EntityStorageAuthenticationService, PasswordHelper, TokenHelper, authenticationLogin, authenticationLogout, authenticationRefreshToken, generateRestRoutesAuthentication, initSchema, restEntryPoints, tagsAuthentication };
798
+ export { AuthHeaderProcessor, AuthenticationUser, EntityStorageAuthenticationAdminService, EntityStorageAuthenticationService, PasswordHelper, TokenHelper, authenticationLogin, authenticationLogout, authenticationRefreshToken, authenticationUpdatePassword, generateRestRoutesAuthentication, initSchema, restEntryPoints, tagsAuthentication };
@@ -1,12 +1,15 @@
1
1
  export * from "./entities/authenticationUser";
2
2
  export * from "./models/IAuthHeaderProcessorConfig";
3
3
  export * from "./models/IAuthHeaderProcessorConstructorOptions";
4
+ export * from "./models/IEntityStorageAuthenticationAdminServiceConfig";
5
+ export * from "./models/IEntityStorageAuthenticationAdminServiceConstructorOptions";
4
6
  export * from "./models/IEntityStorageAuthenticationServiceConfig";
5
7
  export * from "./models/IEntityStorageAuthenticationServiceConstructorOptions";
6
8
  export * from "./processors/authHeaderProcessor";
7
9
  export * from "./restEntryPoints";
8
10
  export * from "./routes/entityStorageAuthenticationRoutes";
9
11
  export * from "./schema";
12
+ export * from "./services/entityStorageAuthenticationAdminService";
10
13
  export * from "./services/entityStorageAuthenticationService";
11
14
  export * from "./utils/passwordHelper";
12
15
  export * from "./utils/tokenHelper";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Configuration for the entity storage authentication admin service.
3
+ */
4
+ export interface IEntityStorageAuthenticationAdminServiceConfig {
5
+ /**
6
+ * The minimum password length.
7
+ * @default 8
8
+ */
9
+ minPasswordLength?: number;
10
+ }
@@ -0,0 +1,15 @@
1
+ import type { IEntityStorageAuthenticationAdminServiceConfig } from "./IEntityStorageAuthenticationAdminServiceConfig";
2
+ /**
3
+ * Options for the EntityStorageAuthenticationAdminService constructor.
4
+ */
5
+ export interface IEntityStorageAuthenticationAdminServiceConstructorOptions {
6
+ /**
7
+ * The entity storage for the users.
8
+ * @default authentication-user
9
+ */
10
+ userEntityStorageType?: string;
11
+ /**
12
+ * The configuration for the authentication.
13
+ */
14
+ config?: IEntityStorageAuthenticationAdminServiceConfig;
15
+ }
@@ -13,6 +13,11 @@ export interface IEntityStorageAuthenticationServiceConstructorOptions {
13
13
  * @default vault
14
14
  */
15
15
  vaultConnectorType?: string;
16
+ /**
17
+ * The admin service.
18
+ * @default authentication-admin
19
+ */
20
+ authenticationAdminServiceType?: string;
16
21
  /**
17
22
  * The configuration for the authentication.
18
23
  */