@tstdl/base 0.92.141 → 0.92.143
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/ai/ai-file.service.js +1 -1
- package/ai/ai.service.js +3 -3
- package/ai/types.d.ts +1 -1
- package/api/client/client.d.ts +1 -1
- package/api/client/client.js +10 -4
- package/api/server/middlewares/content-type.middleware.js +8 -7
- package/api/types.js +1 -1
- package/authentication/client/authentication.service.js +3 -3
- package/authentication/server/authentication-ancillary.service.d.ts +11 -1
- package/authentication/server/authentication-ancillary.service.js +1 -1
- package/authentication/server/authentication-secret-requirements.validator.js +1 -1
- package/authentication/server/authentication.api-controller.js +22 -10
- package/authentication/server/authentication.service.d.ts +11 -5
- package/authentication/server/authentication.service.js +97 -49
- package/authentication/server/drizzle.config.js +2 -2
- package/authentication/server/module.js +1 -1
- package/cancellation/token.d.ts +2 -2
- package/cancellation/token.js +4 -4
- package/cookie/cookie.js +2 -2
- package/css/css-variables.js +1 -1
- package/document-management/api/document-management.api.d.ts +122 -24
- package/document-management/api/document-management.api.js +17 -0
- package/document-management/{server/services → authorization}/document-management-authorization.service.d.ts +7 -7
- package/document-management/authorization/document-management-authorization.service.js +2 -0
- package/document-management/authorization/index.d.ts +2 -0
- package/document-management/authorization/index.js +2 -0
- package/document-management/authorization/policies.d.ts +38 -0
- package/document-management/authorization/policies.js +2 -0
- package/document-management/index.d.ts +1 -0
- package/document-management/index.js +1 -0
- package/document-management/models/document-assignment-scope.model.d.ts +1 -0
- package/document-management/models/document-assignment-scope.model.js +10 -3
- package/document-management/models/document-assignment-task.model.d.ts +1 -0
- package/document-management/models/document-assignment-task.model.js +8 -2
- package/document-management/models/document-category.model.d.ts +1 -0
- package/document-management/models/document-category.model.js +7 -1
- package/document-management/models/document-collection-assignment.model.d.ts +1 -0
- package/document-management/models/document-collection-assignment.model.js +12 -4
- package/document-management/models/document-collection.model.d.ts +2 -0
- package/document-management/models/document-collection.model.js +8 -2
- package/document-management/models/document-management-table.d.ts +3 -1
- package/document-management/models/document-management-table.js +2 -2
- package/document-management/models/document-property-value.model.d.ts +1 -0
- package/document-management/models/document-property-value.model.js +9 -3
- package/document-management/models/document-property.model.d.ts +1 -0
- package/document-management/models/document-property.model.js +8 -2
- package/document-management/models/document-request-collection-assignment.model.d.ts +1 -0
- package/document-management/models/document-request-collection-assignment.model.js +12 -4
- package/document-management/models/document-request-template.d.ts +1 -0
- package/document-management/models/document-request-template.js +6 -1
- package/document-management/models/document-request.model.d.ts +1 -0
- package/document-management/models/document-request.model.js +10 -1
- package/document-management/models/document-requests-template.d.ts +1 -0
- package/document-management/models/document-requests-template.js +7 -3
- package/document-management/models/document-tag-assignment.model.d.ts +8 -0
- package/document-management/models/document-tag-assignment.model.js +40 -0
- package/document-management/models/document-tag.model.d.ts +6 -0
- package/document-management/models/{document-request-submission.model.js → document-tag.model.js} +14 -18
- package/document-management/models/document-type-property.model.d.ts +1 -0
- package/document-management/models/document-type-property.model.js +7 -2
- package/document-management/models/document-type-validation.model.d.ts +1 -0
- package/document-management/models/document-type-validation.model.js +8 -2
- package/document-management/models/document-type.model.d.ts +1 -0
- package/document-management/models/document-type.model.js +7 -2
- package/document-management/models/document-validation-definition.model.d.ts +1 -0
- package/document-management/models/document-validation-definition.model.js +7 -2
- package/document-management/models/document-validation-execution-related-document.model.d.ts +1 -0
- package/document-management/models/document-validation-execution-related-document.model.js +10 -3
- package/document-management/models/document-validation-execution.model.d.ts +1 -0
- package/document-management/models/document-validation-execution.model.js +9 -3
- package/document-management/models/document-workflow.model.d.ts +4 -1
- package/document-management/models/document-workflow.model.js +16 -4
- package/document-management/models/document.model.d.ts +2 -2
- package/document-management/models/document.model.js +9 -8
- package/document-management/models/index.d.ts +2 -1
- package/document-management/models/index.js +2 -1
- package/document-management/server/api/document-management.api.d.ts +4 -1
- package/document-management/server/api/document-management.api.js +113 -22
- package/document-management/server/configure.d.ts +2 -2
- package/document-management/server/configure.js +7 -7
- package/document-management/server/drizzle/0000_parallel_mantis.sql +359 -0
- package/document-management/server/drizzle/meta/0000_snapshot.json +784 -260
- package/document-management/server/drizzle/meta/_journal.json +2 -2
- package/document-management/server/module.d.ts +2 -2
- package/document-management/server/module.js +2 -2
- package/document-management/server/schemas.d.ts +6 -5
- package/document-management/server/schemas.js +12 -11
- package/document-management/server/services/document-category-type.service.d.ts +19 -10
- package/document-management/server/services/document-category-type.service.js +34 -27
- package/document-management/server/services/document-collection.service.d.ts +13 -6
- package/document-management/server/services/document-collection.service.js +36 -12
- package/document-management/server/services/document-file.service.d.ts +8 -7
- package/document-management/server/services/document-file.service.js +28 -33
- package/document-management/server/services/document-management-ai.service.d.ts +5 -4
- package/document-management/server/services/document-management-ai.service.js +51 -28
- package/document-management/server/services/document-management-ancillary.service.d.ts +3 -21
- package/document-management/server/services/document-management-ancillary.service.js +0 -24
- package/document-management/server/services/document-management-observation.service.d.ts +15 -0
- package/document-management/server/services/document-management-observation.service.js +160 -0
- package/document-management/server/services/document-management.service.d.ts +6 -5
- package/document-management/server/services/document-management.service.js +112 -86
- package/document-management/server/services/document-property.service.d.ts +15 -7
- package/document-management/server/services/document-property.service.js +52 -20
- package/document-management/server/services/document-request.service.d.ts +13 -24
- package/document-management/server/services/document-request.service.js +39 -62
- package/document-management/server/services/document-tag.service.d.ts +10 -0
- package/document-management/server/services/document-tag.service.js +59 -0
- package/document-management/server/services/document-validation.service.d.ts +8 -8
- package/document-management/server/services/document-validation.service.js +41 -40
- package/document-management/server/services/document-workflow.service.d.ts +6 -5
- package/document-management/server/services/document-workflow.service.js +54 -43
- package/document-management/server/services/document.service.d.ts +12 -11
- package/document-management/server/services/document.service.js +64 -40
- package/document-management/server/services/index.d.ts +2 -1
- package/document-management/server/services/index.js +2 -1
- package/document-management/server/services/singleton.js +2 -2
- package/document-management/server/validators/ai-validation-executor.js +4 -4
- package/document-management/service-models/document-collection-metadata.service-model.d.ts +14 -0
- package/document-management/service-models/document-collection-metadata.service-model.js +1 -0
- package/document-management/service-models/document-folders.view-model.d.ts +1 -7
- package/document-management/service-models/document-folders.view-model.js +3 -15
- package/document-management/service-models/document-management.view-model.d.ts +20 -6
- package/document-management/service-models/document-management.view-model.js +62 -8
- package/document-management/service-models/document.service-model.d.ts +14 -11
- package/document-management/service-models/document.service-model.js +11 -2
- package/document-management/service-models/enriched/enriched-document-assignment.view.d.ts +1 -1
- package/document-management/service-models/enriched/enriched-document-assignment.view.js +0 -2
- package/document-management/service-models/enriched/enriched-document-category.view.d.ts +11 -1
- package/document-management/service-models/enriched/enriched-document-category.view.js +44 -1
- package/document-management/service-models/enriched/enriched-document-collection.view.d.ts +4 -2
- package/document-management/service-models/enriched/enriched-document-collection.view.js +13 -3
- package/document-management/service-models/enriched/enriched-document-management-data.view.d.ts +2 -0
- package/document-management/service-models/enriched/enriched-document-management-data.view.js +4 -2
- package/document-management/service-models/enriched/enriched-document-request.view.d.ts +1 -0
- package/document-management/service-models/enriched/enriched-document-request.view.js +2 -0
- package/document-management/service-models/enriched/enriched-document-type.view.d.ts +9 -1
- package/document-management/service-models/enriched/enriched-document-type.view.js +28 -1
- package/document-management/service-models/enriched/enriched-document.view.d.ts +7 -6
- package/document-management/service-models/enriched/enriched-document.view.js +29 -6
- package/document-management/service-models/{normalized-requests-template-data.model.d.ts → enriched/enriched-requests-template-data.model.d.ts} +6 -6
- package/document-management/service-models/{normalized-requests-template-data.model.js → enriched/enriched-requests-template-data.model.js} +1 -1
- package/document-management/service-models/enriched/index.d.ts +1 -0
- package/document-management/service-models/enriched/index.js +1 -0
- package/document-management/service-models/index.d.ts +2 -2
- package/document-management/service-models/index.js +2 -2
- package/examples/document-management/categories-and-types.d.ts +33 -31
- package/examples/document-management/categories-and-types.js +33 -0
- package/examples/document-management/main.d.ts +5 -4
- package/examples/document-management/main.js +13 -7
- package/function/log.js +2 -2
- package/http/server/node/module.d.ts +4 -1
- package/http/server/node/module.js +10 -1
- package/http/server/node/node-http-server.d.ts +3 -6
- package/http/server/node/node-http-server.js +68 -67
- package/injector/inject.js +6 -6
- package/injector/injector.js +3 -3
- package/jsx/is-component-class.js +1 -1
- package/key-value-store/key-value.store.d.ts +38 -7
- package/key-value-store/key-value.store.js +2 -1
- package/key-value-store/mongo/mongo-key-value.store.d.ts +1 -0
- package/key-value-store/mongo/mongo-key-value.store.js +14 -5
- package/key-value-store/postgres/drizzle/0000_shocking_slipstream.sql +12 -0
- package/key-value-store/postgres/drizzle/meta/0000_snapshot.json +97 -0
- package/key-value-store/postgres/drizzle/meta/_journal.json +13 -0
- package/key-value-store/postgres/drizzle.config.d.ts +2 -0
- package/key-value-store/postgres/drizzle.config.js +11 -0
- package/key-value-store/postgres/index.d.ts +2 -0
- package/key-value-store/postgres/index.js +2 -0
- package/key-value-store/postgres/key-value-store.service.d.ts +17 -0
- package/key-value-store/postgres/key-value-store.service.js +65 -0
- package/key-value-store/postgres/models/index.d.ts +2 -0
- package/key-value-store/postgres/models/index.js +2 -0
- package/key-value-store/postgres/models/key-value.model.d.ts +7 -0
- package/key-value-store/postgres/models/key-value.model.js +35 -0
- package/key-value-store/postgres/models/schemas.d.ts +3 -0
- package/key-value-store/postgres/models/schemas.js +4 -0
- package/key-value-store/postgres/module.d.ts +6 -0
- package/key-value-store/postgres/module.js +23 -0
- package/lock/web/web-lock.d.ts +0 -1
- package/lock/web/web-lock.js +6 -13
- package/orm/data-types/timestamp.js +1 -1
- package/orm/decorators.d.ts +37 -29
- package/orm/decorators.js +44 -24
- package/orm/entity.d.ts +1 -0
- package/orm/query.d.ts +10 -2
- package/orm/repository.types.d.ts +2 -1
- package/orm/schemas/json.d.ts +12 -6
- package/orm/schemas/json.js +12 -5
- package/orm/server/database.js +5 -2
- package/orm/server/drizzle/schema-converter.js +40 -11
- package/orm/server/query-converter.d.ts +2 -1
- package/orm/server/query-converter.js +57 -34
- package/orm/server/repository.d.ts +26 -43
- package/orm/server/repository.js +106 -39
- package/orm/server/transaction.d.ts +2 -1
- package/orm/server/transaction.js +3 -0
- package/orm/server/transactional.d.ts +5 -1
- package/orm/server/transactional.js +34 -4
- package/package.json +14 -11
- package/process/spawn.js +0 -1
- package/promise/deferred-promise.d.ts +4 -3
- package/promise/deferred-promise.js +13 -5
- package/queue/postgres/queue.js +8 -8
- package/reflection/utils.js +3 -3
- package/schema/decorators/class.js +0 -1
- package/schema/decorators/schema.js +1 -1
- package/schema/schemas/boolean.d.ts +1 -1
- package/schema/schemas/boolean.js +2 -2
- package/schema/schemas/number.js +3 -3
- package/schema/schemas/object.js +5 -6
- package/sse/server-sent-events-source.js +4 -1
- package/utils/compression.js +9 -9
- package/utils/date-time.d.ts +1 -0
- package/utils/date-time.js +18 -4
- package/utils/equals.d.ts +7 -0
- package/utils/equals.js +17 -2
- package/utils/function/memoize.js +10 -2
- package/utils/jwt.js +3 -3
- package/utils/object/property-name.d.ts +2 -2
- package/utils/timing.d.ts +2 -2
- package/utils/timing.js +12 -12
- package/document-management/models/document-request-submission.model.d.ts +0 -7
- package/document-management/server/drizzle/0000_moaning_luckman.sql +0 -305
- package/document-management/server/services/document-management-authorization.service.js +0 -28
package/ai/ai-file.service.js
CHANGED
|
@@ -62,7 +62,7 @@ let AiFileService = class AiFileService {
|
|
|
62
62
|
}
|
|
63
63
|
async getFiles(fileInputs) {
|
|
64
64
|
const ids = createArray(fileInputs.length, () => crypto.randomUUID());
|
|
65
|
-
const files = await AsyncEnumerable.from(fileInputs).parallelMap(5, true, async (file, index) => this.uploadFile(file, ids[index])).toArray();
|
|
65
|
+
const files = await AsyncEnumerable.from(fileInputs).parallelMap(5, true, async (file, index) => await this.uploadFile(file, ids[index])).toArray();
|
|
66
66
|
this.#logger.verbose(`Processing ${fileInputs.length} files...`);
|
|
67
67
|
await this.waitForFilesActive(files);
|
|
68
68
|
return files;
|
package/ai/ai.service.js
CHANGED
|
@@ -53,7 +53,7 @@ let AiService = AiService_1 = class AiService {
|
|
|
53
53
|
apiKey: isUndefined(this.#options.vertex?.project) ? assertDefinedPass(this.#options.apiKey, 'Api key not defined') : undefined,
|
|
54
54
|
});
|
|
55
55
|
#maxOutputTokensCache = new Map();
|
|
56
|
-
defaultModel = this.#options.defaultModel ?? 'gemini-2.
|
|
56
|
+
defaultModel = this.#options.defaultModel ?? 'gemini-2.5-flash-preview-05-20';
|
|
57
57
|
createSession() {
|
|
58
58
|
return new AiSession(this);
|
|
59
59
|
}
|
|
@@ -157,8 +157,8 @@ let AiService = AiService_1 = class AiService {
|
|
|
157
157
|
catch (error) {
|
|
158
158
|
if ((i < 20) && isError(error) && (error.message.includes('429 Too Many Requests') || (error.message.includes('503 Service Unavailable')))) {
|
|
159
159
|
this.#logger.verbose('429 Too Many Requests - trying again in 15 seconds');
|
|
160
|
-
const
|
|
161
|
-
if (
|
|
160
|
+
const timeoutResult = await cancelableTimeout(15 * millisecondsPerSecond, getShutdownSignal());
|
|
161
|
+
if (timeoutResult == 'timeout') {
|
|
162
162
|
continue;
|
|
163
163
|
}
|
|
164
164
|
}
|
package/ai/types.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export type Content = {
|
|
|
53
53
|
};
|
|
54
54
|
export type FunctionCallingMode = 'auto' | 'force' | 'none';
|
|
55
55
|
export type FinishReason = 'stop' | 'maxTokens' | 'unknown';
|
|
56
|
-
export type AiModel = LiteralUnion<'gemini-2.5-pro-
|
|
56
|
+
export type AiModel = LiteralUnion<'gemini-2.5-pro-preview-05-06' | 'gemini-2.5-flash-preview-05-20', string>;
|
|
57
57
|
export type GenerationOptions = {
|
|
58
58
|
maxOutputTokens?: number;
|
|
59
59
|
temperature?: number;
|
package/api/client/client.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { HttpClient, type HttpClientOptions } from '../../http/client/index.js';
|
|
|
2
2
|
import { type Resolvable } from '../../injector/interfaces.js';
|
|
3
3
|
import type { Type } from '../../types.js';
|
|
4
4
|
import { type ApiClientImplementation, type ApiDefinition, type ApiEndpointDefinition } from '../types.js';
|
|
5
|
-
export type ApiClient<T extends ApiDefinition> = Type<ApiClientImplementation<T> & Resolvable<HttpClient | HttpClientOptions>, [httpClientOrOptions?: HttpClient | HttpClientOptions]>;
|
|
5
|
+
export type ApiClient<T extends ApiDefinition> = Type<ApiClientImplementation<T> & Resolvable<HttpClient | HttpClientOptions>, [httpClientOrOptions?: HttpClient | HttpClientOptions]> & Pick<ApiClientImplementation<T>, 'getEndpointResource' | 'getEndpointUrl'>;
|
|
6
6
|
export type ClientOptions = {
|
|
7
7
|
/**
|
|
8
8
|
* Url prefix
|
package/api/client/client.js
CHANGED
|
@@ -19,7 +19,6 @@ export const defaultOptions = {};
|
|
|
19
19
|
export function setDefaultApiClientOptions(options) {
|
|
20
20
|
copyObjectProperties(options, defaultOptions);
|
|
21
21
|
}
|
|
22
|
-
// eslint-disable-next-line max-lines-per-function
|
|
23
22
|
export function compileClient(definition, options = defaultOptions) {
|
|
24
23
|
const { resource: path, endpoints } = definition;
|
|
25
24
|
const constructedApiName = toTitleCase(path[0] ?? '');
|
|
@@ -32,17 +31,24 @@ export function compileClient(definition, options = defaultOptions) {
|
|
|
32
31
|
this[httpClientSymbol] = (httpClientOrOptions instanceof HttpClient) ? httpClientOrOptions : inject(HttpClient, httpClientOrOptions);
|
|
33
32
|
this[apiDefinitionSymbol] = definition;
|
|
34
33
|
}
|
|
35
|
-
getEndpointResource(endpoint, parameters) {
|
|
34
|
+
static getEndpointResource(endpoint, parameters) {
|
|
36
35
|
const resource = getFullApiEndpointResource({ api: definition, endpoint: resolveValueOrProvider(definition.endpoints[endpoint]), defaultPrefix: options.prefix });
|
|
37
36
|
if (isUndefined(parameters)) {
|
|
38
37
|
return resource;
|
|
39
38
|
}
|
|
40
39
|
return buildUrl(resource, parameters).parsedUrl;
|
|
41
40
|
}
|
|
42
|
-
getEndpointUrl(endpoint, parameters) {
|
|
41
|
+
static getEndpointUrl(endpoint, parameters) {
|
|
43
42
|
const url = this.getEndpointResource(endpoint, parameters);
|
|
43
|
+
return new URL(url, 'http://baseurl/');
|
|
44
|
+
}
|
|
45
|
+
getEndpointUrl(endpoint, parameters) {
|
|
46
|
+
const url = api.getEndpointResource(endpoint, parameters);
|
|
44
47
|
return new URL(url, this[httpClientSymbol].options.baseUrl);
|
|
45
48
|
}
|
|
49
|
+
getEndpointResource(endpoint, parameters) {
|
|
50
|
+
return api.getEndpointResource(endpoint, parameters);
|
|
51
|
+
}
|
|
46
52
|
},
|
|
47
53
|
}[apiName];
|
|
48
54
|
Injector.registerSingleton(api, {
|
|
@@ -77,7 +83,7 @@ export function compileClient(definition, options = defaultOptions) {
|
|
|
77
83
|
context,
|
|
78
84
|
});
|
|
79
85
|
const response = await this[httpClientSymbol].rawRequest(request);
|
|
80
|
-
return getResponseBody(response, endpoint.result);
|
|
86
|
+
return await getResponseBody(response, endpoint.result);
|
|
81
87
|
},
|
|
82
88
|
}[name];
|
|
83
89
|
Object.defineProperty(api.prototype, name, {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { match, P } from 'ts-pattern';
|
|
1
2
|
import { isDefined } from '../../../utils/type-guards.js';
|
|
2
3
|
export async function contentTypeMiddleware(context, next) {
|
|
3
4
|
await next();
|
|
@@ -5,11 +6,11 @@ export async function contentTypeMiddleware(context, next) {
|
|
|
5
6
|
if (isDefined(response.headers.contentType)) {
|
|
6
7
|
return;
|
|
7
8
|
}
|
|
8
|
-
response.headers.contentType =
|
|
9
|
-
(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
response.headers.contentType = match(response.body)
|
|
10
|
+
.with({ json: P.select(P.nonNullable) }, () => 'application/json; charset=utf-8')
|
|
11
|
+
.with({ text: P.select(P.nonNullable) }, () => 'text/plain; charset=utf-8')
|
|
12
|
+
.with({ buffer: P.select(P.nonNullable) }, () => 'application/octet-stream')
|
|
13
|
+
.with({ stream: P.select(P.nonNullable) }, () => 'application/octet-stream')
|
|
14
|
+
.with({ events: P.select(P.nonNullable) }, () => 'text/event-stream')
|
|
15
|
+
.otherwise(() => undefined);
|
|
15
16
|
}
|
package/api/types.js
CHANGED
|
@@ -6,7 +6,7 @@ export function defineApi(definition) {
|
|
|
6
6
|
}
|
|
7
7
|
export async function resolveApiEndpointDataProvider(request, context, provider) {
|
|
8
8
|
if (isFunction(provider)) {
|
|
9
|
-
return provider(request, context);
|
|
9
|
+
return await provider(request, context);
|
|
10
10
|
}
|
|
11
11
|
return provider;
|
|
12
12
|
}
|
|
@@ -134,7 +134,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
134
134
|
try {
|
|
135
135
|
await Promise.race([
|
|
136
136
|
this.client.endSession(),
|
|
137
|
-
timeout(150)
|
|
137
|
+
timeout(150),
|
|
138
138
|
]).catch((error) => this.logger.error(error));
|
|
139
139
|
}
|
|
140
140
|
finally {
|
|
@@ -197,7 +197,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
197
197
|
await this.client.resetSecret({ token, newSecret });
|
|
198
198
|
}
|
|
199
199
|
async checkSecret(secret) {
|
|
200
|
-
return this.client.checkSecret({ secret });
|
|
200
|
+
return await this.client.checkSecret({ secret });
|
|
201
201
|
}
|
|
202
202
|
saveToken(token) {
|
|
203
203
|
if (isNullOrUndefined(token)) {
|
|
@@ -223,7 +223,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
223
223
|
async refreshLoop() {
|
|
224
224
|
while (this.disposeToken.isUnset) {
|
|
225
225
|
try {
|
|
226
|
-
await this.lock.use(0, false, async () => this.refreshLoopIteration());
|
|
226
|
+
await this.lock.use(0, false, async () => await this.refreshLoopIteration());
|
|
227
227
|
await firstValueFrom(race([timer(2500), this.disposeToken, this.forceRefreshToken]));
|
|
228
228
|
}
|
|
229
229
|
catch {
|
|
@@ -10,12 +10,22 @@ export type GetTokenPayloadContextAction = EnumType<typeof GetTokenPayloadContex
|
|
|
10
10
|
export type GetTokenPayloadContext = {
|
|
11
11
|
action: GetTokenPayloadContextAction;
|
|
12
12
|
};
|
|
13
|
+
export type ResolveSubjectResult = {
|
|
14
|
+
success: true;
|
|
15
|
+
subject: string;
|
|
16
|
+
} | {
|
|
17
|
+
success: false;
|
|
18
|
+
subject?: undefined;
|
|
19
|
+
};
|
|
13
20
|
export declare abstract class AuthenticationAncillaryService<AdditionalTokenPayload extends Record = Record<never>, AuthenticationData = void, AdditionalInitSecretResetData = void> {
|
|
14
21
|
/**
|
|
15
22
|
* Resolve a provided subject to the actual subject used for authentication.
|
|
16
23
|
* Useful for example if you want to be able to login via mail but actual subject is the user id.
|
|
24
|
+
* @param providedSubject The subject provided by the user, e.g. an email address.
|
|
25
|
+
* @returns An object with success flag and the resolved subject if successful.
|
|
26
|
+
* If the subject cannot be resolved, return an object with success set to false.
|
|
17
27
|
*/
|
|
18
|
-
abstract resolveSubject(providedSubject: string):
|
|
28
|
+
abstract resolveSubject(providedSubject: string): ResolveSubjectResult | Promise<ResolveSubjectResult>;
|
|
19
29
|
abstract getTokenPayload(subject: string, authenticationData: AuthenticationData, context: GetTokenPayloadContext): AdditionalTokenPayload | Promise<AdditionalTokenPayload>;
|
|
20
30
|
abstract handleInitSecretReset(data: InitSecretResetData & AdditionalInitSecretResetData): void | Promise<void>;
|
|
21
31
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineEnum } from '../../enumeration/enumeration.js';
|
|
2
2
|
export const GetTokenPayloadContextAction = defineEnum('GetTokenPayloadContextAction', {
|
|
3
3
|
GetToken: 'get-token',
|
|
4
|
-
Refresh: 'refresh'
|
|
4
|
+
Refresh: 'refresh',
|
|
5
5
|
});
|
|
6
6
|
export class AuthenticationAncillaryService {
|
|
7
7
|
}
|
|
@@ -13,7 +13,7 @@ export class AuthenticationSecretRequirementsValidator {
|
|
|
13
13
|
}
|
|
14
14
|
let DefaultAuthenticationSecretRequirementsValidator = class DefaultAuthenticationSecretRequirementsValidator extends AuthenticationSecretRequirementsValidator {
|
|
15
15
|
async checkSecretRequirements(secret) {
|
|
16
|
-
return checkPassword(secret, { checkForPwned: true });
|
|
16
|
+
return await checkPassword(secret, { checkForPwned: true });
|
|
17
17
|
}
|
|
18
18
|
async testSecretRequirements(secret) {
|
|
19
19
|
const result = await this.checkSecretRequirements(secret);
|
|
@@ -16,7 +16,7 @@ import { authenticationApiDefinition, getAuthenticationApiDefinition } from '../
|
|
|
16
16
|
import { AuthenticationService } from './authentication.service.js';
|
|
17
17
|
import { tryGetAuthorizationTokenStringFromRequest } from './helper.js';
|
|
18
18
|
const cookieBaseOptions = { path: '/', httpOnly: true, secure: true, sameSite: 'strict' };
|
|
19
|
-
const deleteCookie = { value: '', ...cookieBaseOptions,
|
|
19
|
+
const deleteCookie = { value: '', ...cookieBaseOptions, maxAge: -1 };
|
|
20
20
|
let AuthenticationApiController = class AuthenticationApiController {
|
|
21
21
|
authenticationService;
|
|
22
22
|
constructor(authenticationService) {
|
|
@@ -69,11 +69,11 @@ let AuthenticationApiController = class AuthenticationApiController {
|
|
|
69
69
|
cookies: {
|
|
70
70
|
authorization: deleteCookie,
|
|
71
71
|
refreshToken: deleteCookie,
|
|
72
|
-
impersonatorRefreshToken: deleteCookie
|
|
72
|
+
impersonatorRefreshToken: deleteCookie,
|
|
73
73
|
},
|
|
74
74
|
body: {
|
|
75
|
-
json: result
|
|
76
|
-
}
|
|
75
|
+
json: result,
|
|
76
|
+
},
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
async initSecretReset({ parameters }) {
|
|
@@ -85,7 +85,7 @@ let AuthenticationApiController = class AuthenticationApiController {
|
|
|
85
85
|
return 'ok';
|
|
86
86
|
}
|
|
87
87
|
async checkSecret({ parameters }) {
|
|
88
|
-
return this.authenticationService.checkSecret(parameters.secret);
|
|
88
|
+
return await this.authenticationService.checkSecret(parameters.secret);
|
|
89
89
|
}
|
|
90
90
|
timestamp() {
|
|
91
91
|
return currentTimestamp();
|
|
@@ -94,15 +94,27 @@ let AuthenticationApiController = class AuthenticationApiController {
|
|
|
94
94
|
const result = jsonToken.payload;
|
|
95
95
|
const options = {
|
|
96
96
|
cookies: {
|
|
97
|
-
authorization: {
|
|
98
|
-
|
|
97
|
+
authorization: {
|
|
98
|
+
value: `Bearer ${token}`,
|
|
99
|
+
...cookieBaseOptions,
|
|
100
|
+
expires: jsonToken.payload.exp * 1000,
|
|
101
|
+
},
|
|
102
|
+
refreshToken: {
|
|
103
|
+
value: `Bearer ${refreshToken}`,
|
|
104
|
+
...cookieBaseOptions,
|
|
105
|
+
expires: jsonToken.payload.refreshTokenExp * 1000,
|
|
106
|
+
},
|
|
99
107
|
},
|
|
100
108
|
body: {
|
|
101
|
-
json: result
|
|
102
|
-
}
|
|
109
|
+
json: result,
|
|
110
|
+
},
|
|
103
111
|
};
|
|
104
112
|
if (isDefined(impersonatorRefreshToken)) {
|
|
105
|
-
options.cookies['impersonatorRefreshToken'] = {
|
|
113
|
+
options.cookies['impersonatorRefreshToken'] = {
|
|
114
|
+
value: `Bearer ${impersonatorRefreshToken}`,
|
|
115
|
+
...cookieBaseOptions,
|
|
116
|
+
expires: assertDefinedPass(impersonatorRefreshTokenExpiration) * 1000,
|
|
117
|
+
};
|
|
106
118
|
}
|
|
107
119
|
if (omitImpersonatorRefreshToken == true) {
|
|
108
120
|
options.cookies['impersonatorRefreshToken'] = deleteCookie;
|
|
@@ -51,6 +51,8 @@ export type TokenResult<AdditionalTokenPayload extends Record> = {
|
|
|
51
51
|
export type SetCredentialsOptions = {
|
|
52
52
|
/** skip validation for password strength */
|
|
53
53
|
skipValidation?: boolean;
|
|
54
|
+
/** skip session invalidation */
|
|
55
|
+
skipSessionInvalidation?: boolean;
|
|
54
56
|
};
|
|
55
57
|
type CreateTokenResult<AdditionalTokenPayload extends Record> = {
|
|
56
58
|
token: string;
|
|
@@ -63,11 +65,7 @@ type CreateRefreshTokenResult = {
|
|
|
63
65
|
hash: Uint8Array;
|
|
64
66
|
};
|
|
65
67
|
export declare class AuthenticationService<AdditionalTokenPayload extends Record = Record<never>, AuthenticationData = void, AdditionalInitSecretResetData = void> implements AfterResolve {
|
|
66
|
-
private
|
|
67
|
-
private readonly sessionRepository;
|
|
68
|
-
private readonly authenticationSecretRequirementsValidator;
|
|
69
|
-
private readonly authenticationAncillaryService;
|
|
70
|
-
private readonly options;
|
|
68
|
+
#private;
|
|
71
69
|
private readonly tokenVersion;
|
|
72
70
|
private readonly tokenTimeToLive;
|
|
73
71
|
private readonly refreshTokenTimeToLive;
|
|
@@ -96,6 +94,14 @@ export declare class AuthenticationService<AdditionalTokenPayload extends Record
|
|
|
96
94
|
validateToken(token: string): Promise<Token<AdditionalTokenPayload>>;
|
|
97
95
|
validateRefreshToken(token: string): Promise<RefreshToken>;
|
|
98
96
|
validateSecretResetToken(token: string): Promise<SecretResetToken>;
|
|
97
|
+
tryResolveSubject(subject: string): Promise<string | undefined>;
|
|
98
|
+
/**
|
|
99
|
+
* Resolves the subject to the actual subject used for authentication.
|
|
100
|
+
* This should *not* be used for public facing APIs, as it throws an error if the subject is not found that leaks if the subjects exists or not.
|
|
101
|
+
* Instead use {@link tryResolveSubject} to check if the subject exists without leaking information.
|
|
102
|
+
* @param subject The subject to resolve.
|
|
103
|
+
* @returns The resolved subject or the original subject if not found.
|
|
104
|
+
*/
|
|
99
105
|
resolveSubject(subject: string): Promise<string>;
|
|
100
106
|
/** Creates a token without session or refresh token and is not saved in database */
|
|
101
107
|
createToken({ tokenVersion, jwtId, issuedAt, expiration, additionalTokenPayload, subject, sessionId, refreshTokenExpiration, impersonator: impersonatedBy, timestamp }: CreateTokenData<AdditionalTokenPayload>): Promise<CreateTokenResult<AdditionalTokenPayload>>;
|
|
@@ -6,14 +6,18 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { ForbiddenError } from '../../errors/forbidden.error.js';
|
|
8
8
|
import { InvalidTokenError } from '../../errors/invalid-token.error.js';
|
|
9
|
+
import { NotFoundError } from '../../errors/not-found.error.js';
|
|
9
10
|
import { NotImplementedError } from '../../errors/not-implemented.error.js';
|
|
10
11
|
import { Singleton, afterResolve, inject, provide } from '../../injector/index.js';
|
|
12
|
+
import { KeyValueStore } from '../../key-value-store/key-value.store.js';
|
|
13
|
+
import { Logger } from '../../logger/logger.js';
|
|
11
14
|
import { DatabaseConfig } from '../../orm/server/index.js';
|
|
12
15
|
import { EntityRepositoryConfig, injectRepository } from '../../orm/server/repository.js';
|
|
13
16
|
import { Alphabet } from '../../utils/alphabet.js';
|
|
17
|
+
import { decodeBase64, encodeBase64 } from '../../utils/base64.js';
|
|
14
18
|
import { deriveBytesMultiple, importPbkdf2Key } from '../../utils/cryptography.js';
|
|
15
19
|
import { currentTimestamp, timestampToTimestampSeconds } from '../../utils/date-time.js';
|
|
16
|
-
import {
|
|
20
|
+
import { timingSafeBinaryEquals } from '../../utils/equals.js';
|
|
17
21
|
import { createJwtTokenString } from '../../utils/jwt.js';
|
|
18
22
|
import { getRandomBytes, getRandomString } from '../../utils/random.js';
|
|
19
23
|
import { isBinaryData, isString, isUndefined } from '../../utils/type-guards.js';
|
|
@@ -40,15 +44,17 @@ export class AuthenticationServiceOptions {
|
|
|
40
44
|
}
|
|
41
45
|
const SIGNING_SECRETS_LENGTH = 64;
|
|
42
46
|
let AuthenticationService = class AuthenticationService {
|
|
43
|
-
credentialsRepository = injectRepository(AuthenticationCredentials);
|
|
44
|
-
sessionRepository = injectRepository(AuthenticationSession);
|
|
45
|
-
authenticationSecretRequirementsValidator = inject(AuthenticationSecretRequirementsValidator);
|
|
46
|
-
authenticationAncillaryService = inject(AuthenticationAncillaryService, undefined, { optional: true });
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
#credentialsRepository = injectRepository(AuthenticationCredentials);
|
|
48
|
+
#sessionRepository = injectRepository(AuthenticationSession);
|
|
49
|
+
#authenticationSecretRequirementsValidator = inject(AuthenticationSecretRequirementsValidator);
|
|
50
|
+
#authenticationAncillaryService = inject(AuthenticationAncillaryService, undefined, { optional: true });
|
|
51
|
+
#keyValueStore = inject((KeyValueStore), 'authentication');
|
|
52
|
+
#options = inject(AuthenticationServiceOptions);
|
|
53
|
+
#logger = inject(Logger, 'authentication');
|
|
54
|
+
tokenVersion = this.#options.version ?? 1;
|
|
55
|
+
tokenTimeToLive = this.#options.tokenTimeToLive ?? (5 * millisecondsPerMinute);
|
|
56
|
+
refreshTokenTimeToLive = this.#options.refreshTokenTimeToLive ?? (5 * millisecondsPerDay);
|
|
57
|
+
secretResetTokenTimeToLive = this.#options.secretResetTokenTimeToLive ?? (10 * millisecondsPerMinute);
|
|
52
58
|
derivedTokenSigningSecret;
|
|
53
59
|
derivedRefreshTokenSigningSecret;
|
|
54
60
|
derivedSecretResetTokenSigningSecret;
|
|
@@ -56,37 +62,44 @@ let AuthenticationService = class AuthenticationService {
|
|
|
56
62
|
await this.initialize();
|
|
57
63
|
}
|
|
58
64
|
async initialize() {
|
|
59
|
-
if (isString(this
|
|
60
|
-
await this.deriveSigningSecrets(this
|
|
65
|
+
if (isString(this.#options.secret) || isBinaryData(this.#options.secret)) {
|
|
66
|
+
await this.deriveSigningSecrets(this.#options.secret);
|
|
61
67
|
}
|
|
62
68
|
else {
|
|
63
|
-
this.derivedTokenSigningSecret = this
|
|
64
|
-
this.derivedRefreshTokenSigningSecret = this
|
|
65
|
-
this.derivedSecretResetTokenSigningSecret = this
|
|
69
|
+
this.derivedTokenSigningSecret = this.#options.secret.tokenSigningSecret;
|
|
70
|
+
this.derivedRefreshTokenSigningSecret = this.#options.secret.refreshTokenSigningSecret;
|
|
71
|
+
this.derivedSecretResetTokenSigningSecret = this.#options.secret.secretResetTokenSigningSecret;
|
|
66
72
|
}
|
|
67
73
|
}
|
|
68
74
|
async setCredentials(subject, secret, options) {
|
|
75
|
+
// We do not need to avoid information leakage here, as this is a non-public method that is only called by a public api if the secret reset token is valid.
|
|
69
76
|
const actualSubject = await this.resolveSubject(subject);
|
|
70
77
|
if (options?.skipValidation != true) {
|
|
71
|
-
await this
|
|
78
|
+
await this.#authenticationSecretRequirementsValidator.validateSecretRequirements(secret);
|
|
72
79
|
}
|
|
73
80
|
const salt = getRandomBytes(32);
|
|
74
81
|
const hash = await this.getHash(secret, salt);
|
|
75
|
-
await this
|
|
76
|
-
subject
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
await this.#credentialsRepository.transaction(async (tx) => {
|
|
83
|
+
await this.#credentialsRepository.withTransaction(tx).upsert('subject', {
|
|
84
|
+
subject: actualSubject,
|
|
85
|
+
hashVersion: 1,
|
|
86
|
+
salt,
|
|
87
|
+
hash,
|
|
88
|
+
});
|
|
89
|
+
if (options?.skipSessionInvalidation != true) {
|
|
90
|
+
await this.#sessionRepository.withTransaction(tx).updateManyByQuery({ subject: actualSubject }, { end: currentTimestamp() });
|
|
91
|
+
}
|
|
80
92
|
});
|
|
81
93
|
}
|
|
82
94
|
async authenticate(subject, secret) {
|
|
83
|
-
const actualSubject = await this.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
95
|
+
const actualSubject = await this.tryResolveSubject(subject) ?? subject;
|
|
96
|
+
// Always try to load credentials, even if the subject is not resolved, to avoid information leakage.
|
|
97
|
+
// If the subject is not resolved, we will create a new credentials entry with an empty salt and hash.
|
|
98
|
+
// This way, we do not leak if the subject exists or not via timing attacks.
|
|
99
|
+
const credentials = await this.#credentialsRepository.tryLoadByQuery({ subject: actualSubject })
|
|
100
|
+
?? { subject: actualSubject, salt: new Uint8Array(), hash: new Uint8Array() };
|
|
88
101
|
const hash = await this.getHash(secret, credentials.salt);
|
|
89
|
-
const valid =
|
|
102
|
+
const valid = timingSafeBinaryEquals(hash, credentials.hash);
|
|
90
103
|
if (valid) {
|
|
91
104
|
return { success: true, subject: credentials.subject };
|
|
92
105
|
}
|
|
@@ -96,8 +109,8 @@ let AuthenticationService = class AuthenticationService {
|
|
|
96
109
|
const actualSubject = await this.resolveSubject(subject);
|
|
97
110
|
const now = currentTimestamp();
|
|
98
111
|
const end = now + this.refreshTokenTimeToLive;
|
|
99
|
-
return this
|
|
100
|
-
const session = await this
|
|
112
|
+
return await this.#sessionRepository.transaction(async (tx) => {
|
|
113
|
+
const session = await this.#sessionRepository.withTransaction(tx).insert({
|
|
101
114
|
subject: actualSubject,
|
|
102
115
|
begin: now,
|
|
103
116
|
end,
|
|
@@ -105,10 +118,10 @@ let AuthenticationService = class AuthenticationService {
|
|
|
105
118
|
refreshTokenSalt: new Uint8Array(),
|
|
106
119
|
refreshTokenHash: new Uint8Array(),
|
|
107
120
|
});
|
|
108
|
-
const tokenPayload = await this
|
|
121
|
+
const tokenPayload = await this.#authenticationAncillaryService?.getTokenPayload(actualSubject, authenticationData, { action: GetTokenPayloadContextAction.GetToken });
|
|
109
122
|
const { token, jsonToken } = await this.createToken({ additionalTokenPayload: tokenPayload, subject: actualSubject, impersonator, sessionId: session.id, refreshTokenExpiration: end, timestamp: now });
|
|
110
123
|
const refreshToken = await this.createRefreshToken(actualSubject, session.id, end, { impersonator });
|
|
111
|
-
await this
|
|
124
|
+
await this.#sessionRepository.withTransaction(tx).update(session.id, {
|
|
112
125
|
end,
|
|
113
126
|
refreshTokenHashVersion: 1,
|
|
114
127
|
refreshTokenSalt: refreshToken.salt,
|
|
@@ -119,26 +132,26 @@ let AuthenticationService = class AuthenticationService {
|
|
|
119
132
|
}
|
|
120
133
|
async endSession(sessionId) {
|
|
121
134
|
const now = currentTimestamp();
|
|
122
|
-
await this
|
|
135
|
+
await this.#sessionRepository.update(sessionId, { end: now });
|
|
123
136
|
}
|
|
124
137
|
async refresh(refreshToken, authenticationData, { omitImpersonator = false } = {}) {
|
|
125
138
|
const validatedRefreshToken = await this.validateRefreshToken(refreshToken);
|
|
126
139
|
const sessionId = validatedRefreshToken.payload.sessionId;
|
|
127
|
-
const session = await this
|
|
140
|
+
const session = await this.#sessionRepository.load(sessionId);
|
|
128
141
|
const hash = await this.getHash(validatedRefreshToken.payload.secret, session.refreshTokenSalt);
|
|
129
142
|
if (session.end <= currentTimestamp()) {
|
|
130
143
|
throw new InvalidTokenError('Session is expired.');
|
|
131
144
|
}
|
|
132
|
-
if (!
|
|
145
|
+
if (!timingSafeBinaryEquals(hash, session.refreshTokenHash)) {
|
|
133
146
|
throw new InvalidTokenError('Invalid refresh token.');
|
|
134
147
|
}
|
|
135
148
|
const now = currentTimestamp();
|
|
136
149
|
const impersonator = omitImpersonator ? undefined : validatedRefreshToken.payload.impersonator;
|
|
137
150
|
const newEnd = now + this.refreshTokenTimeToLive;
|
|
138
|
-
const tokenPayload = await this
|
|
151
|
+
const tokenPayload = await this.#authenticationAncillaryService?.getTokenPayload(session.subject, authenticationData, { action: GetTokenPayloadContextAction.Refresh });
|
|
139
152
|
const { token, jsonToken } = await this.createToken({ additionalTokenPayload: tokenPayload, subject: session.subject, sessionId, refreshTokenExpiration: newEnd, impersonator, timestamp: now });
|
|
140
153
|
const newRefreshToken = await this.createRefreshToken(validatedRefreshToken.payload.subject, sessionId, newEnd, { impersonator });
|
|
141
|
-
await this
|
|
154
|
+
await this.#sessionRepository.update(sessionId, {
|
|
142
155
|
end: newEnd,
|
|
143
156
|
refreshTokenHashVersion: 1,
|
|
144
157
|
refreshTokenSalt: newRefreshToken.salt,
|
|
@@ -149,7 +162,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
149
162
|
async impersonate(impersonatorRoken, impersonatorRefreshToken, subject, authenticationData) {
|
|
150
163
|
const validatedImpersonatorRoken = await this.validateToken(impersonatorRoken);
|
|
151
164
|
const validatedImpersonatorRefreshToken = await this.validateRefreshToken(impersonatorRefreshToken);
|
|
152
|
-
const allowed = await this
|
|
165
|
+
const allowed = await this.#authenticationAncillaryService?.canImpersonate(validatedImpersonatorRoken.payload, subject, authenticationData) ?? false;
|
|
153
166
|
if (!allowed) {
|
|
154
167
|
throw new ForbiddenError('Impersonation forbidden.');
|
|
155
168
|
}
|
|
@@ -161,45 +174,78 @@ let AuthenticationService = class AuthenticationService {
|
|
|
161
174
|
};
|
|
162
175
|
}
|
|
163
176
|
async unimpersonate(impersonatorRefreshToken, authenticationData) {
|
|
164
|
-
return this.refresh(impersonatorRefreshToken, authenticationData, { omitImpersonator: true });
|
|
177
|
+
return await this.refresh(impersonatorRefreshToken, authenticationData, { omitImpersonator: true });
|
|
165
178
|
}
|
|
166
179
|
async initSecretReset(subject, data) {
|
|
167
|
-
if (isUndefined(this
|
|
180
|
+
if (isUndefined(this.#authenticationAncillaryService)) {
|
|
168
181
|
throw new NotImplementedError();
|
|
169
182
|
}
|
|
170
|
-
const actualSubject = await this.
|
|
183
|
+
const actualSubject = await this.tryResolveSubject(subject);
|
|
184
|
+
if (isUndefined(actualSubject)) {
|
|
185
|
+
this.#logger.warn(`Subject "${subject}" not found for secret reset.`);
|
|
186
|
+
/**
|
|
187
|
+
* If the subject cannot be resolved, we do not throw an error here to avoid information leakage.
|
|
188
|
+
* This is to prevent attackers from discovering valid subjects by trying to reset secrets.
|
|
189
|
+
* Instead, we simply log the attempt and return without performing any action.
|
|
190
|
+
*/
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
171
193
|
const secretResetToken = await this.createSecretResetToken(actualSubject, currentTimestamp() + this.secretResetTokenTimeToLive);
|
|
172
194
|
const initSecretResetData = {
|
|
173
195
|
subject: actualSubject,
|
|
174
196
|
token: secretResetToken.token,
|
|
175
197
|
...data,
|
|
176
198
|
};
|
|
177
|
-
await this
|
|
199
|
+
await this.#authenticationAncillaryService.handleInitSecretReset(initSecretResetData);
|
|
178
200
|
}
|
|
179
201
|
async resetSecret(tokenString, newSecret) {
|
|
180
202
|
const token = await this.validateSecretResetToken(tokenString);
|
|
181
203
|
await this.setCredentials(token.payload.subject, newSecret);
|
|
182
204
|
}
|
|
183
205
|
async checkSecret(secret) {
|
|
184
|
-
return this
|
|
206
|
+
return await this.#authenticationSecretRequirementsValidator.checkSecretRequirements(secret);
|
|
185
207
|
}
|
|
186
208
|
async testSecret(secret) {
|
|
187
|
-
return this
|
|
209
|
+
return await this.#authenticationSecretRequirementsValidator.testSecretRequirements(secret);
|
|
188
210
|
}
|
|
189
211
|
async validateSecret(secret) {
|
|
190
|
-
|
|
212
|
+
await this.#authenticationSecretRequirementsValidator.validateSecretRequirements(secret);
|
|
191
213
|
}
|
|
192
214
|
async validateToken(token) {
|
|
193
|
-
return getTokenFromString(token, this.tokenVersion, this.derivedTokenSigningSecret);
|
|
215
|
+
return await getTokenFromString(token, this.tokenVersion, this.derivedTokenSigningSecret);
|
|
194
216
|
}
|
|
195
217
|
async validateRefreshToken(token) {
|
|
196
|
-
return getRefreshTokenFromString(token, this.derivedRefreshTokenSigningSecret);
|
|
218
|
+
return await getRefreshTokenFromString(token, this.derivedRefreshTokenSigningSecret);
|
|
197
219
|
}
|
|
198
220
|
async validateSecretResetToken(token) {
|
|
199
|
-
return getSecretResetTokenFromString(token, this.derivedSecretResetTokenSigningSecret);
|
|
221
|
+
return await getSecretResetTokenFromString(token, this.derivedSecretResetTokenSigningSecret);
|
|
200
222
|
}
|
|
223
|
+
async tryResolveSubject(subject) {
|
|
224
|
+
if (isUndefined(this.#authenticationAncillaryService)) {
|
|
225
|
+
return subject;
|
|
226
|
+
}
|
|
227
|
+
const result = await this.#authenticationAncillaryService.resolveSubject(subject);
|
|
228
|
+
if (result.success) {
|
|
229
|
+
return result.subject;
|
|
230
|
+
}
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Resolves the subject to the actual subject used for authentication.
|
|
235
|
+
* This should *not* be used for public facing APIs, as it throws an error if the subject is not found that leaks if the subjects exists or not.
|
|
236
|
+
* Instead use {@link tryResolveSubject} to check if the subject exists without leaking information.
|
|
237
|
+
* @param subject The subject to resolve.
|
|
238
|
+
* @returns The resolved subject or the original subject if not found.
|
|
239
|
+
*/
|
|
201
240
|
async resolveSubject(subject) {
|
|
202
|
-
|
|
241
|
+
if (isUndefined(this.#authenticationAncillaryService)) {
|
|
242
|
+
return subject;
|
|
243
|
+
}
|
|
244
|
+
const result = await this.#authenticationAncillaryService.resolveSubject(subject);
|
|
245
|
+
if (result.success) {
|
|
246
|
+
return result.subject;
|
|
247
|
+
}
|
|
248
|
+
throw new NotFoundError(`Subject not found.`);
|
|
203
249
|
}
|
|
204
250
|
/** Creates a token without session or refresh token and is not saved in database */
|
|
205
251
|
async createToken({ tokenVersion, jwtId, issuedAt, expiration, additionalTokenPayload, subject, sessionId, refreshTokenExpiration, impersonator: impersonatedBy, timestamp = currentTimestamp() }) {
|
|
@@ -262,7 +308,9 @@ let AuthenticationService = class AuthenticationService {
|
|
|
262
308
|
}
|
|
263
309
|
async deriveSigningSecrets(secret) {
|
|
264
310
|
const key = await importPbkdf2Key(secret);
|
|
265
|
-
const
|
|
311
|
+
const saltBase64 = await this.#keyValueStore.getOrSet('derivationSalt', encodeBase64(getRandomBytes(32)));
|
|
312
|
+
const salt = decodeBase64(saltBase64);
|
|
313
|
+
const algorithm = { name: 'PBKDF2', hash: 'SHA-512', iterations: 500000, salt };
|
|
266
314
|
const [derivedTokenSigningSecret, derivedRefreshTokenSigningSecret, derivedSecretResetTokenSigningSecret] = await deriveBytesMultiple(algorithm, key, 3, SIGNING_SECRETS_LENGTH);
|
|
267
315
|
this.derivedTokenSigningSecret = derivedTokenSigningSecret;
|
|
268
316
|
this.derivedRefreshTokenSigningSecret = derivedRefreshTokenSigningSecret;
|
|
@@ -33,6 +33,6 @@ export async function migrateAuthenticationSchema() {
|
|
|
33
33
|
await migrate(database, {
|
|
34
34
|
migrationsSchema: 'authentication',
|
|
35
35
|
migrationsTable: '_migrations',
|
|
36
|
-
migrationsFolder: import.meta.resolve('./drizzle').replace('file://', '')
|
|
36
|
+
migrationsFolder: import.meta.resolve('./drizzle').replace('file://', ''),
|
|
37
37
|
});
|
|
38
38
|
}
|
package/cancellation/token.d.ts
CHANGED
|
@@ -40,11 +40,11 @@ export declare class CancellationSignal implements PromiseLike<void>, Subscribab
|
|
|
40
40
|
/**
|
|
41
41
|
* Observable which emits when this token is set.
|
|
42
42
|
*/
|
|
43
|
-
readonly set$: Observable<
|
|
43
|
+
readonly set$: Observable<void>;
|
|
44
44
|
/**
|
|
45
45
|
* Observable which emits when this token is unset.
|
|
46
46
|
*/
|
|
47
|
-
readonly unset$: Observable<
|
|
47
|
+
readonly unset$: Observable<void>;
|
|
48
48
|
/**
|
|
49
49
|
* Returns a promise which is resolved when this token changes its state.
|
|
50
50
|
*/
|