lemma-sdk 0.2.21 → 0.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth.d.ts +8 -0
- package/dist/auth.js +77 -10
- package/dist/browser/lemma-client.js +77 -10
- package/package.json +1 -1
package/dist/auth.d.ts
CHANGED
|
@@ -102,6 +102,14 @@ export declare class AuthManager {
|
|
|
102
102
|
private setState;
|
|
103
103
|
private assertBrowserContext;
|
|
104
104
|
private getCookie;
|
|
105
|
+
private getCookieDomainCandidates;
|
|
106
|
+
private expireCookie;
|
|
107
|
+
/**
|
|
108
|
+
* Defensive cleanup for stale SuperTokens frontend marker cookies/storage.
|
|
109
|
+
* This helps recover when signout/session-expiry paths leave local markers behind.
|
|
110
|
+
*/
|
|
111
|
+
private clearFrontendSessionMarkers;
|
|
112
|
+
private applyUnauthenticatedState;
|
|
105
113
|
private clearInjectedToken;
|
|
106
114
|
private rawSignOutViaBackend;
|
|
107
115
|
/**
|
package/dist/auth.js
CHANGED
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
import Session from "supertokens-web-js/recipe/session";
|
|
19
19
|
import { ensureCookieSessionSupport } from "./supertokens.js";
|
|
20
20
|
const DEFAULT_BLOCKED_REDIRECT_PATHS = ["/login", "/signup", "/auth"];
|
|
21
|
+
const SUPERTOKENS_FRONTEND_MARKER_KEYS = [
|
|
22
|
+
"sFrontToken",
|
|
23
|
+
"st-last-access-token-update",
|
|
24
|
+
"sIRTFrontend",
|
|
25
|
+
"sAntiCsrf",
|
|
26
|
+
"st-access-token",
|
|
27
|
+
"st-refresh-token",
|
|
28
|
+
];
|
|
21
29
|
const LOCALSTORAGE_TOKEN_KEY = "lemma_token";
|
|
22
30
|
function readStorageToken() {
|
|
23
31
|
if (typeof window === "undefined")
|
|
@@ -214,6 +222,68 @@ export class AuthManager {
|
|
|
214
222
|
const match = document.cookie.match(new RegExp(`(?:^|; )${escaped}=([^;]*)`));
|
|
215
223
|
return match ? decodeURIComponent(match[1]) : undefined;
|
|
216
224
|
}
|
|
225
|
+
getCookieDomainCandidates() {
|
|
226
|
+
if (typeof window === "undefined") {
|
|
227
|
+
return [undefined];
|
|
228
|
+
}
|
|
229
|
+
const host = window.location.hostname;
|
|
230
|
+
const isIpv4 = /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host);
|
|
231
|
+
const isIpv6 = host.includes(":");
|
|
232
|
+
if (!host || host === "localhost" || isIpv4 || isIpv6) {
|
|
233
|
+
return [undefined];
|
|
234
|
+
}
|
|
235
|
+
const domains = new Set();
|
|
236
|
+
const parts = host.split(".").filter(Boolean);
|
|
237
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
238
|
+
const candidate = parts.slice(i).join(".");
|
|
239
|
+
if (!candidate)
|
|
240
|
+
continue;
|
|
241
|
+
domains.add(candidate);
|
|
242
|
+
domains.add(`.${candidate}`);
|
|
243
|
+
}
|
|
244
|
+
return [undefined, ...domains];
|
|
245
|
+
}
|
|
246
|
+
expireCookie(name, domain) {
|
|
247
|
+
if (typeof document === "undefined")
|
|
248
|
+
return;
|
|
249
|
+
const domainPart = domain ? `;domain=${domain}` : "";
|
|
250
|
+
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/${domainPart};samesite=lax`;
|
|
251
|
+
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/${domainPart}`;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Defensive cleanup for stale SuperTokens frontend marker cookies/storage.
|
|
255
|
+
* This helps recover when signout/session-expiry paths leave local markers behind.
|
|
256
|
+
*/
|
|
257
|
+
clearFrontendSessionMarkers() {
|
|
258
|
+
if (typeof window === "undefined")
|
|
259
|
+
return;
|
|
260
|
+
for (const key of SUPERTOKENS_FRONTEND_MARKER_KEYS) {
|
|
261
|
+
try {
|
|
262
|
+
window.localStorage.removeItem(key);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// ignore storage errors
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
window.sessionStorage.removeItem(key);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// ignore storage errors
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const domains = this.getCookieDomainCandidates();
|
|
275
|
+
for (const key of SUPERTOKENS_FRONTEND_MARKER_KEYS) {
|
|
276
|
+
for (const domain of domains) {
|
|
277
|
+
this.expireCookie(key, domain);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
applyUnauthenticatedState() {
|
|
282
|
+
this.clearFrontendSessionMarkers();
|
|
283
|
+
const next = { status: "unauthenticated", user: null };
|
|
284
|
+
this.setState(next);
|
|
285
|
+
return next;
|
|
286
|
+
}
|
|
217
287
|
clearInjectedToken() {
|
|
218
288
|
this.injectedToken = null;
|
|
219
289
|
clearTestingToken();
|
|
@@ -322,15 +392,11 @@ export class AuthManager {
|
|
|
322
392
|
const response = await fetch(`${this.apiUrl}/users/me`, this.getRequestInit({ method: "GET" }));
|
|
323
393
|
// Only 401 means not authenticated — 403 means authenticated but forbidden
|
|
324
394
|
if (response.status === 401) {
|
|
325
|
-
|
|
326
|
-
this.setState(next);
|
|
327
|
-
return next;
|
|
395
|
+
return this.applyUnauthenticatedState();
|
|
328
396
|
}
|
|
329
397
|
if (!response.ok) {
|
|
330
398
|
// For non-401 errors on /users/me, treat as unauthenticated (conservative)
|
|
331
|
-
|
|
332
|
-
this.setState(next);
|
|
333
|
-
return next;
|
|
399
|
+
return this.applyUnauthenticatedState();
|
|
334
400
|
}
|
|
335
401
|
const user = (await response.json());
|
|
336
402
|
const next = { status: "authenticated", user };
|
|
@@ -338,9 +404,7 @@ export class AuthManager {
|
|
|
338
404
|
return next;
|
|
339
405
|
}
|
|
340
406
|
catch {
|
|
341
|
-
|
|
342
|
-
this.setState(next);
|
|
343
|
-
return next;
|
|
407
|
+
return this.applyUnauthenticatedState();
|
|
344
408
|
}
|
|
345
409
|
}
|
|
346
410
|
/**
|
|
@@ -348,7 +412,7 @@ export class AuthManager {
|
|
|
348
412
|
* Does NOT redirect — call redirectToAuth() explicitly if desired.
|
|
349
413
|
*/
|
|
350
414
|
markUnauthenticated() {
|
|
351
|
-
this.
|
|
415
|
+
this.applyUnauthenticatedState();
|
|
352
416
|
}
|
|
353
417
|
/**
|
|
354
418
|
* Sign out the current user session.
|
|
@@ -376,6 +440,9 @@ export class AuthManager {
|
|
|
376
440
|
// best effort fallback only
|
|
377
441
|
}
|
|
378
442
|
}
|
|
443
|
+
// Always clear frontend markers on logout attempt, even if backend session
|
|
444
|
+
// cleanup is partial. This avoids stale local "EXISTS" signals.
|
|
445
|
+
this.clearFrontendSessionMarkers();
|
|
379
446
|
const isAuthenticated = await this.isAuthenticatedViaCookie();
|
|
380
447
|
if (!isAuthenticated) {
|
|
381
448
|
this.markUnauthenticated();
|
|
@@ -204,6 +204,14 @@ exports.resolveSafeRedirectUri = resolveSafeRedirectUri;
|
|
|
204
204
|
const session_1 = require("supertokens-web-js/recipe/session");
|
|
205
205
|
const supertokens_js_1 = require("./supertokens.js");
|
|
206
206
|
const DEFAULT_BLOCKED_REDIRECT_PATHS = ["/login", "/signup", "/auth"];
|
|
207
|
+
const SUPERTOKENS_FRONTEND_MARKER_KEYS = [
|
|
208
|
+
"sFrontToken",
|
|
209
|
+
"st-last-access-token-update",
|
|
210
|
+
"sIRTFrontend",
|
|
211
|
+
"sAntiCsrf",
|
|
212
|
+
"st-access-token",
|
|
213
|
+
"st-refresh-token",
|
|
214
|
+
];
|
|
207
215
|
const LOCALSTORAGE_TOKEN_KEY = "lemma_token";
|
|
208
216
|
function readStorageToken() {
|
|
209
217
|
if (typeof window === "undefined")
|
|
@@ -397,6 +405,68 @@ class AuthManager {
|
|
|
397
405
|
const match = document.cookie.match(new RegExp(`(?:^|; )${escaped}=([^;]*)`));
|
|
398
406
|
return match ? decodeURIComponent(match[1]) : undefined;
|
|
399
407
|
}
|
|
408
|
+
getCookieDomainCandidates() {
|
|
409
|
+
if (typeof window === "undefined") {
|
|
410
|
+
return [undefined];
|
|
411
|
+
}
|
|
412
|
+
const host = window.location.hostname;
|
|
413
|
+
const isIpv4 = /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host);
|
|
414
|
+
const isIpv6 = host.includes(":");
|
|
415
|
+
if (!host || host === "localhost" || isIpv4 || isIpv6) {
|
|
416
|
+
return [undefined];
|
|
417
|
+
}
|
|
418
|
+
const domains = new Set();
|
|
419
|
+
const parts = host.split(".").filter(Boolean);
|
|
420
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
421
|
+
const candidate = parts.slice(i).join(".");
|
|
422
|
+
if (!candidate)
|
|
423
|
+
continue;
|
|
424
|
+
domains.add(candidate);
|
|
425
|
+
domains.add(`.${candidate}`);
|
|
426
|
+
}
|
|
427
|
+
return [undefined, ...domains];
|
|
428
|
+
}
|
|
429
|
+
expireCookie(name, domain) {
|
|
430
|
+
if (typeof document === "undefined")
|
|
431
|
+
return;
|
|
432
|
+
const domainPart = domain ? `;domain=${domain}` : "";
|
|
433
|
+
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/${domainPart};samesite=lax`;
|
|
434
|
+
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/${domainPart}`;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Defensive cleanup for stale SuperTokens frontend marker cookies/storage.
|
|
438
|
+
* This helps recover when signout/session-expiry paths leave local markers behind.
|
|
439
|
+
*/
|
|
440
|
+
clearFrontendSessionMarkers() {
|
|
441
|
+
if (typeof window === "undefined")
|
|
442
|
+
return;
|
|
443
|
+
for (const key of SUPERTOKENS_FRONTEND_MARKER_KEYS) {
|
|
444
|
+
try {
|
|
445
|
+
window.localStorage.removeItem(key);
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
// ignore storage errors
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
window.sessionStorage.removeItem(key);
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
// ignore storage errors
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const domains = this.getCookieDomainCandidates();
|
|
458
|
+
for (const key of SUPERTOKENS_FRONTEND_MARKER_KEYS) {
|
|
459
|
+
for (const domain of domains) {
|
|
460
|
+
this.expireCookie(key, domain);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
applyUnauthenticatedState() {
|
|
465
|
+
this.clearFrontendSessionMarkers();
|
|
466
|
+
const next = { status: "unauthenticated", user: null };
|
|
467
|
+
this.setState(next);
|
|
468
|
+
return next;
|
|
469
|
+
}
|
|
400
470
|
clearInjectedToken() {
|
|
401
471
|
this.injectedToken = null;
|
|
402
472
|
clearTestingToken();
|
|
@@ -505,15 +575,11 @@ class AuthManager {
|
|
|
505
575
|
const response = await fetch(`${this.apiUrl}/users/me`, this.getRequestInit({ method: "GET" }));
|
|
506
576
|
// Only 401 means not authenticated — 403 means authenticated but forbidden
|
|
507
577
|
if (response.status === 401) {
|
|
508
|
-
|
|
509
|
-
this.setState(next);
|
|
510
|
-
return next;
|
|
578
|
+
return this.applyUnauthenticatedState();
|
|
511
579
|
}
|
|
512
580
|
if (!response.ok) {
|
|
513
581
|
// For non-401 errors on /users/me, treat as unauthenticated (conservative)
|
|
514
|
-
|
|
515
|
-
this.setState(next);
|
|
516
|
-
return next;
|
|
582
|
+
return this.applyUnauthenticatedState();
|
|
517
583
|
}
|
|
518
584
|
const user = (await response.json());
|
|
519
585
|
const next = { status: "authenticated", user };
|
|
@@ -521,9 +587,7 @@ class AuthManager {
|
|
|
521
587
|
return next;
|
|
522
588
|
}
|
|
523
589
|
catch {
|
|
524
|
-
|
|
525
|
-
this.setState(next);
|
|
526
|
-
return next;
|
|
590
|
+
return this.applyUnauthenticatedState();
|
|
527
591
|
}
|
|
528
592
|
}
|
|
529
593
|
/**
|
|
@@ -531,7 +595,7 @@ class AuthManager {
|
|
|
531
595
|
* Does NOT redirect — call redirectToAuth() explicitly if desired.
|
|
532
596
|
*/
|
|
533
597
|
markUnauthenticated() {
|
|
534
|
-
this.
|
|
598
|
+
this.applyUnauthenticatedState();
|
|
535
599
|
}
|
|
536
600
|
/**
|
|
537
601
|
* Sign out the current user session.
|
|
@@ -559,6 +623,9 @@ class AuthManager {
|
|
|
559
623
|
// best effort fallback only
|
|
560
624
|
}
|
|
561
625
|
}
|
|
626
|
+
// Always clear frontend markers on logout attempt, even if backend session
|
|
627
|
+
// cleanup is partial. This avoids stale local "EXISTS" signals.
|
|
628
|
+
this.clearFrontendSessionMarkers();
|
|
562
629
|
const isAuthenticated = await this.isAuthenticatedViaCookie();
|
|
563
630
|
if (!isAuthenticated) {
|
|
564
631
|
this.markUnauthenticated();
|