Jotzy/frontend/src/stores/authStore.ts
2026-01-06 15:11:08 +00:00

165 lines
4.1 KiB
TypeScript

import { create } from "zustand";
import { persist } from "zustand/middleware";
import {
deriveKey,
generateMasterKey,
unwrapMasterKey,
wrapMasterKey,
} from "../api/encryption";
interface User {
id: number;
username: string;
email: string;
salt: string;
}
interface AuthState {
user: User | null;
encryptionKey: CryptoKey | null;
isAuthenticated: boolean;
rememberMe: boolean;
setRememberMe: (remember: boolean) => void;
login: (username: string, password: string) => Promise<void>;
register: (
username: string,
email: string,
password: string,
) => Promise<void>;
logout: () => Promise<void>;
checkAuth: () => Promise<void>;
initEncryptionKey: (password: string, salt: string) => Promise<void>;
clearAll: () => void;
}
const API_URL = import.meta.env.PROD
? "/api" // ← Same domain, different path
: "http://localhost:8000/api";
export const useAuthStore = create<AuthState>()(
persist(
(set, get) => ({
user: null,
encryptionKey: null,
isAuthenticated: false,
rememberMe: false,
setRememberMe: (bool) => {
set({ rememberMe: bool });
},
initEncryptionKey: async (password: string, salt: string) => {
const key = await deriveKey(password, salt);
set({ encryptionKey: key });
},
register: async (username: string, email: string, password: string) => {
const masterKey = await generateMasterKey();
const salt = crypto.randomUUID();
const kek = await deriveKey(password, salt);
const wrappedMasterKey = await wrapMasterKey(masterKey, kek);
const response = await fetch(`${API_URL}/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({
username,
email,
password,
salt,
wrappedMasterKey,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail);
}
const data = await response.json();
set({
user: data.user,
isAuthenticated: true,
encryptionKey: masterKey,
});
},
login: async (username: string, password: string) => {
const response = await fetch(`${API_URL}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ username, password }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail);
}
const { user } = await response.json();
const kek = await deriveKey(password, user.salt);
const masterKey = await unwrapMasterKey(user.wrapped_master_key, kek);
set({ encryptionKey: masterKey, user, isAuthenticated: true });
},
logout: async () => {
await fetch(`${API_URL}/auth/logout`, {
method: "POST",
credentials: "include",
});
set({
user: null,
encryptionKey: null,
isAuthenticated: false,
});
get().clearAll();
},
checkAuth: async () => {
try {
const response = await fetch(`${API_URL}/auth/me`, {
credentials: "include",
});
if (!response.ok) {
get().logout();
return;
}
const data = await response.json();
set({ user: data.user, isAuthenticated: true });
} catch (e) {
get().logout();
}
},
clearAll: () => {
set({
user: null,
encryptionKey: null,
isAuthenticated: false,
rememberMe: false,
});
localStorage.clear();
},
}),
{
name: "auth-storage",
partialize: (state) => {
return {
user: state.user,
isAuthenticated: state.isAuthenticated,
};
},
},
),
);
if (typeof window !== "undefined") {
(window as any).useAuthStore = useAuthStore;
}