auth-vir 2.6.0 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-client/backend-auth.client.js +22 -0
- package/dist/auth-client/frontend-auth.client.js +10 -0
- package/dist/auth.js +23 -2
- package/dist/csrf-token.js +6 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/log.d.ts +12 -0
- package/dist/log.js +20 -0
- package/package.json +1 -1
- package/src/auth-client/backend-auth.client.ts +29 -0
- package/src/auth-client/frontend-auth.client.ts +10 -0
- package/src/auth.ts +35 -2
- package/src/csrf-token.ts +6 -0
- package/src/index.ts +1 -0
- package/src/log.ts +22 -0
|
@@ -4,6 +4,7 @@ import { extractUserIdFromRequestHeaders, generateLogoutHeaders, generateSuccess
|
|
|
4
4
|
import { AuthCookieName } from '../cookie.js';
|
|
5
5
|
import { AuthHeaderName, mergeHeaderValues } from '../headers.js';
|
|
6
6
|
import { parseJwtKeys } from '../jwt/jwt-keys.js';
|
|
7
|
+
import { authLog } from '../log.js';
|
|
7
8
|
const defaultSessionIdleTimeout = {
|
|
8
9
|
minutes: 20,
|
|
9
10
|
};
|
|
@@ -63,6 +64,10 @@ export class BackendAuthClient {
|
|
|
63
64
|
relativeTo: userIdResult.jwtExpiration,
|
|
64
65
|
});
|
|
65
66
|
if (isExpiredAlready) {
|
|
67
|
+
authLog('auth-vir: SESSION EXPIRED - JWT already expired, user will be logged out', {
|
|
68
|
+
userId: userIdResult.userId,
|
|
69
|
+
jwtExpiration: userIdResult.jwtExpiration,
|
|
70
|
+
});
|
|
66
71
|
return undefined;
|
|
67
72
|
}
|
|
68
73
|
/**
|
|
@@ -78,6 +83,11 @@ export class BackendAuthClient {
|
|
|
78
83
|
relativeTo: maxSessionEndDate,
|
|
79
84
|
});
|
|
80
85
|
if (isSessionExpired) {
|
|
86
|
+
authLog('auth-vir: SESSION EXPIRED - max session duration exceeded, user will be logged out', {
|
|
87
|
+
userId: userIdResult.userId,
|
|
88
|
+
sessionStartedAt: userIdResult.sessionStartedAt,
|
|
89
|
+
maxSessionDuration,
|
|
90
|
+
});
|
|
81
91
|
return undefined;
|
|
82
92
|
}
|
|
83
93
|
}
|
|
@@ -136,6 +146,7 @@ export class BackendAuthClient {
|
|
|
136
146
|
async getSecureUser({ requestHeaders, isSignUpCookie, allowUserAuthRefresh, }) {
|
|
137
147
|
const userIdResult = await extractUserIdFromRequestHeaders(requestHeaders, await this.getJwtParams(), isSignUpCookie ? AuthCookieName.SignUp : AuthCookieName.Auth, this.config.overrides);
|
|
138
148
|
if (!userIdResult) {
|
|
149
|
+
authLog('auth-vir: getSecureUser failed - could not extract user from request');
|
|
139
150
|
return undefined;
|
|
140
151
|
}
|
|
141
152
|
const user = await this.getDatabaseUser({
|
|
@@ -144,6 +155,9 @@ export class BackendAuthClient {
|
|
|
144
155
|
isSignUpCookie,
|
|
145
156
|
});
|
|
146
157
|
if (!user) {
|
|
158
|
+
authLog('auth-vir: getSecureUser failed - user not found in database', {
|
|
159
|
+
userId: userIdResult.userId,
|
|
160
|
+
});
|
|
147
161
|
return undefined;
|
|
148
162
|
}
|
|
149
163
|
const assumedUser = await this.getAssumedUser({
|
|
@@ -181,6 +195,10 @@ export class BackendAuthClient {
|
|
|
181
195
|
}
|
|
182
196
|
/** Use these headers to log out the user. */
|
|
183
197
|
async createLogoutHeaders(params) {
|
|
198
|
+
authLog('auth-vir: LOGOUT - BackendAuthClient.createLogoutHeaders called', {
|
|
199
|
+
allCookies: 'allCookies' in params ? params.allCookies : undefined,
|
|
200
|
+
isSignUpCookie: 'isSignUpCookie' in params ? params.isSignUpCookie : undefined,
|
|
201
|
+
}, new Error().stack);
|
|
184
202
|
const signUpCookieHeaders = params.allCookies || params.isSignUpCookie
|
|
185
203
|
? generateLogoutHeaders(await this.getCookieParams({
|
|
186
204
|
isSignUpCookie: true,
|
|
@@ -250,6 +268,7 @@ export class BackendAuthClient {
|
|
|
250
268
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
251
269
|
const userIdResult = await insecureExtractUserIdFromCookieAlone(requestHeaders, await this.getJwtParams(), AuthCookieName.Auth);
|
|
252
270
|
if (!userIdResult) {
|
|
271
|
+
authLog('auth-vir: getInsecureUser failed - could not extract user from request');
|
|
253
272
|
return undefined;
|
|
254
273
|
}
|
|
255
274
|
const user = await this.getDatabaseUser({
|
|
@@ -258,6 +277,9 @@ export class BackendAuthClient {
|
|
|
258
277
|
assumingUser: undefined,
|
|
259
278
|
});
|
|
260
279
|
if (!user) {
|
|
280
|
+
authLog('auth-vir: getInsecureUser failed - user not found in database', {
|
|
281
|
+
userId: userIdResult.userId,
|
|
282
|
+
});
|
|
261
283
|
return undefined;
|
|
262
284
|
}
|
|
263
285
|
const refreshHeaders = allowUserAuthRefresh &&
|
|
@@ -2,6 +2,7 @@ import { HttpStatus, } from '@augment-vir/common';
|
|
|
2
2
|
import { listenToActivity } from 'detect-activity';
|
|
3
3
|
import { CsrfTokenFailureReason, extractCsrfTokenHeader, getCurrentCsrfToken, storeCsrfToken, wipeCurrentCsrfToken, } from '../csrf-token.js';
|
|
4
4
|
import { AuthHeaderName } from '../headers.js';
|
|
5
|
+
import { authLog } from '../log.js';
|
|
5
6
|
/**
|
|
6
7
|
* An auth client for sending and validating client requests to a backend. This should only be used
|
|
7
8
|
* in a frontend environment as it accesses native browser APIs.
|
|
@@ -44,6 +45,9 @@ export class FrontendAuthClient {
|
|
|
44
45
|
const csrfTokenResult = getCurrentCsrfToken(this.config.overrides);
|
|
45
46
|
if (csrfTokenResult.failure &&
|
|
46
47
|
csrfTokenResult.failure !== CsrfTokenFailureReason.DoesNotExist) {
|
|
48
|
+
authLog('auth-vir: LOGOUT - getCurrentCsrfToken: invalid CSRF token', {
|
|
49
|
+
failure: csrfTokenResult.failure,
|
|
50
|
+
});
|
|
47
51
|
await this.logout();
|
|
48
52
|
return undefined;
|
|
49
53
|
}
|
|
@@ -110,6 +114,7 @@ export class FrontendAuthClient {
|
|
|
110
114
|
}
|
|
111
115
|
/** Wipes the current user auth. */
|
|
112
116
|
async logout() {
|
|
117
|
+
authLog('auth-vir: LOGOUT - FrontendAuthClient.logout called', new Error().stack);
|
|
113
118
|
await this.config.authClearedCallback?.();
|
|
114
119
|
wipeCurrentCsrfToken(this.config.overrides);
|
|
115
120
|
}
|
|
@@ -121,11 +126,13 @@ export class FrontendAuthClient {
|
|
|
121
126
|
*/
|
|
122
127
|
async handleLoginResponse(response) {
|
|
123
128
|
if (!response.ok) {
|
|
129
|
+
authLog('auth-vir: LOGOUT - handleLoginResponse: response not ok');
|
|
124
130
|
await this.logout();
|
|
125
131
|
throw new Error('Login response failed.');
|
|
126
132
|
}
|
|
127
133
|
const { csrfToken } = extractCsrfTokenHeader(response, this.config.overrides);
|
|
128
134
|
if (!csrfToken) {
|
|
135
|
+
authLog('auth-vir: LOGOUT - handleLoginResponse: no CSRF token in response');
|
|
129
136
|
await this.logout();
|
|
130
137
|
throw new Error('Did not receive any CSRF token.');
|
|
131
138
|
}
|
|
@@ -140,6 +147,9 @@ export class FrontendAuthClient {
|
|
|
140
147
|
async verifyResponseAuth(response) {
|
|
141
148
|
if (response.status === HttpStatus.Unauthorized &&
|
|
142
149
|
!response.headers?.get(AuthHeaderName.IsSignUpAuth)) {
|
|
150
|
+
authLog('auth-vir: LOGOUT - verifyResponseAuth: unauthorized response (401)', {
|
|
151
|
+
status: response.status,
|
|
152
|
+
});
|
|
143
153
|
await this.logout();
|
|
144
154
|
return false;
|
|
145
155
|
}
|
package/dist/auth.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AuthCookieName, clearAuthCookie, extractCookieJwt, generateAuthCookie, } from './cookie.js';
|
|
2
2
|
import { extractCsrfTokenHeader, generateCsrfToken, parseCsrfToken, storeCsrfToken, wipeCurrentCsrfToken, } from './csrf-token.js';
|
|
3
3
|
import { AuthHeaderName } from './headers.js';
|
|
4
|
+
import { authLog } from './log.js';
|
|
4
5
|
function readHeader(headers, headerName) {
|
|
5
6
|
if (headers instanceof Headers) {
|
|
6
7
|
return headers.get(headerName) || undefined;
|
|
@@ -38,10 +39,20 @@ export async function extractUserIdFromRequestHeaders(headers, jwtParams, cookie
|
|
|
38
39
|
const csrfToken = readCsrfTokenHeader(headers, overrides);
|
|
39
40
|
const cookie = readHeader(headers, 'cookie');
|
|
40
41
|
if (!cookie || !csrfToken) {
|
|
42
|
+
authLog('auth-vir: extractUserIdFromRequestHeaders failed - missing cookie or CSRF token', {
|
|
43
|
+
hasCookie: !!cookie,
|
|
44
|
+
hasCsrfToken: !!csrfToken,
|
|
45
|
+
cookieName,
|
|
46
|
+
});
|
|
41
47
|
return undefined;
|
|
42
48
|
}
|
|
43
49
|
const jwt = await extractCookieJwt(cookie, jwtParams, cookieName);
|
|
44
50
|
if (!jwt || jwt.data.csrfToken !== csrfToken) {
|
|
51
|
+
authLog('auth-vir: extractUserIdFromRequestHeaders failed - JWT invalid or CSRF mismatch', {
|
|
52
|
+
hasJwt: !!jwt,
|
|
53
|
+
csrfMatch: jwt ? jwt.data.csrfToken === csrfToken : false,
|
|
54
|
+
cookieName,
|
|
55
|
+
});
|
|
45
56
|
return undefined;
|
|
46
57
|
}
|
|
47
58
|
return {
|
|
@@ -51,7 +62,8 @@ export async function extractUserIdFromRequestHeaders(headers, jwtParams, cookie
|
|
|
51
62
|
sessionStartedAt: jwt.data.sessionStartedAt,
|
|
52
63
|
};
|
|
53
64
|
}
|
|
54
|
-
catch {
|
|
65
|
+
catch (error) {
|
|
66
|
+
authLog('auth-vir: extractUserIdFromRequestHeaders error', { error, cookieName });
|
|
55
67
|
return undefined;
|
|
56
68
|
}
|
|
57
69
|
}
|
|
@@ -67,10 +79,12 @@ export async function insecureExtractUserIdFromCookieAlone(headers, jwtParams, c
|
|
|
67
79
|
try {
|
|
68
80
|
const cookie = readHeader(headers, 'cookie');
|
|
69
81
|
if (!cookie) {
|
|
82
|
+
authLog('auth-vir: insecureExtractUserIdFromCookieAlone failed - no cookie');
|
|
70
83
|
return undefined;
|
|
71
84
|
}
|
|
72
85
|
const jwt = await extractCookieJwt(cookie, jwtParams, cookieName);
|
|
73
86
|
if (!jwt) {
|
|
87
|
+
authLog('auth-vir: insecureExtractUserIdFromCookieAlone failed - JWT extraction failed');
|
|
74
88
|
return undefined;
|
|
75
89
|
}
|
|
76
90
|
return {
|
|
@@ -80,7 +94,8 @@ export async function insecureExtractUserIdFromCookieAlone(headers, jwtParams, c
|
|
|
80
94
|
sessionStartedAt: jwt.data.sessionStartedAt,
|
|
81
95
|
};
|
|
82
96
|
}
|
|
83
|
-
catch {
|
|
97
|
+
catch (error) {
|
|
98
|
+
authLog('auth-vir: insecureExtractUserIdFromCookieAlone error', { error });
|
|
84
99
|
return undefined;
|
|
85
100
|
}
|
|
86
101
|
}
|
|
@@ -115,6 +130,9 @@ sessionStartedAt) {
|
|
|
115
130
|
* @category Auth : Host
|
|
116
131
|
*/
|
|
117
132
|
export function generateLogoutHeaders(cookieConfig, overrides = {}) {
|
|
133
|
+
authLog('auth-vir: LOGOUT - generateLogoutHeaders called', {
|
|
134
|
+
cookieName: cookieConfig.cookieName,
|
|
135
|
+
}, new Error().stack);
|
|
118
136
|
const csrfHeaderName = (overrides.csrfHeaderName || AuthHeaderName.CsrfToken);
|
|
119
137
|
return {
|
|
120
138
|
'set-cookie': clearAuthCookie(cookieConfig),
|
|
@@ -132,13 +150,16 @@ export function generateLogoutHeaders(cookieConfig, overrides = {}) {
|
|
|
132
150
|
*/
|
|
133
151
|
export function handleAuthResponse(response, overrides = {}) {
|
|
134
152
|
if (!response.ok) {
|
|
153
|
+
authLog('auth-vir: LOGOUT - handleAuthResponse: response not ok, wiping CSRF token');
|
|
135
154
|
wipeCurrentCsrfToken(overrides);
|
|
136
155
|
return;
|
|
137
156
|
}
|
|
138
157
|
const { csrfToken } = extractCsrfTokenHeader(response, overrides);
|
|
139
158
|
if (!csrfToken) {
|
|
159
|
+
authLog('auth-vir: LOGOUT - handleAuthResponse: no CSRF token in response, wiping');
|
|
140
160
|
wipeCurrentCsrfToken(overrides);
|
|
141
161
|
throw new Error('Did not receive any CSRF token.');
|
|
142
162
|
}
|
|
163
|
+
authLog('auth-vir: handleAuthResponse - successfully stored CSRF token');
|
|
143
164
|
storeCsrfToken(csrfToken, overrides);
|
|
144
165
|
}
|
package/dist/csrf-token.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomString, wrapInTry, } from '@augment-vir/common';
|
|
|
2
2
|
import { calculateRelativeDate, fullDateShape, getNowInUtcTimezone, isDateAfter, } from 'date-vir';
|
|
3
3
|
import { defineShape, parseJsonWithShape } from 'object-shape-tester';
|
|
4
4
|
import { AuthHeaderName } from './headers.js';
|
|
5
|
+
import { authLog } from './log.js';
|
|
5
6
|
/**
|
|
6
7
|
* Shape definition for {@link CsrfToken}.
|
|
7
8
|
*
|
|
@@ -74,6 +75,7 @@ export function parseCsrfToken(value) {
|
|
|
74
75
|
fallbackValue: undefined,
|
|
75
76
|
});
|
|
76
77
|
if (!csrfToken) {
|
|
78
|
+
authLog('auth-vir: CSRF token parse failed - will cause logout if used');
|
|
77
79
|
return {
|
|
78
80
|
failure: CsrfTokenFailureReason.ParseFailed,
|
|
79
81
|
};
|
|
@@ -82,6 +84,9 @@ export function parseCsrfToken(value) {
|
|
|
82
84
|
fullDate: getNowInUtcTimezone(),
|
|
83
85
|
relativeTo: csrfToken.expiration,
|
|
84
86
|
})) {
|
|
87
|
+
authLog('auth-vir: CSRF token expired - will cause logout', {
|
|
88
|
+
expiration: csrfToken.expiration,
|
|
89
|
+
});
|
|
85
90
|
return {
|
|
86
91
|
failure: CsrfTokenFailureReason.Expired,
|
|
87
92
|
};
|
|
@@ -107,5 +112,6 @@ export function getCurrentCsrfToken(overrides = {}) {
|
|
|
107
112
|
* @category Auth : Client
|
|
108
113
|
*/
|
|
109
114
|
export function wipeCurrentCsrfToken(overrides = {}) {
|
|
115
|
+
authLog('auth-vir: wipeCurrentCsrfToken called', new Error().stack);
|
|
110
116
|
return (overrides.localStorage || globalThis.localStorage).removeItem(overrides.csrfHeaderName || AuthHeaderName.CsrfToken);
|
|
111
117
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send logs to the console for debugging.
|
|
3
|
+
*
|
|
4
|
+
* @category Internal
|
|
5
|
+
*/
|
|
6
|
+
export declare function authLog(...params: any[]): void;
|
|
7
|
+
/**
|
|
8
|
+
* Set to `false` to disable logging.
|
|
9
|
+
*
|
|
10
|
+
* @category Internal
|
|
11
|
+
*/
|
|
12
|
+
export declare function setShouldLogAuth(value: boolean): void;
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send logs to the console for debugging.
|
|
3
|
+
*
|
|
4
|
+
* @category Internal
|
|
5
|
+
*/
|
|
6
|
+
export function authLog(...params) {
|
|
7
|
+
if (!shouldLogAuth) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
console.info(...params);
|
|
11
|
+
}
|
|
12
|
+
let shouldLogAuth = true;
|
|
13
|
+
/**
|
|
14
|
+
* Set to `false` to disable logging.
|
|
15
|
+
*
|
|
16
|
+
* @category Internal
|
|
17
|
+
*/
|
|
18
|
+
export function setShouldLogAuth(value) {
|
|
19
|
+
shouldLogAuth = value;
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -26,6 +26,7 @@ import {AuthCookieName, type CookieParams} from '../cookie.js';
|
|
|
26
26
|
import {AuthHeaderName, mergeHeaderValues} from '../headers.js';
|
|
27
27
|
import {generateNewJwtKeys, parseJwtKeys, type JwtKeys, type RawJwtKeys} from '../jwt/jwt-keys.js';
|
|
28
28
|
import {type CreateJwtParams} from '../jwt/jwt.js';
|
|
29
|
+
import {authLog} from '../log.js';
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Output from `BackendAuthClient.getSecureUser()`.
|
|
@@ -260,6 +261,10 @@ export class BackendAuthClient<
|
|
|
260
261
|
});
|
|
261
262
|
|
|
262
263
|
if (isExpiredAlready) {
|
|
264
|
+
authLog('auth-vir: SESSION EXPIRED - JWT already expired, user will be logged out', {
|
|
265
|
+
userId: userIdResult.userId,
|
|
266
|
+
jwtExpiration: userIdResult.jwtExpiration,
|
|
267
|
+
});
|
|
263
268
|
return undefined;
|
|
264
269
|
}
|
|
265
270
|
|
|
@@ -277,6 +282,14 @@ export class BackendAuthClient<
|
|
|
277
282
|
});
|
|
278
283
|
|
|
279
284
|
if (isSessionExpired) {
|
|
285
|
+
authLog(
|
|
286
|
+
'auth-vir: SESSION EXPIRED - max session duration exceeded, user will be logged out',
|
|
287
|
+
{
|
|
288
|
+
userId: userIdResult.userId,
|
|
289
|
+
sessionStartedAt: userIdResult.sessionStartedAt,
|
|
290
|
+
maxSessionDuration,
|
|
291
|
+
},
|
|
292
|
+
);
|
|
280
293
|
return undefined;
|
|
281
294
|
}
|
|
282
295
|
}
|
|
@@ -375,6 +388,7 @@ export class BackendAuthClient<
|
|
|
375
388
|
this.config.overrides,
|
|
376
389
|
);
|
|
377
390
|
if (!userIdResult) {
|
|
391
|
+
authLog('auth-vir: getSecureUser failed - could not extract user from request');
|
|
378
392
|
return undefined;
|
|
379
393
|
}
|
|
380
394
|
|
|
@@ -385,6 +399,9 @@ export class BackendAuthClient<
|
|
|
385
399
|
});
|
|
386
400
|
|
|
387
401
|
if (!user) {
|
|
402
|
+
authLog('auth-vir: getSecureUser failed - user not found in database', {
|
|
403
|
+
userId: userIdResult.userId,
|
|
404
|
+
});
|
|
388
405
|
return undefined;
|
|
389
406
|
}
|
|
390
407
|
|
|
@@ -445,6 +462,14 @@ export class BackendAuthClient<
|
|
|
445
462
|
'set-cookie': string[];
|
|
446
463
|
}
|
|
447
464
|
> {
|
|
465
|
+
authLog(
|
|
466
|
+
'auth-vir: LOGOUT - BackendAuthClient.createLogoutHeaders called',
|
|
467
|
+
{
|
|
468
|
+
allCookies: 'allCookies' in params ? params.allCookies : undefined,
|
|
469
|
+
isSignUpCookie: 'isSignUpCookie' in params ? params.isSignUpCookie : undefined,
|
|
470
|
+
},
|
|
471
|
+
new Error().stack,
|
|
472
|
+
);
|
|
448
473
|
const signUpCookieHeaders =
|
|
449
474
|
params.allCookies || params.isSignUpCookie
|
|
450
475
|
? (generateLogoutHeaders(
|
|
@@ -601,6 +626,7 @@ export class BackendAuthClient<
|
|
|
601
626
|
);
|
|
602
627
|
|
|
603
628
|
if (!userIdResult) {
|
|
629
|
+
authLog('auth-vir: getInsecureUser failed - could not extract user from request');
|
|
604
630
|
return undefined;
|
|
605
631
|
}
|
|
606
632
|
|
|
@@ -611,6 +637,9 @@ export class BackendAuthClient<
|
|
|
611
637
|
});
|
|
612
638
|
|
|
613
639
|
if (!user) {
|
|
640
|
+
authLog('auth-vir: getInsecureUser failed - user not found in database', {
|
|
641
|
+
userId: userIdResult.userId,
|
|
642
|
+
});
|
|
614
643
|
return undefined;
|
|
615
644
|
}
|
|
616
645
|
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
wipeCurrentCsrfToken,
|
|
18
18
|
} from '../csrf-token.js';
|
|
19
19
|
import {AuthHeaderName} from '../headers.js';
|
|
20
|
+
import {authLog} from '../log.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Config for {@link FrontendAuthClient}.
|
|
@@ -119,6 +120,9 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
|
|
|
119
120
|
csrfTokenResult.failure &&
|
|
120
121
|
csrfTokenResult.failure !== CsrfTokenFailureReason.DoesNotExist
|
|
121
122
|
) {
|
|
123
|
+
authLog('auth-vir: LOGOUT - getCurrentCsrfToken: invalid CSRF token', {
|
|
124
|
+
failure: csrfTokenResult.failure,
|
|
125
|
+
});
|
|
122
126
|
await this.logout();
|
|
123
127
|
return undefined;
|
|
124
128
|
} else {
|
|
@@ -200,6 +204,7 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
|
|
|
200
204
|
|
|
201
205
|
/** Wipes the current user auth. */
|
|
202
206
|
public async logout() {
|
|
207
|
+
authLog('auth-vir: LOGOUT - FrontendAuthClient.logout called', new Error().stack);
|
|
203
208
|
await this.config.authClearedCallback?.();
|
|
204
209
|
wipeCurrentCsrfToken(this.config.overrides);
|
|
205
210
|
}
|
|
@@ -222,6 +227,7 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
|
|
|
222
227
|
>,
|
|
223
228
|
): Promise<void> {
|
|
224
229
|
if (!response.ok) {
|
|
230
|
+
authLog('auth-vir: LOGOUT - handleLoginResponse: response not ok');
|
|
225
231
|
await this.logout();
|
|
226
232
|
throw new Error('Login response failed.');
|
|
227
233
|
}
|
|
@@ -229,6 +235,7 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
|
|
|
229
235
|
const {csrfToken} = extractCsrfTokenHeader(response, this.config.overrides);
|
|
230
236
|
|
|
231
237
|
if (!csrfToken) {
|
|
238
|
+
authLog('auth-vir: LOGOUT - handleLoginResponse: no CSRF token in response');
|
|
232
239
|
await this.logout();
|
|
233
240
|
throw new Error('Did not receive any CSRF token.');
|
|
234
241
|
}
|
|
@@ -259,6 +266,9 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
|
|
|
259
266
|
response.status === HttpStatus.Unauthorized &&
|
|
260
267
|
!response.headers?.get(AuthHeaderName.IsSignUpAuth)
|
|
261
268
|
) {
|
|
269
|
+
authLog('auth-vir: LOGOUT - verifyResponseAuth: unauthorized response (401)', {
|
|
270
|
+
status: response.status,
|
|
271
|
+
});
|
|
262
272
|
await this.logout();
|
|
263
273
|
return false;
|
|
264
274
|
}
|
package/src/auth.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import {AuthHeaderName} from './headers.js';
|
|
18
18
|
import {type ParseJwtParams} from './jwt/jwt.js';
|
|
19
19
|
import {type JwtUserData} from './jwt/user-jwt.js';
|
|
20
|
+
import {authLog} from './log.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* All possible headers container types supported by {@link extractUserIdFromRequestHeaders}.
|
|
@@ -93,12 +94,28 @@ export async function extractUserIdFromRequestHeaders<UserId extends string | nu
|
|
|
93
94
|
const cookie = readHeader(headers, 'cookie');
|
|
94
95
|
|
|
95
96
|
if (!cookie || !csrfToken) {
|
|
97
|
+
authLog(
|
|
98
|
+
'auth-vir: extractUserIdFromRequestHeaders failed - missing cookie or CSRF token',
|
|
99
|
+
{
|
|
100
|
+
hasCookie: !!cookie,
|
|
101
|
+
hasCsrfToken: !!csrfToken,
|
|
102
|
+
cookieName,
|
|
103
|
+
},
|
|
104
|
+
);
|
|
96
105
|
return undefined;
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
const jwt = await extractCookieJwt(cookie, jwtParams, cookieName);
|
|
100
109
|
|
|
101
110
|
if (!jwt || jwt.data.csrfToken !== csrfToken) {
|
|
111
|
+
authLog(
|
|
112
|
+
'auth-vir: extractUserIdFromRequestHeaders failed - JWT invalid or CSRF mismatch',
|
|
113
|
+
{
|
|
114
|
+
hasJwt: !!jwt,
|
|
115
|
+
csrfMatch: jwt ? jwt.data.csrfToken === csrfToken : false,
|
|
116
|
+
cookieName,
|
|
117
|
+
},
|
|
118
|
+
);
|
|
102
119
|
return undefined;
|
|
103
120
|
}
|
|
104
121
|
|
|
@@ -108,7 +125,8 @@ export async function extractUserIdFromRequestHeaders<UserId extends string | nu
|
|
|
108
125
|
cookieName,
|
|
109
126
|
sessionStartedAt: jwt.data.sessionStartedAt,
|
|
110
127
|
};
|
|
111
|
-
} catch {
|
|
128
|
+
} catch (error) {
|
|
129
|
+
authLog('auth-vir: extractUserIdFromRequestHeaders error', {error, cookieName});
|
|
112
130
|
return undefined;
|
|
113
131
|
}
|
|
114
132
|
}
|
|
@@ -130,12 +148,16 @@ export async function insecureExtractUserIdFromCookieAlone<UserId extends string
|
|
|
130
148
|
const cookie = readHeader(headers, 'cookie');
|
|
131
149
|
|
|
132
150
|
if (!cookie) {
|
|
151
|
+
authLog('auth-vir: insecureExtractUserIdFromCookieAlone failed - no cookie');
|
|
133
152
|
return undefined;
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
const jwt = await extractCookieJwt(cookie, jwtParams, cookieName);
|
|
137
156
|
|
|
138
157
|
if (!jwt) {
|
|
158
|
+
authLog(
|
|
159
|
+
'auth-vir: insecureExtractUserIdFromCookieAlone failed - JWT extraction failed',
|
|
160
|
+
);
|
|
139
161
|
return undefined;
|
|
140
162
|
}
|
|
141
163
|
|
|
@@ -145,7 +167,8 @@ export async function insecureExtractUserIdFromCookieAlone<UserId extends string
|
|
|
145
167
|
cookieName,
|
|
146
168
|
sessionStartedAt: jwt.data.sessionStartedAt,
|
|
147
169
|
};
|
|
148
|
-
} catch {
|
|
170
|
+
} catch (error) {
|
|
171
|
+
authLog('auth-vir: insecureExtractUserIdFromCookieAlone error', {error});
|
|
149
172
|
return undefined;
|
|
150
173
|
}
|
|
151
174
|
}
|
|
@@ -206,6 +229,13 @@ export function generateLogoutHeaders<CsrfHeaderName extends string = AuthHeader
|
|
|
206
229
|
): {
|
|
207
230
|
'set-cookie': string;
|
|
208
231
|
} & Record<CsrfHeaderName, string> {
|
|
232
|
+
authLog(
|
|
233
|
+
'auth-vir: LOGOUT - generateLogoutHeaders called',
|
|
234
|
+
{
|
|
235
|
+
cookieName: cookieConfig.cookieName,
|
|
236
|
+
},
|
|
237
|
+
new Error().stack,
|
|
238
|
+
);
|
|
209
239
|
const csrfHeaderName = (overrides.csrfHeaderName || AuthHeaderName.CsrfToken) as CsrfHeaderName;
|
|
210
240
|
|
|
211
241
|
return {
|
|
@@ -239,6 +269,7 @@ export function handleAuthResponse(
|
|
|
239
269
|
}> = {},
|
|
240
270
|
) {
|
|
241
271
|
if (!response.ok) {
|
|
272
|
+
authLog('auth-vir: LOGOUT - handleAuthResponse: response not ok, wiping CSRF token');
|
|
242
273
|
wipeCurrentCsrfToken(overrides);
|
|
243
274
|
return;
|
|
244
275
|
}
|
|
@@ -246,9 +277,11 @@ export function handleAuthResponse(
|
|
|
246
277
|
const {csrfToken} = extractCsrfTokenHeader(response, overrides);
|
|
247
278
|
|
|
248
279
|
if (!csrfToken) {
|
|
280
|
+
authLog('auth-vir: LOGOUT - handleAuthResponse: no CSRF token in response, wiping');
|
|
249
281
|
wipeCurrentCsrfToken(overrides);
|
|
250
282
|
throw new Error('Did not receive any CSRF token.');
|
|
251
283
|
}
|
|
252
284
|
|
|
285
|
+
authLog('auth-vir: handleAuthResponse - successfully stored CSRF token');
|
|
253
286
|
storeCsrfToken(csrfToken, overrides);
|
|
254
287
|
}
|
package/src/csrf-token.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import {defineShape, parseJsonWithShape} from 'object-shape-tester';
|
|
15
15
|
import {type RequireExactlyOne} from 'type-fest';
|
|
16
16
|
import {AuthHeaderName} from './headers.js';
|
|
17
|
+
import {authLog} from './log.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Shape definition for {@link CsrfToken}.
|
|
@@ -137,6 +138,7 @@ export function parseCsrfToken(value: string | undefined | null): Readonly<GetCs
|
|
|
137
138
|
);
|
|
138
139
|
|
|
139
140
|
if (!csrfToken) {
|
|
141
|
+
authLog('auth-vir: CSRF token parse failed - will cause logout if used');
|
|
140
142
|
return {
|
|
141
143
|
failure: CsrfTokenFailureReason.ParseFailed,
|
|
142
144
|
};
|
|
@@ -148,6 +150,9 @@ export function parseCsrfToken(value: string | undefined | null): Readonly<GetCs
|
|
|
148
150
|
relativeTo: csrfToken.expiration,
|
|
149
151
|
})
|
|
150
152
|
) {
|
|
153
|
+
authLog('auth-vir: CSRF token expired - will cause logout', {
|
|
154
|
+
expiration: csrfToken.expiration,
|
|
155
|
+
});
|
|
151
156
|
return {
|
|
152
157
|
failure: CsrfTokenFailureReason.Expired,
|
|
153
158
|
};
|
|
@@ -202,6 +207,7 @@ export function wipeCurrentCsrfToken(
|
|
|
202
207
|
csrfHeaderName: string;
|
|
203
208
|
}> = {},
|
|
204
209
|
) {
|
|
210
|
+
authLog('auth-vir: wipeCurrentCsrfToken called', new Error().stack);
|
|
205
211
|
return (overrides.localStorage || globalThis.localStorage).removeItem(
|
|
206
212
|
overrides.csrfHeaderName || AuthHeaderName.CsrfToken,
|
|
207
213
|
);
|
package/src/index.ts
CHANGED
package/src/log.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send logs to the console for debugging.
|
|
3
|
+
*
|
|
4
|
+
* @category Internal
|
|
5
|
+
*/
|
|
6
|
+
export function authLog(...params: any[]) {
|
|
7
|
+
if (!shouldLogAuth) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
console.info(...params);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let shouldLogAuth = true;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Set to `false` to disable logging.
|
|
17
|
+
*
|
|
18
|
+
* @category Internal
|
|
19
|
+
*/
|
|
20
|
+
export function setShouldLogAuth(value: boolean) {
|
|
21
|
+
shouldLogAuth = value;
|
|
22
|
+
}
|