70 lines
2 KiB
TypeScript
70 lines
2 KiB
TypeScript
import { defineStore } from "pinia";
|
|
import { ref, computed } from "vue";
|
|
|
|
interface Claims {
|
|
sub: string;
|
|
permissions: string;
|
|
exp: number;
|
|
}
|
|
|
|
function parseJwt(token: string): Claims | null {
|
|
try {
|
|
const payload = token.split(".")[1];
|
|
return payload ? (JSON.parse(atob(payload)) as Claims) : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export const useAuthStore = defineStore("auth", () => {
|
|
const token = ref<string | null>(localStorage.getItem("mercury_token"));
|
|
const claims = computed<Claims | null>(() =>
|
|
token.value ? parseJwt(token.value) : null,
|
|
);
|
|
const isAuthenticated = computed(() => {
|
|
if (!claims.value) return false;
|
|
return claims.value.exp * 1000 > Date.now();
|
|
});
|
|
const username = computed(() => claims.value?.sub ?? "");
|
|
|
|
function hasPermission(bit: bigint): boolean {
|
|
if (!claims.value) return false;
|
|
const mask = BigInt(claims.value.permissions);
|
|
return (mask & bit) !== 0n;
|
|
}
|
|
|
|
const isSuperAdmin = computed(() => hasPermission(32n));
|
|
|
|
async function login(username: string, password: string): Promise<void> {
|
|
const res = await fetch("/auth/login", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
if (!res.ok) throw new Error("Invalid credentials");
|
|
const data = await res.json();
|
|
token.value = data.token;
|
|
localStorage.setItem("mercury_token", data.token);
|
|
}
|
|
|
|
function logout() {
|
|
token.value = null;
|
|
localStorage.removeItem("mercury_token");
|
|
}
|
|
|
|
function authHeaders(): Record<string, string> {
|
|
return token.value ? { Authorization: `Bearer ${token.value}` } : {};
|
|
}
|
|
|
|
return {
|
|
token,
|
|
claims,
|
|
isAuthenticated,
|
|
username,
|
|
isSuperAdmin,
|
|
hasPermission,
|
|
login,
|
|
logout,
|
|
authHeaders,
|
|
};
|
|
});
|