another step
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* JWT helpers shared by the edge middleware and server components.
|
||||
*
|
||||
* Pure functions with no `next/headers` dependency, so they are safe to import
|
||||
* from `middleware.ts` (edge runtime) and from RSCs alike. They only inspect the
|
||||
* token's `exp` claim — a cheap liveness check for routing/UX, NOT a security
|
||||
* boundary. The signature is never verified here; the API server is the only
|
||||
* authority that actually trusts the token.
|
||||
*/
|
||||
export interface JwtPayload {
|
||||
exp?: number;
|
||||
sub?: string;
|
||||
[claim: string]: unknown;
|
||||
}
|
||||
|
||||
// JWTs use base64url (no padding, `-`/`_` instead of `+`/`/`); atob expects
|
||||
// standard base64, so normalise before decoding.
|
||||
function base64UrlDecode(segment: string): string {
|
||||
const base64 = segment.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');
|
||||
return atob(padded);
|
||||
}
|
||||
|
||||
export function decodeJwtPayload(token: string | undefined): JwtPayload | null {
|
||||
if (!token) return null;
|
||||
const segment = token.split('.')[1];
|
||||
if (!segment) return null;
|
||||
try {
|
||||
return JSON.parse(base64UrlDecode(segment)) as JwtPayload;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function isTokenAlive(token: string | undefined): boolean {
|
||||
const payload = decodeJwtPayload(token);
|
||||
return typeof payload?.exp === 'number' && payload.exp * 1000 > Date.now();
|
||||
}
|
||||
Reference in New Issue
Block a user