source: frontend/src/lib/auth.ts@ b8093a0

Last change on this file since b8093a0 was b8093a0, checked in by imbrsk <boris696boris@…>, 3 days ago

Merge iknow-remaster into frontend/

Bring the Next.js frontend into this repository as a monorepo subdirectory,
preserving its full commit history via a subtree merge.

  • Property mode set to 100644
File size: 3.5 KB
Line 
1import { API_BASE_URL } from './api';
2export type AuthTokens = {
3 accessToken: string;
4 refreshToken: string;
5};
6
7export type UserRole = 'Professor' | 'Student' | 'Admin' | string;
8
9export type AuthSession = AuthTokens & {
10 role: UserRole;
11};
12
13type StoredToken = {
14 value: string;
15 expiresAt: number; // epoch ms
16};
17
18const STORAGE_KEYS = {
19 access: 'iknow.auth.access',
20 refresh: 'iknow.auth.refresh',
21 role: 'iknow.auth.role',
22} as const;
23
24const ACCESS_TOKEN_TTL_MS = 60 * 60 * 1000; // 1 hour
25const REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000; // ~1 month (30 days)
26
27function isBrowser() {
28 return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
29}
30
31function readStoredToken(key: string): StoredToken | null {
32 if (!isBrowser()) return null;
33 const raw = window.localStorage.getItem(key);
34 if (!raw) return null;
35
36 try {
37 const parsed = JSON.parse(raw) as StoredToken;
38 if (!parsed?.value || typeof parsed.expiresAt !== 'number') {
39 window.localStorage.removeItem(key);
40 return null;
41 }
42
43 if (Date.now() >= parsed.expiresAt) {
44 window.localStorage.removeItem(key);
45 return null;
46 }
47
48 return parsed;
49 } catch {
50 window.localStorage.removeItem(key);
51 return null;
52 }
53}
54
55function writeStoredToken(key: string, value: string, ttlMs: number) {
56 if (!isBrowser()) return;
57 const payload: StoredToken = {
58 value,
59 expiresAt: Date.now() + ttlMs,
60 };
61 window.localStorage.setItem(key, JSON.stringify(payload));
62}
63
64export function setAuthTokens(tokens: AuthTokens) {
65 writeStoredToken(STORAGE_KEYS.access, tokens.accessToken, ACCESS_TOKEN_TTL_MS);
66 writeStoredToken(STORAGE_KEYS.refresh, tokens.refreshToken, REFRESH_TOKEN_TTL_MS);
67}
68
69export function setUserRole(role: UserRole) {
70 // Keep role TTL aligned with access token TTL.
71 writeStoredToken(STORAGE_KEYS.role, role, ACCESS_TOKEN_TTL_MS);
72}
73
74export function getAccessToken(): string | null {
75 return readStoredToken(STORAGE_KEYS.access)?.value ?? null;
76}
77
78export function getRefreshToken(): string | null {
79 return readStoredToken(STORAGE_KEYS.refresh)?.value ?? null;
80}
81
82export function getUserRole(): UserRole | null {
83 return readStoredToken(STORAGE_KEYS.role)?.value ?? null;
84}
85
86export function clearAuthTokens() {
87 if (!isBrowser()) return;
88 window.localStorage.removeItem(STORAGE_KEYS.access);
89 window.localStorage.removeItem(STORAGE_KEYS.refresh);
90 window.localStorage.removeItem(STORAGE_KEYS.role);
91}
92
93export async function login(params: { email: string; password: string }): Promise<AuthSession> {
94 const baseUrl = API_BASE_URL;
95
96 const response = await fetch(`${baseUrl}/api/auth/login`, {
97 method: 'POST',
98 headers: {
99 'Content-Type': 'application/json',
100 },
101 body: JSON.stringify({
102 email: params.email,
103 password: params.password,
104 GenerateRefreshToken: true,
105 }),
106 });
107
108 if (!response.ok) {
109 let message = `Login failed (${response.status})`;
110 try {
111 const text = await response.text();
112 if (text) message = text;
113 } catch {
114 // ignore
115 }
116 throw new Error(message);
117 }
118
119 const data = (await response.json()) as {
120 token?: string;
121 refreshToken?: string;
122 role?: string;
123 };
124
125 const accessToken = data?.token;
126 const refreshToken = data?.refreshToken;
127 const role = data?.role;
128
129 if (!accessToken || !refreshToken) {
130 throw new Error('Login response did not contain tokens.');
131 }
132
133 const session: AuthSession = {
134 accessToken,
135 refreshToken,
136 role: role ?? 'Student',
137 };
138
139 setAuthTokens(session);
140 setUserRole(session.role);
141 return session;
142}
Note: See TracBrowser for help on using the repository browser.