Public Access
feat(auth): use the cookie session and refresh on 401
The token is no longer stored; a tbp_session flag remembers the login. apiFetch renews the session once on a 401 and sends the request again. Parallel requests and other tabs share one refresh, because Auth revokes the session when a refresh token is used twice. Only a 401 from the refresh ends the session; a 502 or no network shows the normal error.
This commit is contained in:
1 parent
5ff8d48c53
commit
d9a39dda22
34 files changed
+297
-147
No files matched your search
+26
-17
@@ -1,28 +1,37 @@
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {isTokenValid, safeReturnPath} from './auth.js';
|
||||
// @vitest-environment jsdom
|
||||
import {afterEach, describe, expect, it} from 'vitest';
|
||||
import {clearSession, hasSession, markSession, safeReturnPath, sessionStamp} from './auth.js';
|
||||
|
||||
const tokenWithPayload = (payload) => {
|
||||
const encode = (obj) => btoa(JSON.stringify(obj)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
return `${encode({alg: 'EdDSA'})}.${encode(payload)}.signature`;
|
||||
};
|
||||
afterEach(() => localStorage.clear());
|
||||
|
||||
describe('isTokenValid', () => {
|
||||
const now = Date.UTC(2026, 8, 24, 12, 0, 0);
|
||||
describe('session flag', () => {
|
||||
it('is set by a login and removed by clearSession', () => {
|
||||
expect(hasSession()).toBe(false);
|
||||
|
||||
it('accepts a token that expires in the future', () => {
|
||||
expect(isTokenValid(tokenWithPayload({exp: now / 1000 + 60}), now)).toBe(true);
|
||||
markSession();
|
||||
expect(hasSession()).toBe(true);
|
||||
|
||||
clearSession();
|
||||
expect(hasSession()).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects an expired token', () => {
|
||||
expect(isTokenValid(tokenWithPayload({exp: now / 1000 - 60}), now)).toBe(false);
|
||||
it('removes the token of the old localStorage login', () => {
|
||||
localStorage.setItem('tbp_jwt', 'old');
|
||||
markSession();
|
||||
expect(localStorage.getItem('tbp_jwt')).toBeNull();
|
||||
|
||||
localStorage.setItem('tbp_jwt', 'old');
|
||||
clearSession();
|
||||
expect(localStorage.getItem('tbp_jwt')).toBeNull();
|
||||
});
|
||||
|
||||
it('rejects a token without exp', () => {
|
||||
expect(isTokenValid(tokenWithPayload({sub: 'a@b.de'}), now)).toBe(false);
|
||||
});
|
||||
it('gives a new stamp on every mark', async () => {
|
||||
markSession();
|
||||
const first = sessionStamp();
|
||||
await new Promise((resolve) => setTimeout(resolve, 2));
|
||||
markSession();
|
||||
|
||||
it.each([null, '', 'not-a-jwt', 'a.%%%.c'])('rejects %j', (token) => {
|
||||
expect(isTokenValid(token, now)).toBe(false);
|
||||
expect(sessionStamp()).not.toBe(first);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user