Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

useAuth.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

useAuth.tsBlame95 lines · 1 contributor
9989d86Claude1import { useCallback, useEffect, useState } from 'react';
2import * as SecureStore from 'expo-secure-store';
3import { useAuthStore } from '../store/authStore';
4import { useSettingsStore } from '../store/settingsStore';
5import { validateToken, persistToken, clearToken } from '../api/auth';
6import { PAT_STORE_KEY } from '../api/client';
7
8export interface UseAuthReturn {
9 isAuthenticated: boolean;
10 isLoading: boolean;
11 user: ReturnType<typeof useAuthStore.getState>['user'];
12 login: (host: string, token: string) => Promise<void>;
13 logout: () => Promise<void>;
14 error: string | null;
15}
16
17/**
18 * Primary auth hook. Checks SecureStore on mount and exposes
19 * login / logout helpers that update both SecureStore and the
20 * in-memory Zustand store.
21 */
22export function useAuth(): UseAuthReturn {
23 const { isAuthenticated, user, setAuth, clearAuth } = useAuthStore();
24 const { setHost } = useSettingsStore();
25 const [isLoading, setIsLoading] = useState(true);
26 const [error, setError] = useState<string | null>(null);
27
28 // On mount, check if there is a saved token and auto-login.
29 useEffect(() => {
30 let cancelled = false;
31
32 async function restoreSession() {
33 try {
34 const savedToken = await SecureStore.getItemAsync(PAT_STORE_KEY);
35 const savedHost = await SecureStore.getItemAsync('gluecron_host');
36
37 if (savedToken && savedHost) {
38 setHost(savedHost);
39 const authUser = await validateToken(savedHost, savedToken);
40 if (!cancelled) {
41 setAuth(authUser, savedToken);
42 }
43 }
44 } catch {
45 // Token invalid or network error — stay logged out silently
46 await SecureStore.deleteItemAsync(PAT_STORE_KEY);
47 await SecureStore.deleteItemAsync('gluecron_host');
48 } finally {
49 if (!cancelled) {
50 setIsLoading(false);
51 }
52 }
53 }
54
55 restoreSession();
56 return () => {
57 cancelled = true;
58 };
59 }, [setAuth, setHost]);
60
61 const login = useCallback(
62 async (host: string, token: string) => {
63 setError(null);
64 setIsLoading(true);
65 try {
66 const normalizedHost = host.replace(/\/$/, '');
67 const authUser = await validateToken(normalizedHost, token);
68 await persistToken(token);
69 await SecureStore.setItemAsync('gluecron_host', normalizedHost);
70 setHost(normalizedHost);
71 setAuth(authUser, token);
72 } catch (err) {
73 const msg = err instanceof Error ? err.message : 'Login failed';
74 setError(msg);
75 throw err;
76 } finally {
77 setIsLoading(false);
78 }
79 },
80 [setAuth, setHost],
81 );
82
83 const logout = useCallback(async () => {
84 setIsLoading(true);
85 try {
86 await clearToken();
87 await SecureStore.deleteItemAsync('gluecron_host');
88 } finally {
89 clearAuth();
90 setIsLoading(false);
91 }
92 }, [clearAuth]);
93
94 return { isAuthenticated, isLoading, user, login, logout, error };
95}