-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.js
More file actions
308 lines (269 loc) · 9.87 KB
/
Copy pathauth.js
File metadata and controls
308 lines (269 loc) · 9.87 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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
// ============================================================
// auth.js — Handles all authentication logic for MoodSpace
// Covers: sign up, log in, Google OAuth, logout, password reset
// ============================================================
// === INIT ===
// Wait for Supabase client to be ready (config-loader.js → supabase.js)
// then set up the auth page and check for an existing session
document.addEventListener('DOMContentLoaded', async () => {
await window._supabaseReady.catch(() => {
document.body.innerHTML =
'<p style="padding:2rem;font-family:sans-serif;color:#B33A3A">' +
'⚠️ Could not load app config. If running locally, make sure config.js exists.' +
'</p>'
})
initAuthPage();
checkExistingSession();
});
// === AUTH STATE ===
// Checks if a user is already logged in — redirect if so
async function checkExistingSession() {
const { data: { session } } = await supabaseClient.auth.getSession();
if (session) {
await redirectByRole(session.user);
}
}
// === REDIRECT BY ROLE ===
// Sends students to index.html, counselors to dashboard.html
async function redirectByRole(user) {
try {
const { data: profile, error } = await supabaseClient
.from('profiles')
.select('role')
.eq('id', user.id)
.single();
if (error) throw error;
if (profile && profile.role === 'counselor') {
window.location.href = 'dashboard.html';
} else {
window.location.href = 'index.html';
}
} catch (err) {
console.error('[Auth] Role redirect error:', err.message);
window.location.href = 'index.html';
}
}
// === UI INIT ===
// Sets up tab switching and form event listeners on auth.html
function initAuthPage() {
const loginTab = document.getElementById('login-tab');
const signupTab = document.getElementById('signup-tab');
const loginForm = document.getElementById('login-form');
const signupForm = document.getElementById('signup-form');
const gradeGroup = document.getElementById('grade-group');
const roleSelect = document.getElementById('role');
// Toggle between login and sign-up views
if (loginTab) {
loginTab.addEventListener('click', () => {
loginTab.classList.add('active');
signupTab.classList.remove('active');
loginForm.classList.remove('hidden');
signupForm.classList.add('hidden');
clearMessages();
});
}
if (signupTab) {
signupTab.addEventListener('click', () => {
signupTab.classList.add('active');
loginTab.classList.remove('active');
signupForm.classList.remove('hidden');
loginForm.classList.add('hidden');
clearMessages();
});
}
// Show/hide grade dropdown based on role selection
if (roleSelect) {
roleSelect.addEventListener('change', () => {
if (gradeGroup) {
gradeGroup.style.display = roleSelect.value === 'student' ? 'block' : 'none';
}
});
}
// Wire up form submit events
if (loginForm) loginForm.addEventListener('submit', handleLogin);
if (signupForm) signupForm.addEventListener('submit', handleSignup);
// Google OAuth button
const googleBtn = document.getElementById('google-login-btn');
if (googleBtn) googleBtn.addEventListener('click', handleGoogleLogin);
// Forgot password link
const forgotLink = document.getElementById('forgot-password-link');
if (forgotLink) forgotLink.addEventListener('click', handleForgotPassword);
}
// === SIGN UP ===
// Creates a new Supabase auth user and saves their profile to the DB
async function handleSignup(e) {
e.preventDefault();
clearMessages();
const displayName = document.getElementById('display-name').value.trim();
const email = document.getElementById('signup-email').value.trim();
const password = document.getElementById('signup-password').value;
const role = document.getElementById('role').value;
const school = document.getElementById('school').value.trim();
const grade = document.getElementById('grade') ? document.getElementById('grade').value : null;
if (!displayName || !email || !password || !role) {
showMessage('Please fill in all required fields.', 'error');
return;
}
showLoading(true, 'signup-btn');
console.log('[Auth] Attempting sign up for:', email);
try {
// Create the auth user in Supabase
// We always set role to 'student' initially to prevent privilege escalation via RLS
const defaultRole = 'student';
const { data, error } = await supabaseClient.auth.signUp({
email,
password,
options: {
data: { display_name: displayName, role: defaultRole }
}
});
if (error) throw error;
// Save extended profile info to the profiles table
if (data.user) {
await createProfile(data.user, { displayName, email, role: defaultRole, school, grade });
// If counselor, verify code and let backend elevate role securely
if (role === 'counselor') {
const staffCode = document.getElementById('staff-code')?.value?.trim();
if (staffCode) {
const verifyRes = await fetch('/api/verify-counselor-code', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${data.session.access_token}`
},
body: JSON.stringify({ code: staffCode, school })
});
const result = await verifyRes.json();
if (!result.valid) {
throw new Error('Sign up succeeded, but invalid staff code provided. You are logged in as a student.');
}
} else {
throw new Error('Sign up succeeded, but no staff code provided. You are logged in as a student.');
}
}
}
showMessage('Account created! Check your email to confirm, then log in.', 'success');
console.log('[Auth] Sign up successful for:', email);
} catch (err) {
console.error('[Auth] Sign up error:', err.message);
showMessage(err.message || 'Sign up failed. Please try again.', 'error');
} finally {
showLoading(false, 'signup-btn');
}
}
// === CREATE PROFILE ===
// Inserts a new row in the profiles table with user details
async function createProfile(user, { displayName, email, role, school, grade }) {
try {
const { error } = await supabaseClient.from('profiles').insert([{
id: user.id,
email,
display_name: displayName,
role,
school: school || null,
grade: role === 'student' ? grade : null,
streak_count: 0
}]);
if (error) throw error;
console.log('[Auth] Profile created for user:', user.id);
} catch (err) {
console.error('[Auth] Profile creation error:', err.message);
}
}
// === LOG IN ===
// Signs in with email and password, then redirects by role
async function handleLogin(e) {
e.preventDefault();
clearMessages();
const email = document.getElementById('login-email').value.trim();
const password = document.getElementById('login-password').value;
if (!email || !password) {
showMessage('Please enter your email and password.', 'error');
return;
}
showLoading(true, 'login-btn');
console.log('[Auth] Attempting login for:', email);
try {
const { data, error } = await supabaseClient.auth.signInWithPassword({ email, password });
if (error) throw error;
console.log('[Auth] Login successful for:', email);
await redirectByRole(data.user);
} catch (err) {
console.error('[Auth] Login error:', err.message);
showMessage('Invalid email or password. Please try again.', 'error');
} finally {
showLoading(false, 'login-btn');
}
}
// === GOOGLE OAUTH ===
// Initiates Supabase Google OAuth flow (redirects to Google)
async function handleGoogleLogin() {
console.log('[Auth] Starting Google OAuth');
try {
const { error } = await supabaseClient.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/index.html`
}
});
if (error) throw error;
} catch (err) {
console.error('[Auth] Google OAuth error:', err.message);
showMessage('Google sign-in failed. Please try again.', 'error');
}
}
// === FORGOT PASSWORD ===
// Sends a password reset email via Supabase
async function handleForgotPassword(e) {
e.preventDefault();
const email = document.getElementById('login-email').value.trim();
if (!email) {
showMessage('Enter your email address above first, then click Forgot Password.', 'error');
return;
}
try {
const { error } = await supabaseClient.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth.html`
});
if (error) throw error;
showMessage('Password reset email sent! Check your inbox.', 'success');
console.log('[Auth] Password reset sent to:', email);
} catch (err) {
console.error('[Auth] Reset password error:', err.message);
showMessage('Could not send reset email. Please try again.', 'error');
}
}
// === LOGOUT ===
// Signs the user out and redirects to auth.html
async function logout() {
console.log('[Auth] Logging out');
try {
await supabaseClient.auth.signOut();
window.location.href = 'auth.html';
} catch (err) {
console.error('[Auth] Logout error:', err.message);
}
}
// === UI HELPERS ===
// Shows a success or error message below the form
function showMessage(text, type = 'info') {
const container = document.getElementById('auth-message');
if (!container) return;
container.textContent = text;
container.className = `auth-message ${type}`;
container.style.display = 'block';
}
// Clears any displayed message
function clearMessages() {
const container = document.getElementById('auth-message');
if (!container) return;
container.textContent = '';
container.style.display = 'none';
}
// Shows/hides a loading spinner on a button
function showLoading(isLoading, btnId) {
const btn = document.getElementById(btnId);
if (!btn) return;
btn.disabled = isLoading;
btn.textContent = isLoading ? 'Please wait...' : btn.dataset.label;
}