mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-10-26 15:54:20 -04:00
160 lines
4.1 KiB
TypeScript
160 lines
4.1 KiB
TypeScript
import { ref, computed } from "vue";
|
|
import type { UserOut } from "~/lib/api/types/user";
|
|
|
|
interface AuthData {
|
|
value: UserOut | null;
|
|
}
|
|
|
|
interface AuthStatus {
|
|
value: "loading" | "authenticated" | "unauthenticated";
|
|
}
|
|
|
|
interface AuthState {
|
|
data: AuthData;
|
|
status: AuthStatus;
|
|
signIn: (credentials: FormData, options?: { redirect?: boolean }) => Promise<void>;
|
|
signOut: (callbackUrl?: string) => Promise<void>;
|
|
refresh: () => Promise<void>;
|
|
getSession: () => Promise<void>;
|
|
setToken: (token: string | null) => void;
|
|
}
|
|
|
|
const authUser = ref<UserOut | null>(null);
|
|
const authStatus = ref<"loading" | "authenticated" | "unauthenticated">("unauthenticated");
|
|
|
|
export const useAuthBackend = function (): AuthState {
|
|
const { $axios } = useNuxtApp();
|
|
const router = useRouter();
|
|
const tokenName = useRuntimeConfig().public.AUTH_TOKEN;
|
|
const tokenCookie = useCookie(tokenName);
|
|
|
|
function setToken(token: string | null) {
|
|
tokenCookie.value = token;
|
|
}
|
|
|
|
function handleAuthError(error: any, redirect = false) {
|
|
// Only clear token on auth errors, not network errors
|
|
if (error?.response?.status === 401) {
|
|
setToken(null);
|
|
authUser.value = null;
|
|
authStatus.value = "unauthenticated";
|
|
if (redirect) {
|
|
router.push("/login");
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async function getSession(): Promise<void> {
|
|
if (!tokenCookie.value) {
|
|
authUser.value = null;
|
|
authStatus.value = "unauthenticated";
|
|
return;
|
|
}
|
|
|
|
authStatus.value = "loading";
|
|
try {
|
|
const { data } = await $axios.get<UserOut>("/api/users/self");
|
|
authUser.value = data;
|
|
authStatus.value = "authenticated";
|
|
}
|
|
catch (error: any) {
|
|
handleAuthError(error);
|
|
authStatus.value = "unauthenticated";
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function signIn(credentials: FormData): Promise<void> {
|
|
authStatus.value = "loading";
|
|
|
|
try {
|
|
const response = await $axios.post("/api/auth/token", credentials, {
|
|
headers: {
|
|
"Content-Type": "multipart/form-data",
|
|
},
|
|
});
|
|
|
|
const { access_token } = response.data;
|
|
setToken(access_token);
|
|
await getSession();
|
|
}
|
|
catch (error) {
|
|
authStatus.value = "unauthenticated";
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function signOut(callbackUrl: string = ""): Promise<void> {
|
|
try {
|
|
await $axios.post("/api/auth/logout");
|
|
}
|
|
catch (error) {
|
|
// Continue with logout even if API call fails
|
|
console.warn("Logout API call failed:", error);
|
|
}
|
|
finally {
|
|
setToken(null);
|
|
authUser.value = null;
|
|
authStatus.value = "unauthenticated";
|
|
await router.push(callbackUrl || "/login");
|
|
}
|
|
}
|
|
|
|
async function refresh(): Promise<void> {
|
|
if (!tokenCookie.value) return;
|
|
|
|
try {
|
|
const response = await $axios.get("/api/auth/refresh");
|
|
const { access_token } = response.data;
|
|
setToken(access_token);
|
|
await getSession();
|
|
}
|
|
catch (error: any) {
|
|
handleAuthError(error, true);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Auto-refresh user data periodically when authenticated
|
|
if (import.meta.client) {
|
|
let refreshInterval: NodeJS.Timeout | null = null;
|
|
|
|
watch(() => authStatus.value, (status) => {
|
|
if (status === "authenticated") {
|
|
refreshInterval = setInterval(() => {
|
|
if (tokenCookie.value) {
|
|
getSession().catch(() => {
|
|
// Ignore errors in background refresh
|
|
});
|
|
}
|
|
}, 5 * 60 * 1000); // 5 minutes
|
|
}
|
|
else {
|
|
// Clear interval when not authenticated
|
|
if (refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
refreshInterval = null;
|
|
}
|
|
}
|
|
}, { immediate: true });
|
|
}
|
|
|
|
// Initialize auth state if token exists
|
|
if (import.meta.client && tokenCookie.value && authStatus.value === "unauthenticated") {
|
|
getSession().catch((error: any) => {
|
|
handleAuthError(error);
|
|
});
|
|
}
|
|
|
|
return {
|
|
data: computed(() => authUser.value),
|
|
status: computed(() => authStatus.value),
|
|
signIn,
|
|
signOut,
|
|
refresh,
|
|
getSession,
|
|
setToken,
|
|
};
|
|
};
|