207 lines
4.8 KiB
TypeScript
207 lines
4.8 KiB
TypeScript
export interface User {
|
|
id: string
|
|
email: string
|
|
name?: string
|
|
}
|
|
|
|
export interface JWTPayload {
|
|
sub: string
|
|
email: string
|
|
name?: string
|
|
exp: number
|
|
iat: number
|
|
}
|
|
|
|
export function decodeJWT(token: string): JWTPayload | null {
|
|
try {
|
|
const base64Url = token.split(".")[1]
|
|
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/")
|
|
const jsonPayload = decodeURIComponent(
|
|
atob(base64)
|
|
.split("")
|
|
.map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2))
|
|
.join("")
|
|
)
|
|
return JSON.parse(jsonPayload)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function isTokenExpired(token: string): boolean {
|
|
const payload = decodeJWT(token)
|
|
if (!payload) return true
|
|
return Date.now() >= payload.exp * 1000
|
|
}
|
|
|
|
function getStorage(): Storage {
|
|
if (typeof window === "undefined") return localStorage
|
|
return localStorage.getItem("remember_me") === "true"
|
|
? localStorage
|
|
: sessionStorage
|
|
}
|
|
|
|
function getToken(key: string): string | null {
|
|
return localStorage.getItem(key) ?? sessionStorage.getItem(key)
|
|
}
|
|
|
|
export function setTokens(accessToken: string, refreshToken: string, remember?: boolean) {
|
|
if (typeof window === "undefined") return
|
|
|
|
if (remember !== undefined) {
|
|
localStorage.setItem("remember_me", String(remember))
|
|
}
|
|
|
|
const storage = remember !== undefined
|
|
? (remember ? localStorage : sessionStorage)
|
|
: getStorage()
|
|
|
|
localStorage.removeItem("access_token")
|
|
localStorage.removeItem("refresh_token")
|
|
sessionStorage.removeItem("access_token")
|
|
sessionStorage.removeItem("refresh_token")
|
|
|
|
storage.setItem("access_token", accessToken)
|
|
storage.setItem("refresh_token", refreshToken)
|
|
}
|
|
|
|
function clearTokens() {
|
|
localStorage.removeItem("access_token")
|
|
localStorage.removeItem("refresh_token")
|
|
localStorage.removeItem("remember_me")
|
|
sessionStorage.removeItem("access_token")
|
|
sessionStorage.removeItem("refresh_token")
|
|
}
|
|
|
|
export async function refreshAccessToken(): Promise<string | null> {
|
|
if (typeof window === "undefined") return null
|
|
|
|
const refreshToken = getToken("refresh_token")
|
|
if (!refreshToken) {
|
|
clearTokens()
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const res = await fetch("http://localhost:8080/api/auth/refresh", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
})
|
|
|
|
if (!res.ok) {
|
|
clearTokens()
|
|
return null
|
|
}
|
|
|
|
const data = await res.json()
|
|
setTokens(data.access_token, data.refresh_token)
|
|
return data.access_token
|
|
} catch {
|
|
clearTokens()
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function getUser(): User | null {
|
|
if (typeof window === "undefined") return null
|
|
|
|
const token = getToken("access_token")
|
|
if (!token || isTokenExpired(token)) return null
|
|
|
|
const payload = decodeJWT(token)
|
|
if (!payload) return null
|
|
|
|
return {
|
|
id: payload.sub,
|
|
email: payload.email,
|
|
name: payload.name,
|
|
}
|
|
}
|
|
|
|
export async function getUserAsync(): Promise<User | null> {
|
|
if (typeof window === "undefined") return null
|
|
|
|
let token = getToken("access_token")
|
|
|
|
if (!token || isTokenExpired(token)) {
|
|
token = await refreshAccessToken()
|
|
if (!token) return null
|
|
}
|
|
|
|
const payload = decodeJWT(token)
|
|
if (!payload) return null
|
|
|
|
return {
|
|
id: payload.sub,
|
|
email: payload.email,
|
|
name: payload.name,
|
|
}
|
|
}
|
|
|
|
export function getAccessToken(): string | null {
|
|
if (typeof window === "undefined") return null
|
|
|
|
const token = getToken("access_token")
|
|
if (!token || isTokenExpired(token)) return null
|
|
return token
|
|
}
|
|
|
|
export async function getAccessTokenAsync(): Promise<string | null> {
|
|
if (typeof window === "undefined") return null
|
|
|
|
let token = getToken("access_token")
|
|
if (token && !isTokenExpired(token)) return token
|
|
|
|
token = await refreshAccessToken()
|
|
return token
|
|
}
|
|
|
|
export async function logout() {
|
|
if (typeof window === "undefined") return
|
|
|
|
const token = getToken("access_token")
|
|
const refreshToken = getToken("refresh_token")
|
|
|
|
try {
|
|
await fetch("http://localhost:8080/api/auth/logout", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: token ? `Bearer ${token}` : "",
|
|
},
|
|
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
})
|
|
} catch {
|
|
}
|
|
|
|
clearTokens()
|
|
}
|
|
|
|
export async function fetchWithAuth(url: string, options: RequestInit = {}) {
|
|
let token = await getAccessTokenAsync()
|
|
|
|
const res = await fetch(url, {
|
|
...options,
|
|
headers: {
|
|
...options.headers,
|
|
Authorization: token ? `Bearer ${token}` : "",
|
|
},
|
|
})
|
|
|
|
if (res.status === 401) {
|
|
token = await refreshAccessToken()
|
|
if (token) {
|
|
return fetch(url, {
|
|
...options,
|
|
headers: {
|
|
...options.headers,
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|