New token arch
This commit is contained in:
76
frontend/src/utils/jwt.ts
Normal file
76
frontend/src/utils/jwt.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* JWT Subscription Utilities
|
||||
*
|
||||
* SECURITY NOTE: Subscription data extracted from JWT is for UI/UX purposes ONLY.
|
||||
* - Use for: Showing/hiding menu items, displaying tier badges, feature previews
|
||||
* - NEVER use for: Access control decisions, billing logic, feature enforcement
|
||||
*
|
||||
* All access control is enforced server-side. The backend will return 402 errors
|
||||
* if a user attempts to access features their subscription doesn't include,
|
||||
* regardless of what the frontend displays.
|
||||
*/
|
||||
|
||||
export interface JWTSubscription {
|
||||
readonly tier: 'starter' | 'professional' | 'enterprise';
|
||||
readonly status: 'active' | 'pending_cancellation' | 'inactive';
|
||||
readonly valid_until: string | null;
|
||||
}
|
||||
|
||||
export interface JWTPayload {
|
||||
user_id: string;
|
||||
email: string;
|
||||
exp: number;
|
||||
iat: number;
|
||||
iss: string;
|
||||
tenant_id?: string;
|
||||
tenant_role?: string;
|
||||
subscription?: JWTSubscription;
|
||||
tenant_access?: Array<{
|
||||
id: string;
|
||||
role: string;
|
||||
tier: string;
|
||||
}>;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function decodeJWT(token: string): JWTPayload | null {
|
||||
try {
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 3) return null;
|
||||
|
||||
const payload = parts[1];
|
||||
const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
|
||||
return JSON.parse(decoded);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getSubscriptionFromJWT(token: string | null): Readonly<JWTSubscription> | null {
|
||||
if (!token) return null;
|
||||
const payload = decodeJWT(token);
|
||||
if (!payload?.subscription) return null;
|
||||
|
||||
// Return frozen object to prevent modification
|
||||
return Object.freeze({
|
||||
tier: payload.subscription.tier,
|
||||
status: payload.subscription.status,
|
||||
valid_until: payload.subscription.valid_until
|
||||
});
|
||||
}
|
||||
|
||||
export function getTenantAccessFromJWT(token: string | null): Array<{
|
||||
id: string;
|
||||
role: string;
|
||||
tier: string;
|
||||
}> | null {
|
||||
if (!token) return null;
|
||||
const payload = decodeJWT(token);
|
||||
return payload?.tenant_access ?? null;
|
||||
}
|
||||
|
||||
export function getPrimaryTenantIdFromJWT(token: string | null): string | null {
|
||||
if (!token) return null;
|
||||
const payload = decodeJWT(token);
|
||||
return payload?.tenant_id ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user