@tstdl/base 0.93.84 → 0.93.86
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/authentication/client/authentication.service.d.ts +2 -2
- package/authentication/client/authentication.service.js +2 -2
- package/authentication/models/authentication-credentials.model.d.ts +4 -7
- package/authentication/models/authentication-credentials.model.js +6 -9
- package/authentication/models/authentication-session.model.d.ts +4 -7
- package/authentication/models/authentication-session.model.js +5 -8
- package/authentication/models/token.model.d.ts +12 -30
- package/authentication/server/authentication.audit.d.ts +1 -0
- package/authentication/server/authentication.service.js +58 -25
- package/authentication/server/drizzle/{0000_majestic_proudstar.sql → 0000_normal_paper_doll.sql} +5 -5
- package/authentication/server/drizzle/meta/0000_snapshot.json +10 -10
- package/authentication/server/drizzle/meta/_journal.json +2 -2
- package/examples/document-management/main.js +5 -2
- package/orm/sqls.d.ts +1 -1
- package/orm/sqls.js +1 -1
- package/package.json +3 -3
|
@@ -84,10 +84,10 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
|
|
|
84
84
|
*/
|
|
85
85
|
get definedTenantId(): string;
|
|
86
86
|
/**
|
|
87
|
-
* Get current
|
|
87
|
+
* Get current subjectId or throw if not available
|
|
88
88
|
* @throws Will throw if subject is not available
|
|
89
89
|
*/
|
|
90
|
-
get
|
|
90
|
+
get definedSubjectId(): string;
|
|
91
91
|
/** Whether a valid token is available (not undefined and not expired) */
|
|
92
92
|
get hasValidToken(): boolean;
|
|
93
93
|
constructor();
|
|
@@ -140,10 +140,10 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
140
140
|
return this.definedToken.tenant;
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
143
|
-
* Get current
|
|
143
|
+
* Get current subjectId or throw if not available
|
|
144
144
|
* @throws Will throw if subject is not available
|
|
145
145
|
*/
|
|
146
|
-
get
|
|
146
|
+
get definedSubjectId() {
|
|
147
147
|
return this.definedToken.subject;
|
|
148
148
|
}
|
|
149
149
|
/** Whether a valid token is available (not undefined and not expired) */
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { TenantEntity, type Uuid } from '../../orm/index.js';
|
|
2
2
|
export declare class AuthenticationCredentials extends TenantEntity {
|
|
3
|
-
|
|
3
|
+
subjectId: Uuid;
|
|
4
|
+
/** The version of the hash algorithm used. */
|
|
4
5
|
hashVersion: number;
|
|
5
|
-
/**
|
|
6
|
-
* The salt used to hash the secret.
|
|
7
|
-
*/
|
|
6
|
+
/** The salt used to hash the secret. */
|
|
8
7
|
salt: Uint8Array<ArrayBuffer>;
|
|
9
|
-
/**
|
|
10
|
-
* The hashed secret.
|
|
11
|
-
*/
|
|
8
|
+
/** The hashed secret. */
|
|
12
9
|
hash: Uint8Array<ArrayBuffer>;
|
|
13
10
|
}
|
|
@@ -11,22 +11,19 @@ import { Table, TenantEntity, TenantReference, Unique, UuidProperty } from '../.
|
|
|
11
11
|
import { Integer, Uint8ArrayProperty } from '../../schema/index.js';
|
|
12
12
|
import { Subject } from './subject.model.js';
|
|
13
13
|
let AuthenticationCredentials = class AuthenticationCredentials extends TenantEntity {
|
|
14
|
-
|
|
14
|
+
subjectId;
|
|
15
|
+
/** The version of the hash algorithm used. */
|
|
15
16
|
hashVersion;
|
|
16
|
-
/**
|
|
17
|
-
* The salt used to hash the secret.
|
|
18
|
-
*/
|
|
17
|
+
/** The salt used to hash the secret. */
|
|
19
18
|
salt;
|
|
20
|
-
/**
|
|
21
|
-
* The hashed secret.
|
|
22
|
-
*/
|
|
19
|
+
/** The hashed secret. */
|
|
23
20
|
hash;
|
|
24
21
|
};
|
|
25
22
|
__decorate([
|
|
26
23
|
TenantReference(() => Subject),
|
|
27
24
|
UuidProperty(),
|
|
28
25
|
__metadata("design:type", String)
|
|
29
|
-
], AuthenticationCredentials.prototype, "
|
|
26
|
+
], AuthenticationCredentials.prototype, "subjectId", void 0);
|
|
30
27
|
__decorate([
|
|
31
28
|
Integer(),
|
|
32
29
|
__metadata("design:type", Number)
|
|
@@ -41,6 +38,6 @@ __decorate([
|
|
|
41
38
|
], AuthenticationCredentials.prototype, "hash", void 0);
|
|
42
39
|
AuthenticationCredentials = __decorate([
|
|
43
40
|
Table('credentials', { schema: 'authentication' }),
|
|
44
|
-
Unique(['tenantId', '
|
|
41
|
+
Unique(['tenantId', 'subjectId'])
|
|
45
42
|
], AuthenticationCredentials);
|
|
46
43
|
export { AuthenticationCredentials };
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type { Timestamp, Uuid } from '../../orm/index.js';
|
|
2
2
|
import { TenantEntity } from '../../orm/index.js';
|
|
3
3
|
export declare class AuthenticationSession extends TenantEntity {
|
|
4
|
-
|
|
4
|
+
subjectId: Uuid;
|
|
5
5
|
begin: Timestamp;
|
|
6
6
|
end: Timestamp;
|
|
7
|
+
/** The version of the hash algorithm used. */
|
|
7
8
|
refreshTokenHashVersion: number;
|
|
8
|
-
/**
|
|
9
|
-
* The salt used to hash the refresh token.
|
|
10
|
-
*/
|
|
9
|
+
/** The salt used to hash the refresh token. */
|
|
11
10
|
refreshTokenSalt: Uint8Array<ArrayBuffer>;
|
|
12
|
-
/**
|
|
13
|
-
* The hashed refresh token.
|
|
14
|
-
*/
|
|
11
|
+
/** The hashed refresh token. */
|
|
15
12
|
refreshTokenHash: Uint8Array<ArrayBuffer>;
|
|
16
13
|
}
|
|
@@ -11,24 +11,21 @@ import { Table, TenantEntity, TenantReference, TimestampProperty, UuidProperty }
|
|
|
11
11
|
import { Integer, Uint8ArrayProperty } from '../../schema/index.js';
|
|
12
12
|
import { Subject } from './subject.model.js';
|
|
13
13
|
let AuthenticationSession = class AuthenticationSession extends TenantEntity {
|
|
14
|
-
|
|
14
|
+
subjectId;
|
|
15
15
|
begin;
|
|
16
16
|
end;
|
|
17
|
+
/** The version of the hash algorithm used. */
|
|
17
18
|
refreshTokenHashVersion;
|
|
18
|
-
/**
|
|
19
|
-
* The salt used to hash the refresh token.
|
|
20
|
-
*/
|
|
19
|
+
/** The salt used to hash the refresh token. */
|
|
21
20
|
refreshTokenSalt;
|
|
22
|
-
/**
|
|
23
|
-
* The hashed refresh token.
|
|
24
|
-
*/
|
|
21
|
+
/** The hashed refresh token. */
|
|
25
22
|
refreshTokenHash;
|
|
26
23
|
};
|
|
27
24
|
__decorate([
|
|
28
25
|
TenantReference(() => Subject),
|
|
29
26
|
UuidProperty(),
|
|
30
27
|
__metadata("design:type", String)
|
|
31
|
-
], AuthenticationSession.prototype, "
|
|
28
|
+
], AuthenticationSession.prototype, "subjectId", void 0);
|
|
32
29
|
__decorate([
|
|
33
30
|
TimestampProperty(),
|
|
34
31
|
__metadata("design:type", Number)
|
|
@@ -2,9 +2,7 @@ import type { Record } from '../../types/index.js';
|
|
|
2
2
|
import type { JwtToken, JwtTokenHeader } from '../../utils/jwt.js';
|
|
3
3
|
import type { TokenPayloadBase } from './token-payload-base.model.js';
|
|
4
4
|
export type TokenHeader = {
|
|
5
|
-
/**
|
|
6
|
-
* Token version.
|
|
7
|
-
*/
|
|
5
|
+
/** Token version. */
|
|
8
6
|
v: number;
|
|
9
7
|
};
|
|
10
8
|
/**
|
|
@@ -18,42 +16,26 @@ export type Token<AdditionalTokenPayload extends Record = Record<never>> = JwtTo
|
|
|
18
16
|
*/
|
|
19
17
|
export type TokenPayload<T extends Record = Record<never>> = T & TokenPayloadBase;
|
|
20
18
|
export type RefreshToken = JwtToken<{
|
|
21
|
-
/**
|
|
22
|
-
* Expiration timestamp in seconds.
|
|
23
|
-
*/
|
|
19
|
+
/** Expiration timestamp in seconds. */
|
|
24
20
|
exp: number;
|
|
25
|
-
/**
|
|
26
|
-
* The tenant id.
|
|
27
|
-
*/
|
|
21
|
+
/** The tenant id. */
|
|
28
22
|
tenant: string;
|
|
29
|
-
/**
|
|
30
|
-
* The subject of the token.
|
|
31
|
-
*/
|
|
23
|
+
/** The subject of the token. */
|
|
32
24
|
subject: string;
|
|
33
|
-
/**
|
|
34
|
-
* The subject of the impersonator, if any.
|
|
35
|
-
*/
|
|
25
|
+
/** The subject of the impersonator, if any. */
|
|
36
26
|
impersonator?: string;
|
|
37
|
-
/**
|
|
38
|
-
* The id of the session.
|
|
39
|
-
*/
|
|
27
|
+
/** The id of the session. */
|
|
40
28
|
session: string;
|
|
41
|
-
/**
|
|
42
|
-
* The secret to use for refreshing the token.
|
|
43
|
-
*/
|
|
29
|
+
/** The secret to use for refreshing the token. */
|
|
44
30
|
secret: string;
|
|
45
31
|
}>;
|
|
46
32
|
export type SecretResetToken = JwtToken<{
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
33
|
+
/** Issued at timestamp in seconds. */
|
|
34
|
+
iat: number;
|
|
35
|
+
/** Expiration timestamp in seconds. */
|
|
50
36
|
exp: number;
|
|
51
|
-
/**
|
|
52
|
-
* The tenant id.
|
|
53
|
-
*/
|
|
37
|
+
/** The tenant id. */
|
|
54
38
|
tenant: string;
|
|
55
|
-
/**
|
|
56
|
-
* The subject for which to reset the secret.
|
|
57
|
-
*/
|
|
39
|
+
/** The subject for which to reset the secret. */
|
|
58
40
|
subject: string;
|
|
59
41
|
}>;
|
|
@@ -25,7 +25,7 @@ import { currentTimestamp, timestampToTimestampSeconds } from '../../utils/date-
|
|
|
25
25
|
import { timingSafeBinaryEquals } from '../../utils/equals.js';
|
|
26
26
|
import { createJwtTokenString } from '../../utils/jwt.js';
|
|
27
27
|
import { getRandomBytes, getRandomString } from '../../utils/random.js';
|
|
28
|
-
import {
|
|
28
|
+
import { isBinaryData, isDefined, isString, isUndefined } from '../../utils/type-guards.js';
|
|
29
29
|
import { millisecondsPerDay, millisecondsPerMinute } from '../../utils/units.js';
|
|
30
30
|
import { AuthenticationCredentials, AuthenticationSession, Subject, User } from '../models/index.js';
|
|
31
31
|
import { AuthenticationAncillaryService, GetTokenPayloadContextAction } from './authentication-ancillary.service.js';
|
|
@@ -63,6 +63,13 @@ export class AuthenticationServiceOptions {
|
|
|
63
63
|
*/
|
|
64
64
|
secretResetTokenTimeToLive;
|
|
65
65
|
}
|
|
66
|
+
const HASH_ITERATIONS = 250000;
|
|
67
|
+
const HASH_LENGTH_BITS = 512;
|
|
68
|
+
const HASH_LENGTH_BYTES = HASH_LENGTH_BITS / 8;
|
|
69
|
+
const JWT_ID_LENGTH = 24;
|
|
70
|
+
const REFRESH_TOKEN_SECRET_LENGTH = 64;
|
|
71
|
+
const SALT_LENGTH = 32;
|
|
72
|
+
const SIGNING_SECRETS_DERIVATION_ITERATIONS = 500000;
|
|
66
73
|
const SIGNING_SECRETS_LENGTH = 64;
|
|
67
74
|
/**
|
|
68
75
|
* Handles authentication on server side.
|
|
@@ -136,18 +143,18 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
136
143
|
if (options?.skipValidation != true) {
|
|
137
144
|
await this.#authenticationSecretRequirementsValidator.validateSecretRequirements(secret);
|
|
138
145
|
}
|
|
139
|
-
const salt = getRandomBytes(
|
|
146
|
+
const salt = getRandomBytes(SALT_LENGTH);
|
|
140
147
|
const hash = await this.getHash(secret, salt);
|
|
141
148
|
await this.#credentialsRepository.transaction(async (tx) => {
|
|
142
|
-
await this.#credentialsRepository.withTransaction(tx).upsert('
|
|
149
|
+
await this.#credentialsRepository.withTransaction(tx).upsert(['tenantId', 'subjectId'], {
|
|
143
150
|
tenantId: subject.tenantId,
|
|
144
|
-
|
|
151
|
+
subjectId: subject.id,
|
|
145
152
|
hashVersion: 1,
|
|
146
153
|
salt,
|
|
147
154
|
hash,
|
|
148
155
|
});
|
|
149
156
|
if (options?.skipSessionInvalidation != true) {
|
|
150
|
-
await this.#sessionRepository.withTransaction(tx).updateManyByQuery({ tenantId: subject.tenantId,
|
|
157
|
+
await this.#sessionRepository.withTransaction(tx).updateManyByQuery({ tenantId: subject.tenantId, subjectId: subject.id }, { end: currentTimestamp() });
|
|
151
158
|
}
|
|
152
159
|
});
|
|
153
160
|
}
|
|
@@ -159,15 +166,18 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
159
166
|
*/
|
|
160
167
|
async authenticate(subject, secret) {
|
|
161
168
|
const actualSubject = await this.tryResolveSubject(subject);
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
169
|
+
// we use a random uuid instead of null here to reduce timing attack surface by DB optimiziations
|
|
170
|
+
const queryTenantId = actualSubject?.tenantId ?? crypto.randomUUID();
|
|
171
|
+
const querySubjectId = actualSubject?.id ?? crypto.randomUUID();
|
|
172
|
+
const loadedCredentials = await this.#credentialsRepository.tryLoadByQuery({
|
|
173
|
+
tenantId: queryTenantId,
|
|
174
|
+
subjectId: querySubjectId,
|
|
175
|
+
});
|
|
176
|
+
const credentials = loadedCredentials ?? { salt: new Uint8Array(SALT_LENGTH), hash: new Uint8Array(HASH_LENGTH_BYTES) };
|
|
167
177
|
const hash = await this.getHash(secret, credentials.salt);
|
|
168
178
|
const valid = timingSafeBinaryEquals(hash, credentials.hash);
|
|
169
|
-
if (valid) {
|
|
170
|
-
return { success: true, subject:
|
|
179
|
+
if (valid && isDefined(actualSubject) && isDefined(loadedCredentials)) {
|
|
180
|
+
return { success: true, subject: actualSubject };
|
|
171
181
|
}
|
|
172
182
|
return { success: false };
|
|
173
183
|
}
|
|
@@ -184,7 +194,7 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
184
194
|
return await this.#sessionRepository.transaction(async (tx) => {
|
|
185
195
|
const session = await this.#sessionRepository.withTransaction(tx).insert({
|
|
186
196
|
tenantId: subject.tenantId,
|
|
187
|
-
|
|
197
|
+
subjectId: subject.id,
|
|
188
198
|
begin: now,
|
|
189
199
|
end,
|
|
190
200
|
refreshTokenHashVersion: 0,
|
|
@@ -255,9 +265,9 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
255
265
|
await this.#sessionRepository.update(sessionId, { end: now });
|
|
256
266
|
await authAuditor.info('logout', {
|
|
257
267
|
tenantId: session.tenantId,
|
|
258
|
-
actor: session.
|
|
268
|
+
actor: session.subjectId,
|
|
259
269
|
actorType: ActorType.User,
|
|
260
|
-
targetId: session.
|
|
270
|
+
targetId: session.subjectId,
|
|
261
271
|
targetType: 'User',
|
|
262
272
|
details: { sessionId },
|
|
263
273
|
});
|
|
@@ -282,12 +292,18 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
282
292
|
throw new InvalidTokenError('Session is expired.');
|
|
283
293
|
}
|
|
284
294
|
if (!timingSafeBinaryEquals(hash, session.refreshTokenHash)) {
|
|
295
|
+
await this.endSession(sessionId, auditor);
|
|
296
|
+
await authAuditor.warn('refresh-failure', {
|
|
297
|
+
targetId: session.tenantId,
|
|
298
|
+
targetType: 'User',
|
|
299
|
+
details: { sessionId, reason: 'Token reuse detected. Session revoked.' },
|
|
300
|
+
});
|
|
285
301
|
throw new InvalidTokenError('Invalid refresh token.');
|
|
286
302
|
}
|
|
287
303
|
const now = currentTimestamp();
|
|
288
304
|
const impersonator = (options.omitImpersonator == true) ? undefined : validatedRefreshToken.payload.impersonator;
|
|
289
305
|
const newEnd = now + this.refreshTokenTimeToLive;
|
|
290
|
-
const subject = await this.#subjectRepository.loadByQuery({ tenantId: session.tenantId, id: session.
|
|
306
|
+
const subject = await this.#subjectRepository.loadByQuery({ tenantId: session.tenantId, id: session.subjectId });
|
|
291
307
|
const tokenPayload = await this.#authenticationAncillaryService?.getTokenPayload(subject, authenticationData, { action: GetTokenPayloadContextAction.Refresh });
|
|
292
308
|
const { token, jsonToken } = await this.createToken({ additionalTokenPayload: tokenPayload, subject, sessionId, refreshTokenExpiration: newEnd, impersonator, timestamp: now });
|
|
293
309
|
const newRefreshToken = await this.createRefreshToken(subject, sessionId, newEnd, { impersonator });
|
|
@@ -299,9 +315,9 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
299
315
|
});
|
|
300
316
|
await authAuditor.info('refresh-success', {
|
|
301
317
|
tenantId: session.tenantId,
|
|
302
|
-
actor: session.
|
|
318
|
+
actor: session.subjectId,
|
|
303
319
|
actorType: ActorType.User,
|
|
304
|
-
targetId: session.
|
|
320
|
+
targetId: session.subjectId,
|
|
305
321
|
targetType: 'User',
|
|
306
322
|
details: { sessionId },
|
|
307
323
|
});
|
|
@@ -311,7 +327,7 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
311
327
|
await authAuditor.warn('refresh-failure', {
|
|
312
328
|
targetId: NIL_UUID,
|
|
313
329
|
targetType: 'User',
|
|
314
|
-
details: { reason: error.message },
|
|
330
|
+
details: { sessionId: null, reason: error.message },
|
|
315
331
|
});
|
|
316
332
|
throw error;
|
|
317
333
|
}
|
|
@@ -458,6 +474,21 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
458
474
|
const authAuditor = auditor.fork(AuthenticationService_1.name);
|
|
459
475
|
try {
|
|
460
476
|
const token = await this.validateSecretResetToken(tokenString);
|
|
477
|
+
const credentials = await this.#credentialsRepository.tryLoadByQuery({
|
|
478
|
+
tenantId: token.payload.tenant,
|
|
479
|
+
subjectId: token.payload.subject,
|
|
480
|
+
});
|
|
481
|
+
if (isDefined(credentials)) {
|
|
482
|
+
const lastUpdateSeconds = timestampToTimestampSeconds(credentials.metadata.revisionTimestamp);
|
|
483
|
+
if (token.payload.iat < lastUpdateSeconds) {
|
|
484
|
+
await authAuditor.info('reset-secret-failure', {
|
|
485
|
+
targetId: token.payload.subject,
|
|
486
|
+
targetType: 'User',
|
|
487
|
+
details: { reason: 'Token is invalid (credentials have already been changed).' },
|
|
488
|
+
});
|
|
489
|
+
throw new InvalidTokenError();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
461
492
|
const subject = await this.#subjectRepository.loadByQuery({ tenantId: token.payload.tenant, id: token.payload.subject });
|
|
462
493
|
await this.setCredentials(subject, newSecret);
|
|
463
494
|
await authAuditor.info('reset-secret-success', {
|
|
@@ -580,7 +611,7 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
580
611
|
typ: 'JWT',
|
|
581
612
|
};
|
|
582
613
|
const payload = {
|
|
583
|
-
jti: jwtId ?? getRandomString(
|
|
614
|
+
jti: jwtId ?? getRandomString(JWT_ID_LENGTH, Alphabet.LowerUpperCaseNumbers),
|
|
584
615
|
iat: issuedAt ?? timestampToTimestampSeconds(timestamp),
|
|
585
616
|
exp: expiration ?? timestampToTimestampSeconds(timestamp + this.tokenTimeToLive),
|
|
586
617
|
refreshTokenExp: timestampToTimestampSeconds(refreshTokenExpiration),
|
|
@@ -607,8 +638,8 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
607
638
|
* @returns The created refresh token.
|
|
608
639
|
*/
|
|
609
640
|
async createRefreshToken(subject, sessionId, expirationTimestamp, options) {
|
|
610
|
-
const secret = getRandomString(
|
|
611
|
-
const salt = getRandomBytes(
|
|
641
|
+
const secret = getRandomString(REFRESH_TOKEN_SECRET_LENGTH, Alphabet.LowerUpperCaseNumbers);
|
|
642
|
+
const salt = getRandomBytes(SALT_LENGTH);
|
|
612
643
|
const hash = await this.getHash(secret, salt);
|
|
613
644
|
const jsonToken = {
|
|
614
645
|
header: {
|
|
@@ -647,12 +678,14 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
647
678
|
return subjects[0];
|
|
648
679
|
}
|
|
649
680
|
async createSecretResetToken(subject, expirationTimestamp) {
|
|
681
|
+
const iat = timestampToTimestampSeconds(currentTimestamp());
|
|
650
682
|
const jsonToken = {
|
|
651
683
|
header: {
|
|
652
684
|
alg: 'HS256',
|
|
653
685
|
typ: 'JWT',
|
|
654
686
|
},
|
|
655
687
|
payload: {
|
|
688
|
+
iat,
|
|
656
689
|
exp: timestampToTimestampSeconds(expirationTimestamp),
|
|
657
690
|
subject: subject.id,
|
|
658
691
|
tenant: subject.tenantId,
|
|
@@ -663,9 +696,9 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
663
696
|
}
|
|
664
697
|
async deriveSigningSecrets(secret) {
|
|
665
698
|
const key = await importPbkdf2Key(secret);
|
|
666
|
-
const saltBase64 = await this.#keyValueStore.getOrSet('derivationSalt', encodeBase64(getRandomBytes(
|
|
699
|
+
const saltBase64 = await this.#keyValueStore.getOrSet('derivationSalt', encodeBase64(getRandomBytes(SALT_LENGTH)));
|
|
667
700
|
const salt = decodeBase64(saltBase64);
|
|
668
|
-
const algorithm = { name: 'PBKDF2', hash: 'SHA-512', iterations:
|
|
701
|
+
const algorithm = { name: 'PBKDF2', hash: 'SHA-512', iterations: SIGNING_SECRETS_DERIVATION_ITERATIONS, salt };
|
|
669
702
|
const [derivedTokenSigningSecret, derivedRefreshTokenSigningSecret, derivedSecretResetTokenSigningSecret] = await deriveBytesMultiple(algorithm, key, 3, SIGNING_SECRETS_LENGTH);
|
|
670
703
|
this.derivedTokenSigningSecret = derivedTokenSigningSecret;
|
|
671
704
|
this.derivedRefreshTokenSigningSecret = derivedRefreshTokenSigningSecret;
|
|
@@ -673,7 +706,7 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
673
706
|
}
|
|
674
707
|
async getHash(secret, salt) {
|
|
675
708
|
const key = await importPbkdf2Key(secret);
|
|
676
|
-
const hash = await globalThis.crypto.subtle.deriveBits({ name: 'PBKDF2', hash: 'SHA-512', iterations:
|
|
709
|
+
const hash = await globalThis.crypto.subtle.deriveBits({ name: 'PBKDF2', hash: 'SHA-512', iterations: HASH_ITERATIONS, salt }, key, HASH_LENGTH_BITS);
|
|
677
710
|
return new Uint8Array(hash);
|
|
678
711
|
}
|
|
679
712
|
};
|
package/authentication/server/drizzle/{0000_majestic_proudstar.sql → 0000_normal_paper_doll.sql}
RENAMED
|
@@ -3,7 +3,7 @@ CREATE TYPE "authentication"."user_status" AS ENUM('active', 'suspended', 'pendi
|
|
|
3
3
|
CREATE TABLE "authentication"."credentials" (
|
|
4
4
|
"id" uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
5
5
|
"tenant_id" uuid NOT NULL,
|
|
6
|
-
"
|
|
6
|
+
"subject_id" uuid NOT NULL,
|
|
7
7
|
"hash_version" integer NOT NULL,
|
|
8
8
|
"salt" "bytea" NOT NULL,
|
|
9
9
|
"hash" "bytea" NOT NULL,
|
|
@@ -13,13 +13,13 @@ CREATE TABLE "authentication"."credentials" (
|
|
|
13
13
|
"delete_timestamp" timestamp with time zone,
|
|
14
14
|
"attributes" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
15
15
|
CONSTRAINT "credentials_tenant_id_id_pk" PRIMARY KEY("tenant_id","id"),
|
|
16
|
-
CONSTRAINT "
|
|
16
|
+
CONSTRAINT "credentials_tenant_id_subject_id_unique" UNIQUE("tenant_id","subject_id")
|
|
17
17
|
);
|
|
18
18
|
--> statement-breakpoint
|
|
19
19
|
CREATE TABLE "authentication"."session" (
|
|
20
20
|
"id" uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
21
21
|
"tenant_id" uuid NOT NULL,
|
|
22
|
-
"
|
|
22
|
+
"subject_id" uuid NOT NULL,
|
|
23
23
|
"begin" timestamp with time zone NOT NULL,
|
|
24
24
|
"end" timestamp with time zone NOT NULL,
|
|
25
25
|
"refresh_token_hash_version" integer NOT NULL,
|
|
@@ -96,8 +96,8 @@ CREATE TABLE "authentication"."user" (
|
|
|
96
96
|
CONSTRAINT "user_tenant_id_email_unique" UNIQUE("tenant_id","email")
|
|
97
97
|
);
|
|
98
98
|
--> statement-breakpoint
|
|
99
|
-
ALTER TABLE "authentication"."credentials" ADD CONSTRAINT "credentials_id_subject_fkey" FOREIGN KEY ("tenant_id","
|
|
100
|
-
ALTER TABLE "authentication"."session" ADD CONSTRAINT "session_id_subject_fkey" FOREIGN KEY ("tenant_id","
|
|
99
|
+
ALTER TABLE "authentication"."credentials" ADD CONSTRAINT "credentials_id_subject_fkey" FOREIGN KEY ("tenant_id","subject_id") REFERENCES "authentication"."subject"("tenant_id","id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
100
|
+
ALTER TABLE "authentication"."session" ADD CONSTRAINT "session_id_subject_fkey" FOREIGN KEY ("tenant_id","subject_id") REFERENCES "authentication"."subject"("tenant_id","id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
101
101
|
ALTER TABLE "authentication"."service_account" ADD CONSTRAINT "service_account_id_subject_fkey" FOREIGN KEY ("tenant_id","parent") REFERENCES "authentication"."subject"("tenant_id","id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
102
102
|
ALTER TABLE "authentication"."subject" ADD CONSTRAINT "subject_id_system_account_fkey" FOREIGN KEY ("tenant_id","system_account_id") REFERENCES "authentication"."system_account"("tenant_id","id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
103
103
|
ALTER TABLE "authentication"."subject" ADD CONSTRAINT "subject_id_user_fkey" FOREIGN KEY ("tenant_id","user_id") REFERENCES "authentication"."user"("tenant_id","id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
2
|
+
"id": "685f89da-0f2a-4523-9cbc-c9daea5f70c2",
|
|
3
3
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
4
|
"version": "7",
|
|
5
5
|
"dialect": "postgresql",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"primaryKey": false,
|
|
22
22
|
"notNull": true
|
|
23
23
|
},
|
|
24
|
-
"
|
|
25
|
-
"name": "
|
|
24
|
+
"subject_id": {
|
|
25
|
+
"name": "subject_id",
|
|
26
26
|
"type": "uuid",
|
|
27
27
|
"primaryKey": false,
|
|
28
28
|
"notNull": true
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"schemaTo": "authentication",
|
|
87
87
|
"columnsFrom": [
|
|
88
88
|
"tenant_id",
|
|
89
|
-
"
|
|
89
|
+
"subject_id"
|
|
90
90
|
],
|
|
91
91
|
"columnsTo": [
|
|
92
92
|
"tenant_id",
|
|
@@ -106,12 +106,12 @@
|
|
|
106
106
|
}
|
|
107
107
|
},
|
|
108
108
|
"uniqueConstraints": {
|
|
109
|
-
"
|
|
110
|
-
"name": "
|
|
109
|
+
"credentials_tenant_id_subject_id_unique": {
|
|
110
|
+
"name": "credentials_tenant_id_subject_id_unique",
|
|
111
111
|
"nullsNotDistinct": false,
|
|
112
112
|
"columns": [
|
|
113
113
|
"tenant_id",
|
|
114
|
-
"
|
|
114
|
+
"subject_id"
|
|
115
115
|
]
|
|
116
116
|
}
|
|
117
117
|
},
|
|
@@ -136,8 +136,8 @@
|
|
|
136
136
|
"primaryKey": false,
|
|
137
137
|
"notNull": true
|
|
138
138
|
},
|
|
139
|
-
"
|
|
140
|
-
"name": "
|
|
139
|
+
"subject_id": {
|
|
140
|
+
"name": "subject_id",
|
|
141
141
|
"type": "uuid",
|
|
142
142
|
"primaryKey": false,
|
|
143
143
|
"notNull": true
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
"schemaTo": "authentication",
|
|
214
214
|
"columnsFrom": [
|
|
215
215
|
"tenant_id",
|
|
216
|
-
"
|
|
216
|
+
"subject_id"
|
|
217
217
|
],
|
|
218
218
|
"columnsTo": [
|
|
219
219
|
"tenant_id",
|
|
@@ -28,6 +28,7 @@ import { configurePostgresQueue, migratePostgresQueueSchema } from '../../queue/
|
|
|
28
28
|
import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
|
|
29
29
|
import { boolean, positiveInteger, string } from '../../utils/config-parser.js';
|
|
30
30
|
import { TstdlCategoryParents, TstdlDocumentCategoryLabels, TstdlDocumentPropertyConfiguration, TstdlDocumentTypeCategories, TstdlDocumentTypeLabels, TstdlDocumentTypeProperties } from './categories-and-types.js';
|
|
31
|
+
import { configurePostgresCircuitBreaker, migratePostgresCircuitBreaker } from '../../circuit-breaker/postgres/module.js';
|
|
31
32
|
const config = {
|
|
32
33
|
database: {
|
|
33
34
|
host: string('DATABASE_HOST', '127.0.0.1'),
|
|
@@ -87,6 +88,7 @@ async function bootstrap() {
|
|
|
87
88
|
const injector = inject(Injector);
|
|
88
89
|
configureNodeHttpServer();
|
|
89
90
|
configurePostgresQueue();
|
|
91
|
+
configurePostgresCircuitBreaker();
|
|
90
92
|
configureLocalMessageBus();
|
|
91
93
|
configureDefaultSignalsImplementation();
|
|
92
94
|
configureGenkit({
|
|
@@ -134,8 +136,9 @@ async function bootstrap() {
|
|
|
134
136
|
apiKey: config.ai.apiKey,
|
|
135
137
|
keyFile: config.ai.keyFile,
|
|
136
138
|
});
|
|
137
|
-
await runInInjectionContext(injector,
|
|
138
|
-
await runInInjectionContext(injector,
|
|
139
|
+
await runInInjectionContext(injector, migrateDocumentManagementSchema);
|
|
140
|
+
await runInInjectionContext(injector, migratePostgresCircuitBreaker);
|
|
141
|
+
await runInInjectionContext(injector, migratePostgresQueueSchema);
|
|
139
142
|
}
|
|
140
143
|
async function main() {
|
|
141
144
|
const tenantId = '00000000-0000-0000-0000-000000000000';
|
package/orm/sqls.d.ts
CHANGED
|
@@ -91,7 +91,7 @@ export declare function exclusiveNotNull(...columns: Column[]): SQL;
|
|
|
91
91
|
* that defines the default condition to apply when a `Column` is provided in `conditionMapping`.
|
|
92
92
|
* By default, it generates an `IS NOT NULL` check.
|
|
93
93
|
*/
|
|
94
|
-
export declare function enumerationCaseWhen<T extends EnumerationObject>(enumeration: T, discriminator: Column, conditionMapping: Record<EnumerationValue<T>, Column | boolean | SQL>, defaultColumnCondition?: (column: Column) => SQL<unknown>): SQL;
|
|
94
|
+
export declare function enumerationCaseWhen<T extends EnumerationObject>(enumeration: T, discriminator: Column, conditionMapping: Record<EnumerationValue<T>, Column | [Column, ...Column[]] | boolean | SQL>, defaultColumnCondition?: (column: [Column, ...Column[]]) => SQL<unknown> | undefined): SQL;
|
|
95
95
|
export declare function array<T>(values: SQL<T>[]): SQL<T[]>;
|
|
96
96
|
export declare function array<T = unknown>(values: SQLChunk[]): SQL<T[]>;
|
|
97
97
|
export declare function autoAlias<T>(column: AnyColumn<{
|
package/orm/sqls.js
CHANGED
|
@@ -68,7 +68,7 @@ export function exclusiveNotNull(...columns) {
|
|
|
68
68
|
* that defines the default condition to apply when a `Column` is provided in `conditionMapping`.
|
|
69
69
|
* By default, it generates an `IS NOT NULL` check.
|
|
70
70
|
*/
|
|
71
|
-
export function enumerationCaseWhen(enumeration, discriminator, conditionMapping, defaultColumnCondition = (column) => sqlIsNotNull(column)) {
|
|
71
|
+
export function enumerationCaseWhen(enumeration, discriminator, conditionMapping, defaultColumnCondition = (column) => isArray(column) ? and(...column.map((col) => sqlIsNotNull(col))) : sqlIsNotNull(column)) {
|
|
72
72
|
const whens = [];
|
|
73
73
|
for (const [key, value] of objectEntries(conditionMapping)) {
|
|
74
74
|
const condition = match(value)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.86",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
"peerDependencies": {
|
|
146
146
|
"@genkit-ai/google-genai": "^1.27",
|
|
147
147
|
"@google-cloud/storage": "^7.18",
|
|
148
|
-
"@google/genai": "^1.
|
|
148
|
+
"@google/genai": "^1.35",
|
|
149
149
|
"@toon-format/toon": "^2.1.0",
|
|
150
150
|
"@tstdl/angular": "^0.93",
|
|
151
151
|
"@zxcvbn-ts/core": "^3.0",
|
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
}
|
|
175
175
|
},
|
|
176
176
|
"devDependencies": {
|
|
177
|
-
"@stylistic/eslint-plugin": "5.
|
|
177
|
+
"@stylistic/eslint-plugin": "5.7",
|
|
178
178
|
"@types/koa__router": "12.0",
|
|
179
179
|
"@types/luxon": "3.7",
|
|
180
180
|
"@types/mjml": "4.7",
|