@twin.org/api-auth-entity-storage-service 0.0.3-next.21 → 0.0.3-next.23
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/es/entities/authenticationAuditEntry.js +101 -0
- package/dist/es/entities/authenticationAuditEntry.js.map +1 -0
- package/dist/es/entities/authenticationRateEntry.js +37 -0
- package/dist/es/entities/authenticationRateEntry.js.map +1 -0
- package/dist/es/index.js +9 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IAuthHeaderProcessorConstructorOptions.js.map +1 -1
- package/dist/es/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js.map +1 -1
- package/dist/es/models/IEntityStorageAuthenticationAuditServiceConfig.js +4 -0
- package/dist/es/models/IEntityStorageAuthenticationAuditServiceConfig.js.map +1 -0
- package/dist/es/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.js +2 -0
- package/dist/es/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.js.map +1 -0
- package/dist/es/models/IEntityStorageAuthenticationRateServiceConfig.js +2 -0
- package/dist/es/models/IEntityStorageAuthenticationRateServiceConfig.js.map +1 -0
- package/dist/es/models/IEntityStorageAuthenticationRateServiceConstructorOptions.js +2 -0
- package/dist/es/models/IEntityStorageAuthenticationRateServiceConstructorOptions.js.map +1 -0
- package/dist/es/models/IEntityStorageAuthenticationServiceConfig.js +0 -2
- package/dist/es/models/IEntityStorageAuthenticationServiceConfig.js.map +1 -1
- package/dist/es/models/IEntityStorageAuthenticationServiceConstructorOptions.js.map +1 -1
- package/dist/es/processors/authHeaderProcessor.js +18 -4
- package/dist/es/processors/authHeaderProcessor.js.map +1 -1
- package/dist/es/restEntryPoints.js +7 -0
- package/dist/es/restEntryPoints.js.map +1 -1
- package/dist/es/routes/entityStorageAuthenticationAuditRoutes.js +174 -0
- package/dist/es/routes/entityStorageAuthenticationAuditRoutes.js.map +1 -0
- package/dist/es/schema.js +4 -0
- package/dist/es/schema.js.map +1 -1
- package/dist/es/services/entityStorageAuthenticationAdminService.js +59 -4
- package/dist/es/services/entityStorageAuthenticationAdminService.js.map +1 -1
- package/dist/es/services/entityStorageAuthenticationAuditService.js +178 -0
- package/dist/es/services/entityStorageAuthenticationAuditService.js.map +1 -0
- package/dist/es/services/entityStorageAuthenticationRateService.js +202 -0
- package/dist/es/services/entityStorageAuthenticationRateService.js.map +1 -0
- package/dist/es/services/entityStorageAuthenticationService.js +152 -8
- package/dist/es/services/entityStorageAuthenticationService.js.map +1 -1
- package/dist/es/utils/tokenHelper.js +11 -1
- package/dist/es/utils/tokenHelper.js.map +1 -1
- package/dist/types/entities/authenticationAuditEntry.d.ts +49 -0
- package/dist/types/entities/authenticationRateEntry.d.ts +17 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/models/IAuthHeaderProcessorConstructorOptions.d.ts +5 -0
- package/dist/types/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.d.ts +5 -0
- package/dist/types/models/IEntityStorageAuthenticationAuditServiceConfig.d.ts +9 -0
- package/dist/types/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.d.ts +15 -0
- package/dist/types/models/IEntityStorageAuthenticationRateServiceConfig.d.ts +10 -0
- package/dist/types/models/IEntityStorageAuthenticationRateServiceConstructorOptions.d.ts +20 -0
- package/dist/types/models/IEntityStorageAuthenticationServiceConfig.d.ts +17 -1
- package/dist/types/models/IEntityStorageAuthenticationServiceConstructorOptions.d.ts +10 -0
- package/dist/types/routes/entityStorageAuthenticationAuditRoutes.d.ts +29 -0
- package/dist/types/services/entityStorageAuthenticationAuditService.d.ts +59 -0
- package/dist/types/services/entityStorageAuthenticationRateService.d.ts +60 -0
- package/dist/types/services/entityStorageAuthenticationService.d.ts +6 -0
- package/dist/types/utils/tokenHelper.d.ts +2 -1
- package/docs/changelog.md +32 -0
- package/docs/reference/classes/AuthHeaderProcessor.md +9 -9
- package/docs/reference/classes/AuthenticationAuditEntry.md +101 -0
- package/docs/reference/classes/AuthenticationRateEntry.md +37 -0
- package/docs/reference/classes/AuthenticationUser.md +6 -6
- package/docs/reference/classes/EntityStorageAuthenticationAdminService.md +12 -12
- package/docs/reference/classes/EntityStorageAuthenticationAuditService.md +157 -0
- package/docs/reference/classes/EntityStorageAuthenticationRateService.md +227 -0
- package/docs/reference/classes/EntityStorageAuthenticationService.md +33 -7
- package/docs/reference/classes/TokenHelper.md +17 -11
- package/docs/reference/functions/authenticationAuditCreate.md +31 -0
- package/docs/reference/functions/authenticationAuditQuery.md +31 -0
- package/docs/reference/functions/generateRestRoutesAuthenticationAudit.md +25 -0
- package/docs/reference/index.md +12 -0
- package/docs/reference/interfaces/IAuthHeaderProcessorConfig.md +16 -4
- package/docs/reference/interfaces/IAuthHeaderProcessorConstructorOptions.md +24 -4
- package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConfig.md +8 -2
- package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md +24 -4
- package/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConfig.md +11 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConstructorOptions.md +25 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConfig.md +17 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConstructorOptions.md +39 -0
- package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConfig.md +58 -4
- package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConstructorOptions.md +54 -8
- package/docs/reference/variables/tagsAuthenticationAudit.md +5 -0
- package/locales/en.json +10 -1
- package/package.json +5 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entityStorageAuthenticationRateService.js","sourceRoot":"","sources":["../../../src/services/entityStorageAuthenticationRateService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIAuthenticationRateActionConfig,\n\tIAuthenticationRateComponent\n} from \"@twin.org/api-auth-entity-storage-models\";\nimport { TooManyRequestsError } from \"@twin.org/api-models\";\nimport type { ITaskSchedulerComponent } from \"@twin.org/background-task-models\";\nimport { ComponentFactory, Converter, GeneralError, Guards, Is } from \"@twin.org/core\";\nimport { Sha256 } from \"@twin.org/crypto\";\nimport { ComparisonOperator } from \"@twin.org/entity\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { AuthenticationRateEntry } from \"../entities/authenticationRateEntry.js\";\nimport type { IEntityStorageAuthenticationRateServiceConstructorOptions } from \"../models/IEntityStorageAuthenticationRateServiceConstructorOptions.js\";\n\n/**\n * Implementation of the authentication rate component using entity storage.\n */\nexport class EntityStorageAuthenticationRateService implements IAuthenticationRateComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntityStorageAuthenticationRateService>();\n\n\t/**\n\t * Cleanup task id.\n\t * @internal\n\t */\n\tprivate static readonly _CLEANUP_TASK_ID = \"authentication-rate-cleanup\";\n\n\t/**\n\t * Default cleanup interval in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_CLEANUP_INTERVAL_MINUTES = 5;\n\n\t/**\n\t * Number of entries to retrieve in each cleanup page.\n\t * @internal\n\t */\n\tprivate static readonly _CLEANUP_PAGE_SIZE = 250;\n\n\t/**\n\t * The entity storage for authentication rate entries.\n\t * @internal\n\t */\n\tprivate readonly _authenticationRateEntryStorage: IEntityStorageConnector<AuthenticationRateEntry>;\n\n\t/**\n\t * The rate limits for each action.\n\t * @internal\n\t */\n\tprivate readonly _actionConfigs: { [action: string]: IAuthenticationRateActionConfig };\n\n\t/**\n\t * The task scheduler.\n\t * @internal\n\t */\n\tprivate readonly _taskScheduler: ITaskSchedulerComponent;\n\n\t/**\n\t * The cleanup interval in minutes.\n\t * @internal\n\t */\n\tprivate readonly _cleanupIntervalMinutes: number;\n\n\t/**\n\t * Create a new instance of EntityStorageAuthenticationRateService.\n\t * @param options The constructor options.\n\t */\n\tconstructor(options?: IEntityStorageAuthenticationRateServiceConstructorOptions) {\n\t\tthis._authenticationRateEntryStorage = EntityStorageConnectorFactory.get(\n\t\t\toptions?.authenticationRateEntryStorageType ?? \"authentication-rate-entry\"\n\t\t);\n\t\tthis._actionConfigs = {};\n\t\tthis._taskScheduler = ComponentFactory.get<ITaskSchedulerComponent>(\n\t\t\toptions?.taskSchedulerComponentType ?? \"task-scheduler\"\n\t\t);\n\t\tthis._cleanupIntervalMinutes =\n\t\t\toptions?.config?.cleanupIntervalMinutes ??\n\t\t\tEntityStorageAuthenticationRateService._DEFAULT_CLEANUP_INTERVAL_MINUTES;\n\t}\n\n\t/**\n\t * Register or update rate-limit configuration for an action.\n\t * @param action The action name.\n\t * @param config The action configuration.\n\t * @returns Nothing.\n\t */\n\tpublic async registerAction(\n\t\taction: string,\n\t\tconfig: IAuthenticationRateActionConfig\n\t): Promise<void> {\n\t\tGuards.stringValue(EntityStorageAuthenticationRateService.CLASS_NAME, nameof(action), action);\n\t\tGuards.object<IAuthenticationRateActionConfig>(\n\t\t\tEntityStorageAuthenticationRateService.CLASS_NAME,\n\t\t\tnameof(config),\n\t\t\tconfig\n\t\t);\n\n\t\tthis._actionConfigs[action] = {\n\t\t\tmaxAttempts: config.maxAttempts,\n\t\t\twindowMinutes: config.windowMinutes\n\t\t};\n\t}\n\n\t/**\n\t * Unregister rate-limit configuration for an action.\n\t * @param action The action name.\n\t * @returns Nothing.\n\t */\n\tpublic async unregisterAction(action: string): Promise<void> {\n\t\tGuards.stringValue(EntityStorageAuthenticationRateService.CLASS_NAME, nameof(action), action);\n\n\t\tdelete this._actionConfigs[action];\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 EntityStorageAuthenticationRateService.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\tawait this._taskScheduler.addTask(\n\t\t\tEntityStorageAuthenticationRateService._CLEANUP_TASK_ID,\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\tintervalMinutes: this._cleanupIntervalMinutes\n\t\t\t\t}\n\t\t\t],\n\t\t\tasync () => this.cleanupExpiredEntries()\n\t\t);\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tawait this._taskScheduler.removeTask(EntityStorageAuthenticationRateService._CLEANUP_TASK_ID);\n\t}\n\n\t/**\n\t * Check the authentication rate for a given action and identifier.\n\t * @param action The action to be checked.\n\t * @param identifier The identifier to be checked.\n\t * @returns The rate entry id.\n\t */\n\tpublic async check(action: string, identifier: string): Promise<string> {\n\t\tGuards.stringValue(EntityStorageAuthenticationRateService.CLASS_NAME, nameof(action), action);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationRateService.CLASS_NAME,\n\t\t\tnameof(identifier),\n\t\t\tidentifier\n\t\t);\n\n\t\tconst actionConfig = this._actionConfigs[action];\n\n\t\tif (!Is.object(actionConfig)) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tEntityStorageAuthenticationRateService.CLASS_NAME,\n\t\t\t\t\"actionConfigMissing\",\n\t\t\t\t{\n\t\t\t\t\taction\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\n\t\tconst hashedIdentifier = Converter.bytesToHex(Sha256.sum256(Converter.utf8ToBytes(identifier)));\n\t\tconst compositeId = `|${action}|${hashedIdentifier}|`;\n\n\t\tconst now = Date.now();\n\t\tconst nowIso = new Date(now).toISOString();\n\t\tconst windowMs = actionConfig.windowMinutes * 60 * 1000;\n\t\tconst cutoff = now - windowMs;\n\n\t\t// Resolve the existing rate entry for this action + identifier pair.\n\t\tconst existing = await this._authenticationRateEntryStorage.get(compositeId);\n\t\t// Keep only attempts inside the configured sliding window.\n\t\tconst activeTimestamps = (existing?.timestamps ?? []).filter(\n\t\t\ttimestamp => new Date(timestamp).getTime() > cutoff\n\t\t);\n\n\t\tif (activeTimestamps.length >= actionConfig.maxAttempts) {\n\t\t\tconst oldestTimestamp = new Date(activeTimestamps[0]).getTime();\n\t\t\tconst nextRequestTime = new Date(oldestTimestamp + windowMs).toISOString();\n\t\t\tconst retryAfterSeconds = Math.ceil((oldestTimestamp + windowMs - now) / 1000);\n\n\t\t\tthrow new TooManyRequestsError(\n\t\t\t\tEntityStorageAuthenticationRateService.CLASS_NAME,\n\t\t\t\t\"rateLimitExceeded\",\n\t\t\t\tactiveTimestamps.length,\n\t\t\t\tnextRequestTime,\n\t\t\t\t{\n\t\t\t\t\taction,\n\t\t\t\t\tretryAfterSeconds\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\n\t\tactiveTimestamps.push(nowIso);\n\n\t\tawait this._authenticationRateEntryStorage.set({\n\t\t\tid: compositeId,\n\t\t\ttimestamps: activeTimestamps,\n\t\t\tdateModified: nowIso\n\t\t});\n\n\t\treturn compositeId;\n\t}\n\n\t/**\n\t * Clear the authentication rate entry for the given action and identifier.\n\t * @param action The action to clear.\n\t * @param identifier The identifier to clear.\n\t * @returns Nothing.\n\t */\n\tpublic async clear(action: string, identifier: string): Promise<void> {\n\t\tGuards.stringValue(EntityStorageAuthenticationRateService.CLASS_NAME, nameof(action), action);\n\t\tGuards.stringValue(\n\t\t\tEntityStorageAuthenticationRateService.CLASS_NAME,\n\t\t\tnameof(identifier),\n\t\t\tidentifier\n\t\t);\n\n\t\tconst hashedIdentifier = Converter.bytesToHex(Sha256.sum256(Converter.utf8ToBytes(identifier)));\n\t\tconst compositeId = `|${action}|${hashedIdentifier}|`;\n\n\t\tawait this._authenticationRateEntryStorage.remove(compositeId);\n\t}\n\n\t/**\n\t * Cleanup expired rate limit entries.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async cleanupExpiredEntries(): Promise<void> {\n\t\tconst now = Date.now();\n\n\t\tfor (const action of Object.keys(this._actionConfigs)) {\n\t\t\tconst actionConfig = this._actionConfigs[action];\n\t\t\tconst windowMs = actionConfig.windowMinutes * 60 * 1000;\n\t\t\tconst cutoffIso = new Date(now - windowMs).toISOString();\n\t\t\tlet cursor: string | undefined;\n\n\t\t\tdo {\n\t\t\t\t// Query by composite rate entry id prefix and expiry window for this action.\n\t\t\t\tconst result = await this._authenticationRateEntryStorage.query(\n\t\t\t\t\t{\n\t\t\t\t\t\tconditions: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproperty: \"id\",\n\t\t\t\t\t\t\t\tvalue: `|${action}|`,\n\t\t\t\t\t\t\t\tcomparison: ComparisonOperator.Includes\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproperty: \"dateModified\",\n\t\t\t\t\t\t\t\tvalue: cutoffIso,\n\t\t\t\t\t\t\t\tcomparison: ComparisonOperator.LessThanOrEqual\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tcursor,\n\t\t\t\t\tEntityStorageAuthenticationRateService._CLEANUP_PAGE_SIZE\n\t\t\t\t);\n\n\t\t\t\tfor (const entity of result.entities as AuthenticationRateEntry[]) {\n\t\t\t\t\tawait this._authenticationRateEntryStorage.remove(entity.id);\n\t\t\t\t}\n\n\t\t\t\tcursor = result.cursor;\n\t\t\t} while (!Is.empty(cursor));\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AuthAuditEvent } from "@twin.org/api-auth-entity-storage-models";
|
|
1
2
|
import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@twin.org/context";
|
|
2
3
|
import { Coerce, ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError, UnauthorizedError } from "@twin.org/core";
|
|
3
4
|
import { PasswordGenerator, PasswordValidator } from "@twin.org/crypto";
|
|
@@ -17,11 +18,51 @@ export class EntityStorageAuthenticationService {
|
|
|
17
18
|
* @internal
|
|
18
19
|
*/
|
|
19
20
|
static _DEFAULT_TTL_MINUTES = 60;
|
|
21
|
+
/**
|
|
22
|
+
* Default maximum login attempts in a rate window.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
static _DEFAULT_LOGIN_RATE_MAX_ATTEMPTS = 5;
|
|
26
|
+
/**
|
|
27
|
+
* Default login rate window in minutes.
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
static _DEFAULT_LOGIN_RATE_WINDOW_MINUTES = 15;
|
|
31
|
+
/**
|
|
32
|
+
* Default maximum password change attempts in a rate window.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
static _DEFAULT_PASSWORD_CHANGE_RATE_MAX_ATTEMPTS = 5;
|
|
36
|
+
/**
|
|
37
|
+
* Default password change rate window in minutes.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
static _DEFAULT_PASSWORD_CHANGE_RATE_WINDOW_MINUTES = 15;
|
|
41
|
+
/**
|
|
42
|
+
* Default maximum token refresh attempts in a rate window.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
static _DEFAULT_TOKEN_REFRESH_RATE_MAX_ATTEMPTS = 30;
|
|
46
|
+
/**
|
|
47
|
+
* Default token refresh rate window in minutes.
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
static _DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES = 60;
|
|
20
51
|
/**
|
|
21
52
|
* The user admin service.
|
|
22
53
|
* @internal
|
|
23
54
|
*/
|
|
24
55
|
_authenticationAdminService;
|
|
56
|
+
/**
|
|
57
|
+
* The audit service.
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
_authenticationAuditService;
|
|
61
|
+
/**
|
|
62
|
+
* The rate service.
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
_authenticationRateService;
|
|
25
66
|
/**
|
|
26
67
|
* The entity storage for users.
|
|
27
68
|
* @internal
|
|
@@ -42,6 +83,21 @@ export class EntityStorageAuthenticationService {
|
|
|
42
83
|
* @internal
|
|
43
84
|
*/
|
|
44
85
|
_defaultTtlMinutes;
|
|
86
|
+
/**
|
|
87
|
+
* Rate limit configuration for login failures.
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
_loginRateLimit;
|
|
91
|
+
/**
|
|
92
|
+
* Rate limit configuration for password changes.
|
|
93
|
+
* @internal
|
|
94
|
+
*/
|
|
95
|
+
_passwordChangeRateLimit;
|
|
96
|
+
/**
|
|
97
|
+
* Rate limit configuration for token refresh.
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
_tokenRefreshRateLimit;
|
|
45
101
|
/**
|
|
46
102
|
* The node identity.
|
|
47
103
|
* @internal
|
|
@@ -55,9 +111,29 @@ export class EntityStorageAuthenticationService {
|
|
|
55
111
|
this._userEntityStorage = EntityStorageConnectorFactory.get(options?.userEntityStorageType ?? "authentication-user");
|
|
56
112
|
this._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
|
|
57
113
|
this._authenticationAdminService = ComponentFactory.get(options?.authenticationAdminServiceType ?? "authentication-admin");
|
|
114
|
+
this._authenticationAuditService = ComponentFactory.getIfExists(options?.authenticationAuditServiceType ?? "authentication-audit");
|
|
115
|
+
this._authenticationRateService = ComponentFactory.get(options?.authenticationRateServiceType ?? "authentication-rate");
|
|
58
116
|
this._signingKeyName = options?.config?.signingKeyName ?? "auth-signing";
|
|
59
117
|
this._defaultTtlMinutes =
|
|
60
118
|
options?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES;
|
|
119
|
+
this._loginRateLimit = {
|
|
120
|
+
maxAttempts: options?.config?.loginRateLimit?.maxAttempts ??
|
|
121
|
+
EntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_MAX_ATTEMPTS,
|
|
122
|
+
windowMinutes: options?.config?.loginRateLimit?.windowMinutes ??
|
|
123
|
+
EntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_WINDOW_MINUTES
|
|
124
|
+
};
|
|
125
|
+
this._passwordChangeRateLimit = {
|
|
126
|
+
maxAttempts: options?.config?.passwordChangeRateLimit?.maxAttempts ??
|
|
127
|
+
EntityStorageAuthenticationService._DEFAULT_PASSWORD_CHANGE_RATE_MAX_ATTEMPTS,
|
|
128
|
+
windowMinutes: options?.config?.passwordChangeRateLimit?.windowMinutes ??
|
|
129
|
+
EntityStorageAuthenticationService._DEFAULT_PASSWORD_CHANGE_RATE_WINDOW_MINUTES
|
|
130
|
+
};
|
|
131
|
+
this._tokenRefreshRateLimit = {
|
|
132
|
+
maxAttempts: options?.config?.tokenRefreshRateLimit?.maxAttempts ??
|
|
133
|
+
EntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_MAX_ATTEMPTS,
|
|
134
|
+
windowMinutes: options?.config?.tokenRefreshRateLimit?.windowMinutes ??
|
|
135
|
+
EntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES
|
|
136
|
+
};
|
|
61
137
|
}
|
|
62
138
|
/**
|
|
63
139
|
* Returns the class name of the component.
|
|
@@ -75,6 +151,19 @@ export class EntityStorageAuthenticationService {
|
|
|
75
151
|
const contextIds = await ContextIdStore.getContextIds();
|
|
76
152
|
ContextIdHelper.guard(contextIds, ContextIdKeys.Node);
|
|
77
153
|
this._nodeId = contextIds[ContextIdKeys.Node];
|
|
154
|
+
await this._authenticationRateService.registerAction("login", this._loginRateLimit);
|
|
155
|
+
await this._authenticationRateService.registerAction("password-change", this._passwordChangeRateLimit);
|
|
156
|
+
await this._authenticationRateService.registerAction("token-refresh", this._tokenRefreshRateLimit);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The component needs to be stopped when the node is closed.
|
|
160
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
161
|
+
* @returns Nothing.
|
|
162
|
+
*/
|
|
163
|
+
async stop(nodeLoggingComponentType) {
|
|
164
|
+
await this._authenticationRateService.unregisterAction("login");
|
|
165
|
+
await this._authenticationRateService.unregisterAction("password-change");
|
|
166
|
+
await this._authenticationRateService.unregisterAction("token-refresh");
|
|
78
167
|
}
|
|
79
168
|
/**
|
|
80
169
|
* Perform a login for the user.
|
|
@@ -85,7 +174,11 @@ export class EntityStorageAuthenticationService {
|
|
|
85
174
|
async login(email, password) {
|
|
86
175
|
Guards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, "email", email);
|
|
87
176
|
Guards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, "password", password);
|
|
177
|
+
let loginUser;
|
|
178
|
+
let loginTenantId;
|
|
179
|
+
let tokenAndExpiry;
|
|
88
180
|
try {
|
|
181
|
+
await this._authenticationRateService.check("login", email);
|
|
89
182
|
const user = await this._userEntityStorage.get(email);
|
|
90
183
|
if (!user) {
|
|
91
184
|
throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "userNotFound");
|
|
@@ -100,13 +193,29 @@ export class EntityStorageAuthenticationService {
|
|
|
100
193
|
// if is verified during the token processing, tenant id will be matched against
|
|
101
194
|
// the context
|
|
102
195
|
const contextIds = await ContextIdStore.getContextIds();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
196
|
+
loginTenantId = contextIds?.[ContextIdKeys.Tenant];
|
|
197
|
+
tokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, user.identity, user.organization, loginTenantId, this._defaultTtlMinutes, user.scope);
|
|
198
|
+
loginUser = user;
|
|
106
199
|
}
|
|
107
200
|
catch (error) {
|
|
201
|
+
await this._authenticationAuditService?.create({
|
|
202
|
+
actorId: email,
|
|
203
|
+
event: AuthAuditEvent.LoginFailure
|
|
204
|
+
});
|
|
108
205
|
throw new UnauthorizedError(EntityStorageAuthenticationService.CLASS_NAME, "loginFailed", undefined, error);
|
|
109
206
|
}
|
|
207
|
+
await this._authenticationRateService.clear("login", email);
|
|
208
|
+
await this._authenticationAuditService?.create({
|
|
209
|
+
actorId: email,
|
|
210
|
+
event: AuthAuditEvent.LoginSuccess,
|
|
211
|
+
data: {
|
|
212
|
+
userIdentity: loginUser.identity,
|
|
213
|
+
organizationIdentity: loginUser.organization,
|
|
214
|
+
tenantId: loginTenantId,
|
|
215
|
+
scope: loginUser.scope.split(",")
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return tokenAndExpiry;
|
|
110
219
|
}
|
|
111
220
|
/**
|
|
112
221
|
* Logout the current user.
|
|
@@ -116,6 +225,14 @@ export class EntityStorageAuthenticationService {
|
|
|
116
225
|
async logout(token) {
|
|
117
226
|
// Nothing to do here, as we are stateless.
|
|
118
227
|
// The cookie will be revoked by the REST route handling
|
|
228
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
229
|
+
const identifier = contextIds?.[ContextIdKeys.User];
|
|
230
|
+
if (Is.stringValue(identifier)) {
|
|
231
|
+
await this._authenticationAuditService?.create({
|
|
232
|
+
actorId: identifier,
|
|
233
|
+
event: AuthAuditEvent.Logout
|
|
234
|
+
});
|
|
235
|
+
}
|
|
119
236
|
}
|
|
120
237
|
/**
|
|
121
238
|
* Refresh the token.
|
|
@@ -124,8 +241,32 @@ export class EntityStorageAuthenticationService {
|
|
|
124
241
|
*/
|
|
125
242
|
async refresh(token) {
|
|
126
243
|
// If the verify fails on the current token then it will throw an exception.
|
|
127
|
-
const headerAndPayload = await TokenHelper.verify(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, token)
|
|
128
|
-
|
|
244
|
+
const headerAndPayload = await TokenHelper.verify(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, token, undefined, async (userIdentity, organizationIdentity) => {
|
|
245
|
+
const validParts = [];
|
|
246
|
+
const user = await this._authenticationAdminService.getByIdentity(userIdentity);
|
|
247
|
+
if (user?.userIdentity === userIdentity) {
|
|
248
|
+
validParts.push("user");
|
|
249
|
+
}
|
|
250
|
+
if (user?.organizationIdentity === organizationIdentity) {
|
|
251
|
+
validParts.push("organization");
|
|
252
|
+
}
|
|
253
|
+
return validParts;
|
|
254
|
+
});
|
|
255
|
+
const refreshSub = headerAndPayload.payload.sub ?? "";
|
|
256
|
+
await this._authenticationRateService.check("token-refresh", refreshSub);
|
|
257
|
+
const refreshTokenAndExpiry = await TokenHelper.createToken(this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`, refreshSub, Is.stringValue(headerAndPayload.payload.org) ? headerAndPayload.payload.org : "", Is.stringValue(headerAndPayload.payload.tid) ? headerAndPayload.payload.tid : "", this._defaultTtlMinutes, Coerce.string(headerAndPayload.payload?.scope));
|
|
258
|
+
const refreshScope = Coerce.string(headerAndPayload.payload?.scope) ?? "";
|
|
259
|
+
await this._authenticationAuditService?.create({
|
|
260
|
+
actorId: refreshSub,
|
|
261
|
+
event: AuthAuditEvent.TokenRefreshed,
|
|
262
|
+
data: {
|
|
263
|
+
organizationIdentity: Is.stringValue(headerAndPayload.payload.org)
|
|
264
|
+
? headerAndPayload.payload.org
|
|
265
|
+
: "",
|
|
266
|
+
tenantId: Is.stringValue(headerAndPayload.payload.tid) ? headerAndPayload.payload.tid : "",
|
|
267
|
+
scope: refreshScope.split(",").filter(scope => scope.length > 0)
|
|
268
|
+
}
|
|
269
|
+
});
|
|
129
270
|
return refreshTokenAndExpiry;
|
|
130
271
|
}
|
|
131
272
|
/**
|
|
@@ -137,11 +278,14 @@ export class EntityStorageAuthenticationService {
|
|
|
137
278
|
async updatePassword(currentPassword, newPassword) {
|
|
138
279
|
const contextIds = await ContextIdStore.getContextIds();
|
|
139
280
|
ContextIdHelper.guard(contextIds, ContextIdKeys.User);
|
|
140
|
-
const
|
|
281
|
+
const userIdentity = contextIds[ContextIdKeys.User];
|
|
282
|
+
await this._authenticationRateService.check("password-change", userIdentity);
|
|
283
|
+
const user = await this._userEntityStorage.get(userIdentity);
|
|
141
284
|
if (!Is.object(user)) {
|
|
142
|
-
throw new NotFoundError(EntityStorageAuthenticationService.CLASS_NAME, "userNotFound",
|
|
285
|
+
throw new NotFoundError(EntityStorageAuthenticationService.CLASS_NAME, "userNotFound", userIdentity);
|
|
143
286
|
}
|
|
144
|
-
|
|
287
|
+
await this._authenticationAdminService.updatePassword(user.email, newPassword, currentPassword);
|
|
288
|
+
await this._authenticationRateService.clear("password-change", userIdentity);
|
|
145
289
|
}
|
|
146
290
|
}
|
|
147
291
|
//# sourceMappingURL=entityStorageAuthenticationService.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entityStorageAuthenticationService.js","sourceRoot":"","sources":["../../../src/services/entityStorageAuthenticationService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIAuthenticationAdminComponent,\n\tIAuthenticationComponent\n} from \"@twin.org/api-auth-entity-storage-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tCoerce,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tUnauthorizedError\n} from \"@twin.org/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@twin.org/crypto\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { VaultConnectorFactory, type IVaultConnector } from \"@twin.org/vault-models\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\nimport type { IEntityStorageAuthenticationServiceConstructorOptions } from \"../models/IEntityStorageAuthenticationServiceConstructorOptions.js\";\nimport { TokenHelper } from \"../utils/tokenHelper.js\";\n\n/**\n * Implementation of the authentication component using entity storage.\n */\nexport class EntityStorageAuthenticationService implements IAuthenticationComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntityStorageAuthenticationService>();\n\n\t/**\n\t * Default TTL in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TTL_MINUTES: number = 60;\n\n\t/**\n\t * The user admin service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationAdminService: IAuthenticationAdminComponent;\n\n\t/**\n\t * The entity storage for users.\n\t * @internal\n\t */\n\tprivate readonly _userEntityStorage: IEntityStorageConnector<AuthenticationUser>;\n\n\t/**\n\t * The vault for the keys.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\n\n\t/**\n\t * The name of the key to retrieve from the vault for signing JWT.\n\t * @internal\n\t */\n\tprivate readonly _signingKeyName: string;\n\n\t/**\n\t * The default TTL for the token.\n\t * @internal\n\t */\n\tprivate readonly _defaultTtlMinutes: number;\n\n\t/**\n\t * The node identity.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of EntityStorageAuthentication.\n\t * @param options The dependencies for the identity connector.\n\t */\n\tconstructor(options?: IEntityStorageAuthenticationServiceConstructorOptions) {\n\t\tthis._userEntityStorage = EntityStorageConnectorFactory.get(\n\t\t\toptions?.userEntityStorageType ?? \"authentication-user\"\n\t\t);\n\n\t\tthis._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? \"vault\");\n\n\t\tthis._authenticationAdminService = ComponentFactory.get<IAuthenticationAdminComponent>(\n\t\t\toptions?.authenticationAdminServiceType ?? \"authentication-admin\"\n\t\t);\n\n\t\tthis._signingKeyName = options?.config?.signingKeyName ?? \"auth-signing\";\n\t\tthis._defaultTtlMinutes =\n\t\t\toptions?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn EntityStorageAuthenticationService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The service needs to be started when the application is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.Node);\n\t\tthis._nodeId = contextIds[ContextIdKeys.Node];\n\t}\n\n\t/**\n\t * Perform a login for the user.\n\t * @param email The email address for the user.\n\t * @param password The password for the user.\n\t * @returns The authentication token for the user, if it uses a mechanism with public access.\n\t */\n\tpublic async login(\n\t\temail: string,\n\t\tpassword: string\n\t): Promise<{\n\t\ttoken?: string;\n\t\texpiry: number;\n\t}> {\n\t\tGuards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, nameof(email), email);\n\t\tGuards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, nameof(password), password);\n\n\t\ttry {\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!user) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"userNotFound\");\n\t\t\t}\n\n\t\t\tconst saltBytes = Converter.base64ToBytes(user.salt);\n\t\t\tconst passwordBytes = Converter.utf8ToBytes(password);\n\n\t\t\tconst hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\n\t\t\tif (!PasswordValidator.comparePasswordHashes(hashedPassword, user.password)) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"passwordMismatch\");\n\t\t\t}\n\n\t\t\t// This might be undefined if the login is performed in a single tenant context\n\t\t\t// if is verified during the token processing, tenant id will be matched against\n\t\t\t// the context\n\t\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\t\tconst tenantId = contextIds?.[ContextIdKeys.Tenant];\n\n\t\t\tconst tokenAndExpiry = await TokenHelper.createToken(\n\t\t\t\tthis._vaultConnector,\n\t\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\t\tuser.identity,\n\t\t\t\tuser.organization,\n\t\t\t\ttenantId,\n\t\t\t\tthis._defaultTtlMinutes,\n\t\t\t\tuser.scope\n\t\t\t);\n\n\t\t\treturn tokenAndExpiry;\n\t\t} catch (error) {\n\t\t\tthrow new UnauthorizedError(\n\t\t\t\tEntityStorageAuthenticationService.CLASS_NAME,\n\t\t\t\t\"loginFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Logout the current user.\n\t * @param token The token to logout, if it uses a mechanism with public access.\n\t * @returns Nothing.\n\t */\n\tpublic async logout(token?: string): Promise<void> {\n\t\t// Nothing to do here, as we are stateless.\n\t\t// The cookie will be revoked by the REST route handling\n\t}\n\n\t/**\n\t * Refresh the token.\n\t * @param token The token to refresh, if it uses a mechanism with public access.\n\t * @returns The refreshed token, if it uses a mechanism with public access.\n\t */\n\tpublic async refresh(token?: string): Promise<{\n\t\ttoken?: string;\n\t\texpiry: number;\n\t}> {\n\t\t// If the verify fails on the current token then it will throw an exception.\n\t\tconst headerAndPayload = await TokenHelper.verify(\n\t\t\tthis._vaultConnector,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\ttoken\n\t\t);\n\n\t\tconst refreshTokenAndExpiry = await TokenHelper.createToken(\n\t\t\tthis._vaultConnector,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\theaderAndPayload.payload.sub ?? \"\",\n\t\t\tIs.stringValue(headerAndPayload.payload.org) ? headerAndPayload.payload.org : \"\",\n\t\t\tIs.stringValue(headerAndPayload.payload.tid) ? headerAndPayload.payload.tid : \"\",\n\t\t\tthis._defaultTtlMinutes,\n\t\t\tCoerce.string(headerAndPayload.payload?.scope)\n\t\t);\n\n\t\treturn refreshTokenAndExpiry;\n\t}\n\n\t/**\n\t * Update the user's password.\n\t * @param currentPassword The current password for the user.\n\t * @param newPassword The new password for the user.\n\t * @returns Nothing.\n\t */\n\tpublic async updatePassword(currentPassword: string, newPassword: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.User);\n\n\t\tconst user = await this._userEntityStorage.get(contextIds[ContextIdKeys.User]);\n\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\tthrow new NotFoundError(\n\t\t\t\tEntityStorageAuthenticationService.CLASS_NAME,\n\t\t\t\t\"userNotFound\",\n\t\t\t\tcontextIds[ContextIdKeys.User]\n\t\t\t);\n\t\t}\n\n\t\treturn this._authenticationAdminService.updatePassword(\n\t\t\tuser.email,\n\t\t\tnewPassword,\n\t\t\tcurrentPassword\n\t\t);\n\t}\n}\n"]}
|
|
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\tIAuthenticationRateActionConfig,\n\tIAuthenticationRateComponent,\n\tIAuthenticationAuditComponent,\n\tIAuthenticationAdminComponent,\n\tIAuthenticationComponent\n} from \"@twin.org/api-auth-entity-storage-models\";\nimport { AuthAuditEvent } from \"@twin.org/api-auth-entity-storage-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tCoerce,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tUnauthorizedError\n} from \"@twin.org/core\";\nimport { PasswordGenerator, PasswordValidator } from \"@twin.org/crypto\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { VaultConnectorFactory, type IVaultConnector } from \"@twin.org/vault-models\";\nimport type { AuthenticationUser } from \"../entities/authenticationUser.js\";\nimport type { IEntityStorageAuthenticationServiceConstructorOptions } from \"../models/IEntityStorageAuthenticationServiceConstructorOptions.js\";\nimport { TokenHelper } from \"../utils/tokenHelper.js\";\n\n/**\n * Implementation of the authentication component using entity storage.\n */\nexport class EntityStorageAuthenticationService implements IAuthenticationComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntityStorageAuthenticationService>();\n\n\t/**\n\t * Default TTL in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TTL_MINUTES: number = 60;\n\n\t/**\n\t * Default maximum login attempts in a rate window.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_LOGIN_RATE_MAX_ATTEMPTS: number = 5;\n\n\t/**\n\t * Default login rate window in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_LOGIN_RATE_WINDOW_MINUTES: number = 15;\n\n\t/**\n\t * Default maximum password change attempts in a rate window.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_PASSWORD_CHANGE_RATE_MAX_ATTEMPTS: number = 5;\n\n\t/**\n\t * Default password change rate window in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_PASSWORD_CHANGE_RATE_WINDOW_MINUTES: number = 15;\n\n\t/**\n\t * Default maximum token refresh attempts in a rate window.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TOKEN_REFRESH_RATE_MAX_ATTEMPTS: number = 30;\n\n\t/**\n\t * Default token refresh rate window in minutes.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES: number = 60;\n\n\t/**\n\t * The user admin service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationAdminService: IAuthenticationAdminComponent;\n\n\t/**\n\t * The audit service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationAuditService?: IAuthenticationAuditComponent;\n\n\t/**\n\t * The rate service.\n\t * @internal\n\t */\n\tprivate readonly _authenticationRateService: IAuthenticationRateComponent;\n\n\t/**\n\t * The entity storage for users.\n\t * @internal\n\t */\n\tprivate readonly _userEntityStorage: IEntityStorageConnector<AuthenticationUser>;\n\n\t/**\n\t * The vault for the keys.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\n\n\t/**\n\t * The name of the key to retrieve from the vault for signing JWT.\n\t * @internal\n\t */\n\tprivate readonly _signingKeyName: string;\n\n\t/**\n\t * The default TTL for the token.\n\t * @internal\n\t */\n\tprivate readonly _defaultTtlMinutes: number;\n\n\t/**\n\t * Rate limit configuration for login failures.\n\t * @internal\n\t */\n\tprivate readonly _loginRateLimit: IAuthenticationRateActionConfig;\n\n\t/**\n\t * Rate limit configuration for password changes.\n\t * @internal\n\t */\n\tprivate readonly _passwordChangeRateLimit: IAuthenticationRateActionConfig;\n\n\t/**\n\t * Rate limit configuration for token refresh.\n\t * @internal\n\t */\n\tprivate readonly _tokenRefreshRateLimit: IAuthenticationRateActionConfig;\n\n\t/**\n\t * The 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._authenticationAuditService = ComponentFactory.getIfExists<IAuthenticationAuditComponent>(\n\t\t\toptions?.authenticationAuditServiceType ?? \"authentication-audit\"\n\t\t);\n\n\t\tthis._authenticationRateService = ComponentFactory.get<IAuthenticationRateComponent>(\n\t\t\toptions?.authenticationRateServiceType ?? \"authentication-rate\"\n\t\t);\n\n\t\tthis._signingKeyName = options?.config?.signingKeyName ?? \"auth-signing\";\n\t\tthis._defaultTtlMinutes =\n\t\t\toptions?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES;\n\t\tthis._loginRateLimit = {\n\t\t\tmaxAttempts:\n\t\t\t\toptions?.config?.loginRateLimit?.maxAttempts ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_MAX_ATTEMPTS,\n\t\t\twindowMinutes:\n\t\t\t\toptions?.config?.loginRateLimit?.windowMinutes ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_LOGIN_RATE_WINDOW_MINUTES\n\t\t};\n\t\tthis._passwordChangeRateLimit = {\n\t\t\tmaxAttempts:\n\t\t\t\toptions?.config?.passwordChangeRateLimit?.maxAttempts ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_PASSWORD_CHANGE_RATE_MAX_ATTEMPTS,\n\t\t\twindowMinutes:\n\t\t\t\toptions?.config?.passwordChangeRateLimit?.windowMinutes ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_PASSWORD_CHANGE_RATE_WINDOW_MINUTES\n\t\t};\n\t\tthis._tokenRefreshRateLimit = {\n\t\t\tmaxAttempts:\n\t\t\t\toptions?.config?.tokenRefreshRateLimit?.maxAttempts ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_MAX_ATTEMPTS,\n\t\t\twindowMinutes:\n\t\t\t\toptions?.config?.tokenRefreshRateLimit?.windowMinutes ??\n\t\t\t\tEntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES\n\t\t};\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn EntityStorageAuthenticationService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The service needs to be started when the application is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns 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\n\t\tawait this._authenticationRateService.registerAction(\"login\", this._loginRateLimit);\n\t\tawait this._authenticationRateService.registerAction(\n\t\t\t\"password-change\",\n\t\t\tthis._passwordChangeRateLimit\n\t\t);\n\t\tawait this._authenticationRateService.registerAction(\n\t\t\t\"token-refresh\",\n\t\t\tthis._tokenRefreshRateLimit\n\t\t);\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tawait this._authenticationRateService.unregisterAction(\"login\");\n\t\tawait this._authenticationRateService.unregisterAction(\"password-change\");\n\t\tawait this._authenticationRateService.unregisterAction(\"token-refresh\");\n\t}\n\n\t/**\n\t * Perform a login for the user.\n\t * @param email The email address for the user.\n\t * @param password The password for the user.\n\t * @returns The authentication token for the user, if it uses a mechanism with public access.\n\t */\n\tpublic async login(\n\t\temail: string,\n\t\tpassword: string\n\t): Promise<{\n\t\ttoken?: string;\n\t\texpiry: number;\n\t}> {\n\t\tGuards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, nameof(email), email);\n\t\tGuards.stringValue(EntityStorageAuthenticationService.CLASS_NAME, nameof(password), password);\n\n\t\tlet loginUser: AuthenticationUser | undefined;\n\t\tlet loginTenantId: string | undefined;\n\t\tlet tokenAndExpiry: { token?: string; expiry: number } | undefined;\n\n\t\ttry {\n\t\t\tawait this._authenticationRateService.check(\"login\", email);\n\n\t\t\tconst user = await this._userEntityStorage.get(email);\n\t\t\tif (!user) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"userNotFound\");\n\t\t\t}\n\n\t\t\tconst saltBytes = Converter.base64ToBytes(user.salt);\n\t\t\tconst passwordBytes = Converter.utf8ToBytes(password);\n\n\t\t\tconst hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);\n\n\t\t\tif (!PasswordValidator.comparePasswordHashes(hashedPassword, user.password)) {\n\t\t\t\tthrow new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, \"passwordMismatch\");\n\t\t\t}\n\n\t\t\t// This might be undefined if the login is performed in a single tenant context\n\t\t\t// 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\tloginTenantId = contextIds?.[ContextIdKeys.Tenant];\n\n\t\t\ttokenAndExpiry = 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\tloginTenantId,\n\t\t\t\tthis._defaultTtlMinutes,\n\t\t\t\tuser.scope\n\t\t\t);\n\t\t\tloginUser = user;\n\t\t} catch (error) {\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: email,\n\t\t\t\tevent: AuthAuditEvent.LoginFailure\n\t\t\t});\n\n\t\t\tthrow new UnauthorizedError(\n\t\t\t\tEntityStorageAuthenticationService.CLASS_NAME,\n\t\t\t\t\"loginFailed\",\n\t\t\t\tundefined,\n\t\t\t\terror\n\t\t\t);\n\t\t}\n\n\t\tawait this._authenticationRateService.clear(\"login\", email);\n\n\t\tawait this._authenticationAuditService?.create({\n\t\t\tactorId: email,\n\t\t\tevent: AuthAuditEvent.LoginSuccess,\n\t\t\tdata: {\n\t\t\t\tuserIdentity: loginUser.identity,\n\t\t\t\torganizationIdentity: loginUser.organization,\n\t\t\t\ttenantId: loginTenantId,\n\t\t\t\tscope: loginUser.scope.split(\",\")\n\t\t\t}\n\t\t});\n\n\t\treturn tokenAndExpiry;\n\t}\n\n\t/**\n\t * Logout the current user.\n\t * @param token The token to logout, if it uses a mechanism with public access.\n\t * @returns Nothing.\n\t */\n\tpublic async logout(token?: string): Promise<void> {\n\t\t// Nothing to do here, as we are stateless.\n\t\t// The cookie will be revoked by the REST route handling\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst identifier = contextIds?.[ContextIdKeys.User];\n\t\tif (Is.stringValue(identifier)) {\n\t\t\tawait this._authenticationAuditService?.create({\n\t\t\t\tactorId: identifier,\n\t\t\t\tevent: AuthAuditEvent.Logout\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Refresh the token.\n\t * @param token The token to refresh, if it uses a mechanism with public access.\n\t * @returns The refreshed token, if it uses a mechanism with public access.\n\t */\n\tpublic async refresh(token?: string): Promise<{\n\t\ttoken?: string;\n\t\texpiry: number;\n\t}> {\n\t\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\tundefined,\n\t\t\tasync (userIdentity, organizationIdentity) => {\n\t\t\t\tconst validParts = [];\n\t\t\t\tconst user = await this._authenticationAdminService.getByIdentity(userIdentity);\n\t\t\t\tif (user?.userIdentity === userIdentity) {\n\t\t\t\t\tvalidParts.push(\"user\");\n\t\t\t\t}\n\t\t\t\tif (user?.organizationIdentity === organizationIdentity) {\n\t\t\t\t\tvalidParts.push(\"organization\");\n\t\t\t\t}\n\t\t\t\treturn validParts;\n\t\t\t}\n\t\t);\n\n\t\tconst refreshSub = headerAndPayload.payload.sub ?? \"\";\n\t\tawait this._authenticationRateService.check(\"token-refresh\", refreshSub);\n\n\t\tconst refreshTokenAndExpiry = await TokenHelper.createToken(\n\t\t\tthis._vaultConnector,\n\t\t\t`${this._nodeId}/${this._signingKeyName}`,\n\t\t\trefreshSub,\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\t\tconst refreshScope = Coerce.string(headerAndPayload.payload?.scope) ?? \"\";\n\n\t\tawait this._authenticationAuditService?.create({\n\t\t\tactorId: refreshSub,\n\t\t\tevent: AuthAuditEvent.TokenRefreshed,\n\t\t\tdata: {\n\t\t\t\torganizationIdentity: Is.stringValue(headerAndPayload.payload.org)\n\t\t\t\t\t? headerAndPayload.payload.org\n\t\t\t\t\t: \"\",\n\t\t\t\ttenantId: Is.stringValue(headerAndPayload.payload.tid) ? headerAndPayload.payload.tid : \"\",\n\t\t\t\tscope: refreshScope.split(\",\").filter(scope => scope.length > 0)\n\t\t\t}\n\t\t});\n\n\t\treturn refreshTokenAndExpiry;\n\t}\n\n\t/**\n\t * Update the user's password.\n\t * @param currentPassword The current password for the user.\n\t * @param newPassword The new password for the user.\n\t * @returns Nothing.\n\t */\n\tpublic async updatePassword(currentPassword: string, newPassword: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.User);\n\n\t\tconst userIdentity = contextIds[ContextIdKeys.User];\n\t\tawait this._authenticationRateService.check(\"password-change\", userIdentity);\n\n\t\tconst user = await this._userEntityStorage.get(userIdentity);\n\t\tif (!Is.object<AuthenticationUser>(user)) {\n\t\t\tthrow new NotFoundError(\n\t\t\t\tEntityStorageAuthenticationService.CLASS_NAME,\n\t\t\t\t\"userNotFound\",\n\t\t\t\tuserIdentity\n\t\t\t);\n\t\t}\n\n\t\tawait this._authenticationAdminService.updatePassword(user.email, newPassword, currentPassword);\n\n\t\tawait this._authenticationRateService.clear(\"password-change\", userIdentity);\n\t}\n}\n"]}
|
|
@@ -43,10 +43,11 @@ export class TokenHelper {
|
|
|
43
43
|
* @param signingKeyName The signing key name.
|
|
44
44
|
* @param token The token to verify.
|
|
45
45
|
* @param requiredScopes The required scopes.
|
|
46
|
+
* @param verifyUser A function to verify the user identity and organization, which can be used to check if the user is still active or not.
|
|
46
47
|
* @returns The verified details.
|
|
47
48
|
* @throws UnauthorizedError if the token is missing, invalid or expired.
|
|
48
49
|
*/
|
|
49
|
-
static async verify(vaultConnector, signingKeyName, token, requiredScopes) {
|
|
50
|
+
static async verify(vaultConnector, signingKeyName, token, requiredScopes, verifyUser) {
|
|
50
51
|
if (!Is.stringValue(token)) {
|
|
51
52
|
throw new UnauthorizedError(TokenHelper.CLASS_NAME, "missing");
|
|
52
53
|
}
|
|
@@ -62,6 +63,15 @@ export class TokenHelper {
|
|
|
62
63
|
decoded.payload.exp < Math.trunc(Date.now() / 1000)) {
|
|
63
64
|
throw new UnauthorizedError(TokenHelper.CLASS_NAME, "expired");
|
|
64
65
|
}
|
|
66
|
+
if (Is.function(verifyUser)) {
|
|
67
|
+
const userVerified = await verifyUser(decoded.payload.sub, decoded.payload.org);
|
|
68
|
+
if (!userVerified.includes("user")) {
|
|
69
|
+
throw new UnauthorizedError(TokenHelper.CLASS_NAME, "userNotVerified");
|
|
70
|
+
}
|
|
71
|
+
else if (!userVerified.includes("organization")) {
|
|
72
|
+
throw new UnauthorizedError(TokenHelper.CLASS_NAME, "organizationNotVerified");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
65
75
|
if (Is.arrayValue(requiredScopes)) {
|
|
66
76
|
const tokenScopes = Is.stringValue(decoded.payload.scope)
|
|
67
77
|
? decoded.payload.scope.split(",")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenHelper.js","sourceRoot":"","sources":["../../../src/utils/tokenHelper.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAwB,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,EACN,YAAY,EACZ,YAAY,EACZ,WAAW,EAIX,GAAG,EACH,MAAM,eAAe,CAAC;AAEvB;;GAEG;AACH,MAAM,OAAO,WAAW;IACvB;;OAEG;IACI,MAAM,CAAU,UAAU,iBAAiC;IAElE;;;;;;;;;;OAUG;IACI,MAAM,CAAC,KAAK,CAAC,WAAW,CAC9B,cAA+B,EAC/B,cAAsB,EACtB,YAAoB,EACpB,oBAAwC,EACxC,QAA4B,EAC5B,UAAkB,EAClB,KAAc;QAKd,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,UAAU,GAAG,EAAE,CAAC;QAEnC,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,gBAAgB,CACrC,EAAE,GAAG,EAAE,OAAO,EAAE,EAChB;YACC,GAAG,EAAE,YAAY;YACjB,GAAG,EAAE,oBAAoB;YACzB,GAAG,EAAE,QAAQ;YACb,GAAG,EAAE,UAAU,GAAG,UAAU;YAC5B,KAAK;SACL,EACD,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,CACzB,oBAAoB,CAAC,SAAS,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,CAChF,CAAC;QAEF,OAAO;YACN,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,IAAI;SACxC,CAAC;IACH,CAAC;IAED
|
|
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 * @param verifyUser A function to verify the user identity and organization, which can be used to check if the user is still active or not.\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\tverifyUser?: (userIdentity: string, organizationIdentity: string) => Promise<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.function(verifyUser)) {\n\t\t\tconst userVerified = await verifyUser(decoded.payload.sub, decoded.payload.org);\n\t\t\tif (!userVerified.includes(\"user\")) {\n\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"userNotVerified\");\n\t\t\t} else if (!userVerified.includes(\"organization\")) {\n\t\t\t\tthrow new UnauthorizedError(TokenHelper.CLASS_NAME, \"organizationNotVerified\");\n\t\t\t}\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"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class defining the storage for authentication audit entries.
|
|
3
|
+
*/
|
|
4
|
+
export declare class AuthenticationAuditEntry {
|
|
5
|
+
/**
|
|
6
|
+
* The unique identifier for the audit entry.
|
|
7
|
+
*/
|
|
8
|
+
id: string;
|
|
9
|
+
/**
|
|
10
|
+
* The timestamp of the audit entry in ISO 8601 format.
|
|
11
|
+
*/
|
|
12
|
+
dateCreated: string;
|
|
13
|
+
/**
|
|
14
|
+
* The audit event that occurred.
|
|
15
|
+
*/
|
|
16
|
+
event: string;
|
|
17
|
+
/**
|
|
18
|
+
* The actor identifier, could be e-mail, username, or other unique identifier.
|
|
19
|
+
*/
|
|
20
|
+
actorId?: string;
|
|
21
|
+
/**
|
|
22
|
+
* The node identifier associated with the audit entry, if applicable.
|
|
23
|
+
*/
|
|
24
|
+
nodeId?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The organization identifier associated with the audit entry, if applicable.
|
|
27
|
+
*/
|
|
28
|
+
organizationId?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The tenant identifier associated with the audit entry, if applicable.
|
|
31
|
+
*/
|
|
32
|
+
tenantId?: string;
|
|
33
|
+
/**
|
|
34
|
+
* The hashed IP addresses of the client.
|
|
35
|
+
*/
|
|
36
|
+
ipAddressHashes?: string[];
|
|
37
|
+
/**
|
|
38
|
+
* The user agent string of the client.
|
|
39
|
+
*/
|
|
40
|
+
userAgent?: string;
|
|
41
|
+
/**
|
|
42
|
+
* The correlation ID for request tracing.
|
|
43
|
+
*/
|
|
44
|
+
correlationId?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Additional data related to the audit entry, such as IP address, user agent, etc.
|
|
47
|
+
*/
|
|
48
|
+
data?: unknown;
|
|
49
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class defining the storage for authentication rate entries.
|
|
3
|
+
*/
|
|
4
|
+
export declare class AuthenticationRateEntry {
|
|
5
|
+
/**
|
|
6
|
+
* The id for the rate entry.
|
|
7
|
+
*/
|
|
8
|
+
id: string;
|
|
9
|
+
/**
|
|
10
|
+
* Array of ISO date strings representing timestamps of failed attempts.
|
|
11
|
+
*/
|
|
12
|
+
timestamps: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Last modification time in ISO date format.
|
|
15
|
+
*/
|
|
16
|
+
dateModified: string;
|
|
17
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
export * from "./entities/authenticationAuditEntry.js";
|
|
2
|
+
export * from "./entities/authenticationRateEntry.js";
|
|
1
3
|
export * from "./entities/authenticationUser.js";
|
|
2
4
|
export * from "./models/IAuthHeaderProcessorConfig.js";
|
|
3
5
|
export * from "./models/IAuthHeaderProcessorConstructorOptions.js";
|
|
4
6
|
export * from "./models/IEntityStorageAuthenticationAdminServiceConfig.js";
|
|
5
7
|
export * from "./models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js";
|
|
8
|
+
export * from "./models/IEntityStorageAuthenticationAuditServiceConfig.js";
|
|
9
|
+
export * from "./models/IEntityStorageAuthenticationAuditServiceConstructorOptions.js";
|
|
10
|
+
export * from "./models/IEntityStorageAuthenticationRateServiceConfig.js";
|
|
11
|
+
export * from "./models/IEntityStorageAuthenticationRateServiceConstructorOptions.js";
|
|
6
12
|
export * from "./models/IEntityStorageAuthenticationServiceConfig.js";
|
|
7
13
|
export * from "./models/IEntityStorageAuthenticationServiceConstructorOptions.js";
|
|
8
14
|
export * from "./processors/authHeaderProcessor.js";
|
|
9
15
|
export * from "./restEntryPoints.js";
|
|
10
16
|
export * from "./routes/entityStorageAuthenticationAdminRoutes.js";
|
|
17
|
+
export * from "./routes/entityStorageAuthenticationAuditRoutes.js";
|
|
11
18
|
export * from "./routes/entityStorageAuthenticationRoutes.js";
|
|
12
19
|
export * from "./schema.js";
|
|
13
20
|
export * from "./services/entityStorageAuthenticationAdminService.js";
|
|
21
|
+
export * from "./services/entityStorageAuthenticationAuditService.js";
|
|
22
|
+
export * from "./services/entityStorageAuthenticationRateService.js";
|
|
14
23
|
export * from "./services/entityStorageAuthenticationService.js";
|
|
15
24
|
export * from "./utils/tokenHelper.js";
|
|
@@ -3,6 +3,11 @@ import type { IAuthHeaderProcessorConfig } from "./IAuthHeaderProcessorConfig.js
|
|
|
3
3
|
* Options for the AuthHeaderProcessor constructor.
|
|
4
4
|
*/
|
|
5
5
|
export interface IAuthHeaderProcessorConstructorOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The admin service.
|
|
8
|
+
* @default authentication-admin
|
|
9
|
+
*/
|
|
10
|
+
authenticationAdminServiceType?: string;
|
|
6
11
|
/**
|
|
7
12
|
* The vault for the private keys.
|
|
8
13
|
* @default vault
|
|
@@ -8,6 +8,11 @@ export interface IEntityStorageAuthenticationAdminServiceConstructorOptions {
|
|
|
8
8
|
* @default authentication-user
|
|
9
9
|
*/
|
|
10
10
|
userEntityStorageType?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The audit service.
|
|
13
|
+
* @default authentication-audit
|
|
14
|
+
*/
|
|
15
|
+
authenticationAuditServiceType?: string;
|
|
11
16
|
/**
|
|
12
17
|
* The configuration for the authentication.
|
|
13
18
|
*/
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config for the EntityStorageAuthenticationAuditService constructor.
|
|
3
|
+
*/
|
|
4
|
+
export interface IEntityStorageAuthenticationAuditServiceConfig {
|
|
5
|
+
/**
|
|
6
|
+
* The server-side salt for hashing IP addresses in audit logs, if configured.
|
|
7
|
+
*/
|
|
8
|
+
ipHashSalt?: string;
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IEntityStorageAuthenticationAuditServiceConfig } from "./IEntityStorageAuthenticationAuditServiceConfig.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options for the EntityStorageAuthenticationAuditService constructor.
|
|
4
|
+
*/
|
|
5
|
+
export interface IEntityStorageAuthenticationAuditServiceConstructorOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The entity storage for the audit entries.
|
|
8
|
+
* @default authentication-audit-entry
|
|
9
|
+
*/
|
|
10
|
+
authenticationAuditEntryStorageType?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The configuration for the authentication audit service.
|
|
13
|
+
*/
|
|
14
|
+
config?: IEntityStorageAuthenticationAuditServiceConfig;
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IEntityStorageAuthenticationRateServiceConfig } from "./IEntityStorageAuthenticationRateServiceConfig.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options for the EntityStorageAuthenticationRateService constructor.
|
|
4
|
+
*/
|
|
5
|
+
export interface IEntityStorageAuthenticationRateServiceConstructorOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The entity storage for authentication rate entries.
|
|
8
|
+
* @default authentication-rate-entry
|
|
9
|
+
*/
|
|
10
|
+
authenticationRateEntryStorageType?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The task scheduler component type.
|
|
13
|
+
* @default task-scheduler
|
|
14
|
+
*/
|
|
15
|
+
taskSchedulerComponentType?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The configuration for the authentication rate service.
|
|
18
|
+
*/
|
|
19
|
+
config?: IEntityStorageAuthenticationRateServiceConfig;
|
|
20
|
+
}
|