auth-vir 3.1.1 → 4.0.0
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/README.md +3 -3
- package/dist/auth-client/backend-auth.client.js +2 -5
- package/dist/auth-client/frontend-auth.client.d.ts +1 -8
- package/dist/auth-client/frontend-auth.client.js +4 -21
- package/dist/auth.js +7 -15
- package/dist/csrf-token.d.ts +7 -84
- package/dist/csrf-token.js +11 -78
- package/package.json +1 -1
- package/src/auth-client/backend-auth.client.ts +2 -5
- package/src/auth-client/frontend-auth.client.ts +3 -31
- package/src/auth.ts +6 -18
- package/src/csrf-token.ts +15 -154
package/README.md
CHANGED
|
@@ -281,7 +281,7 @@ export async function sendLoginRequest(
|
|
|
281
281
|
userLoginData: {username: string; password: string},
|
|
282
282
|
loginUrl: string,
|
|
283
283
|
) {
|
|
284
|
-
if (
|
|
284
|
+
if (await getCurrentCsrfToken(csrfOption)) {
|
|
285
285
|
throw new Error('Already logged in.');
|
|
286
286
|
}
|
|
287
287
|
|
|
@@ -302,7 +302,7 @@ export async function sendAuthenticatedRequest(
|
|
|
302
302
|
requestInit: Omit<RequestInit, 'headers'> = {},
|
|
303
303
|
headers: Record<string, string> = {},
|
|
304
304
|
) {
|
|
305
|
-
const
|
|
305
|
+
const csrfToken = await getCurrentCsrfToken(csrfOption);
|
|
306
306
|
|
|
307
307
|
if (!csrfToken) {
|
|
308
308
|
throw new Error('Not authenticated.');
|
|
@@ -313,7 +313,7 @@ export async function sendAuthenticatedRequest(
|
|
|
313
313
|
credentials: 'include',
|
|
314
314
|
headers: {
|
|
315
315
|
...headers,
|
|
316
|
-
[resolveCsrfHeaderName(csrfOption)]: csrfToken
|
|
316
|
+
[resolveCsrfHeaderName(csrfOption)]: csrfToken,
|
|
317
317
|
},
|
|
318
318
|
});
|
|
319
319
|
|
|
@@ -139,17 +139,14 @@ export class BackendAuthClient {
|
|
|
139
139
|
requestHeaders,
|
|
140
140
|
});
|
|
141
141
|
const csrfHeaderName = resolveCsrfHeaderName(this.config.csrf);
|
|
142
|
-
const { cookie
|
|
142
|
+
const { cookie } = await generateAuthCookie({
|
|
143
143
|
csrfToken: userIdResult.csrfToken,
|
|
144
144
|
userId: userIdResult.userId,
|
|
145
145
|
sessionStartedAt: userIdResult.sessionStartedAt || Date.now(),
|
|
146
146
|
}, cookieParams);
|
|
147
147
|
return {
|
|
148
148
|
'set-cookie': cookie,
|
|
149
|
-
[csrfHeaderName]:
|
|
150
|
-
token: userIdResult.csrfToken,
|
|
151
|
-
expiration,
|
|
152
|
-
}),
|
|
149
|
+
[csrfHeaderName]: userIdResult.csrfToken,
|
|
153
150
|
};
|
|
154
151
|
}
|
|
155
152
|
else {
|
|
@@ -49,13 +49,6 @@ export type FrontendAuthClientConfig = Readonly<{
|
|
|
49
49
|
* another user.
|
|
50
50
|
*/
|
|
51
51
|
assumedUserHeaderName: string;
|
|
52
|
-
/**
|
|
53
|
-
* Allowed clock skew tolerance for CSRF token expiration checks. Accounts for differences
|
|
54
|
-
* between server and client clocks.
|
|
55
|
-
*
|
|
56
|
-
* @default {minutes: 5}
|
|
57
|
-
*/
|
|
58
|
-
allowedClockSkew: Readonly<AnyDuration>;
|
|
59
52
|
overrides: PartialWithUndefined<{
|
|
60
53
|
localStorage: Pick<Storage, 'setItem' | 'removeItem' | 'getItem'>;
|
|
61
54
|
csrfTokenStore: CsrfTokenStore;
|
|
@@ -79,7 +72,7 @@ export declare class FrontendAuthClient<AssumedUserParams extends JsonCompatible
|
|
|
79
72
|
* interval).
|
|
80
73
|
*/
|
|
81
74
|
destroy(): void;
|
|
82
|
-
/** Wraps {@link getCurrentCsrfToken} to
|
|
75
|
+
/** Wraps {@link getCurrentCsrfToken} to retrieve the stored CSRF token string. */
|
|
83
76
|
getCurrentCsrfToken(): Promise<string | undefined>;
|
|
84
77
|
/**
|
|
85
78
|
* Assume the given user. Pass `undefined` to wipe the currently assumed user.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HttpStatus, } from '@augment-vir/common';
|
|
2
2
|
import { listenToActivity } from 'detect-activity';
|
|
3
|
-
import {
|
|
3
|
+
import { extractCsrfTokenHeader, getCurrentCsrfToken, resolveCsrfHeaderName, storeCsrfToken, } from '../csrf-token.js';
|
|
4
4
|
import { AuthHeaderName } from '../headers.js';
|
|
5
5
|
/**
|
|
6
6
|
* An auth client for sending and validating client requests to a backend. This should only be used
|
|
@@ -41,17 +41,12 @@ export class FrontendAuthClient {
|
|
|
41
41
|
this.userCheckInterval?.clearInterval();
|
|
42
42
|
this.removeActivityListener?.();
|
|
43
43
|
}
|
|
44
|
-
/** Wraps {@link getCurrentCsrfToken} to
|
|
44
|
+
/** Wraps {@link getCurrentCsrfToken} to retrieve the stored CSRF token string. */
|
|
45
45
|
async getCurrentCsrfToken() {
|
|
46
|
-
|
|
46
|
+
return await getCurrentCsrfToken({
|
|
47
47
|
...this.config.csrf,
|
|
48
48
|
csrfTokenStore: this.config.overrides?.csrfTokenStore,
|
|
49
|
-
allowedClockSkew: this.config.allowedClockSkew || defaultAllowedClockSkew,
|
|
50
49
|
});
|
|
51
|
-
if (csrfTokenResult.failure) {
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
return csrfTokenResult.csrfToken.token;
|
|
55
50
|
}
|
|
56
51
|
/**
|
|
57
52
|
* Assume the given user. Pass `undefined` to wipe the currently assumed user.
|
|
@@ -125,9 +120,7 @@ export class FrontendAuthClient {
|
|
|
125
120
|
await this.logout();
|
|
126
121
|
throw new Error('Login response failed.');
|
|
127
122
|
}
|
|
128
|
-
const
|
|
129
|
-
allowedClockSkew: this.config.allowedClockSkew || defaultAllowedClockSkew,
|
|
130
|
-
});
|
|
123
|
+
const csrfToken = extractCsrfTokenHeader(response, this.config.csrf);
|
|
131
124
|
if (!csrfToken) {
|
|
132
125
|
await this.logout();
|
|
133
126
|
throw new Error('Did not receive any CSRF token.');
|
|
@@ -149,16 +142,6 @@ export class FrontendAuthClient {
|
|
|
149
142
|
await this.logout();
|
|
150
143
|
return false;
|
|
151
144
|
}
|
|
152
|
-
/** If the response has a new CSRF token, store it. */
|
|
153
|
-
const { csrfToken } = extractCsrfTokenHeader(response, this.config.csrf, {
|
|
154
|
-
allowedClockSkew: this.config.allowedClockSkew || defaultAllowedClockSkew,
|
|
155
|
-
});
|
|
156
|
-
if (csrfToken) {
|
|
157
|
-
await storeCsrfToken(csrfToken, {
|
|
158
|
-
...this.config.csrf,
|
|
159
|
-
csrfTokenStore: this.config.overrides?.csrfTokenStore,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
145
|
return true;
|
|
163
146
|
}
|
|
164
147
|
}
|
package/dist/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AuthCookieName, clearAuthCookie, extractCookieJwt, generateAuthCookie, } from './cookie.js';
|
|
2
|
-
import { extractCsrfTokenHeader, generateCsrfToken,
|
|
2
|
+
import { extractCsrfTokenHeader, generateCsrfToken, resolveCsrfHeaderName, storeCsrfToken, } from './csrf-token.js';
|
|
3
3
|
function readHeader(headers, headerName) {
|
|
4
4
|
if (headers instanceof Headers) {
|
|
5
5
|
return headers.get(headerName) || undefined;
|
|
@@ -18,12 +18,7 @@ function readHeader(headers, headerName) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
function readCsrfTokenHeader(headers, csrfHeaderNameOption) {
|
|
21
|
-
|
|
22
|
-
if (!rawCsrfToken) {
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
const token = parseCsrfToken(rawCsrfToken).csrfToken?.token || rawCsrfToken;
|
|
26
|
-
return token;
|
|
21
|
+
return readHeader(headers, resolveCsrfHeaderName(csrfHeaderNameOption));
|
|
27
22
|
}
|
|
28
23
|
/**
|
|
29
24
|
* Extract the user id from a request by checking both the request cookie and CSRF token. This is
|
|
@@ -101,19 +96,16 @@ userId, cookieConfig, csrfHeaderNameOption,
|
|
|
101
96
|
* time will be used (for new sessions).
|
|
102
97
|
*/
|
|
103
98
|
sessionStartedAt) {
|
|
104
|
-
const csrfToken = generateCsrfToken(
|
|
99
|
+
const csrfToken = generateCsrfToken();
|
|
105
100
|
const csrfHeaderName = resolveCsrfHeaderName(csrfHeaderNameOption);
|
|
106
|
-
const { cookie
|
|
107
|
-
csrfToken
|
|
101
|
+
const { cookie } = await generateAuthCookie({
|
|
102
|
+
csrfToken,
|
|
108
103
|
userId,
|
|
109
104
|
sessionStartedAt: sessionStartedAt ?? Date.now(),
|
|
110
105
|
}, cookieConfig);
|
|
111
106
|
return {
|
|
112
107
|
'set-cookie': cookie,
|
|
113
|
-
[csrfHeaderName]:
|
|
114
|
-
token: csrfToken.token,
|
|
115
|
-
expiration,
|
|
116
|
-
}),
|
|
108
|
+
[csrfHeaderName]: csrfToken,
|
|
117
109
|
};
|
|
118
110
|
}
|
|
119
111
|
/**
|
|
@@ -140,7 +132,7 @@ export async function handleAuthResponse(response, options) {
|
|
|
140
132
|
if (!response.ok) {
|
|
141
133
|
return;
|
|
142
134
|
}
|
|
143
|
-
const
|
|
135
|
+
const csrfToken = extractCsrfTokenHeader(response, options);
|
|
144
136
|
if (!csrfToken) {
|
|
145
137
|
throw new Error('Did not receive any CSRF token.');
|
|
146
138
|
}
|
package/dist/csrf-token.d.ts
CHANGED
|
@@ -3,60 +3,19 @@ import { type AnyDuration } from 'date-vir';
|
|
|
3
3
|
import { type RequireExactlyOne } from 'type-fest';
|
|
4
4
|
import { type CsrfTokenStore } from './csrf-token-store.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @category Internal
|
|
9
|
-
*/
|
|
10
|
-
export declare const csrfTokenShape: import("object-shape-tester").Shape<{
|
|
11
|
-
token: string;
|
|
12
|
-
expiration: import("object-shape-tester").Shape<import("object-shape-tester").Shape<import("@sinclair/typebox").TUnsafe<{
|
|
13
|
-
hour: import("date-vir").Hour;
|
|
14
|
-
minute: import("date-vir").Minute;
|
|
15
|
-
second: import("date-vir").Minute;
|
|
16
|
-
millisecond: number;
|
|
17
|
-
timezone: "Africa/Abidjan" | "Africa/Accra" | "Africa/Addis_Ababa" | "Africa/Algiers" | "Africa/Asmara" | "Africa/Bamako" | "Africa/Bangui" | "Africa/Banjul" | "Africa/Bissau" | "Africa/Blantyre" | "Africa/Brazzaville" | "Africa/Bujumbura" | "Africa/Cairo" | "Africa/Casablanca" | "Africa/Ceuta" | "Africa/Conakry" | "Africa/Dakar" | "Africa/Dar_es_Salaam" | "Africa/Djibouti" | "Africa/Douala" | "Africa/El_Aaiun" | "Africa/Freetown" | "Africa/Gaborone" | "Africa/Harare" | "Africa/Johannesburg" | "Africa/Juba" | "Africa/Kampala" | "Africa/Khartoum" | "Africa/Kigali" | "Africa/Kinshasa" | "Africa/Lagos" | "Africa/Libreville" | "Africa/Lome" | "Africa/Luanda" | "Africa/Lubumbashi" | "Africa/Lusaka" | "Africa/Malabo" | "Africa/Maputo" | "Africa/Maseru" | "Africa/Mbabane" | "Africa/Mogadishu" | "Africa/Monrovia" | "Africa/Nairobi" | "Africa/Ndjamena" | "Africa/Niamey" | "Africa/Nouakchott" | "Africa/Ouagadougou" | "Africa/Porto-Novo" | "Africa/Sao_Tome" | "Africa/Timbuktu" | "Africa/Tripoli" | "Africa/Tunis" | "Africa/Windhoek" | "America/Adak" | "America/Anchorage" | "America/Anguilla" | "America/Antigua" | "America/Araguaina" | "America/Argentina/Buenos_Aires" | "America/Argentina/Catamarca" | "America/Argentina/ComodRivadavia" | "America/Argentina/Cordoba" | "America/Argentina/Jujuy" | "America/Argentina/La_Rioja" | "America/Argentina/Mendoza" | "America/Argentina/Rio_Gallegos" | "America/Argentina/Salta" | "America/Argentina/San_Juan" | "America/Argentina/San_Luis" | "America/Argentina/Tucuman" | "America/Argentina/Ushuaia" | "America/Aruba" | "America/Asuncion" | "America/Atikokan" | "America/Bahia" | "America/Bahia_Banderas" | "America/Barbados" | "America/Belem" | "America/Belize" | "America/Blanc-Sablon" | "America/Boa_Vista" | "America/Bogota" | "America/Boise" | "America/Cambridge_Bay" | "America/Campo_Grande" | "America/Cancun" | "America/Caracas" | "America/Cayenne" | "America/Cayman" | "America/Chicago" | "America/Chihuahua" | "America/Coral_Harbour" | "America/Costa_Rica" | "America/Creston" | "America/Cuiaba" | "America/Curacao" | "America/Danmarkshavn" | "America/Dawson" | "America/Dawson_Creek" | "America/Denver" | "America/Detroit" | "America/Dominica" | "America/Edmonton" | "America/Eirunepe" | "America/El_Salvador" | "America/Ensenada" | "America/Fort_Nelson" | "America/Fortaleza" | "America/Glace_Bay" | "America/Goose_Bay" | "America/Grand_Turk" | "America/Grenada" | "America/Guadeloupe" | "America/Guatemala" | "America/Guayaquil" | "America/Guyana" | "America/Halifax" | "America/Havana" | "America/Hermosillo" | "America/Indiana/Indianapolis" | "America/Indiana/Knox" | "America/Indiana/Marengo" | "America/Indiana/Petersburg" | "America/Indiana/Tell_City" | "America/Indiana/Vevay" | "America/Indiana/Vincennes" | "America/Indiana/Winamac" | "America/Inuvik" | "America/Iqaluit" | "America/Jamaica" | "America/Juneau" | "America/Kentucky/Louisville" | "America/Kentucky/Monticello" | "America/La_Paz" | "America/Lima" | "America/Los_Angeles" | "America/Maceio" | "America/Managua" | "America/Manaus" | "America/Martinique" | "America/Matamoros" | "America/Mazatlan" | "America/Menominee" | "America/Merida" | "America/Metlakatla" | "America/Mexico_City" | "America/Miquelon" | "America/Moncton" | "America/Monterrey" | "America/Montevideo" | "America/Montreal" | "America/Montserrat" | "America/Nassau" | "America/New_York" | "America/Nipigon" | "America/Nome" | "America/Noronha" | "America/North_Dakota/Beulah" | "America/North_Dakota/Center" | "America/North_Dakota/New_Salem" | "America/Nuuk" | "America/Ojinaga" | "America/Panama" | "America/Pangnirtung" | "America/Paramaribo" | "America/Phoenix" | "America/Port-au-Prince" | "America/Port_of_Spain" | "America/Porto_Velho" | "America/Puerto_Rico" | "America/Punta_Arenas" | "America/Rainy_River" | "America/Rankin_Inlet" | "America/Recife" | "America/Regina" | "America/Resolute" | "America/Rio_Branco" | "America/Rosario" | "America/Santarem" | "America/Santiago" | "America/Santo_Domingo" | "America/Sao_Paulo" | "America/Scoresbysund" | "America/Sitka" | "America/St_Johns" | "America/St_Kitts" | "America/St_Lucia" | "America/St_Thomas" | "America/St_Vincent" | "America/Swift_Current" | "America/Tegucigalpa" | "America/Thule" | "America/Thunder_Bay" | "America/Tijuana" | "America/Toronto" | "America/Tortola" | "America/Vancouver" | "America/Whitehorse" | "America/Winnipeg" | "America/Yakutat" | "America/Yellowknife" | "Antarctica/Casey" | "Antarctica/Davis" | "Antarctica/DumontDUrville" | "Antarctica/Macquarie" | "Antarctica/Mawson" | "Antarctica/McMurdo" | "Antarctica/Palmer" | "Antarctica/Rothera" | "Antarctica/Syowa" | "Antarctica/Troll" | "Antarctica/Vostok" | "Asia/Aden" | "Asia/Almaty" | "Asia/Amman" | "Asia/Anadyr" | "Asia/Aqtau" | "Asia/Aqtobe" | "Asia/Ashgabat" | "Asia/Atyrau" | "Asia/Baghdad" | "Asia/Bahrain" | "Asia/Baku" | "Asia/Bangkok" | "Asia/Barnaul" | "Asia/Beirut" | "Asia/Bishkek" | "Asia/Brunei" | "Asia/Chita" | "Asia/Choibalsan" | "Asia/Chongqing" | "Asia/Colombo" | "Asia/Damascus" | "Asia/Dhaka" | "Asia/Dili" | "Asia/Dubai" | "Asia/Dushanbe" | "Asia/Famagusta" | "Asia/Gaza" | "Asia/Harbin" | "Asia/Hebron" | "Asia/Ho_Chi_Minh" | "Asia/Hong_Kong" | "Asia/Hovd" | "Asia/Irkutsk" | "Asia/Jakarta" | "Asia/Jayapura" | "Asia/Jerusalem" | "Asia/Kabul" | "Asia/Kamchatka" | "Asia/Karachi" | "Asia/Kashgar" | "Asia/Kathmandu" | "Asia/Khandyga" | "Asia/Kolkata" | "Asia/Krasnoyarsk" | "Asia/Kuala_Lumpur" | "Asia/Kuching" | "Asia/Kuwait" | "Asia/Macau" | "Asia/Magadan" | "Asia/Makassar" | "Asia/Manila" | "Asia/Muscat" | "Asia/Nicosia" | "Asia/Novokuznetsk" | "Asia/Novosibirsk" | "Asia/Omsk" | "Asia/Oral" | "Asia/Phnom_Penh" | "Asia/Pontianak" | "Asia/Pyongyang" | "Asia/Qatar" | "Asia/Qostanay" | "Asia/Qyzylorda" | "Asia/Riyadh" | "Asia/Sakhalin" | "Asia/Samarkand" | "Asia/Seoul" | "Asia/Shanghai" | "Asia/Singapore" | "Asia/Srednekolymsk" | "Asia/Taipei" | "Asia/Tashkent" | "Asia/Tbilisi" | "Asia/Tehran" | "Asia/Tel_Aviv" | "Asia/Thimphu" | "Asia/Tokyo" | "Asia/Tomsk" | "Asia/Ulaanbaatar" | "Asia/Urumqi" | "Asia/Ust-Nera" | "Asia/Vientiane" | "Asia/Vladivostok" | "Asia/Yakutsk" | "Asia/Yangon" | "Asia/Yekaterinburg" | "Asia/Yerevan" | "Atlantic/Azores" | "Atlantic/Bermuda" | "Atlantic/Canary" | "Atlantic/Cape_Verde" | "Atlantic/Faroe" | "Atlantic/Jan_Mayen" | "Atlantic/Madeira" | "Atlantic/Reykjavik" | "Atlantic/South_Georgia" | "Atlantic/St_Helena" | "Atlantic/Stanley" | "Australia/Adelaide" | "Australia/Brisbane" | "Australia/Broken_Hill" | "Australia/Currie" | "Australia/Darwin" | "Australia/Eucla" | "Australia/Hobart" | "Australia/Lindeman" | "Australia/Lord_Howe" | "Australia/Melbourne" | "Australia/Perth" | "Australia/Sydney" | "CET" | "CST6CDT" | "EET" | "EST" | "EST5EDT" | "Etc/GMT+1" | "Etc/GMT+10" | "Etc/GMT+11" | "Etc/GMT+12" | "Etc/GMT+2" | "Etc/GMT+3" | "Etc/GMT+4" | "Etc/GMT+5" | "Etc/GMT+6" | "Etc/GMT+7" | "Etc/GMT+8" | "Etc/GMT+9" | "Etc/GMT-1" | "Etc/GMT-10" | "Etc/GMT-11" | "Etc/GMT-12" | "Etc/GMT-13" | "Etc/GMT-14" | "Etc/GMT-2" | "Etc/GMT-3" | "Etc/GMT-4" | "Etc/GMT-5" | "Etc/GMT-6" | "Etc/GMT-7" | "Etc/GMT-8" | "Etc/GMT-9" | "Europe/Amsterdam" | "Europe/Andorra" | "Europe/Astrakhan" | "Europe/Athens" | "Europe/Belfast" | "Europe/Belgrade" | "Europe/Berlin" | "Europe/Brussels" | "Europe/Bucharest" | "Europe/Budapest" | "Europe/Chisinau" | "Europe/Copenhagen" | "Europe/Dublin" | "Europe/Gibraltar" | "Europe/Guernsey" | "Europe/Helsinki" | "Europe/Isle_of_Man" | "Europe/Istanbul" | "Europe/Jersey" | "Europe/Kaliningrad" | "Europe/Kirov" | "Europe/Kyiv" | "Europe/Lisbon" | "Europe/Ljubljana" | "Europe/London" | "Europe/Luxembourg" | "Europe/Madrid" | "Europe/Malta" | "Europe/Minsk" | "Europe/Monaco" | "Europe/Moscow" | "Europe/Oslo" | "Europe/Paris" | "Europe/Prague" | "Europe/Riga" | "Europe/Rome" | "Europe/Samara" | "Europe/Sarajevo" | "Europe/Saratov" | "Europe/Simferopol" | "Europe/Skopje" | "Europe/Sofia" | "Europe/Stockholm" | "Europe/Tallinn" | "Europe/Tirane" | "Europe/Tiraspol" | "Europe/Ulyanovsk" | "Europe/Uzhgorod" | "Europe/Vaduz" | "Europe/Vienna" | "Europe/Vilnius" | "Europe/Volgograd" | "Europe/Warsaw" | "Europe/Zagreb" | "Europe/Zaporozhye" | "Europe/Zurich" | "HST" | "Indian/Antananarivo" | "Indian/Chagos" | "Indian/Christmas" | "Indian/Cocos" | "Indian/Comoro" | "Indian/Kerguelen" | "Indian/Mahe" | "Indian/Maldives" | "Indian/Mauritius" | "Indian/Mayotte" | "Indian/Reunion" | "MET" | "MST" | "MST7MDT" | "PST8PDT" | "Pacific/Apia" | "Pacific/Auckland" | "Pacific/Bougainville" | "Pacific/Chatham" | "Pacific/Chuuk" | "Pacific/Easter" | "Pacific/Efate" | "Pacific/Enderbury" | "Pacific/Fakaofo" | "Pacific/Fiji" | "Pacific/Funafuti" | "Pacific/Galapagos" | "Pacific/Gambier" | "Pacific/Guadalcanal" | "Pacific/Guam" | "Pacific/Honolulu" | "Pacific/Johnston" | "Pacific/Kanton" | "Pacific/Kiritimati" | "Pacific/Kosrae" | "Pacific/Kwajalein" | "Pacific/Majuro" | "Pacific/Marquesas" | "Pacific/Midway" | "Pacific/Nauru" | "Pacific/Niue" | "Pacific/Norfolk" | "Pacific/Noumea" | "Pacific/Pago_Pago" | "Pacific/Palau" | "Pacific/Pitcairn" | "Pacific/Pohnpei" | "Pacific/Port_Moresby" | "Pacific/Rarotonga" | "Pacific/Saipan" | "Pacific/Tahiti" | "Pacific/Tarawa" | "Pacific/Tongatapu" | "Pacific/Wake" | "Pacific/Wallis" | "UTC" | "WET";
|
|
18
|
-
} & {
|
|
19
|
-
year: number;
|
|
20
|
-
month: import("date-vir").MonthNumber;
|
|
21
|
-
day: import("date-vir").DayOfMonth;
|
|
22
|
-
timezone: "Africa/Abidjan" | "Africa/Accra" | "Africa/Addis_Ababa" | "Africa/Algiers" | "Africa/Asmara" | "Africa/Bamako" | "Africa/Bangui" | "Africa/Banjul" | "Africa/Bissau" | "Africa/Blantyre" | "Africa/Brazzaville" | "Africa/Bujumbura" | "Africa/Cairo" | "Africa/Casablanca" | "Africa/Ceuta" | "Africa/Conakry" | "Africa/Dakar" | "Africa/Dar_es_Salaam" | "Africa/Djibouti" | "Africa/Douala" | "Africa/El_Aaiun" | "Africa/Freetown" | "Africa/Gaborone" | "Africa/Harare" | "Africa/Johannesburg" | "Africa/Juba" | "Africa/Kampala" | "Africa/Khartoum" | "Africa/Kigali" | "Africa/Kinshasa" | "Africa/Lagos" | "Africa/Libreville" | "Africa/Lome" | "Africa/Luanda" | "Africa/Lubumbashi" | "Africa/Lusaka" | "Africa/Malabo" | "Africa/Maputo" | "Africa/Maseru" | "Africa/Mbabane" | "Africa/Mogadishu" | "Africa/Monrovia" | "Africa/Nairobi" | "Africa/Ndjamena" | "Africa/Niamey" | "Africa/Nouakchott" | "Africa/Ouagadougou" | "Africa/Porto-Novo" | "Africa/Sao_Tome" | "Africa/Timbuktu" | "Africa/Tripoli" | "Africa/Tunis" | "Africa/Windhoek" | "America/Adak" | "America/Anchorage" | "America/Anguilla" | "America/Antigua" | "America/Araguaina" | "America/Argentina/Buenos_Aires" | "America/Argentina/Catamarca" | "America/Argentina/ComodRivadavia" | "America/Argentina/Cordoba" | "America/Argentina/Jujuy" | "America/Argentina/La_Rioja" | "America/Argentina/Mendoza" | "America/Argentina/Rio_Gallegos" | "America/Argentina/Salta" | "America/Argentina/San_Juan" | "America/Argentina/San_Luis" | "America/Argentina/Tucuman" | "America/Argentina/Ushuaia" | "America/Aruba" | "America/Asuncion" | "America/Atikokan" | "America/Bahia" | "America/Bahia_Banderas" | "America/Barbados" | "America/Belem" | "America/Belize" | "America/Blanc-Sablon" | "America/Boa_Vista" | "America/Bogota" | "America/Boise" | "America/Cambridge_Bay" | "America/Campo_Grande" | "America/Cancun" | "America/Caracas" | "America/Cayenne" | "America/Cayman" | "America/Chicago" | "America/Chihuahua" | "America/Coral_Harbour" | "America/Costa_Rica" | "America/Creston" | "America/Cuiaba" | "America/Curacao" | "America/Danmarkshavn" | "America/Dawson" | "America/Dawson_Creek" | "America/Denver" | "America/Detroit" | "America/Dominica" | "America/Edmonton" | "America/Eirunepe" | "America/El_Salvador" | "America/Ensenada" | "America/Fort_Nelson" | "America/Fortaleza" | "America/Glace_Bay" | "America/Goose_Bay" | "America/Grand_Turk" | "America/Grenada" | "America/Guadeloupe" | "America/Guatemala" | "America/Guayaquil" | "America/Guyana" | "America/Halifax" | "America/Havana" | "America/Hermosillo" | "America/Indiana/Indianapolis" | "America/Indiana/Knox" | "America/Indiana/Marengo" | "America/Indiana/Petersburg" | "America/Indiana/Tell_City" | "America/Indiana/Vevay" | "America/Indiana/Vincennes" | "America/Indiana/Winamac" | "America/Inuvik" | "America/Iqaluit" | "America/Jamaica" | "America/Juneau" | "America/Kentucky/Louisville" | "America/Kentucky/Monticello" | "America/La_Paz" | "America/Lima" | "America/Los_Angeles" | "America/Maceio" | "America/Managua" | "America/Manaus" | "America/Martinique" | "America/Matamoros" | "America/Mazatlan" | "America/Menominee" | "America/Merida" | "America/Metlakatla" | "America/Mexico_City" | "America/Miquelon" | "America/Moncton" | "America/Monterrey" | "America/Montevideo" | "America/Montreal" | "America/Montserrat" | "America/Nassau" | "America/New_York" | "America/Nipigon" | "America/Nome" | "America/Noronha" | "America/North_Dakota/Beulah" | "America/North_Dakota/Center" | "America/North_Dakota/New_Salem" | "America/Nuuk" | "America/Ojinaga" | "America/Panama" | "America/Pangnirtung" | "America/Paramaribo" | "America/Phoenix" | "America/Port-au-Prince" | "America/Port_of_Spain" | "America/Porto_Velho" | "America/Puerto_Rico" | "America/Punta_Arenas" | "America/Rainy_River" | "America/Rankin_Inlet" | "America/Recife" | "America/Regina" | "America/Resolute" | "America/Rio_Branco" | "America/Rosario" | "America/Santarem" | "America/Santiago" | "America/Santo_Domingo" | "America/Sao_Paulo" | "America/Scoresbysund" | "America/Sitka" | "America/St_Johns" | "America/St_Kitts" | "America/St_Lucia" | "America/St_Thomas" | "America/St_Vincent" | "America/Swift_Current" | "America/Tegucigalpa" | "America/Thule" | "America/Thunder_Bay" | "America/Tijuana" | "America/Toronto" | "America/Tortola" | "America/Vancouver" | "America/Whitehorse" | "America/Winnipeg" | "America/Yakutat" | "America/Yellowknife" | "Antarctica/Casey" | "Antarctica/Davis" | "Antarctica/DumontDUrville" | "Antarctica/Macquarie" | "Antarctica/Mawson" | "Antarctica/McMurdo" | "Antarctica/Palmer" | "Antarctica/Rothera" | "Antarctica/Syowa" | "Antarctica/Troll" | "Antarctica/Vostok" | "Asia/Aden" | "Asia/Almaty" | "Asia/Amman" | "Asia/Anadyr" | "Asia/Aqtau" | "Asia/Aqtobe" | "Asia/Ashgabat" | "Asia/Atyrau" | "Asia/Baghdad" | "Asia/Bahrain" | "Asia/Baku" | "Asia/Bangkok" | "Asia/Barnaul" | "Asia/Beirut" | "Asia/Bishkek" | "Asia/Brunei" | "Asia/Chita" | "Asia/Choibalsan" | "Asia/Chongqing" | "Asia/Colombo" | "Asia/Damascus" | "Asia/Dhaka" | "Asia/Dili" | "Asia/Dubai" | "Asia/Dushanbe" | "Asia/Famagusta" | "Asia/Gaza" | "Asia/Harbin" | "Asia/Hebron" | "Asia/Ho_Chi_Minh" | "Asia/Hong_Kong" | "Asia/Hovd" | "Asia/Irkutsk" | "Asia/Jakarta" | "Asia/Jayapura" | "Asia/Jerusalem" | "Asia/Kabul" | "Asia/Kamchatka" | "Asia/Karachi" | "Asia/Kashgar" | "Asia/Kathmandu" | "Asia/Khandyga" | "Asia/Kolkata" | "Asia/Krasnoyarsk" | "Asia/Kuala_Lumpur" | "Asia/Kuching" | "Asia/Kuwait" | "Asia/Macau" | "Asia/Magadan" | "Asia/Makassar" | "Asia/Manila" | "Asia/Muscat" | "Asia/Nicosia" | "Asia/Novokuznetsk" | "Asia/Novosibirsk" | "Asia/Omsk" | "Asia/Oral" | "Asia/Phnom_Penh" | "Asia/Pontianak" | "Asia/Pyongyang" | "Asia/Qatar" | "Asia/Qostanay" | "Asia/Qyzylorda" | "Asia/Riyadh" | "Asia/Sakhalin" | "Asia/Samarkand" | "Asia/Seoul" | "Asia/Shanghai" | "Asia/Singapore" | "Asia/Srednekolymsk" | "Asia/Taipei" | "Asia/Tashkent" | "Asia/Tbilisi" | "Asia/Tehran" | "Asia/Tel_Aviv" | "Asia/Thimphu" | "Asia/Tokyo" | "Asia/Tomsk" | "Asia/Ulaanbaatar" | "Asia/Urumqi" | "Asia/Ust-Nera" | "Asia/Vientiane" | "Asia/Vladivostok" | "Asia/Yakutsk" | "Asia/Yangon" | "Asia/Yekaterinburg" | "Asia/Yerevan" | "Atlantic/Azores" | "Atlantic/Bermuda" | "Atlantic/Canary" | "Atlantic/Cape_Verde" | "Atlantic/Faroe" | "Atlantic/Jan_Mayen" | "Atlantic/Madeira" | "Atlantic/Reykjavik" | "Atlantic/South_Georgia" | "Atlantic/St_Helena" | "Atlantic/Stanley" | "Australia/Adelaide" | "Australia/Brisbane" | "Australia/Broken_Hill" | "Australia/Currie" | "Australia/Darwin" | "Australia/Eucla" | "Australia/Hobart" | "Australia/Lindeman" | "Australia/Lord_Howe" | "Australia/Melbourne" | "Australia/Perth" | "Australia/Sydney" | "CET" | "CST6CDT" | "EET" | "EST" | "EST5EDT" | "Etc/GMT+1" | "Etc/GMT+10" | "Etc/GMT+11" | "Etc/GMT+12" | "Etc/GMT+2" | "Etc/GMT+3" | "Etc/GMT+4" | "Etc/GMT+5" | "Etc/GMT+6" | "Etc/GMT+7" | "Etc/GMT+8" | "Etc/GMT+9" | "Etc/GMT-1" | "Etc/GMT-10" | "Etc/GMT-11" | "Etc/GMT-12" | "Etc/GMT-13" | "Etc/GMT-14" | "Etc/GMT-2" | "Etc/GMT-3" | "Etc/GMT-4" | "Etc/GMT-5" | "Etc/GMT-6" | "Etc/GMT-7" | "Etc/GMT-8" | "Etc/GMT-9" | "Europe/Amsterdam" | "Europe/Andorra" | "Europe/Astrakhan" | "Europe/Athens" | "Europe/Belfast" | "Europe/Belgrade" | "Europe/Berlin" | "Europe/Brussels" | "Europe/Bucharest" | "Europe/Budapest" | "Europe/Chisinau" | "Europe/Copenhagen" | "Europe/Dublin" | "Europe/Gibraltar" | "Europe/Guernsey" | "Europe/Helsinki" | "Europe/Isle_of_Man" | "Europe/Istanbul" | "Europe/Jersey" | "Europe/Kaliningrad" | "Europe/Kirov" | "Europe/Kyiv" | "Europe/Lisbon" | "Europe/Ljubljana" | "Europe/London" | "Europe/Luxembourg" | "Europe/Madrid" | "Europe/Malta" | "Europe/Minsk" | "Europe/Monaco" | "Europe/Moscow" | "Europe/Oslo" | "Europe/Paris" | "Europe/Prague" | "Europe/Riga" | "Europe/Rome" | "Europe/Samara" | "Europe/Sarajevo" | "Europe/Saratov" | "Europe/Simferopol" | "Europe/Skopje" | "Europe/Sofia" | "Europe/Stockholm" | "Europe/Tallinn" | "Europe/Tirane" | "Europe/Tiraspol" | "Europe/Ulyanovsk" | "Europe/Uzhgorod" | "Europe/Vaduz" | "Europe/Vienna" | "Europe/Vilnius" | "Europe/Volgograd" | "Europe/Warsaw" | "Europe/Zagreb" | "Europe/Zaporozhye" | "Europe/Zurich" | "HST" | "Indian/Antananarivo" | "Indian/Chagos" | "Indian/Christmas" | "Indian/Cocos" | "Indian/Comoro" | "Indian/Kerguelen" | "Indian/Mahe" | "Indian/Maldives" | "Indian/Mauritius" | "Indian/Mayotte" | "Indian/Reunion" | "MET" | "MST" | "MST7MDT" | "PST8PDT" | "Pacific/Apia" | "Pacific/Auckland" | "Pacific/Bougainville" | "Pacific/Chatham" | "Pacific/Chuuk" | "Pacific/Easter" | "Pacific/Efate" | "Pacific/Enderbury" | "Pacific/Fakaofo" | "Pacific/Fiji" | "Pacific/Funafuti" | "Pacific/Galapagos" | "Pacific/Gambier" | "Pacific/Guadalcanal" | "Pacific/Guam" | "Pacific/Honolulu" | "Pacific/Johnston" | "Pacific/Kanton" | "Pacific/Kiritimati" | "Pacific/Kosrae" | "Pacific/Kwajalein" | "Pacific/Majuro" | "Pacific/Marquesas" | "Pacific/Midway" | "Pacific/Nauru" | "Pacific/Niue" | "Pacific/Norfolk" | "Pacific/Noumea" | "Pacific/Pago_Pago" | "Pacific/Palau" | "Pacific/Pitcairn" | "Pacific/Pohnpei" | "Pacific/Port_Moresby" | "Pacific/Rarotonga" | "Pacific/Saipan" | "Pacific/Tahiti" | "Pacific/Tarawa" | "Pacific/Tongatapu" | "Pacific/Wake" | "Pacific/Wallis" | "UTC" | "WET";
|
|
23
|
-
}>>>;
|
|
24
|
-
}>;
|
|
25
|
-
/**
|
|
26
|
-
* A cryptographically CSRF token with expiration date.
|
|
27
|
-
*
|
|
28
|
-
* @category Internal
|
|
29
|
-
*/
|
|
30
|
-
export type CsrfToken = typeof csrfTokenShape.runtimeType;
|
|
31
|
-
/**
|
|
32
|
-
* Default allowed clock skew for CSRF token expiration checks. Accounts for differences between
|
|
33
|
-
* server and client clocks when checking token expiration.
|
|
6
|
+
* Default allowed clock skew for JWT expiration checks. Accounts for differences between server and
|
|
7
|
+
* client clocks.
|
|
34
8
|
*
|
|
35
9
|
* @category Internal
|
|
36
10
|
* @default {minutes: 5}
|
|
37
11
|
*/
|
|
38
12
|
export declare const defaultAllowedClockSkew: Readonly<AnyDuration>;
|
|
39
13
|
/**
|
|
40
|
-
* Generates a random, cryptographically secure CSRF token.
|
|
41
|
-
*
|
|
42
|
-
* @category Internal
|
|
43
|
-
*/
|
|
44
|
-
export declare function generateCsrfToken(
|
|
45
|
-
/** How long the CSRF token is valid for. */
|
|
46
|
-
duration: Readonly<AnyDuration>): CsrfToken;
|
|
47
|
-
/**
|
|
48
|
-
* CSRF token failure reasons for {@link GetCsrfTokenResult}.
|
|
14
|
+
* Generates a random, cryptographically secure CSRF token string.
|
|
49
15
|
*
|
|
50
16
|
* @category Internal
|
|
51
17
|
*/
|
|
52
|
-
export declare
|
|
53
|
-
/** No CSRF token was found. */
|
|
54
|
-
DoesNotExist = "does-not-exist",
|
|
55
|
-
/** A CSRF token was found but parsing it failed. */
|
|
56
|
-
ParseFailed = "parse-failed",
|
|
57
|
-
/** A CSRF token was found and parsed but is expired. */
|
|
58
|
-
Expired = "expired"
|
|
59
|
-
}
|
|
18
|
+
export declare function generateCsrfToken(): string;
|
|
60
19
|
/**
|
|
61
20
|
* Options for specifying the CSRF token header name.
|
|
62
21
|
*
|
|
@@ -76,15 +35,6 @@ export type CsrfHeaderNameOption = RequireExactlyOne<{
|
|
|
76
35
|
* @category Auth : Host
|
|
77
36
|
*/
|
|
78
37
|
export declare function resolveCsrfHeaderName(option: Readonly<CsrfHeaderNameOption>): string;
|
|
79
|
-
/**
|
|
80
|
-
* Output from {@link getCurrentCsrfToken}.
|
|
81
|
-
*
|
|
82
|
-
* @category Internal
|
|
83
|
-
*/
|
|
84
|
-
export type GetCsrfTokenResult = RequireExactlyOne<{
|
|
85
|
-
csrfToken: Readonly<CsrfToken>;
|
|
86
|
-
failure: CsrfTokenFailureReason;
|
|
87
|
-
}>;
|
|
88
38
|
/**
|
|
89
39
|
* Extract the CSRF token header from a response.
|
|
90
40
|
*
|
|
@@ -92,20 +42,13 @@ export type GetCsrfTokenResult = RequireExactlyOne<{
|
|
|
92
42
|
*/
|
|
93
43
|
export declare function extractCsrfTokenHeader(response: Readonly<PartialWithUndefined<SelectFrom<Response, {
|
|
94
44
|
headers: true;
|
|
95
|
-
}>>>, csrfHeaderNameOption: Readonly<CsrfHeaderNameOption
|
|
96
|
-
/**
|
|
97
|
-
* Allowed clock skew tolerance for CSRF token expiration checks.
|
|
98
|
-
*
|
|
99
|
-
* @default {minutes: 5}
|
|
100
|
-
*/
|
|
101
|
-
allowedClockSkew: Readonly<AnyDuration>;
|
|
102
|
-
}>): Readonly<GetCsrfTokenResult>;
|
|
45
|
+
}>>>, csrfHeaderNameOption: Readonly<CsrfHeaderNameOption>): string | undefined;
|
|
103
46
|
/**
|
|
104
47
|
* Stores the given CSRF token into IndexedDB.
|
|
105
48
|
*
|
|
106
49
|
* @category Auth : Client
|
|
107
50
|
*/
|
|
108
|
-
export declare function storeCsrfToken(csrfToken:
|
|
51
|
+
export declare function storeCsrfToken(csrfToken: string, options: Readonly<CsrfHeaderNameOption> & PartialWithUndefined<{
|
|
109
52
|
/**
|
|
110
53
|
* Allows mocking or overriding the default CSRF token store.
|
|
111
54
|
*
|
|
@@ -113,20 +56,6 @@ export declare function storeCsrfToken(csrfToken: Readonly<CsrfToken>, options:
|
|
|
113
56
|
*/
|
|
114
57
|
csrfTokenStore: CsrfTokenStore;
|
|
115
58
|
}>): Promise<void>;
|
|
116
|
-
/**
|
|
117
|
-
* Parse a raw CSRF token JSON string.
|
|
118
|
-
*
|
|
119
|
-
* @category Internal
|
|
120
|
-
*/
|
|
121
|
-
export declare function parseCsrfToken(value: string | undefined | null, options?: PartialWithUndefined<{
|
|
122
|
-
/**
|
|
123
|
-
* Allowed clock skew tolerance for CSRF token expiration checks. Accounts for differences
|
|
124
|
-
* between server and client clocks.
|
|
125
|
-
*
|
|
126
|
-
* @default {minutes: 5}
|
|
127
|
-
*/
|
|
128
|
-
allowedClockSkew: Readonly<AnyDuration>;
|
|
129
|
-
}>): Readonly<GetCsrfTokenResult>;
|
|
130
59
|
/**
|
|
131
60
|
* Used in client (frontend) code to retrieve the current CSRF token in order to send it with
|
|
132
61
|
* requests to the host (backend).
|
|
@@ -140,13 +69,7 @@ export declare function getCurrentCsrfToken(options: Readonly<CsrfHeaderNameOpti
|
|
|
140
69
|
* @default getDefaultCsrfTokenStore()
|
|
141
70
|
*/
|
|
142
71
|
csrfTokenStore: CsrfTokenStore;
|
|
143
|
-
|
|
144
|
-
* Allowed clock skew tolerance for CSRF token expiration checks.
|
|
145
|
-
*
|
|
146
|
-
* @default {minutes: 5}
|
|
147
|
-
*/
|
|
148
|
-
allowedClockSkew: Readonly<AnyDuration>;
|
|
149
|
-
}>): Promise<Readonly<GetCsrfTokenResult>>;
|
|
72
|
+
}>): Promise<string | undefined>;
|
|
150
73
|
/**
|
|
151
74
|
* Wipes the current stored CSRF token. This should be used by client (frontend) code to react to a
|
|
152
75
|
* session timeout.
|
package/dist/csrf-token.js
CHANGED
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
import { randomString
|
|
2
|
-
import { calculateRelativeDate, fullDateShape, getNowInUtcTimezone, isDateAfter, } from 'date-vir';
|
|
3
|
-
import { defineShape, parseJsonWithShape } from 'object-shape-tester';
|
|
1
|
+
import { randomString } from '@augment-vir/common';
|
|
4
2
|
import { getDefaultCsrfTokenStore } from './csrf-token-store.js';
|
|
5
3
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @category Internal
|
|
9
|
-
*/
|
|
10
|
-
export const csrfTokenShape = defineShape({
|
|
11
|
-
token: '',
|
|
12
|
-
expiration: fullDateShape,
|
|
13
|
-
});
|
|
14
|
-
/**
|
|
15
|
-
* Default allowed clock skew for CSRF token expiration checks. Accounts for differences between
|
|
16
|
-
* server and client clocks when checking token expiration.
|
|
4
|
+
* Default allowed clock skew for JWT expiration checks. Accounts for differences between server and
|
|
5
|
+
* client clocks.
|
|
17
6
|
*
|
|
18
7
|
* @category Internal
|
|
19
8
|
* @default {minutes: 5}
|
|
@@ -22,32 +11,13 @@ export const defaultAllowedClockSkew = {
|
|
|
22
11
|
minutes: 5,
|
|
23
12
|
};
|
|
24
13
|
/**
|
|
25
|
-
* Generates a random, cryptographically secure CSRF token.
|
|
14
|
+
* Generates a random, cryptographically secure CSRF token string.
|
|
26
15
|
*
|
|
27
16
|
* @category Internal
|
|
28
17
|
*/
|
|
29
|
-
export function generateCsrfToken(
|
|
30
|
-
|
|
31
|
-
duration) {
|
|
32
|
-
return {
|
|
33
|
-
token: randomString(256),
|
|
34
|
-
expiration: calculateRelativeDate(getNowInUtcTimezone(), duration),
|
|
35
|
-
};
|
|
18
|
+
export function generateCsrfToken() {
|
|
19
|
+
return randomString(256);
|
|
36
20
|
}
|
|
37
|
-
/**
|
|
38
|
-
* CSRF token failure reasons for {@link GetCsrfTokenResult}.
|
|
39
|
-
*
|
|
40
|
-
* @category Internal
|
|
41
|
-
*/
|
|
42
|
-
export var CsrfTokenFailureReason;
|
|
43
|
-
(function (CsrfTokenFailureReason) {
|
|
44
|
-
/** No CSRF token was found. */
|
|
45
|
-
CsrfTokenFailureReason["DoesNotExist"] = "does-not-exist";
|
|
46
|
-
/** A CSRF token was found but parsing it failed. */
|
|
47
|
-
CsrfTokenFailureReason["ParseFailed"] = "parse-failed";
|
|
48
|
-
/** A CSRF token was found and parsed but is expired. */
|
|
49
|
-
CsrfTokenFailureReason["Expired"] = "expired";
|
|
50
|
-
})(CsrfTokenFailureReason || (CsrfTokenFailureReason = {}));
|
|
51
21
|
/**
|
|
52
22
|
* Resolves a {@link CsrfHeaderNameOption} to the actual header name string.
|
|
53
23
|
*
|
|
@@ -71,10 +41,9 @@ export function resolveCsrfHeaderName(option) {
|
|
|
71
41
|
*
|
|
72
42
|
* @category Auth : Client
|
|
73
43
|
*/
|
|
74
|
-
export function extractCsrfTokenHeader(response, csrfHeaderNameOption
|
|
44
|
+
export function extractCsrfTokenHeader(response, csrfHeaderNameOption) {
|
|
75
45
|
const csrfTokenHeaderName = resolveCsrfHeaderName(csrfHeaderNameOption);
|
|
76
|
-
|
|
77
|
-
return parseCsrfToken(rawCsrfToken, options);
|
|
46
|
+
return response.headers?.get(csrfTokenHeaderName) || undefined;
|
|
78
47
|
}
|
|
79
48
|
/**
|
|
80
49
|
* Stores the given CSRF token into IndexedDB.
|
|
@@ -82,42 +51,7 @@ export function extractCsrfTokenHeader(response, csrfHeaderNameOption, options)
|
|
|
82
51
|
* @category Auth : Client
|
|
83
52
|
*/
|
|
84
53
|
export async function storeCsrfToken(csrfToken, options) {
|
|
85
|
-
await (options.csrfTokenStore || (await getDefaultCsrfTokenStore())).setCsrfToken(
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Parse a raw CSRF token JSON string.
|
|
89
|
-
*
|
|
90
|
-
* @category Internal
|
|
91
|
-
*/
|
|
92
|
-
export function parseCsrfToken(value, options) {
|
|
93
|
-
if (!value) {
|
|
94
|
-
return {
|
|
95
|
-
failure: CsrfTokenFailureReason.DoesNotExist,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
const csrfToken = wrapInTry(() => parseJsonWithShape(value, csrfTokenShape, {
|
|
99
|
-
/** For forwards / backwards compatibility. */
|
|
100
|
-
allowExtraKeys: true,
|
|
101
|
-
}), {
|
|
102
|
-
fallbackValue: undefined,
|
|
103
|
-
});
|
|
104
|
-
if (!csrfToken) {
|
|
105
|
-
return {
|
|
106
|
-
failure: CsrfTokenFailureReason.ParseFailed,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
const effectiveExpiration = calculateRelativeDate(csrfToken.expiration, options?.allowedClockSkew || defaultAllowedClockSkew);
|
|
110
|
-
if (isDateAfter({
|
|
111
|
-
fullDate: getNowInUtcTimezone(),
|
|
112
|
-
relativeTo: effectiveExpiration,
|
|
113
|
-
})) {
|
|
114
|
-
return {
|
|
115
|
-
failure: CsrfTokenFailureReason.Expired,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
return {
|
|
119
|
-
csrfToken,
|
|
120
|
-
};
|
|
54
|
+
await (options.csrfTokenStore || (await getDefaultCsrfTokenStore())).setCsrfToken(csrfToken);
|
|
121
55
|
}
|
|
122
56
|
/**
|
|
123
57
|
* Used in client (frontend) code to retrieve the current CSRF token in order to send it with
|
|
@@ -126,9 +60,8 @@ export function parseCsrfToken(value, options) {
|
|
|
126
60
|
* @category Auth : Client
|
|
127
61
|
*/
|
|
128
62
|
export async function getCurrentCsrfToken(options) {
|
|
129
|
-
|
|
130
|
-
undefined;
|
|
131
|
-
return parseCsrfToken(rawCsrfToken, options);
|
|
63
|
+
return ((await (options.csrfTokenStore || (await getDefaultCsrfTokenStore())).getCsrfToken()) ||
|
|
64
|
+
undefined);
|
|
132
65
|
}
|
|
133
66
|
/**
|
|
134
67
|
* Wipes the current stored CSRF token. This should be used by client (frontend) code to react to a
|
package/package.json
CHANGED
|
@@ -380,7 +380,7 @@ export class BackendAuthClient<
|
|
|
380
380
|
});
|
|
381
381
|
|
|
382
382
|
const csrfHeaderName = resolveCsrfHeaderName(this.config.csrf);
|
|
383
|
-
const {cookie
|
|
383
|
+
const {cookie} = await generateAuthCookie(
|
|
384
384
|
{
|
|
385
385
|
csrfToken: userIdResult.csrfToken,
|
|
386
386
|
userId: userIdResult.userId,
|
|
@@ -391,10 +391,7 @@ export class BackendAuthClient<
|
|
|
391
391
|
|
|
392
392
|
return {
|
|
393
393
|
'set-cookie': cookie,
|
|
394
|
-
[csrfHeaderName]:
|
|
395
|
-
token: userIdResult.csrfToken,
|
|
396
|
-
expiration,
|
|
397
|
-
}),
|
|
394
|
+
[csrfHeaderName]: userIdResult.csrfToken,
|
|
398
395
|
};
|
|
399
396
|
} else {
|
|
400
397
|
this.logForUser(
|
|
@@ -12,7 +12,6 @@ import {type EmptyObject} from 'type-fest';
|
|
|
12
12
|
import {type CsrfTokenStore} from '../csrf-token-store.js';
|
|
13
13
|
import {
|
|
14
14
|
type CsrfHeaderNameOption,
|
|
15
|
-
defaultAllowedClockSkew,
|
|
16
15
|
extractCsrfTokenHeader,
|
|
17
16
|
getCurrentCsrfToken,
|
|
18
17
|
resolveCsrfHeaderName,
|
|
@@ -74,13 +73,6 @@ export type FrontendAuthClientConfig = Readonly<{
|
|
|
74
73
|
* another user.
|
|
75
74
|
*/
|
|
76
75
|
assumedUserHeaderName: string;
|
|
77
|
-
/**
|
|
78
|
-
* Allowed clock skew tolerance for CSRF token expiration checks. Accounts for differences
|
|
79
|
-
* between server and client clocks.
|
|
80
|
-
*
|
|
81
|
-
* @default {minutes: 5}
|
|
82
|
-
*/
|
|
83
|
-
allowedClockSkew: Readonly<AnyDuration>;
|
|
84
76
|
|
|
85
77
|
overrides: PartialWithUndefined<{
|
|
86
78
|
localStorage: Pick<Storage, 'setItem' | 'removeItem' | 'getItem'>;
|
|
@@ -129,19 +121,12 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
|
|
|
129
121
|
this.removeActivityListener?.();
|
|
130
122
|
}
|
|
131
123
|
|
|
132
|
-
/** Wraps {@link getCurrentCsrfToken} to
|
|
124
|
+
/** Wraps {@link getCurrentCsrfToken} to retrieve the stored CSRF token string. */
|
|
133
125
|
public async getCurrentCsrfToken(): Promise<string | undefined> {
|
|
134
|
-
|
|
126
|
+
return await getCurrentCsrfToken({
|
|
135
127
|
...this.config.csrf,
|
|
136
128
|
csrfTokenStore: this.config.overrides?.csrfTokenStore,
|
|
137
|
-
allowedClockSkew: this.config.allowedClockSkew || defaultAllowedClockSkew,
|
|
138
129
|
});
|
|
139
|
-
|
|
140
|
-
if (csrfTokenResult.failure) {
|
|
141
|
-
return undefined;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return csrfTokenResult.csrfToken.token;
|
|
145
130
|
}
|
|
146
131
|
|
|
147
132
|
/**
|
|
@@ -240,9 +225,7 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
|
|
|
240
225
|
throw new Error('Login response failed.');
|
|
241
226
|
}
|
|
242
227
|
|
|
243
|
-
const
|
|
244
|
-
allowedClockSkew: this.config.allowedClockSkew || defaultAllowedClockSkew,
|
|
245
|
-
});
|
|
228
|
+
const csrfToken = extractCsrfTokenHeader(response, this.config.csrf);
|
|
246
229
|
|
|
247
230
|
if (!csrfToken) {
|
|
248
231
|
await this.logout();
|
|
@@ -282,17 +265,6 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
|
|
|
282
265
|
return false;
|
|
283
266
|
}
|
|
284
267
|
|
|
285
|
-
/** If the response has a new CSRF token, store it. */
|
|
286
|
-
const {csrfToken} = extractCsrfTokenHeader(response, this.config.csrf, {
|
|
287
|
-
allowedClockSkew: this.config.allowedClockSkew || defaultAllowedClockSkew,
|
|
288
|
-
});
|
|
289
|
-
if (csrfToken) {
|
|
290
|
-
await storeCsrfToken(csrfToken, {
|
|
291
|
-
...this.config.csrf,
|
|
292
|
-
csrfTokenStore: this.config.overrides?.csrfTokenStore,
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
268
|
return true;
|
|
297
269
|
}
|
|
298
270
|
}
|
package/src/auth.ts
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
type CsrfHeaderNameOption,
|
|
13
13
|
extractCsrfTokenHeader,
|
|
14
14
|
generateCsrfToken,
|
|
15
|
-
parseCsrfToken,
|
|
16
15
|
resolveCsrfHeaderName,
|
|
17
16
|
storeCsrfToken,
|
|
18
17
|
} from './csrf-token.js';
|
|
@@ -66,15 +65,7 @@ function readCsrfTokenHeader(
|
|
|
66
65
|
headers: HeaderContainer,
|
|
67
66
|
csrfHeaderNameOption: Readonly<CsrfHeaderNameOption>,
|
|
68
67
|
): string | undefined {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!rawCsrfToken) {
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const token = parseCsrfToken(rawCsrfToken).csrfToken?.token || rawCsrfToken;
|
|
76
|
-
|
|
77
|
-
return token;
|
|
68
|
+
return readHeader(headers, resolveCsrfHeaderName(csrfHeaderNameOption));
|
|
78
69
|
}
|
|
79
70
|
|
|
80
71
|
/**
|
|
@@ -173,12 +164,12 @@ export async function generateSuccessfulLoginHeaders(
|
|
|
173
164
|
*/
|
|
174
165
|
sessionStartedAt?: number | undefined,
|
|
175
166
|
): Promise<Record<string, string>> {
|
|
176
|
-
const csrfToken = generateCsrfToken(
|
|
167
|
+
const csrfToken = generateCsrfToken();
|
|
177
168
|
const csrfHeaderName = resolveCsrfHeaderName(csrfHeaderNameOption);
|
|
178
169
|
|
|
179
|
-
const {cookie
|
|
170
|
+
const {cookie} = await generateAuthCookie(
|
|
180
171
|
{
|
|
181
|
-
csrfToken
|
|
172
|
+
csrfToken,
|
|
182
173
|
userId,
|
|
183
174
|
sessionStartedAt: sessionStartedAt ?? Date.now(),
|
|
184
175
|
},
|
|
@@ -187,10 +178,7 @@ export async function generateSuccessfulLoginHeaders(
|
|
|
187
178
|
|
|
188
179
|
return {
|
|
189
180
|
'set-cookie': cookie,
|
|
190
|
-
[csrfHeaderName]:
|
|
191
|
-
token: csrfToken.token,
|
|
192
|
-
expiration,
|
|
193
|
-
}),
|
|
181
|
+
[csrfHeaderName]: csrfToken,
|
|
194
182
|
};
|
|
195
183
|
}
|
|
196
184
|
|
|
@@ -233,7 +221,7 @@ export async function handleAuthResponse(
|
|
|
233
221
|
return;
|
|
234
222
|
}
|
|
235
223
|
|
|
236
|
-
const
|
|
224
|
+
const csrfToken = extractCsrfTokenHeader(response, options);
|
|
237
225
|
|
|
238
226
|
if (!csrfToken) {
|
|
239
227
|
throw new Error('Did not receive any CSRF token.');
|
package/src/csrf-token.ts
CHANGED
|
@@ -1,40 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
wrapInTry,
|
|
4
|
-
type PartialWithUndefined,
|
|
5
|
-
type SelectFrom,
|
|
6
|
-
} from '@augment-vir/common';
|
|
7
|
-
import {
|
|
8
|
-
calculateRelativeDate,
|
|
9
|
-
fullDateShape,
|
|
10
|
-
getNowInUtcTimezone,
|
|
11
|
-
isDateAfter,
|
|
12
|
-
type AnyDuration,
|
|
13
|
-
} from 'date-vir';
|
|
14
|
-
import {defineShape, parseJsonWithShape} from 'object-shape-tester';
|
|
1
|
+
import {randomString, type PartialWithUndefined, type SelectFrom} from '@augment-vir/common';
|
|
2
|
+
import {type AnyDuration} from 'date-vir';
|
|
15
3
|
import {type RequireExactlyOne} from 'type-fest';
|
|
16
4
|
import {getDefaultCsrfTokenStore, type CsrfTokenStore} from './csrf-token-store.js';
|
|
17
5
|
|
|
18
6
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @category Internal
|
|
22
|
-
*/
|
|
23
|
-
export const csrfTokenShape = defineShape({
|
|
24
|
-
token: '',
|
|
25
|
-
expiration: fullDateShape,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* A cryptographically CSRF token with expiration date.
|
|
30
|
-
*
|
|
31
|
-
* @category Internal
|
|
32
|
-
*/
|
|
33
|
-
export type CsrfToken = typeof csrfTokenShape.runtimeType;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Default allowed clock skew for CSRF token expiration checks. Accounts for differences between
|
|
37
|
-
* server and client clocks when checking token expiration.
|
|
7
|
+
* Default allowed clock skew for JWT expiration checks. Accounts for differences between server and
|
|
8
|
+
* client clocks.
|
|
38
9
|
*
|
|
39
10
|
* @category Internal
|
|
40
11
|
* @default {minutes: 5}
|
|
@@ -44,32 +15,12 @@ export const defaultAllowedClockSkew: Readonly<AnyDuration> = {
|
|
|
44
15
|
};
|
|
45
16
|
|
|
46
17
|
/**
|
|
47
|
-
* Generates a random, cryptographically secure CSRF token.
|
|
18
|
+
* Generates a random, cryptographically secure CSRF token string.
|
|
48
19
|
*
|
|
49
20
|
* @category Internal
|
|
50
21
|
*/
|
|
51
|
-
export function generateCsrfToken(
|
|
52
|
-
|
|
53
|
-
duration: Readonly<AnyDuration>,
|
|
54
|
-
): CsrfToken {
|
|
55
|
-
return {
|
|
56
|
-
token: randomString(256),
|
|
57
|
-
expiration: calculateRelativeDate(getNowInUtcTimezone(), duration),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* CSRF token failure reasons for {@link GetCsrfTokenResult}.
|
|
63
|
-
*
|
|
64
|
-
* @category Internal
|
|
65
|
-
*/
|
|
66
|
-
export enum CsrfTokenFailureReason {
|
|
67
|
-
/** No CSRF token was found. */
|
|
68
|
-
DoesNotExist = 'does-not-exist',
|
|
69
|
-
/** A CSRF token was found but parsing it failed. */
|
|
70
|
-
ParseFailed = 'parse-failed',
|
|
71
|
-
/** A CSRF token was found and parsed but is expired. */
|
|
72
|
-
Expired = 'expired',
|
|
22
|
+
export function generateCsrfToken(): string {
|
|
23
|
+
return randomString(256);
|
|
73
24
|
}
|
|
74
25
|
|
|
75
26
|
/**
|
|
@@ -103,16 +54,6 @@ export function resolveCsrfHeaderName(option: Readonly<CsrfHeaderNameOption>): s
|
|
|
103
54
|
}
|
|
104
55
|
}
|
|
105
56
|
|
|
106
|
-
/**
|
|
107
|
-
* Output from {@link getCurrentCsrfToken}.
|
|
108
|
-
*
|
|
109
|
-
* @category Internal
|
|
110
|
-
*/
|
|
111
|
-
export type GetCsrfTokenResult = RequireExactlyOne<{
|
|
112
|
-
csrfToken: Readonly<CsrfToken>;
|
|
113
|
-
failure: CsrfTokenFailureReason;
|
|
114
|
-
}>;
|
|
115
|
-
|
|
116
57
|
/**
|
|
117
58
|
* Extract the CSRF token header from a response.
|
|
118
59
|
*
|
|
@@ -121,20 +62,10 @@ export type GetCsrfTokenResult = RequireExactlyOne<{
|
|
|
121
62
|
export function extractCsrfTokenHeader(
|
|
122
63
|
response: Readonly<PartialWithUndefined<SelectFrom<Response, {headers: true}>>>,
|
|
123
64
|
csrfHeaderNameOption: Readonly<CsrfHeaderNameOption>,
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Allowed clock skew tolerance for CSRF token expiration checks.
|
|
127
|
-
*
|
|
128
|
-
* @default {minutes: 5}
|
|
129
|
-
*/
|
|
130
|
-
allowedClockSkew: Readonly<AnyDuration>;
|
|
131
|
-
}>,
|
|
132
|
-
): Readonly<GetCsrfTokenResult> {
|
|
65
|
+
): string | undefined {
|
|
133
66
|
const csrfTokenHeaderName = resolveCsrfHeaderName(csrfHeaderNameOption);
|
|
134
67
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return parseCsrfToken(rawCsrfToken, options);
|
|
68
|
+
return response.headers?.get(csrfTokenHeaderName) || undefined;
|
|
138
69
|
}
|
|
139
70
|
|
|
140
71
|
/**
|
|
@@ -143,7 +74,7 @@ export function extractCsrfTokenHeader(
|
|
|
143
74
|
* @category Auth : Client
|
|
144
75
|
*/
|
|
145
76
|
export async function storeCsrfToken(
|
|
146
|
-
csrfToken:
|
|
77
|
+
csrfToken: string,
|
|
147
78
|
options: Readonly<CsrfHeaderNameOption> &
|
|
148
79
|
PartialWithUndefined<{
|
|
149
80
|
/**
|
|
@@ -154,70 +85,7 @@ export async function storeCsrfToken(
|
|
|
154
85
|
csrfTokenStore: CsrfTokenStore;
|
|
155
86
|
}>,
|
|
156
87
|
): Promise<void> {
|
|
157
|
-
await (options.csrfTokenStore || (await getDefaultCsrfTokenStore())).setCsrfToken(
|
|
158
|
-
JSON.stringify(csrfToken),
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Parse a raw CSRF token JSON string.
|
|
164
|
-
*
|
|
165
|
-
* @category Internal
|
|
166
|
-
*/
|
|
167
|
-
export function parseCsrfToken(
|
|
168
|
-
value: string | undefined | null,
|
|
169
|
-
options?: PartialWithUndefined<{
|
|
170
|
-
/**
|
|
171
|
-
* Allowed clock skew tolerance for CSRF token expiration checks. Accounts for differences
|
|
172
|
-
* between server and client clocks.
|
|
173
|
-
*
|
|
174
|
-
* @default {minutes: 5}
|
|
175
|
-
*/
|
|
176
|
-
allowedClockSkew: Readonly<AnyDuration>;
|
|
177
|
-
}>,
|
|
178
|
-
): Readonly<GetCsrfTokenResult> {
|
|
179
|
-
if (!value) {
|
|
180
|
-
return {
|
|
181
|
-
failure: CsrfTokenFailureReason.DoesNotExist,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const csrfToken: CsrfToken | undefined = wrapInTry(
|
|
186
|
-
() =>
|
|
187
|
-
parseJsonWithShape(value, csrfTokenShape, {
|
|
188
|
-
/** For forwards / backwards compatibility. */
|
|
189
|
-
allowExtraKeys: true,
|
|
190
|
-
}),
|
|
191
|
-
{
|
|
192
|
-
fallbackValue: undefined,
|
|
193
|
-
},
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
if (!csrfToken) {
|
|
197
|
-
return {
|
|
198
|
-
failure: CsrfTokenFailureReason.ParseFailed,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const effectiveExpiration = calculateRelativeDate(
|
|
203
|
-
csrfToken.expiration,
|
|
204
|
-
options?.allowedClockSkew || defaultAllowedClockSkew,
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
isDateAfter({
|
|
209
|
-
fullDate: getNowInUtcTimezone(),
|
|
210
|
-
relativeTo: effectiveExpiration,
|
|
211
|
-
})
|
|
212
|
-
) {
|
|
213
|
-
return {
|
|
214
|
-
failure: CsrfTokenFailureReason.Expired,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return {
|
|
219
|
-
csrfToken,
|
|
220
|
-
};
|
|
88
|
+
await (options.csrfTokenStore || (await getDefaultCsrfTokenStore())).setCsrfToken(csrfToken);
|
|
221
89
|
}
|
|
222
90
|
|
|
223
91
|
/**
|
|
@@ -235,19 +103,12 @@ export async function getCurrentCsrfToken(
|
|
|
235
103
|
* @default getDefaultCsrfTokenStore()
|
|
236
104
|
*/
|
|
237
105
|
csrfTokenStore: CsrfTokenStore;
|
|
238
|
-
/**
|
|
239
|
-
* Allowed clock skew tolerance for CSRF token expiration checks.
|
|
240
|
-
*
|
|
241
|
-
* @default {minutes: 5}
|
|
242
|
-
*/
|
|
243
|
-
allowedClockSkew: Readonly<AnyDuration>;
|
|
244
106
|
}>,
|
|
245
|
-
): Promise<
|
|
246
|
-
|
|
107
|
+
): Promise<string | undefined> {
|
|
108
|
+
return (
|
|
247
109
|
(await (options.csrfTokenStore || (await getDefaultCsrfTokenStore())).getCsrfToken()) ||
|
|
248
|
-
undefined
|
|
249
|
-
|
|
250
|
-
return parseCsrfToken(rawCsrfToken, options);
|
|
110
|
+
undefined
|
|
111
|
+
);
|
|
251
112
|
}
|
|
252
113
|
|
|
253
114
|
/**
|