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 CHANGED
@@ -281,7 +281,7 @@ export async function sendLoginRequest(
281
281
  userLoginData: {username: string; password: string},
282
282
  loginUrl: string,
283
283
  ) {
284
- if ((await getCurrentCsrfToken(csrfOption)).csrfToken) {
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 {csrfToken} = await getCurrentCsrfToken(csrfOption);
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.token,
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, expiration } = await generateAuthCookie({
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]: JSON.stringify({
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 automatically handle wiping an invalid CSRF token. */
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 { defaultAllowedClockSkew, extractCsrfTokenHeader, getCurrentCsrfToken, resolveCsrfHeaderName, storeCsrfToken, } from '../csrf-token.js';
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 automatically handle wiping an invalid CSRF token. */
44
+ /** Wraps {@link getCurrentCsrfToken} to retrieve the stored CSRF token string. */
45
45
  async getCurrentCsrfToken() {
46
- const csrfTokenResult = await getCurrentCsrfToken({
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 { csrfToken } = extractCsrfTokenHeader(response, this.config.csrf, {
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, parseCsrfToken, resolveCsrfHeaderName, storeCsrfToken, } from './csrf-token.js';
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
- const rawCsrfToken = readHeader(headers, resolveCsrfHeaderName(csrfHeaderNameOption));
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(cookieConfig.cookieDuration);
99
+ const csrfToken = generateCsrfToken();
105
100
  const csrfHeaderName = resolveCsrfHeaderName(csrfHeaderNameOption);
106
- const { cookie, expiration } = await generateAuthCookie({
107
- csrfToken: csrfToken.token,
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]: JSON.stringify({
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 { csrfToken } = extractCsrfTokenHeader(response, options);
135
+ const csrfToken = extractCsrfTokenHeader(response, options);
144
136
  if (!csrfToken) {
145
137
  throw new Error('Did not receive any CSRF token.');
146
138
  }
@@ -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
- * Shape definition for {@link CsrfToken}.
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 enum CsrfTokenFailureReason {
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>, options?: PartialWithUndefined<{
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: Readonly<CsrfToken>, options: Readonly<CsrfHeaderNameOption> & PartialWithUndefined<{
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.
@@ -1,19 +1,8 @@
1
- import { randomString, wrapInTry, } from '@augment-vir/common';
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
- * Shape definition for {@link CsrfToken}.
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
- /** How long the CSRF token is valid for. */
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, options) {
44
+ export function extractCsrfTokenHeader(response, csrfHeaderNameOption) {
75
45
  const csrfTokenHeaderName = resolveCsrfHeaderName(csrfHeaderNameOption);
76
- const rawCsrfToken = response.headers?.get(csrfTokenHeaderName);
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(JSON.stringify(csrfToken));
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
- const rawCsrfToken = (await (options.csrfTokenStore || (await getDefaultCsrfTokenStore())).getCsrfToken()) ||
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auth-vir",
3
- "version": "3.1.1",
3
+ "version": "4.0.0",
4
4
  "description": "Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.",
5
5
  "keywords": [
6
6
  "auth",
@@ -380,7 +380,7 @@ export class BackendAuthClient<
380
380
  });
381
381
 
382
382
  const csrfHeaderName = resolveCsrfHeaderName(this.config.csrf);
383
- const {cookie, expiration} = await generateAuthCookie(
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]: JSON.stringify({
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 automatically handle wiping an invalid CSRF token. */
124
+ /** Wraps {@link getCurrentCsrfToken} to retrieve the stored CSRF token string. */
133
125
  public async getCurrentCsrfToken(): Promise<string | undefined> {
134
- const csrfTokenResult = await getCurrentCsrfToken({
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 {csrfToken} = extractCsrfTokenHeader(response, this.config.csrf, {
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
- const rawCsrfToken = readHeader(headers, resolveCsrfHeaderName(csrfHeaderNameOption));
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(cookieConfig.cookieDuration);
167
+ const csrfToken = generateCsrfToken();
177
168
  const csrfHeaderName = resolveCsrfHeaderName(csrfHeaderNameOption);
178
169
 
179
- const {cookie, expiration} = await generateAuthCookie(
170
+ const {cookie} = await generateAuthCookie(
180
171
  {
181
- csrfToken: csrfToken.token,
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]: JSON.stringify({
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 {csrfToken} = extractCsrfTokenHeader(response, options);
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
- randomString,
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
- * Shape definition for {@link CsrfToken}.
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
- /** How long the CSRF token is valid for. */
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
- options?: PartialWithUndefined<{
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
- const rawCsrfToken = response.headers?.get(csrfTokenHeaderName);
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: Readonly<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<Readonly<GetCsrfTokenResult>> {
246
- const rawCsrfToken: string | undefined =
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
  /**