-
Notifications
You must be signed in to change notification settings - Fork 255
Expand file tree
/
Copy pathuseAuthStore.ts
More file actions
250 lines (211 loc) · 6.3 KB
/
Copy pathuseAuthStore.ts
File metadata and controls
250 lines (211 loc) · 6.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/**
* Auth Store
*
* Manages authentication state using Supabase Auth
*/
import { create } from 'zustand';
import { createBrowserClient } from '../lib/supabase-browser';
import { extractRoleFromUser } from '@/lib/roles';
import type { User, Session } from '@supabase/supabase-js';
interface AuthState {
user: User | null;
session: Session | null;
role: string | null;
loading: boolean;
initialized: boolean;
error: string | null;
}
interface AuthActions {
initialize: () => Promise<void>;
signUp: (email: string, password: string) => Promise<{ error: string | null }>;
signIn: (email: string, password: string) => Promise<{ error: string | null }>;
signOut: () => Promise<void>;
checkSession: () => Promise<void>;
setError: (error: string | null) => void;
}
type AuthStore = AuthState & AuthActions;
export const useAuthStore = create<AuthStore>((set, get) => ({
user: null,
session: null,
role: null,
loading: false,
initialized: false,
error: null,
/**
* Initialize auth state and listen for auth changes
* Gracefully handles missing Supabase config (expected during setup)
*/
initialize: async () => {
if (get().initialized) return;
try {
const supabase = await createBrowserClient();
// If Supabase is not configured, skip initialization (expected during setup)
if (!supabase) {
set({
initialized: true,
error: null,
});
return;
}
// Validate session server-side (getUser verifies the JWT, unlike getSession)
const { data: { user } } = await supabase.auth.getUser();
if (user) {
// Refresh the session so the JWT contains the latest app_metadata
// (role changes via Admin API don't update existing JWTs)
await supabase.auth.refreshSession();
}
const { data: { session } } = await supabase.auth.getSession();
set({
user: user ?? null,
session: user ? session : null,
role: extractRoleFromUser(user),
initialized: true,
});
supabase.auth.onAuthStateChange((_event, session) => {
set({
user: session?.user ?? null,
session,
role: extractRoleFromUser(session?.user ?? null),
});
});
} catch (error) {
console.error('Failed to initialize auth:', error);
set({
error: error instanceof Error ? error.message : 'Failed to initialize auth',
initialized: true,
});
}
},
/**
* Sign up a new user
*/
signUp: async (email, password) => {
set({ loading: true, error: null });
try {
const supabase = await createBrowserClient();
if (!supabase) {
set({ loading: false, error: 'Supabase not configured. Please complete setup first.' });
return { error: 'Supabase not configured. Please complete setup first.' };
}
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/ycode`,
// Note: Email confirmation should be disabled in Supabase Dashboard
// (Authentication → Providers → Email → Disable "Confirm email")
// This is recommended for self-hosted single-admin setups
},
});
if (error) {
set({ loading: false, error: error.message });
return { error: error.message };
}
// Check if email confirmation is required
if (data.user && !data.session) {
const message = 'Email confirmation required. Please disable email confirmation in your Supabase project settings (Authentication → Providers → Email).';
set({ loading: false, error: message });
return { error: message };
}
set({
user: data.user,
session: data.session,
role: extractRoleFromUser(data.user),
loading: false,
});
return { error: null };
} catch (error) {
const message = error instanceof Error ? error.message : 'Sign up failed';
set({ loading: false, error: message });
return { error: message };
}
},
/**
* Sign in existing user
*/
signIn: async (email, password) => {
set({ loading: true, error: null });
try {
const supabase = await createBrowserClient();
if (!supabase) {
set({ loading: false, error: 'Supabase not configured. Please complete setup first.' });
return { error: 'Supabase not configured. Please complete setup first.' };
}
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
set({ loading: false, error: error.message });
return { error: error.message };
}
set({
user: data.user,
session: data.session,
role: extractRoleFromUser(data.user),
loading: false,
});
return { error: null };
} catch (error) {
const message = error instanceof Error ? error.message : 'Sign in failed';
set({ loading: false, error: message });
return { error: message };
}
},
/**
* Sign out current user
*/
signOut: async () => {
set({ loading: true, error: null });
try {
const supabase = await createBrowserClient();
if (!supabase) {
set({
user: null,
session: null,
role: null,
loading: false,
});
return;
}
const { error } = await supabase.auth.signOut();
if (error) {
set({ loading: false, error: error.message });
return;
}
set({
user: null,
session: null,
role: null,
loading: false,
});
} catch (error) {
const message = error instanceof Error ? error.message : 'Sign out failed';
set({ loading: false, error: message });
}
},
/**
* Check current session
*/
checkSession: async () => {
try {
const supabase = await createBrowserClient();
if (!supabase) {
return;
}
const { data: { session } } = await supabase.auth.getSession();
set({
user: session?.user ?? null,
session,
});
} catch (error) {
console.error('Failed to check session:', error);
}
},
/**
* Set error message
*/
setError: (error) => {
set({ error });
},
}));