@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.
- package/dist/cjs/index.cjs +208 -15
- package/dist/esm/index.mjs +209 -18
- package/dist/types/index.d.ts +3 -0
- package/dist/types/models/IEntityStorageAuthenticationAdminServiceConfig.d.ts +10 -0
- package/dist/types/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.d.ts +15 -0
- package/dist/types/models/IEntityStorageAuthenticationServiceConstructorOptions.d.ts +5 -0
- package/dist/types/processors/authHeaderProcessor.d.ts +2 -6
- package/dist/types/routes/entityStorageAuthenticationRoutes.d.ts +9 -1
- package/dist/types/services/entityStorageAuthenticationAdminService.d.ts +38 -0
- package/dist/types/services/entityStorageAuthenticationService.d.ts +10 -6
- package/docs/changelog.md +166 -0
- package/docs/reference/classes/AuthHeaderProcessor.md +3 -11
- package/docs/reference/classes/EntityStorageAuthenticationAdminService.md +141 -0
- package/docs/reference/classes/EntityStorageAuthenticationService.md +41 -11
- package/docs/reference/functions/authenticationUpdatePassword.md +31 -0
- package/docs/reference/index.md +4 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConfig.md +17 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md +25 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConstructorOptions.md +14 -0
- package/locales/en.json +9 -0
- package/package.json +12 -12
package/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
-
|
|
120
|
+
const bearerToken = web.HeaderHelper.extractBearer(authHeader);
|
|
121
|
+
if (core.Is.stringValue(bearerToken)) {
|
|
121
122
|
return {
|
|
122
|
-
token:
|
|
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
|
|
194
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
198
195
|
* @returns Nothing.
|
|
199
196
|
*/
|
|
200
|
-
async start(nodeIdentity,
|
|
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
|
-
|
|
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
|
|
549
|
+
class EntityStorageAuthenticationAdminService {
|
|
502
550
|
/**
|
|
503
|
-
* The
|
|
551
|
+
* The minimum password length.
|
|
552
|
+
* @internal
|
|
504
553
|
*/
|
|
505
|
-
static
|
|
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
|
|
735
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
555
736
|
* @returns Nothing.
|
|
556
737
|
*/
|
|
557
|
-
async start(nodeIdentity,
|
|
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;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -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
|
-
|
|
118
|
+
const bearerToken = HeaderHelper.extractBearer(authHeader);
|
|
119
|
+
if (Is.stringValue(bearerToken)) {
|
|
119
120
|
return {
|
|
120
|
-
token:
|
|
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
|
|
192
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
196
193
|
* @returns Nothing.
|
|
197
194
|
*/
|
|
198
|
-
async start(nodeIdentity,
|
|
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
|
-
|
|
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
|
|
547
|
+
class EntityStorageAuthenticationAdminService {
|
|
500
548
|
/**
|
|
501
|
-
* The
|
|
549
|
+
* The minimum password length.
|
|
550
|
+
* @internal
|
|
502
551
|
*/
|
|
503
|
-
static
|
|
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
|
|
733
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
553
734
|
* @returns Nothing.
|
|
554
735
|
*/
|
|
555
|
-
async start(nodeIdentity,
|
|
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 };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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,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
|
*/
|