FauziIsyrinApridal commited on
Commit
5dd6e15
Β·
1 Parent(s): 063328b
Files changed (1) hide show
  1. app/auth.py +283 -164
app/auth.py CHANGED
@@ -32,208 +32,327 @@ def auth_view():
32
  unsafe_allow_html=True
33
  )
34
 
35
- # --- Password recovery handler (Supabase redirect) ---
36
- # 1) Move hash params to query params on first load, then reload once
37
- # Try in main document first (no iframe), so it can read the top-level hash reliably
38
  st.markdown(
39
  """
40
  <script>
41
- (function () {
42
- try {
43
- var hash = window.location.hash;
44
- if (hash && hash.length > 1 && !sessionStorage.getItem('hash_migrated')) {
45
- var hashParams = new URLSearchParams(hash.substring(1));
46
- var queryParams = new URLSearchParams(window.location.search);
47
- var hasParams = false;
48
- hashParams.forEach(function (value, key) {
49
- queryParams.set(key, value);
50
- hasParams = true;
51
- });
52
- if (hasParams) {
53
- sessionStorage.setItem('hash_migrated', 'true');
54
- var newUrl = window.location.origin + window.location.pathname + '?' + queryParams.toString();
55
- window.location.replace(newUrl);
56
- }
57
- }
58
- } catch (e) {
59
- console && console.warn && console.warn('main-doc hash->query failed', e);
60
- }
61
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  </script>
63
  """,
64
- unsafe_allow_html=True,
65
  )
66
- components.html(
67
- """
68
- <script>
69
- (function () {
70
- try {
71
- // In iframe context, read and write via parent to ensure access to top-level URL
72
- var w = window.parent || window;
73
- var hash = w.location.hash;
74
- // Only act if hash contains params and we haven't migrated
75
- if (hash && hash.length > 1 && !w.sessionStorage.getItem('hash_migrated')) {
76
- var hashParams = new URLSearchParams(hash.substring(1));
77
- var queryParams = new URLSearchParams(w.location.search);
78
-
79
- var hasParams = false;
80
- hashParams.forEach(function (value, key) {
81
- queryParams.set(key, value);
82
- hasParams = true;
83
- });
84
 
85
- if (hasParams) {
86
- // Mark as migrated to avoid loops
87
- w.sessionStorage.setItem('hash_migrated', 'true');
88
- // Build absolute URL (preserve origin + path), clear hash, and REPLACE (no extra reloads)
89
- var newUrl = w.location.origin + w.location.pathname + '?' + queryParams.toString();
90
- w.location.replace(newUrl);
91
- // Fallback after a short delay in case replace is ignored
92
- setTimeout(function(){
93
- if (w.location.hash) {
94
- w.location.href = newUrl;
95
- }
96
- }, 250);
97
- }
98
- }
99
- } catch (e) {
100
- console.error('iframe hash->query migration failed', e);
101
- }
102
- })();
103
- </script>
104
- """,
105
- height=1,
106
- )
107
 
108
- # 2) Read query params for recovery flow
109
  try:
110
- qp = st.query_params # Streamlit >= 1.30
111
- get_q = lambda k: qp.get(k, None)
112
  except Exception:
113
- qp = st.experimental_get_query_params()
114
- get_q = lambda k: (qp.get(k, [None])[0] if isinstance(qp.get(k, None), list) else qp.get(k, None))
 
 
115
 
116
  q_type = get_q("type")
 
 
 
 
 
 
 
 
 
 
117
  if q_type == "recovery":
118
- st.info("Reset password: silakan masukkan password baru Anda.")
119
- access_token = get_q("access_token")
120
- refresh_token = get_q("refresh_token")
121
- with st.form("reset_password_form"):
122
- npw = st.text_input("Password Baru", type="password")
123
- npw2 = st.text_input("Konfirmasi Password Baru", type="password")
124
- submit_reset = st.form_submit_button("Set Password Baru")
 
 
125
  if submit_reset:
126
- if not npw or len(npw) < 6:
127
- st.error("Password minimal 6 karakter.")
128
- elif npw != npw2:
129
- st.error("Konfirmasi password tidak sama.")
 
 
130
  elif not access_token or not refresh_token:
131
- st.error("Token pemulihan tidak ditemukan. Coba klik ulang tautan dari email.")
 
132
  else:
133
  try:
134
- # Set current session using tokens from redirect
135
- supabase.auth.set_session(access_token, refresh_token)
136
- # Update user password
137
- supabase.auth.update_user({"password": npw})
138
- st.success("Password berhasil diubah. Silakan login kembali.")
139
- # Optional: clear recovery query params
140
- try:
141
- # Best-effort to clear params
142
- st.markdown(
143
- """
144
- <script>
145
- (function(){
146
- const qp = new URLSearchParams(window.location.search);
147
- ["type","access_token","refresh_token","expires_in","expires_at","token_type"].forEach(k=>qp.delete(k));
148
- const newUrl = window.location.pathname + (qp.toString()?('?'+qp.toString()):'');
149
- window.history.replaceState(null, '', newUrl);
150
- })();
151
- </script>
152
- """,
153
- unsafe_allow_html=True,
154
- )
155
- except Exception:
156
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  except Exception as e:
158
- st.error(f"Gagal mengubah password: {e}")
159
- # When in recovery mode, do not render login/register tabs
160
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
- # Auth tabs inside wrapper
163
- tab_login, tab_register, tab_forgot = st.tabs(["Login", "Register", "Forgot Password"])
164
 
165
  with tab_login:
 
166
  with st.form("login_form"):
167
- email = st.text_input("Email")
168
- password = st.text_input("Password", type="password")
169
- submitted = st.form_submit_button("Login")
 
170
  if submitted:
 
 
 
 
171
  # Demo password fallback
172
  shared_pw = os.getenv("APP_DEMO_PASSWORD")
173
  if shared_pw and password == shared_pw:
174
  st.session_state["user"] = {"id": "demo-user", "email": email or "demo@example.com"}
175
- st.success("Login demo berhasil")
176
  st.rerun()
 
 
177
  try:
178
- auth_res = supabase.auth.sign_in_with_password({
179
- "email": email,
180
- "password": password,
181
- })
182
- user = getattr(auth_res, "user", None)
183
- if user:
184
- st.session_state["user"] = {"id": user.id, "email": getattr(user, "email", email)}
185
- session_obj = getattr(auth_res, "session", None)
186
- if session_obj:
187
- st.session_state["sb_session"] = {
188
- "access_token": getattr(session_obj, "access_token", None),
189
- "refresh_token": getattr(session_obj, "refresh_token", None),
190
- }
191
- st.success("Login berhasil")
192
- st.rerun()
193
- else:
194
- st.error("Email atau password salah.")
 
195
  except Exception as e:
196
- st.error(f"Gagal login: {e}")
197
 
198
  with tab_register:
199
- st.caption("Buat akun baru. Anda akan menerima email konfirmasi.")
 
200
  with st.form("register_form"):
201
- r_email = st.text_input("Email", key="reg_email")
202
- r_password = st.text_input("Password", type="password", key="reg_password")
203
- r_password2 = st.text_input("Konfirmasi Password", type="password", key="reg_password2")
204
- submitted_r = st.form_submit_button("Register")
 
205
  if submitted_r:
206
- if r_password != r_password2:
207
- st.error("Password tidak sama.")
 
 
 
 
208
  else:
209
  try:
210
- # Prefer explicit env, then generic site URL, then localhost for dev
211
- redirect_url = os.getenv(
212
- "SUPABASE_EMAIL_REDIRECT",
213
- os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/"),
214
- )
215
- supabase.auth.sign_up({
216
- "email": r_email,
217
- "password": r_password,
218
- "options": {"email_redirect_to": redirect_url}
219
- })
220
- st.success("Registrasi berhasil. Silakan cek email untuk konfirmasi.")
 
 
221
  except Exception as e:
222
- st.error(f"Gagal registrasi: {e}")
223
 
224
  with tab_forgot:
225
- st.caption("Kirim tautan reset password ke email Anda.")
 
226
  with st.form("forgot_form"):
227
- f_email = st.text_input("Email", key="forgot_email")
228
- submitted_f = st.form_submit_button("Kirim Link Reset")
 
229
  if submitted_f:
230
- try:
231
- # Prefer explicit env, then generic site URL, then localhost for dev
232
- redirect_url = os.getenv(
233
- "SUPABASE_EMAIL_REDIRECT",
234
- os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/"),
235
- )
236
- supabase.auth.reset_password_for_email(f_email, {"redirect_to": redirect_url})
237
- st.success("Email reset password telah dikirim. Periksa kotak masuk Anda.")
238
- except Exception as e:
239
- st.error(f"Gagal mengirim email reset password: {e}")
 
 
 
 
 
 
 
 
 
32
  unsafe_allow_html=True
33
  )
34
 
35
+ # --- Hash to Query Parameter Migration ---
36
+ # Use more compatible approach for Streamlit
 
37
  st.markdown(
38
  """
39
  <script>
40
+ // Streamlit-compatible hash migration
41
+ if (typeof window !== 'undefined') {
42
+ (function() {
43
+ try {
44
+ var hash = window.location.hash;
45
+ var hasParams = hash && hash.length > 1;
46
+ var migrated = sessionStorage ? sessionStorage.getItem('hash_migrated') : null;
47
+
48
+ if (hasParams && !migrated) {
49
+ // Simple string parsing to avoid URL constructor issues
50
+ var hashContent = hash.substring(1);
51
+ var pairs = hashContent.split('&');
52
+ var queryParams = new URLSearchParams(window.location.search);
53
+ var hasAuthParams = false;
54
+
55
+ for (var i = 0; i < pairs.length; i++) {
56
+ var pair = pairs[i].split('=');
57
+ if (pair.length === 2) {
58
+ var key = decodeURIComponent(pair[0]);
59
+ var value = decodeURIComponent(pair[1]);
60
+
61
+ // Check for Supabase auth parameters
62
+ if (key === 'access_token' || key === 'type' || key === 'refresh_token') {
63
+ hasAuthParams = true;
64
+ }
65
+
66
+ queryParams.set(key, value);
67
+ }
68
+ }
69
+
70
+ if (hasAuthParams) {
71
+ if (sessionStorage) {
72
+ sessionStorage.setItem('hash_migrated', 'true');
73
+ }
74
+
75
+ var newUrl = window.location.origin + window.location.pathname;
76
+ if (queryParams.toString()) {
77
+ newUrl += '?' + queryParams.toString();
78
+ }
79
+
80
+ window.location.replace(newUrl);
81
+ }
82
+ }
83
+ } catch (e) {
84
+ console && console.log && console.log('Hash migration error:', e);
85
+ }
86
+ })();
87
+ }
88
  </script>
89
  """,
90
+ unsafe_allow_html=True
91
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
+ # Clear migration flag after successful page load with query params
94
+ try:
95
+ qp = st.query_params if hasattr(st, 'query_params') else st.experimental_get_query_params()
96
+ get_q = lambda k: qp.get(k, None) if hasattr(st, 'query_params') else (qp.get(k, [None])[0] if isinstance(qp.get(k, None), list) else qp.get(k, None))
97
+
98
+ # If we have recovery parameters, clear the migration flag
99
+ if get_q("type") == "recovery":
100
+ st.markdown(
101
+ """
102
+ <script>
103
+ if (typeof sessionStorage !== 'undefined') {
104
+ sessionStorage.removeItem('hash_migrated');
105
+ }
106
+ </script>
107
+ """,
108
+ unsafe_allow_html=True
109
+ )
110
+ except Exception as e:
111
+ pass # Silently handle errors in production
 
 
 
112
 
113
+ # Read query params for recovery flow
114
  try:
115
+ qp = st.query_params if hasattr(st, 'query_params') else st.experimental_get_query_params()
116
+ get_q = lambda k: qp.get(k, None) if hasattr(st, 'query_params') else (qp.get(k, [None])[0] if isinstance(qp.get(k, None), list) else qp.get(k, None))
117
  except Exception:
118
+ # Fallback for older Streamlit versions
119
+ import urllib.parse as urlparse
120
+ qp = {}
121
+ get_q = lambda k: None
122
 
123
  q_type = get_q("type")
124
+ access_token = get_q("access_token")
125
+ refresh_token = get_q("refresh_token")
126
+
127
+ # Debug info (remove in production)
128
+ if os.getenv("DEBUG_MODE") == "true" and st.checkbox("Show debug info"):
129
+ st.write("Query params:", dict(qp) if hasattr(qp, 'items') else qp)
130
+ st.write("Type:", q_type)
131
+ st.write("Has access token:", bool(access_token))
132
+ st.write("Has refresh token:", bool(refresh_token))
133
+
134
  if q_type == "recovery":
135
+ st.success("πŸ”‘ Halaman Reset Password")
136
+ st.info("Silakan masukkan password baru Anda di bawah ini.")
137
+
138
+ with st.form("reset_password_form", clear_on_submit=False):
139
+ st.markdown("### Set Password Baru")
140
+ new_password = st.text_input("Password Baru", type="password", help="Minimal 6 karakter")
141
+ confirm_password = st.text_input("Konfirmasi Password Baru", type="password")
142
+ submit_reset = st.form_submit_button(" Update Password", use_container_width=True)
143
+
144
  if submit_reset:
145
+ if not new_password:
146
+ st.error(" Password tidak boleh kosong.")
147
+ elif len(new_password) < 6:
148
+ st.error(" Password minimal 6 karakter.")
149
+ elif new_password != confirm_password:
150
+ st.error(" Konfirmasi password tidak sama.")
151
  elif not access_token or not refresh_token:
152
+ st.error(" Token pemulihan tidak valid. Silakan klik ulang tautan dari email.")
153
+ st.info("πŸ’‘ Tip: Pastikan Anda mengklik tautan langsung dari email tanpa menyalin-paste.")
154
  else:
155
  try:
156
+ with st.spinner("Mengupdate password..."):
157
+ # Set session dengan token dari URL
158
+ supabase.auth.set_session(access_token, refresh_token)
159
+
160
+ # Update password
161
+ result = supabase.auth.update_user({"password": new_password})
162
+
163
+ if result:
164
+ st.success(" Password berhasil diubah!")
165
+ st.info("πŸ”„ Silakan login dengan password baru Anda.")
166
+
167
+ # Clear query parameters setelah sukses
168
+ st.markdown(
169
+ """
170
+ <script>
171
+ if (typeof window !== 'undefined') {
172
+ setTimeout(function() {
173
+ try {
174
+ var params = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type'];
175
+ var search = new URLSearchParams(window.location.search);
176
+ var changed = false;
177
+
178
+ for (var i = 0; i < params.length; i++) {
179
+ if (search.has(params[i])) {
180
+ search.delete(params[i]);
181
+ changed = true;
182
+ }
183
+ }
184
+
185
+ if (changed) {
186
+ var newUrl = window.location.pathname;
187
+ if (search.toString()) {
188
+ newUrl += '?' + search.toString();
189
+ }
190
+ window.history.replaceState({}, document.title, newUrl);
191
+ }
192
+
193
+ if (typeof sessionStorage !== 'undefined') {
194
+ sessionStorage.removeItem('hash_migrated');
195
+ }
196
+
197
+ // Optional: reload page to show login form
198
+ setTimeout(function() { window.location.reload(); }, 2000);
199
+ } catch (e) {
200
+ console && console.log && console.log('Cleanup error:', e);
201
+ window.location.reload();
202
+ }
203
+ }, 1000);
204
+ }
205
+ </script>
206
+ """,
207
+ unsafe_allow_html=True
208
+ )
209
+ else:
210
+ st.error(" Gagal mengubah password. Silakan coba lagi.")
211
+
212
  except Exception as e:
213
+ st.error(f" Error: {str(e)}")
214
+ st.info("πŸ’‘ Jika masalah berlanjut, minta tautan reset password baru.")
215
+
216
+ # Tampilkan tombol untuk kembali ke login
217
+ if st.button("← Kembali ke Login", use_container_width=True):
218
+ # Clear query params and reload
219
+ st.markdown(
220
+ """
221
+ <script>
222
+ if (typeof window !== 'undefined') {
223
+ try {
224
+ var params = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type'];
225
+ var search = new URLSearchParams(window.location.search);
226
+
227
+ for (var i = 0; i < params.length; i++) {
228
+ search.delete(params[i]);
229
+ }
230
+
231
+ var newUrl = window.location.pathname;
232
+ if (search.toString()) {
233
+ newUrl += '?' + search.toString();
234
+ }
235
+
236
+ window.history.replaceState({}, document.title, newUrl);
237
+
238
+ if (typeof sessionStorage !== 'undefined') {
239
+ sessionStorage.removeItem('hash_migrated');
240
+ }
241
+
242
+ window.location.reload();
243
+ } catch (e) {
244
+ window.location.href = window.location.pathname;
245
+ }
246
+ }
247
+ </script>
248
+ """,
249
+ unsafe_allow_html=True
250
+ )
251
+
252
+ return # Exit early, don't show other tabs
253
 
254
+ # Auth tabs inside wrapper (only show when not in recovery mode)
255
+ tab_login, tab_register, tab_forgot = st.tabs([" Login", "πŸ“ Register", "πŸ”‘ Forgot Password"])
256
 
257
  with tab_login:
258
+ st.markdown("### Masuk ke Akun Anda")
259
  with st.form("login_form"):
260
+ email = st.text_input(" Email", placeholder="contoh@email.com")
261
+ password = st.text_input(" Password", type="password")
262
+ submitted = st.form_submit_button(" Login", use_container_width=True)
263
+
264
  if submitted:
265
+ if not email or not password:
266
+ st.error(" Email dan password harus diisi.")
267
+ return
268
+
269
  # Demo password fallback
270
  shared_pw = os.getenv("APP_DEMO_PASSWORD")
271
  if shared_pw and password == shared_pw:
272
  st.session_state["user"] = {"id": "demo-user", "email": email or "demo@example.com"}
273
+ st.success(" Login demo berhasil")
274
  st.rerun()
275
+ return
276
+
277
  try:
278
+ with st.spinner("Logging in..."):
279
+ auth_res = supabase.auth.sign_in_with_password({
280
+ "email": email,
281
+ "password": password,
282
+ })
283
+ user = getattr(auth_res, "user", None)
284
+ if user:
285
+ st.session_state["user"] = {"id": user.id, "email": getattr(user, "email", email)}
286
+ session_obj = getattr(auth_res, "session", None)
287
+ if session_obj:
288
+ st.session_state["sb_session"] = {
289
+ "access_token": getattr(session_obj, "access_token", None),
290
+ "refresh_token": getattr(session_obj, "refresh_token", None),
291
+ }
292
+ st.success(" Login berhasil!")
293
+ st.rerun()
294
+ else:
295
+ st.error(" Email atau password salah.")
296
  except Exception as e:
297
+ st.error(f" Gagal login: {str(e)}")
298
 
299
  with tab_register:
300
+ st.markdown("### Buat Akun Baru")
301
+ st.caption(" Anda akan menerima email konfirmasi setelah registrasi.")
302
  with st.form("register_form"):
303
+ r_email = st.text_input(" Email", key="reg_email", placeholder="contoh@email.com")
304
+ r_password = st.text_input(" Password", type="password", key="reg_password", help="Minimal 6 karakter")
305
+ r_password2 = st.text_input(" Konfirmasi Password", type="password", key="reg_password2")
306
+ submitted_r = st.form_submit_button("πŸ“ Daftar", use_container_width=True)
307
+
308
  if submitted_r:
309
+ if not r_email or not r_password:
310
+ st.error(" Email dan password harus diisi.")
311
+ elif len(r_password) < 6:
312
+ st.error(" Password minimal 6 karakter.")
313
+ elif r_password != r_password2:
314
+ st.error(" Konfirmasi password tidak sama.")
315
  else:
316
  try:
317
+ with st.spinner("Mendaftarkan akun..."):
318
+ # Set redirect URL dengan prioritas yang lebih clear
319
+ redirect_url = os.getenv("SUPABASE_EMAIL_REDIRECT")
320
+ if not redirect_url:
321
+ redirect_url = os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/")
322
+
323
+ supabase.auth.sign_up({
324
+ "email": r_email,
325
+ "password": r_password,
326
+ "options": {"email_redirect_to": redirect_url}
327
+ })
328
+ st.success(" Registrasi berhasil!")
329
+ st.info(" Silakan cek email untuk konfirmasi akun.")
330
  except Exception as e:
331
+ st.error(f" Gagal registrasi: {str(e)}")
332
 
333
  with tab_forgot:
334
+ st.markdown("### Reset Password")
335
+ st.caption(" Kami akan mengirim tautan reset password ke email Anda.")
336
  with st.form("forgot_form"):
337
+ f_email = st.text_input(" Email", key="forgot_email", placeholder="contoh@email.com")
338
+ submitted_f = st.form_submit_button(" Kirim Link Reset", use_container_width=True)
339
+
340
  if submitted_f:
341
+ if not f_email:
342
+ st.error(" Email harus diisi.")
343
+ else:
344
+ try:
345
+ with st.spinner("Mengirim email reset..."):
346
+ # Set redirect URL dengan prioritas yang lebih clear
347
+ redirect_url = os.getenv("SUPABASE_EMAIL_REDIRECT")
348
+ if not redirect_url:
349
+ redirect_url = os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/")
350
+
351
+ supabase.auth.reset_password_for_email(
352
+ f_email,
353
+ {"redirect_to": redirect_url}
354
+ )
355
+ st.success(" Email reset password telah dikirim!")
356
+ st.info(" Periksa kotak masuk (dan folder spam) untuk tautan reset password.")
357
+ except Exception as e:
358
+ st.error(f" Gagal mengirim email: {str(e)}")