Public Access
feat(nav): add header navigation and show Steam ID on dashboard
Header links to Dashboard, Library and Settings. The logo, the login page and the landing page lead logged-in users to the dashboard. Expired tokens are removed, and a 403 on the dashboard logs out. Remove the dev dummy token.
This commit is contained in:
1 parent
44e23de6d6
commit
8fa605aff0
8 files changed
+124
-23
No files matched your search
@@ -0,0 +1,26 @@
|
||||
const TOKEN_KEY = 'tbp_jwt';
|
||||
|
||||
/**
|
||||
* Only checks `exp`. The signature is checked by the backends.
|
||||
*/
|
||||
export function isTokenValid(token, nowMs = Date.now()) {
|
||||
try {
|
||||
const payload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
|
||||
const {exp} = JSON.parse(atob(payload));
|
||||
return typeof exp === 'number' && exp * 1000 > nowMs;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stored token, or null. Removes an expired token.
|
||||
*/
|
||||
export function getValidToken() {
|
||||
const token = localStorage.getItem(TOKEN_KEY);
|
||||
if (token && !isTokenValid(token)) {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
return null;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {isTokenValid} 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`;
|
||||
};
|
||||
|
||||
describe('isTokenValid', () => {
|
||||
const now = Date.UTC(2026, 8, 24, 12, 0, 0);
|
||||
|
||||
it('accepts a token that expires in the future', () => {
|
||||
expect(isTokenValid(tokenWithPayload({exp: now / 1000 + 60}), now)).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects an expired token', () => {
|
||||
expect(isTokenValid(tokenWithPayload({exp: now / 1000 - 60}), now)).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects a token without exp', () => {
|
||||
expect(isTokenValid(tokenWithPayload({sub: 'a@b.de'}), now)).toBe(false);
|
||||
});
|
||||
|
||||
it.each([null, '', 'not-a-jwt', 'a.%%%.c'])('rejects %j', (token) => {
|
||||
expect(isTokenValid(token, now)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user