@tstdl/base 0.90.36 → 0.90.38
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/authentication.api.d.ts +78 -3
- package/authentication/authentication.api.js +26 -1
- package/authentication/client/authentication.service.d.ts +5 -1
- package/authentication/client/authentication.service.js +46 -8
- package/authentication/models/token-payload-base.model.d.ts +1 -0
- package/authentication/models/token-payload-base.model.js +5 -0
- package/authentication/models/token.model.d.ts +1 -0
- package/authentication/server/authentication-ancillary.service.d.ts +25 -0
- package/authentication/server/{authentication-token-payload.provider.js → authentication-ancillary.service.js} +1 -1
- package/authentication/server/authentication.api-controller.d.ts +4 -2
- package/authentication/server/authentication.api-controller.js +29 -8
- package/authentication/server/authentication.service.d.ts +17 -15
- package/authentication/server/authentication.service.js +50 -62
- package/authentication/server/helper.js +1 -1
- package/authentication/server/index.d.ts +1 -3
- package/authentication/server/index.js +1 -3
- package/authentication/server/module.d.ts +2 -4
- package/authentication/server/module.js +3 -7
- package/examples/api/custom-authentication.js +14 -5
- package/package.json +1 -1
- package/authentication/server/authentication-secret-reset.handler.d.ts +0 -4
- package/authentication/server/authentication-secret-reset.handler.js +0 -2
- package/authentication/server/authentication-subject.resolver.d.ts +0 -7
- package/authentication/server/authentication-subject.resolver.js +0 -6
- package/authentication/server/authentication-token-payload.provider.d.ts +0 -11
|
@@ -12,7 +12,7 @@ export type AuthenticationApiDefinition<AdditionalTokenPayload extends Record =
|
|
|
12
12
|
export declare const authenticationApiDefinition: {
|
|
13
13
|
resource: string;
|
|
14
14
|
endpoints: {
|
|
15
|
-
|
|
15
|
+
getToken: {
|
|
16
16
|
resource: string;
|
|
17
17
|
method: "POST";
|
|
18
18
|
parameters: ObjectSchema<{
|
|
@@ -38,6 +38,31 @@ export declare const authenticationApiDefinition: {
|
|
|
38
38
|
[dontWaitForValidToken]: boolean;
|
|
39
39
|
};
|
|
40
40
|
};
|
|
41
|
+
impersonate: {
|
|
42
|
+
resource: string;
|
|
43
|
+
method: "POST";
|
|
44
|
+
parameters: ObjectSchema<{
|
|
45
|
+
subject: string;
|
|
46
|
+
data: unknown;
|
|
47
|
+
}>;
|
|
48
|
+
result: ObjectSchema<TokenPayloadBase>;
|
|
49
|
+
credentials: true;
|
|
50
|
+
data: {
|
|
51
|
+
[dontWaitForValidToken]: boolean;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
unimpersonate: {
|
|
55
|
+
resource: string;
|
|
56
|
+
method: "POST";
|
|
57
|
+
parameters: ObjectSchema<{
|
|
58
|
+
data: unknown;
|
|
59
|
+
}>;
|
|
60
|
+
result: ObjectSchema<TokenPayloadBase>;
|
|
61
|
+
credentials: true;
|
|
62
|
+
data: {
|
|
63
|
+
[dontWaitForValidToken]: boolean;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
41
66
|
endSession: {
|
|
42
67
|
resource: string;
|
|
43
68
|
method: "POST";
|
|
@@ -82,7 +107,7 @@ export declare const authenticationApiDefinition: {
|
|
|
82
107
|
export declare function getAuthenticationApiDefinition<AdditionalTokenPayload extends Record, AuthenticationData, AdditionalInitSecretResetData, AdditionalEndpoints>(additionalTokenPayloadSchema: ObjectSchemaOrType<AdditionalTokenPayload>, authenticationDataSchema: SchemaTestable<AuthenticationData>, initSecretResetDataSchema: ObjectSchemaOrType<AdditionalInitSecretResetData>, resource?: string, additionalEndpoints?: AdditionalEndpoints): {
|
|
83
108
|
resource: string;
|
|
84
109
|
endpoints: {
|
|
85
|
-
|
|
110
|
+
getToken: {
|
|
86
111
|
resource: string;
|
|
87
112
|
method: "POST";
|
|
88
113
|
parameters: ObjectSchema<{
|
|
@@ -108,6 +133,31 @@ export declare function getAuthenticationApiDefinition<AdditionalTokenPayload ex
|
|
|
108
133
|
[dontWaitForValidToken]: boolean;
|
|
109
134
|
};
|
|
110
135
|
};
|
|
136
|
+
impersonate: {
|
|
137
|
+
resource: string;
|
|
138
|
+
method: "POST";
|
|
139
|
+
parameters: ObjectSchema<{
|
|
140
|
+
subject: string;
|
|
141
|
+
data: AuthenticationData;
|
|
142
|
+
}>;
|
|
143
|
+
result: ObjectSchema<TokenPayload<AdditionalTokenPayload>>;
|
|
144
|
+
credentials: true;
|
|
145
|
+
data: {
|
|
146
|
+
[dontWaitForValidToken]: boolean;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
unimpersonate: {
|
|
150
|
+
resource: string;
|
|
151
|
+
method: "POST";
|
|
152
|
+
parameters: ObjectSchema<{
|
|
153
|
+
data: AuthenticationData;
|
|
154
|
+
}>;
|
|
155
|
+
result: ObjectSchema<TokenPayload<AdditionalTokenPayload>>;
|
|
156
|
+
credentials: true;
|
|
157
|
+
data: {
|
|
158
|
+
[dontWaitForValidToken]: boolean;
|
|
159
|
+
};
|
|
160
|
+
};
|
|
111
161
|
endSession: {
|
|
112
162
|
resource: string;
|
|
113
163
|
method: "POST";
|
|
@@ -150,7 +200,7 @@ export declare function getAuthenticationApiDefinition<AdditionalTokenPayload ex
|
|
|
150
200
|
};
|
|
151
201
|
};
|
|
152
202
|
export declare function getAuthenticationApiEndpointsDefinition<AdditionalTokenPayload extends Record, AuthenticationData, AdditionalInitSecretResetData>(additionalTokenPayloadSchema: ObjectSchemaOrType<AdditionalTokenPayload>, authenticationDataSchema: SchemaTestable<AuthenticationData>, additionalInitSecretResetDataSchema: ObjectSchemaOrType<AdditionalInitSecretResetData>): {
|
|
153
|
-
|
|
203
|
+
getToken: {
|
|
154
204
|
resource: string;
|
|
155
205
|
method: "POST";
|
|
156
206
|
parameters: ObjectSchema<{
|
|
@@ -176,6 +226,31 @@ export declare function getAuthenticationApiEndpointsDefinition<AdditionalTokenP
|
|
|
176
226
|
[dontWaitForValidToken]: boolean;
|
|
177
227
|
};
|
|
178
228
|
};
|
|
229
|
+
impersonate: {
|
|
230
|
+
resource: string;
|
|
231
|
+
method: "POST";
|
|
232
|
+
parameters: ObjectSchema<{
|
|
233
|
+
subject: string;
|
|
234
|
+
data: AuthenticationData;
|
|
235
|
+
}>;
|
|
236
|
+
result: ObjectSchema<TokenPayload<AdditionalTokenPayload>>;
|
|
237
|
+
credentials: true;
|
|
238
|
+
data: {
|
|
239
|
+
[dontWaitForValidToken]: boolean;
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
unimpersonate: {
|
|
243
|
+
resource: string;
|
|
244
|
+
method: "POST";
|
|
245
|
+
parameters: ObjectSchema<{
|
|
246
|
+
data: AuthenticationData;
|
|
247
|
+
}>;
|
|
248
|
+
result: ObjectSchema<TokenPayload<AdditionalTokenPayload>>;
|
|
249
|
+
credentials: true;
|
|
250
|
+
data: {
|
|
251
|
+
[dontWaitForValidToken]: boolean;
|
|
252
|
+
};
|
|
253
|
+
};
|
|
179
254
|
endSession: {
|
|
180
255
|
resource: string;
|
|
181
256
|
method: "POST";
|
|
@@ -23,7 +23,7 @@ export function getAuthenticationApiDefinition(additionalTokenPayloadSchema, aut
|
|
|
23
23
|
export function getAuthenticationApiEndpointsDefinition(additionalTokenPayloadSchema, authenticationDataSchema, additionalInitSecretResetDataSchema) {
|
|
24
24
|
const tokenResultSchema = assign(TokenPayloadBase, additionalTokenPayloadSchema);
|
|
25
25
|
return {
|
|
26
|
-
|
|
26
|
+
getToken: {
|
|
27
27
|
resource: 'token',
|
|
28
28
|
method: 'POST',
|
|
29
29
|
parameters: explicitObject({
|
|
@@ -49,6 +49,31 @@ export function getAuthenticationApiEndpointsDefinition(additionalTokenPayloadSc
|
|
|
49
49
|
[dontWaitForValidToken]: true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
impersonate: {
|
|
53
|
+
resource: 'impersonate',
|
|
54
|
+
method: 'POST',
|
|
55
|
+
parameters: explicitObject({
|
|
56
|
+
subject: string(),
|
|
57
|
+
data: authenticationDataSchema
|
|
58
|
+
}),
|
|
59
|
+
result: tokenResultSchema,
|
|
60
|
+
credentials: true,
|
|
61
|
+
data: {
|
|
62
|
+
[dontWaitForValidToken]: true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
unimpersonate: {
|
|
66
|
+
resource: 'unimpersonate',
|
|
67
|
+
method: 'POST',
|
|
68
|
+
parameters: explicitObject({
|
|
69
|
+
data: authenticationDataSchema
|
|
70
|
+
}),
|
|
71
|
+
result: tokenResultSchema,
|
|
72
|
+
credentials: true,
|
|
73
|
+
data: {
|
|
74
|
+
[dontWaitForValidToken]: true
|
|
75
|
+
}
|
|
76
|
+
},
|
|
52
77
|
endSession: {
|
|
53
78
|
resource: 'end-session',
|
|
54
79
|
method: 'POST',
|
|
@@ -10,7 +10,7 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
|
|
|
10
10
|
private readonly tokenUpdateBus;
|
|
11
11
|
private readonly loggedOutBus;
|
|
12
12
|
private readonly forceRefreshToken;
|
|
13
|
-
private readonly
|
|
13
|
+
private readonly lock;
|
|
14
14
|
private readonly logger;
|
|
15
15
|
private readonly disposeToken;
|
|
16
16
|
readonly error$: import("rxjs").Observable<Error>;
|
|
@@ -29,6 +29,8 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
|
|
|
29
29
|
readonly loggedOut$: import("rxjs").Observable<void>;
|
|
30
30
|
private get authenticationData();
|
|
31
31
|
private set authenticationData(value);
|
|
32
|
+
private get impersonatorAuthenticationData();
|
|
33
|
+
private set impersonatorAuthenticationData(value);
|
|
32
34
|
get definedToken(): TokenPayload<AdditionalTokenPayload>;
|
|
33
35
|
get definedSubject(): string;
|
|
34
36
|
get definedSessionId(): string;
|
|
@@ -43,6 +45,8 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
|
|
|
43
45
|
logout(): Promise<void>;
|
|
44
46
|
requestRefresh(data?: AuthenticationData): void;
|
|
45
47
|
refresh(data?: AuthenticationData): Promise<void>;
|
|
48
|
+
impersonate(subject: string, data?: AuthenticationData): Promise<void>;
|
|
49
|
+
unimpersonate(data?: AuthenticationData): Promise<void>;
|
|
46
50
|
initResetSecret(subject: string, data: AdditionalInitSecretResetData): Promise<void>;
|
|
47
51
|
resetSecret(token: string, newSecret: string): Promise<void>;
|
|
48
52
|
checkSecret(secret: string): Promise<SecretCheckResult>;
|
|
@@ -30,6 +30,7 @@ import { assertDefinedPass, isDefined, isNullOrUndefined, isString, isUndefined
|
|
|
30
30
|
import { AUTHENTICATION_API_CLIENT, INITIAL_AUTHENTICATION_DATA } from './tokens.js';
|
|
31
31
|
const tokenStorageKey = 'AuthenticationService:token';
|
|
32
32
|
const authenticationDataStorageKey = 'AuthenticationService:authentication-data';
|
|
33
|
+
const impersonatorAuthenticationDataStorageKey = 'AuthenticationService:impersonator-authentication-data';
|
|
33
34
|
const tokenUpdateBusName = 'AuthenticationService:tokenUpdate';
|
|
34
35
|
const loggedOutBusName = 'AuthenticationService:loggedOut';
|
|
35
36
|
const refreshLockResource = 'AuthenticationService:refresh';
|
|
@@ -39,7 +40,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
39
40
|
tokenUpdateBus = inject((MessageBus), tokenUpdateBusName);
|
|
40
41
|
loggedOutBus = inject((MessageBus), loggedOutBusName);
|
|
41
42
|
forceRefreshToken = new CancellationToken();
|
|
42
|
-
|
|
43
|
+
lock = inject(Lock, refreshLockResource);
|
|
43
44
|
logger = inject(Logger, 'AuthenticationService');
|
|
44
45
|
disposeToken = new CancellationToken();
|
|
45
46
|
error$ = this.errorSubject.asObservable();
|
|
@@ -69,6 +70,19 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
69
70
|
localStorage.setItem(authenticationDataStorageKey, json);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
73
|
+
get impersonatorAuthenticationData() {
|
|
74
|
+
const data = localStorage.getItem(impersonatorAuthenticationDataStorageKey);
|
|
75
|
+
return isNullOrUndefined(data) ? undefined : JSON.parse(data);
|
|
76
|
+
}
|
|
77
|
+
set impersonatorAuthenticationData(data) {
|
|
78
|
+
if (isUndefined(data)) {
|
|
79
|
+
localStorage.removeItem(impersonatorAuthenticationDataStorageKey);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
const json = JSON.stringify(data);
|
|
83
|
+
localStorage.setItem(impersonatorAuthenticationDataStorageKey, json);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
72
86
|
get definedToken() {
|
|
73
87
|
return assertDefinedPass(this.token(), 'No token available.');
|
|
74
88
|
}
|
|
@@ -110,7 +124,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
110
124
|
if (isDefined(data)) {
|
|
111
125
|
this.setAdditionalData(data);
|
|
112
126
|
}
|
|
113
|
-
const token = await this.client.
|
|
127
|
+
const token = await this.client.getToken({ subject, secret, data: this.authenticationData });
|
|
114
128
|
this.setNewToken(token);
|
|
115
129
|
}
|
|
116
130
|
async logout() {
|
|
@@ -144,6 +158,35 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
144
158
|
throw error;
|
|
145
159
|
}
|
|
146
160
|
}
|
|
161
|
+
async impersonate(subject, data) {
|
|
162
|
+
await this.lock.use(10000, true, async () => {
|
|
163
|
+
this.impersonatorAuthenticationData = this.authenticationData;
|
|
164
|
+
this.authenticationData = data;
|
|
165
|
+
try {
|
|
166
|
+
const token = await this.client.impersonate({ subject, data: data });
|
|
167
|
+
this.setNewToken(token);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
await this.handleRefreshError(error);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async unimpersonate(data) {
|
|
176
|
+
await this.lock.use(10000, true, async () => {
|
|
177
|
+
const newData = data ?? this.impersonatorAuthenticationData;
|
|
178
|
+
try {
|
|
179
|
+
const token = await this.client.unimpersonate({ data: newData });
|
|
180
|
+
this.authenticationData = newData;
|
|
181
|
+
this.impersonatorAuthenticationData = undefined;
|
|
182
|
+
this.setNewToken(token);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
await this.handleRefreshError(error);
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
147
190
|
async initResetSecret(subject, data) {
|
|
148
191
|
await this.client.initSecretReset({ subject, data });
|
|
149
192
|
}
|
|
@@ -180,12 +223,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
180
223
|
async refreshLoop() {
|
|
181
224
|
while (this.disposeToken.isUnset) {
|
|
182
225
|
try {
|
|
183
|
-
|
|
184
|
-
await this.refreshLock.use(0, false, async () => this.refreshLoopIteration());
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
await this.refreshLoopIteration();
|
|
188
|
-
}
|
|
226
|
+
await this.lock.use(0, false, async () => this.refreshLoopIteration());
|
|
189
227
|
await firstValueFrom(race([timer(2500), this.disposeToken, this.forceRefreshToken]));
|
|
190
228
|
}
|
|
191
229
|
catch {
|
|
@@ -19,6 +19,7 @@ export class TokenPayloadBase {
|
|
|
19
19
|
refreshTokenExp;
|
|
20
20
|
sessionId;
|
|
21
21
|
subject;
|
|
22
|
+
impersonator;
|
|
22
23
|
}
|
|
23
24
|
__decorate([
|
|
24
25
|
Property(),
|
|
@@ -44,3 +45,7 @@ __decorate([
|
|
|
44
45
|
Property(),
|
|
45
46
|
__metadata("design:type", String)
|
|
46
47
|
], TokenPayloadBase.prototype, "subject", void 0);
|
|
48
|
+
__decorate([
|
|
49
|
+
Property({ optional: true }),
|
|
50
|
+
__metadata("design:type", String)
|
|
51
|
+
], TokenPayloadBase.prototype, "impersonator", void 0);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Record } from '../../types.js';
|
|
2
|
+
import type { TokenPayload } from '../index.js';
|
|
3
|
+
import type { InitSecretResetData } from '../models/init-secret-reset-data.model.js';
|
|
4
|
+
export declare enum GetTokenPayloadContextAction {
|
|
5
|
+
GetToken = 0,
|
|
6
|
+
Refresh = 1
|
|
7
|
+
}
|
|
8
|
+
export type GetTokenPayloadContext = {
|
|
9
|
+
action: GetTokenPayloadContextAction;
|
|
10
|
+
};
|
|
11
|
+
export declare abstract class AuthenticationAncillaryService<AdditionalTokenPayload extends Record = Record<never>, AuthenticationData = void, AdditionalInitSecretResetData extends Record = Record<never>> {
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a provided subject to the actual subject used for authentication.
|
|
14
|
+
* Useful for example if you want to be able to login via mail but actual subject is the user id.
|
|
15
|
+
*/
|
|
16
|
+
abstract resolveSubject(providedSubject: string): string | Promise<string>;
|
|
17
|
+
abstract getTokenPayload(subject: string, authenticationData: AuthenticationData, context: GetTokenPayloadContext): AdditionalTokenPayload | Promise<AdditionalTokenPayload>;
|
|
18
|
+
abstract handleInitSecretReset(data: InitSecretResetData & AdditionalInitSecretResetData): void | Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Check if token is allowed to impersonate subject
|
|
21
|
+
* @param token Token which tries to impersonate
|
|
22
|
+
* @param subject Subject to impersonate
|
|
23
|
+
*/
|
|
24
|
+
abstract canImpersonate(token: TokenPayload<AdditionalTokenPayload>, subject: string, authenticationData: AuthenticationData): boolean | Promise<boolean>;
|
|
25
|
+
}
|
|
@@ -3,5 +3,5 @@ export var GetTokenPayloadContextAction;
|
|
|
3
3
|
GetTokenPayloadContextAction[GetTokenPayloadContextAction["GetToken"] = 0] = "GetToken";
|
|
4
4
|
GetTokenPayloadContextAction[GetTokenPayloadContextAction["Refresh"] = 1] = "Refresh";
|
|
5
5
|
})(GetTokenPayloadContextAction || (GetTokenPayloadContextAction = {}));
|
|
6
|
-
export class
|
|
6
|
+
export class AuthenticationAncillaryService {
|
|
7
7
|
}
|
|
@@ -8,14 +8,16 @@ import { AuthenticationService } from './authentication.service.js';
|
|
|
8
8
|
export declare class AuthenticationApiController<AdditionalTokenPayload extends Record, AuthenticationData, AdditionalInitSecretResetData extends Record> implements ApiController<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>> {
|
|
9
9
|
readonly authenticationService: AuthenticationService<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>;
|
|
10
10
|
constructor(authenticationService: AuthenticationService<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>);
|
|
11
|
-
|
|
11
|
+
getToken({ parameters }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'getToken'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'getToken'>>;
|
|
12
12
|
refresh({ request, parameters }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'refresh'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'refresh'>>;
|
|
13
|
+
impersonate({ request, parameters }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'impersonate'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'impersonate'>>;
|
|
14
|
+
unimpersonate({ request, parameters }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'unimpersonate'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'unimpersonate'>>;
|
|
13
15
|
endSession({ request }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'endSession'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'endSession'>>;
|
|
14
16
|
initSecretReset({ parameters }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'initSecretReset'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'initSecretReset'>>;
|
|
15
17
|
resetSecret({ parameters }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'resetSecret'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'resetSecret'>>;
|
|
16
18
|
checkSecret({ parameters }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'checkSecret'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'checkSecret'>>;
|
|
17
19
|
timestamp(): ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'timestamp'>;
|
|
18
|
-
protected getTokenResponse({ token, jsonToken, refreshToken }: TokenResult<AdditionalTokenPayload>): HttpServerResponse;
|
|
20
|
+
protected getTokenResponse({ token, jsonToken, refreshToken, omitImpersonatorRefreshToken, impersonatorRefreshToken, impersonatorRefreshTokenExpiration }: TokenResult<AdditionalTokenPayload>): HttpServerResponse;
|
|
19
21
|
}
|
|
20
22
|
export declare function getAuthenticationApiController<AdditionalTokenPayload extends Record, AuthenticationData, AdditionalInitSecretResetData extends Record>(// eslint-disable-line @typescript-eslint/explicit-function-return-type
|
|
21
23
|
additionalTokenPayloadSchema: ObjectSchemaOrType<AdditionalTokenPayload>, authenticationDataSchema: SchemaTestable<AuthenticationData>, additionalInitSecretResetData: ObjectSchemaOrType<AdditionalInitSecretResetData>): Type<AuthenticationApiController<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>>;
|
|
@@ -11,16 +11,18 @@ import { apiController } from '../../api/server/index.js';
|
|
|
11
11
|
import { InvalidCredentialsError } from '../../errors/invalid-credentials.error.js';
|
|
12
12
|
import { HttpServerResponse } from '../../http/server/index.js';
|
|
13
13
|
import { currentTimestamp } from '../../utils/date-time.js';
|
|
14
|
+
import { assertDefinedPass, isDefined } from '../../utils/type-guards.js';
|
|
14
15
|
import { authenticationApiDefinition, getAuthenticationApiDefinition } from '../authentication.api.js';
|
|
15
16
|
import { AuthenticationService } from './authentication.service.js';
|
|
16
17
|
import { tryGetAuthorizationTokenStringFromRequest } from './helper.js';
|
|
17
18
|
const cookieBaseOptions = { path: '/', httpOnly: true, secure: true, sameSite: 'strict' };
|
|
19
|
+
const deleteCookie = { value: '', ...cookieBaseOptions, expires: -1 };
|
|
18
20
|
let AuthenticationApiController = class AuthenticationApiController {
|
|
19
21
|
authenticationService;
|
|
20
22
|
constructor(authenticationService) {
|
|
21
23
|
this.authenticationService = authenticationService;
|
|
22
24
|
}
|
|
23
|
-
async
|
|
25
|
+
async getToken({ parameters }) {
|
|
24
26
|
const authenticationResult = await this.authenticationService.authenticate(parameters.subject, parameters.secret);
|
|
25
27
|
if (!authenticationResult.success) {
|
|
26
28
|
throw new InvalidCredentialsError();
|
|
@@ -29,8 +31,19 @@ let AuthenticationApiController = class AuthenticationApiController {
|
|
|
29
31
|
return this.getTokenResponse(result);
|
|
30
32
|
}
|
|
31
33
|
async refresh({ request, parameters }) {
|
|
32
|
-
const
|
|
33
|
-
const result = await this.authenticationService.refresh(
|
|
34
|
+
const refreshTokenString = tryGetAuthorizationTokenStringFromRequest(request, 'refreshToken') ?? '';
|
|
35
|
+
const result = await this.authenticationService.refresh(refreshTokenString, parameters.data);
|
|
36
|
+
return this.getTokenResponse(result);
|
|
37
|
+
}
|
|
38
|
+
async impersonate({ request, parameters }) {
|
|
39
|
+
const tokenString = tryGetAuthorizationTokenStringFromRequest(request) ?? '';
|
|
40
|
+
const refreshTokenString = tryGetAuthorizationTokenStringFromRequest(request, 'refreshToken') ?? '';
|
|
41
|
+
const impersonatorResult = await this.authenticationService.impersonate(tokenString, refreshTokenString, parameters.subject, parameters.data);
|
|
42
|
+
return this.getTokenResponse(impersonatorResult);
|
|
43
|
+
}
|
|
44
|
+
async unimpersonate({ request, parameters }) {
|
|
45
|
+
const impersonatorRefreshTokenString = tryGetAuthorizationTokenStringFromRequest(request, 'impersonatorRefreshToken') ?? '';
|
|
46
|
+
const result = await this.authenticationService.refresh(impersonatorRefreshTokenString, parameters.data, { omitImpersonator: true });
|
|
34
47
|
return this.getTokenResponse(result);
|
|
35
48
|
}
|
|
36
49
|
async endSession({ request }) {
|
|
@@ -54,8 +67,9 @@ let AuthenticationApiController = class AuthenticationApiController {
|
|
|
54
67
|
const result = 'ok';
|
|
55
68
|
return new HttpServerResponse({
|
|
56
69
|
cookies: {
|
|
57
|
-
authorization:
|
|
58
|
-
refreshToken:
|
|
70
|
+
authorization: deleteCookie,
|
|
71
|
+
refreshToken: deleteCookie,
|
|
72
|
+
impersonatorRefreshToken: deleteCookie
|
|
59
73
|
},
|
|
60
74
|
body: {
|
|
61
75
|
json: result
|
|
@@ -76,9 +90,9 @@ let AuthenticationApiController = class AuthenticationApiController {
|
|
|
76
90
|
timestamp() {
|
|
77
91
|
return currentTimestamp();
|
|
78
92
|
}
|
|
79
|
-
getTokenResponse({ token, jsonToken, refreshToken }) {
|
|
93
|
+
getTokenResponse({ token, jsonToken, refreshToken, omitImpersonatorRefreshToken, impersonatorRefreshToken, impersonatorRefreshTokenExpiration }) {
|
|
80
94
|
const result = jsonToken.payload;
|
|
81
|
-
|
|
95
|
+
const options = {
|
|
82
96
|
cookies: {
|
|
83
97
|
authorization: { value: `Bearer ${token}`, ...cookieBaseOptions, expires: jsonToken.payload.exp * 1000 },
|
|
84
98
|
refreshToken: { value: `Bearer ${refreshToken}`, ...cookieBaseOptions, expires: jsonToken.payload.refreshTokenExp * 1000 }
|
|
@@ -86,7 +100,14 @@ let AuthenticationApiController = class AuthenticationApiController {
|
|
|
86
100
|
body: {
|
|
87
101
|
json: result
|
|
88
102
|
}
|
|
89
|
-
}
|
|
103
|
+
};
|
|
104
|
+
if (isDefined(impersonatorRefreshToken)) {
|
|
105
|
+
options.cookies['impersonatorRefreshToken'] = { value: `Bearer ${refreshToken}`, ...cookieBaseOptions, expires: assertDefinedPass(impersonatorRefreshTokenExpiration) * 1000 };
|
|
106
|
+
}
|
|
107
|
+
if (omitImpersonatorRefreshToken == true) {
|
|
108
|
+
options.cookies['impersonatorRefreshToken'] = deleteCookie;
|
|
109
|
+
}
|
|
110
|
+
return new HttpServerResponse(options);
|
|
90
111
|
}
|
|
91
112
|
};
|
|
92
113
|
AuthenticationApiController = __decorate([
|
|
@@ -2,13 +2,7 @@ import type { AfterResolve } from '../../injector/index.js';
|
|
|
2
2
|
import { afterResolve } from '../../injector/index.js';
|
|
3
3
|
import type { Record } from '../../types.js';
|
|
4
4
|
import type { RefreshToken, SecretCheckResult, SecretResetToken, Token } from '../models/index.js';
|
|
5
|
-
import { AuthenticationCredentialsRepository } from './authentication-credentials.repository.js';
|
|
6
5
|
import type { SecretTestResult } from './authentication-secret-requirements.validator.js';
|
|
7
|
-
import { AuthenticationSecretRequirementsValidator } from './authentication-secret-requirements.validator.js';
|
|
8
|
-
import { AuthenticationSecretResetHandler } from './authentication-secret-reset.handler.js';
|
|
9
|
-
import { AuthenticationSessionRepository } from './authentication-session.repository.js';
|
|
10
|
-
import { AuthenticationSubjectResolver } from './authentication-subject.resolver.js';
|
|
11
|
-
import { AuthenticationTokenPayloadProvider } from './authentication-token-payload.provider.js';
|
|
12
6
|
export type CreateTokenData<AdditionalTokenPayload extends Record> = {
|
|
13
7
|
tokenVersion?: number;
|
|
14
8
|
jwtId?: string;
|
|
@@ -17,6 +11,7 @@ export type CreateTokenData<AdditionalTokenPayload extends Record> = {
|
|
|
17
11
|
additionalTokenPayload: AdditionalTokenPayload;
|
|
18
12
|
subject: string;
|
|
19
13
|
sessionId: string;
|
|
14
|
+
impersonator: string | undefined;
|
|
20
15
|
refreshTokenExpiration: number;
|
|
21
16
|
timestamp?: number;
|
|
22
17
|
};
|
|
@@ -50,6 +45,9 @@ export type TokenResult<AdditionalTokenPayload extends Record> = {
|
|
|
50
45
|
token: string;
|
|
51
46
|
jsonToken: Token<AdditionalTokenPayload>;
|
|
52
47
|
refreshToken: string;
|
|
48
|
+
omitImpersonatorRefreshToken?: boolean;
|
|
49
|
+
impersonatorRefreshToken?: string;
|
|
50
|
+
impersonatorRefreshTokenExpiration?: number;
|
|
53
51
|
};
|
|
54
52
|
export type SetCredentialsOptions = {
|
|
55
53
|
/** skip validation for password strength */
|
|
@@ -69,11 +67,8 @@ export declare class AuthenticationService<AdditionalTokenPayload extends Record
|
|
|
69
67
|
private readonly credentialsRepository;
|
|
70
68
|
private readonly sessionRepository;
|
|
71
69
|
private readonly authenticationSecretRequirementsValidator;
|
|
72
|
-
private readonly
|
|
73
|
-
private readonly subjectResolver;
|
|
74
|
-
private readonly authenticationResetSecretHandler;
|
|
70
|
+
private readonly authenticationAncillaryService;
|
|
75
71
|
private readonly options;
|
|
76
|
-
private readonly secret;
|
|
77
72
|
private readonly tokenVersion;
|
|
78
73
|
private readonly tokenTimeToLive;
|
|
79
74
|
private readonly refreshTokenTimeToLive;
|
|
@@ -81,14 +76,19 @@ export declare class AuthenticationService<AdditionalTokenPayload extends Record
|
|
|
81
76
|
private derivedTokenSigningSecret;
|
|
82
77
|
private derivedRefreshTokenSigningSecret;
|
|
83
78
|
private derivedSecretResetTokenSigningSecret;
|
|
84
|
-
constructor(credentialsRepository: AuthenticationCredentialsRepository, sessionRepository: AuthenticationSessionRepository, authenticationSecretRequirementsValidator: AuthenticationSecretRequirementsValidator, subjectResolver: AuthenticationSubjectResolver | undefined, tokenPayloadProvider: AuthenticationTokenPayloadProvider<AdditionalTokenPayload, AuthenticationData> | undefined, authenticationResetSecretHandler: AuthenticationSecretResetHandler<AdditionalInitSecretResetData> | undefined, options: AuthenticationServiceOptions);
|
|
85
79
|
[afterResolve](): Promise<void>;
|
|
86
80
|
initialize(): Promise<void>;
|
|
87
81
|
setCredentials(subject: string, secret: string, options?: SetCredentialsOptions): Promise<void>;
|
|
88
82
|
authenticate(subject: string, secret: string): Promise<AuthenticationResult>;
|
|
89
|
-
getToken(subject: string, authenticationData: AuthenticationData
|
|
83
|
+
getToken(subject: string, authenticationData: AuthenticationData, { impersonator }?: {
|
|
84
|
+
impersonator?: string;
|
|
85
|
+
}): Promise<TokenResult<AdditionalTokenPayload>>;
|
|
90
86
|
endSession(sessionId: string): Promise<void>;
|
|
91
|
-
refresh(refreshToken: string, authenticationData: AuthenticationData
|
|
87
|
+
refresh(refreshToken: string, authenticationData: AuthenticationData, { omitImpersonator }?: {
|
|
88
|
+
omitImpersonator?: boolean;
|
|
89
|
+
}): Promise<TokenResult<AdditionalTokenPayload>>;
|
|
90
|
+
impersonate(impersonatorRoken: string, impersonatorRefreshToken: string, subject: string, authenticationData: AuthenticationData): Promise<TokenResult<AdditionalTokenPayload>>;
|
|
91
|
+
unimpersonate(impersonatorRefreshToken: string, authenticationData: AuthenticationData): Promise<TokenResult<AdditionalTokenPayload>>;
|
|
92
92
|
initSecretReset(subject: string, data: AdditionalInitSecretResetData): Promise<void>;
|
|
93
93
|
resetSecret(tokenString: string, newSecret: string): Promise<void>;
|
|
94
94
|
checkSecret(secret: string): Promise<SecretCheckResult>;
|
|
@@ -99,9 +99,11 @@ export declare class AuthenticationService<AdditionalTokenPayload extends Record
|
|
|
99
99
|
validateSecretResetToken(token: string): Promise<SecretResetToken>;
|
|
100
100
|
resolveSubject(subject: string): Promise<string>;
|
|
101
101
|
/** Creates a token without session or refresh token and is not saved in database */
|
|
102
|
-
createToken({ tokenVersion, jwtId, issuedAt, expiration, additionalTokenPayload, subject, sessionId, refreshTokenExpiration, timestamp }: CreateTokenData<AdditionalTokenPayload>): Promise<CreateTokenResult<AdditionalTokenPayload>>;
|
|
102
|
+
createToken({ tokenVersion, jwtId, issuedAt, expiration, additionalTokenPayload, subject, sessionId, refreshTokenExpiration, impersonator: impersonatedBy, timestamp }: CreateTokenData<AdditionalTokenPayload>): Promise<CreateTokenResult<AdditionalTokenPayload>>;
|
|
103
103
|
/** Creates a refresh token without session or something else. */
|
|
104
|
-
createRefreshToken(subject: string, sessionId: string, expirationTimestamp: number
|
|
104
|
+
createRefreshToken(subject: string, sessionId: string, expirationTimestamp: number, options?: {
|
|
105
|
+
impersonator?: string;
|
|
106
|
+
}): Promise<CreateRefreshTokenResult>;
|
|
105
107
|
private createSecretResetToken;
|
|
106
108
|
private deriveSigningSecrets;
|
|
107
109
|
private getHash;
|
|
@@ -4,15 +4,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
};
|
|
10
|
-
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
-
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
-
};
|
|
7
|
+
import { ForbiddenError } from '../../errors/forbidden.error.js';
|
|
13
8
|
import { InvalidTokenError } from '../../errors/invalid-token.error.js';
|
|
14
9
|
import { NotImplementedError } from '../../errors/not-implemented.error.js';
|
|
15
|
-
import {
|
|
10
|
+
import { Singleton, afterResolve, inject } from '../../injector/index.js';
|
|
16
11
|
import { Alphabet } from '../../utils/alphabet.js';
|
|
17
12
|
import { deriveBytesMultiple, importPbkdf2Key } from '../../utils/cryptography.js';
|
|
18
13
|
import { currentTimestamp, timestampToTimestampSeconds } from '../../utils/date-time.js';
|
|
@@ -21,12 +16,10 @@ import { createJwtTokenString } from '../../utils/jwt.js';
|
|
|
21
16
|
import { getRandomBytes, getRandomString } from '../../utils/random.js';
|
|
22
17
|
import { isBinaryData, isString, isUndefined } from '../../utils/type-guards.js';
|
|
23
18
|
import { millisecondsPerDay, millisecondsPerMinute } from '../../utils/units.js';
|
|
19
|
+
import { AuthenticationAncillaryService, GetTokenPayloadContextAction } from './authentication-ancillary.service.js';
|
|
24
20
|
import { AuthenticationCredentialsRepository } from './authentication-credentials.repository.js';
|
|
25
21
|
import { AuthenticationSecretRequirementsValidator } from './authentication-secret-requirements.validator.js';
|
|
26
|
-
import { AuthenticationSecretResetHandler } from './authentication-secret-reset.handler.js';
|
|
27
22
|
import { AuthenticationSessionRepository } from './authentication-session.repository.js';
|
|
28
|
-
import { AuthenticationSubjectResolver } from './authentication-subject.resolver.js';
|
|
29
|
-
import { AuthenticationTokenPayloadProvider, GetTokenPayloadContextAction } from './authentication-token-payload.provider.js';
|
|
30
23
|
import { getRefreshTokenFromString, getSecretResetTokenFromString, getTokenFromString } from './helper.js';
|
|
31
24
|
export class AuthenticationServiceOptions {
|
|
32
25
|
/**
|
|
@@ -45,34 +38,18 @@ export class AuthenticationServiceOptions {
|
|
|
45
38
|
}
|
|
46
39
|
const SIGNING_SECRETS_LENGTH = 64;
|
|
47
40
|
let AuthenticationService = class AuthenticationService {
|
|
48
|
-
credentialsRepository;
|
|
49
|
-
sessionRepository;
|
|
50
|
-
authenticationSecretRequirementsValidator;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
options;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
tokenTimeToLive;
|
|
58
|
-
refreshTokenTimeToLive;
|
|
59
|
-
secretResetTokenTimeToLive;
|
|
41
|
+
credentialsRepository = inject(AuthenticationCredentialsRepository);
|
|
42
|
+
sessionRepository = inject(AuthenticationSessionRepository);
|
|
43
|
+
authenticationSecretRequirementsValidator = inject(AuthenticationSecretRequirementsValidator);
|
|
44
|
+
authenticationAncillaryService = inject(AuthenticationAncillaryService, undefined, { optional: true });
|
|
45
|
+
options = inject(AuthenticationServiceOptions);
|
|
46
|
+
tokenVersion = this.options.version ?? 1;
|
|
47
|
+
tokenTimeToLive = this.options.tokenTimeToLive ?? (5 * millisecondsPerMinute);
|
|
48
|
+
refreshTokenTimeToLive = this.options.refreshTokenTimeToLive ?? (5 * millisecondsPerDay);
|
|
49
|
+
secretResetTokenTimeToLive = this.options.secretResetTokenTimeToLive ?? (10 * millisecondsPerMinute);
|
|
60
50
|
derivedTokenSigningSecret;
|
|
61
51
|
derivedRefreshTokenSigningSecret;
|
|
62
52
|
derivedSecretResetTokenSigningSecret;
|
|
63
|
-
constructor(credentialsRepository, sessionRepository, authenticationSecretRequirementsValidator, subjectResolver, tokenPayloadProvider, authenticationResetSecretHandler, options) {
|
|
64
|
-
this.credentialsRepository = credentialsRepository;
|
|
65
|
-
this.sessionRepository = sessionRepository;
|
|
66
|
-
this.authenticationSecretRequirementsValidator = authenticationSecretRequirementsValidator;
|
|
67
|
-
this.subjectResolver = subjectResolver;
|
|
68
|
-
this.tokenPayloadProvider = tokenPayloadProvider;
|
|
69
|
-
this.authenticationResetSecretHandler = authenticationResetSecretHandler;
|
|
70
|
-
this.options = options;
|
|
71
|
-
this.tokenVersion = options.version ?? 1;
|
|
72
|
-
this.tokenTimeToLive = options.tokenTimeToLive ?? (5 * millisecondsPerMinute);
|
|
73
|
-
this.refreshTokenTimeToLive = options.refreshTokenTimeToLive ?? (5 * millisecondsPerDay);
|
|
74
|
-
this.secretResetTokenTimeToLive = options.secretResetTokenTimeToLive ?? (10 * millisecondsPerMinute);
|
|
75
|
-
}
|
|
76
53
|
async [afterResolve]() {
|
|
77
54
|
await this.initialize();
|
|
78
55
|
}
|
|
@@ -114,7 +91,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
114
91
|
}
|
|
115
92
|
return { success: false };
|
|
116
93
|
}
|
|
117
|
-
async getToken(subject, authenticationData) {
|
|
94
|
+
async getToken(subject, authenticationData, { impersonator } = {}) {
|
|
118
95
|
const actualSubject = await this.resolveSubject(subject);
|
|
119
96
|
const now = currentTimestamp();
|
|
120
97
|
const end = now + this.refreshTokenTimeToLive;
|
|
@@ -126,9 +103,9 @@ let AuthenticationService = class AuthenticationService {
|
|
|
126
103
|
refreshTokenSalt: new Uint8Array(),
|
|
127
104
|
refreshTokenHash: new Uint8Array()
|
|
128
105
|
});
|
|
129
|
-
const tokenPayload = await this.
|
|
130
|
-
const { token, jsonToken } = await this.createToken({ additionalTokenPayload: tokenPayload, subject: actualSubject, sessionId: session.id, refreshTokenExpiration: end, timestamp: now });
|
|
131
|
-
const refreshToken = await this.createRefreshToken(actualSubject, session.id, end);
|
|
106
|
+
const tokenPayload = await this.authenticationAncillaryService?.getTokenPayload(actualSubject, authenticationData, { action: GetTokenPayloadContextAction.GetToken });
|
|
107
|
+
const { token, jsonToken } = await this.createToken({ additionalTokenPayload: tokenPayload, subject: actualSubject, impersonator, sessionId: session.id, refreshTokenExpiration: end, timestamp: now });
|
|
108
|
+
const refreshToken = await this.createRefreshToken(actualSubject, session.id, end, { impersonator });
|
|
132
109
|
await this.sessionRepository.extend(session.id, {
|
|
133
110
|
end,
|
|
134
111
|
refreshTokenHashVersion: 1,
|
|
@@ -141,11 +118,11 @@ let AuthenticationService = class AuthenticationService {
|
|
|
141
118
|
const now = currentTimestamp();
|
|
142
119
|
await this.sessionRepository.end(sessionId, now);
|
|
143
120
|
}
|
|
144
|
-
async refresh(refreshToken, authenticationData) {
|
|
145
|
-
const
|
|
146
|
-
const sessionId =
|
|
121
|
+
async refresh(refreshToken, authenticationData, { omitImpersonator = false } = {}) {
|
|
122
|
+
const validatedRefreshToken = await this.validateRefreshToken(refreshToken);
|
|
123
|
+
const sessionId = validatedRefreshToken.payload.sessionId;
|
|
147
124
|
const session = await this.sessionRepository.load(sessionId);
|
|
148
|
-
const hash = await this.getHash(
|
|
125
|
+
const hash = await this.getHash(validatedRefreshToken.payload.secret, session.refreshTokenSalt);
|
|
149
126
|
if (session.end <= currentTimestamp()) {
|
|
150
127
|
throw new InvalidTokenError('Session is expired.');
|
|
151
128
|
}
|
|
@@ -153,20 +130,38 @@ let AuthenticationService = class AuthenticationService {
|
|
|
153
130
|
throw new InvalidTokenError('Invalid refresh token.');
|
|
154
131
|
}
|
|
155
132
|
const now = currentTimestamp();
|
|
133
|
+
const impersonator = omitImpersonator ? undefined : validatedRefreshToken.payload.impersonator;
|
|
156
134
|
const newEnd = now + this.refreshTokenTimeToLive;
|
|
157
|
-
const tokenPayload = await this.
|
|
158
|
-
const { token, jsonToken } = await this.createToken({ additionalTokenPayload: tokenPayload, subject: session.subject, sessionId, refreshTokenExpiration: newEnd, timestamp: now });
|
|
159
|
-
const newRefreshToken = await this.createRefreshToken(
|
|
135
|
+
const tokenPayload = await this.authenticationAncillaryService?.getTokenPayload(session.subject, authenticationData, { action: GetTokenPayloadContextAction.Refresh });
|
|
136
|
+
const { token, jsonToken } = await this.createToken({ additionalTokenPayload: tokenPayload, subject: session.subject, sessionId, refreshTokenExpiration: newEnd, impersonator, timestamp: now });
|
|
137
|
+
const newRefreshToken = await this.createRefreshToken(validatedRefreshToken.payload.subject, sessionId, newEnd, { impersonator });
|
|
160
138
|
await this.sessionRepository.extend(sessionId, {
|
|
161
139
|
end: newEnd,
|
|
162
140
|
refreshTokenHashVersion: 1,
|
|
163
141
|
refreshTokenSalt: newRefreshToken.salt,
|
|
164
142
|
refreshTokenHash: newRefreshToken.hash
|
|
165
143
|
});
|
|
166
|
-
return { token, jsonToken, refreshToken: newRefreshToken.token };
|
|
144
|
+
return { token, jsonToken, refreshToken: newRefreshToken.token, omitImpersonatorRefreshToken: omitImpersonator };
|
|
145
|
+
}
|
|
146
|
+
async impersonate(impersonatorRoken, impersonatorRefreshToken, subject, authenticationData) {
|
|
147
|
+
const validatedImpersonatorRoken = await this.validateToken(impersonatorRoken);
|
|
148
|
+
const validatedImpersonatorRefreshToken = await this.validateRefreshToken(impersonatorRefreshToken);
|
|
149
|
+
const allowed = await this.authenticationAncillaryService?.canImpersonate(validatedImpersonatorRoken.payload, subject, authenticationData) ?? false;
|
|
150
|
+
if (!allowed) {
|
|
151
|
+
throw new ForbiddenError('Impersonation forbidden.');
|
|
152
|
+
}
|
|
153
|
+
const tokenResult = await this.getToken(subject, authenticationData, { impersonator: validatedImpersonatorRoken.payload.subject });
|
|
154
|
+
return {
|
|
155
|
+
...tokenResult,
|
|
156
|
+
impersonatorRefreshToken,
|
|
157
|
+
impersonatorRefreshTokenExpiration: validatedImpersonatorRefreshToken.payload.exp
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async unimpersonate(impersonatorRefreshToken, authenticationData) {
|
|
161
|
+
return this.refresh(impersonatorRefreshToken, authenticationData, { omitImpersonator: true });
|
|
167
162
|
}
|
|
168
163
|
async initSecretReset(subject, data) {
|
|
169
|
-
if (isUndefined(this.
|
|
164
|
+
if (isUndefined(this.authenticationAncillaryService)) {
|
|
170
165
|
throw new NotImplementedError();
|
|
171
166
|
}
|
|
172
167
|
const actualSubject = await this.resolveSubject(subject);
|
|
@@ -176,7 +171,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
176
171
|
token: secretResetToken.token,
|
|
177
172
|
...data
|
|
178
173
|
};
|
|
179
|
-
await this.
|
|
174
|
+
await this.authenticationAncillaryService.handleInitSecretReset(initSecretResetData);
|
|
180
175
|
}
|
|
181
176
|
async resetSecret(tokenString, newSecret) {
|
|
182
177
|
const token = await this.validateSecretResetToken(tokenString);
|
|
@@ -201,10 +196,10 @@ let AuthenticationService = class AuthenticationService {
|
|
|
201
196
|
return getSecretResetTokenFromString(token, this.derivedSecretResetTokenSigningSecret);
|
|
202
197
|
}
|
|
203
198
|
async resolveSubject(subject) {
|
|
204
|
-
return this.
|
|
199
|
+
return this.authenticationAncillaryService?.resolveSubject(subject) ?? subject;
|
|
205
200
|
}
|
|
206
201
|
/** Creates a token without session or refresh token and is not saved in database */
|
|
207
|
-
async createToken({ tokenVersion, jwtId, issuedAt, expiration, additionalTokenPayload, subject, sessionId, refreshTokenExpiration, timestamp = currentTimestamp() }) {
|
|
202
|
+
async createToken({ tokenVersion, jwtId, issuedAt, expiration, additionalTokenPayload, subject, sessionId, refreshTokenExpiration, impersonator: impersonatedBy, timestamp = currentTimestamp() }) {
|
|
208
203
|
const header = {
|
|
209
204
|
v: tokenVersion ?? this.tokenVersion,
|
|
210
205
|
alg: 'HS256',
|
|
@@ -217,6 +212,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
217
212
|
refreshTokenExp: timestampToTimestampSeconds(refreshTokenExpiration),
|
|
218
213
|
sessionId,
|
|
219
214
|
subject,
|
|
215
|
+
impersonator: impersonatedBy,
|
|
220
216
|
...additionalTokenPayload
|
|
221
217
|
};
|
|
222
218
|
const jsonToken = {
|
|
@@ -227,7 +223,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
227
223
|
return { token, jsonToken };
|
|
228
224
|
}
|
|
229
225
|
/** Creates a refresh token without session or something else. */
|
|
230
|
-
async createRefreshToken(subject, sessionId, expirationTimestamp) {
|
|
226
|
+
async createRefreshToken(subject, sessionId, expirationTimestamp, options) {
|
|
231
227
|
const secret = getRandomString(64, Alphabet.LowerUpperCaseNumbers);
|
|
232
228
|
const salt = getRandomBytes(32);
|
|
233
229
|
const hash = await this.getHash(secret, salt);
|
|
@@ -239,6 +235,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
239
235
|
payload: {
|
|
240
236
|
exp: timestampToTimestampSeconds(expirationTimestamp),
|
|
241
237
|
subject,
|
|
238
|
+
impersonator: options?.impersonator,
|
|
242
239
|
sessionId,
|
|
243
240
|
secret
|
|
244
241
|
}
|
|
@@ -275,15 +272,6 @@ let AuthenticationService = class AuthenticationService {
|
|
|
275
272
|
}
|
|
276
273
|
};
|
|
277
274
|
AuthenticationService = __decorate([
|
|
278
|
-
Singleton()
|
|
279
|
-
__param(3, Inject(AuthenticationSubjectResolver)),
|
|
280
|
-
__param(3, Optional()),
|
|
281
|
-
__param(4, Inject(AuthenticationTokenPayloadProvider)),
|
|
282
|
-
__param(4, Optional()),
|
|
283
|
-
__param(5, Inject(AuthenticationSecretResetHandler)),
|
|
284
|
-
__param(5, Optional()),
|
|
285
|
-
__metadata("design:paramtypes", [AuthenticationCredentialsRepository,
|
|
286
|
-
AuthenticationSessionRepository,
|
|
287
|
-
AuthenticationSecretRequirementsValidator, Object, Object, Object, AuthenticationServiceOptions])
|
|
275
|
+
Singleton()
|
|
288
276
|
], AuthenticationService);
|
|
289
277
|
export { AuthenticationService };
|
|
@@ -46,7 +46,7 @@ export async function getTokenFromString(tokenString, tokenVersion, secret) {
|
|
|
46
46
|
if (validatedToken.header.v != tokenVersion) {
|
|
47
47
|
throw new InvalidTokenError('Invalid token version');
|
|
48
48
|
}
|
|
49
|
-
if (
|
|
49
|
+
if (validatedToken.payload.exp <= currentTimestampSeconds()) {
|
|
50
50
|
throw new InvalidTokenError('Token expired');
|
|
51
51
|
}
|
|
52
52
|
return validatedToken;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
export * from './authentication-ancillary.service.js';
|
|
1
2
|
export * from './authentication-api-request-token.provider.js';
|
|
2
3
|
export * from './authentication-credentials.repository.js';
|
|
3
4
|
export * from './authentication-secret-requirements.validator.js';
|
|
4
|
-
export * from './authentication-secret-reset.handler.js';
|
|
5
5
|
export * from './authentication-session.repository.js';
|
|
6
|
-
export * from './authentication-subject.resolver.js';
|
|
7
|
-
export * from './authentication-token-payload.provider.js';
|
|
8
6
|
export * from './authentication.api-controller.js';
|
|
9
7
|
export * from './authentication.service.js';
|
|
10
8
|
export * from './helper.js';
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
export * from './authentication-ancillary.service.js';
|
|
1
2
|
export * from './authentication-api-request-token.provider.js';
|
|
2
3
|
export * from './authentication-credentials.repository.js';
|
|
3
4
|
export * from './authentication-secret-requirements.validator.js';
|
|
4
|
-
export * from './authentication-secret-reset.handler.js';
|
|
5
5
|
export * from './authentication-session.repository.js';
|
|
6
|
-
export * from './authentication-subject.resolver.js';
|
|
7
|
-
export * from './authentication-token-payload.provider.js';
|
|
8
6
|
export * from './authentication.api-controller.js';
|
|
9
7
|
export * from './authentication.service.js';
|
|
10
8
|
export * from './helper.js';
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { Provider } from '../../injector/provider.js';
|
|
2
2
|
import type { InjectionToken } from '../../injector/token.js';
|
|
3
|
+
import { AuthenticationAncillaryService } from './authentication-ancillary.service.js';
|
|
3
4
|
import { AuthenticationCredentialsRepository } from './authentication-credentials.repository.js';
|
|
4
5
|
import { AuthenticationSessionRepository } from './authentication-session.repository.js';
|
|
5
|
-
import { AuthenticationSubjectResolver } from './authentication-subject.resolver.js';
|
|
6
|
-
import { AuthenticationTokenPayloadProvider } from './authentication-token-payload.provider.js';
|
|
7
6
|
import { AuthenticationService, AuthenticationServiceOptions } from './authentication.service.js';
|
|
8
7
|
export type AuthenticationModuleConfig = {
|
|
9
8
|
serviceOptions?: AuthenticationServiceOptions | Provider<AuthenticationServiceOptions>;
|
|
@@ -11,7 +10,6 @@ export type AuthenticationModuleConfig = {
|
|
|
11
10
|
sessionRepository: InjectionToken<AuthenticationSessionRepository>;
|
|
12
11
|
/** override default AuthenticationService */
|
|
13
12
|
authenticationService?: InjectionToken<AuthenticationService<any, any, any>>;
|
|
14
|
-
|
|
15
|
-
subjectResolver?: InjectionToken<AuthenticationSubjectResolver>;
|
|
13
|
+
authenticationAncillaryService?: InjectionToken<AuthenticationAncillaryService<any, any, any>>;
|
|
16
14
|
};
|
|
17
15
|
export declare function configureAuthenticationServer(config: AuthenticationModuleConfig): void;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Injector } from '../../injector/injector.js';
|
|
2
2
|
import { isProvider } from '../../injector/provider.js';
|
|
3
3
|
import { isDefined } from '../../utils/type-guards.js';
|
|
4
|
+
import { AuthenticationAncillaryService } from './authentication-ancillary.service.js';
|
|
4
5
|
import { AuthenticationCredentialsRepository } from './authentication-credentials.repository.js';
|
|
5
6
|
import { AuthenticationSessionRepository } from './authentication-session.repository.js';
|
|
6
|
-
import { AuthenticationSubjectResolver } from './authentication-subject.resolver.js';
|
|
7
|
-
import { AuthenticationTokenPayloadProvider } from './authentication-token-payload.provider.js';
|
|
8
7
|
import { AuthenticationService, AuthenticationServiceOptions } from './authentication.service.js';
|
|
9
8
|
export function configureAuthenticationServer(config) {
|
|
10
9
|
if (isDefined(config.serviceOptions)) {
|
|
@@ -18,10 +17,7 @@ export function configureAuthenticationServer(config) {
|
|
|
18
17
|
if (isDefined(config.authenticationService)) {
|
|
19
18
|
Injector.registerSingleton(AuthenticationService, { useToken: config.authenticationService });
|
|
20
19
|
}
|
|
21
|
-
if (isDefined(config.
|
|
22
|
-
Injector.registerSingleton(
|
|
23
|
-
}
|
|
24
|
-
if (isDefined(config.subjectResolver)) {
|
|
25
|
-
Injector.registerSingleton(AuthenticationSubjectResolver, { useToken: config.subjectResolver });
|
|
20
|
+
if (isDefined(config.authenticationAncillaryService)) {
|
|
21
|
+
Injector.registerSingleton(AuthenticationAncillaryService, { useToken: config.authenticationAncillaryService });
|
|
26
22
|
}
|
|
27
23
|
}
|
|
@@ -13,7 +13,7 @@ import { Agent } from 'undici';
|
|
|
13
13
|
import { configureApiServer } from '../../api/server/index.js';
|
|
14
14
|
import { Application } from '../../application/application.js';
|
|
15
15
|
import { AuthenticationClientService, configureAuthenticationClient, getAuthenticationApiClient } from '../../authentication/client/index.js';
|
|
16
|
-
import {
|
|
16
|
+
import { AuthenticationAncillaryService } from '../../authentication/index.js';
|
|
17
17
|
import { AuthenticationApiController } from '../../authentication/server/authentication.api-controller.js';
|
|
18
18
|
import { AuthenticationService as AuthenticationServerService } from '../../authentication/server/authentication.service.js';
|
|
19
19
|
import { configureAuthenticationServer } from '../../authentication/server/module.js';
|
|
@@ -68,14 +68,23 @@ __decorate([
|
|
|
68
68
|
__metadata("design:type", String)
|
|
69
69
|
], AuthenticationData.prototype, "deviceId", void 0);
|
|
70
70
|
const CustomAuthenticationApiClient = getAuthenticationApiClient(CustomTokenPaylod, AuthenticationData, emptyObjectSchema);
|
|
71
|
-
let
|
|
71
|
+
let CustomAuthenticationAncillaryService = class CustomAuthenticationAncillaryService extends AuthenticationAncillaryService {
|
|
72
72
|
getTokenPayload(_subject, authenticationData) {
|
|
73
73
|
return { deviceRegistrationId: `registration:${authenticationData.deviceId}` };
|
|
74
74
|
}
|
|
75
|
+
resolveSubject() {
|
|
76
|
+
throw new Error('Method not implemented.');
|
|
77
|
+
}
|
|
78
|
+
handleInitSecretReset() {
|
|
79
|
+
throw new Error('Method not implemented.');
|
|
80
|
+
}
|
|
81
|
+
canImpersonate() {
|
|
82
|
+
throw new Error('Method not implemented.');
|
|
83
|
+
}
|
|
75
84
|
};
|
|
76
|
-
|
|
85
|
+
CustomAuthenticationAncillaryService = __decorate([
|
|
77
86
|
Singleton()
|
|
78
|
-
],
|
|
87
|
+
], CustomAuthenticationAncillaryService);
|
|
79
88
|
async function serverTest() {
|
|
80
89
|
const authenticationService = await injectAsync(AuthenticationServerService);
|
|
81
90
|
await authenticationService.setCredentials('foobar', 'supersecret-dupidupudoo9275');
|
|
@@ -101,7 +110,7 @@ function bootstrap() {
|
|
|
101
110
|
serviceOptions: { secret: 'djp0fq23576aq' },
|
|
102
111
|
credentialsRepository: MongoAuthenticationCredentialsRepository,
|
|
103
112
|
sessionRepository: MongoAuthenticationSessionRepository,
|
|
104
|
-
|
|
113
|
+
authenticationAncillaryService: CustomAuthenticationAncillaryService
|
|
105
114
|
});
|
|
106
115
|
configureMongoAuthenticationCredentialsRepository({ collection: 'credentials' });
|
|
107
116
|
configureMongoAuthenticationSessionRepository({ collection: 'sessions' });
|
package/package.json
CHANGED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { InitSecretResetData } from '../models/init-secret-reset-data.model.js';
|
|
2
|
-
export declare abstract class AuthenticationSecretResetHandler<AdditionalInitSecretResetData> {
|
|
3
|
-
abstract handleInitSecretReset(data: InitSecretResetData & AdditionalInitSecretResetData): void | Promise<void>;
|
|
4
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve a provided subject to the actual subject used for authentication.
|
|
3
|
-
* Useful for example if you want to be able to login via mail but actual subject is the user id.
|
|
4
|
-
*/
|
|
5
|
-
export declare abstract class AuthenticationSubjectResolver {
|
|
6
|
-
abstract resolveSubject(providedSubject: string): string | Promise<string>;
|
|
7
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Record } from '../../types.js';
|
|
2
|
-
export declare enum GetTokenPayloadContextAction {
|
|
3
|
-
GetToken = 0,
|
|
4
|
-
Refresh = 1
|
|
5
|
-
}
|
|
6
|
-
export type GetTokenPayloadContext = {
|
|
7
|
-
action: GetTokenPayloadContextAction;
|
|
8
|
-
};
|
|
9
|
-
export declare abstract class AuthenticationTokenPayloadProvider<AdditionalTokenPayload = Record<never>, AuthenticationData = void> {
|
|
10
|
-
abstract getTokenPayload(subject: string, authenticationData: AuthenticationData, context: GetTokenPayloadContext): AdditionalTokenPayload | Promise<AdditionalTokenPayload>;
|
|
11
|
-
}
|