FauziIsyrinApridal commited on
Commit
245e2db
·
1 Parent(s): 1b6f6c4
Files changed (1) hide show
  1. app/auth.py +106 -329
app/auth.py CHANGED
@@ -9,17 +9,15 @@ def auth_view():
9
  # Wrapper (centered) for all auth content
10
  left, center, right = st.columns([1, 2, 1])
11
  with center:
12
- # Header: smaller PNP logo and centered title
13
  logo_path = os.path.join("assets", "pnp-logo.png")
14
 
15
- # Convert ke base64 biar bisa di-embed langsung
16
  def get_base64_image(path):
17
  with open(path, "rb") as f:
18
  return base64.b64encode(f.read()).decode()
19
 
20
  encoded_logo = get_base64_image(logo_path)
21
 
22
- # Render dalam satu div center
23
  st.markdown(
24
  f"""
25
  <div style="text-align:center;">
@@ -30,365 +28,144 @@ def auth_view():
30
  unsafe_allow_html=True
31
  )
32
 
33
- # Read query params for recovery flow
34
- try:
35
- if hasattr(st, 'query_params'):
36
- qp = st.query_params
37
- get_q = lambda k: qp.get(k, None)
38
- else:
39
- qp = st.experimental_get_query_params()
40
- get_q = lambda k: (qp.get(k, [None])[0] if isinstance(qp.get(k, None), list) else qp.get(k, None))
41
- except Exception:
42
- qp = {}
43
- get_q = lambda k: None
44
 
45
- q_type = get_q("type")
46
- access_token = get_q("access_token")
47
- refresh_token = get_q("refresh_token")
48
-
49
- # Check if user is coming from email but params are in wrong format
50
- # Tampilkan helper hanya jika tidak ada query params yang valid
51
- if not q_type and not access_token:
52
- st.markdown("---")
53
- st.info("Datang dari link email reset password? Jika halaman reset tidak muncul otomatis, klik tombol di bawah:")
54
-
55
- col1, col2 = st.columns([1, 1])
56
- with col1:
57
- if st.button("Saya dari Link Email", use_container_width=True, help="Klik jika Anda mengakses dari link email reset password"):
58
- st.info("Instruksi sederhana:")
59
- st.markdown("""
60
- 1. **Lihat URL di address bar browser Anda**
61
- 2. **Ganti tanda `#` dengan `?`** (jika ada)
62
- 3. **Tekan Enter** untuk reload halaman
63
-
64
- **Contoh:**
65
- - Dari: `app.com/#access_token=abc`
66
- - Jadi: `app.com/?access_token=abc`
67
- """)
68
-
69
- with col2:
70
- if st.button("Butuh Bantuan", use_container_width=True):
71
- st.markdown("""
72
- **Masalah umum dan solusi:**
73
-
74
- **Link tidak berfungsi?**
75
- Coba minta link reset password baru
76
-
77
- **Halaman kosong setelah klik link?**
78
- Ganti `#` dengan `?` di URL browser
79
-
80
- **Masih tidak bisa?**
81
- Gunakan tab "Forgot Password" di bawah
82
- """)
83
 
84
- # Debug info untuk development (hanya tampil jika ada query params)
85
- if os.getenv("DEBUG_MODE") == "true" and (q_type or access_token):
86
- with st.expander("Debug Info", expanded=False):
87
- st.write("Query params:", dict(qp) if hasattr(qp, 'items') else qp)
88
- st.write("Type:", q_type)
89
- st.write("Has access token:", bool(access_token))
90
- st.write("Has refresh token:", bool(refresh_token))
91
 
92
- # RECOVERY MODE - Reset Password
93
  if q_type == "recovery":
94
- st.success("Halaman Reset Password")
95
- st.info("Silakan masukkan password baru Anda di bawah ini.")
96
-
97
- # Validasi token availability
98
- if not access_token or not refresh_token:
99
- st.error("Token reset password tidak valid atau sudah expired.")
100
- st.markdown("**Kemungkinan penyebab:**")
101
- st.markdown("- Link sudah pernah digunakan sebelumnya")
102
- st.markdown("- Link sudah expired (biasanya berlaku 1 jam)")
103
- st.markdown("- URL tidak lengkap")
104
-
105
- st.info("**Solusi:** Minta link reset password yang baru menggunakan form di bawah.")
106
-
107
- if st.button("Minta Link Reset Baru", use_container_width=True):
108
- # Clear query params dan redirect ke forgot password
109
- if hasattr(st, 'query_params'):
110
- params_to_clear = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type']
111
- for param in params_to_clear:
112
- if param in st.query_params:
113
- del st.query_params[param]
114
- st.rerun()
115
- return
116
-
117
- # Form untuk reset password
118
- with st.form("reset_password_form", clear_on_submit=False):
119
- st.markdown("### Set Password Baru")
120
- new_password = st.text_input("Password Baru", type="password", help="Minimal 6 karakter")
121
- confirm_password = st.text_input("Konfirmasi Password Baru", type="password")
122
-
123
- # Password strength indicator
124
- if new_password:
125
- strength_score = 0
126
- feedback = []
127
-
128
- if len(new_password) >= 6:
129
- strength_score += 1
130
- else:
131
- feedback.append("Minimal 6 karakter")
132
-
133
- if any(c.isupper() for c in new_password):
134
- strength_score += 1
135
- else:
136
- feedback.append("Gunakan huruf besar")
137
-
138
- if any(c.islower() for c in new_password):
139
- strength_score += 1
140
- else:
141
- feedback.append("Gunakan huruf kecil")
142
-
143
- if any(c.isdigit() for c in new_password):
144
- strength_score += 1
145
- else:
146
- feedback.append("Gunakan angka")
147
-
148
- # Display strength
149
- if strength_score <= 1:
150
- st.error(f"Password lemah: {', '.join(feedback)}")
151
- elif strength_score <= 2:
152
- st.warning(f"Password cukup: {', '.join(feedback)}")
153
- elif strength_score <= 3:
154
- st.info("Password baik")
155
- else:
156
- st.success("Password sangat kuat!")
157
-
158
- submit_reset = st.form_submit_button("Update Password", use_container_width=True)
159
-
160
  if submit_reset:
161
- # Validasi input
162
- if not new_password:
163
- st.error("Password tidak boleh kosong.")
164
- elif len(new_password) < 6:
165
  st.error("Password minimal 6 karakter.")
166
- elif new_password != confirm_password:
167
  st.error("Konfirmasi password tidak sama.")
 
 
168
  else:
169
- # Proses update password
170
  try:
171
- with st.spinner("Mengupdate password..."):
172
- # Set session dengan token dari URL
173
- supabase.auth.set_session(access_token, refresh_token)
174
-
175
- # Update password
176
- result = supabase.auth.update_user({"password": new_password})
177
-
178
- if result:
179
- st.success("Password berhasil diubah!")
180
- st.success("Sekarang Anda dapat login dengan password baru!")
181
-
182
- # Show success message with countdown
183
- placeholder = st.empty()
184
- for i in range(5, 0, -1):
185
- placeholder.info(f"Halaman akan kembali ke login dalam {i} detik...")
186
- import time
187
- time.sleep(1)
188
-
189
- # Clear query params dan redirect
190
- if hasattr(st, 'query_params'):
191
- params_to_clear = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type']
192
- for param in params_to_clear:
193
- if param in st.query_params:
194
- del st.query_params[param]
195
-
196
- placeholder.empty()
197
- st.rerun()
198
- else:
199
- st.error("Gagal mengubah password. Silakan coba lagi.")
200
-
201
  except Exception as e:
202
- st.error(f"Error: {str(e)}")
203
- st.info("Jika masalah berlanjut, minta link reset password baru.")
204
-
205
- # Tombol alternatif
206
- st.markdown("---")
207
- col1, col2 = st.columns([1, 1])
208
- with col1:
209
- if st.button("Kembali ke Login", use_container_width=True):
210
- if hasattr(st, 'query_params'):
211
- params_to_clear = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type']
212
- for param in params_to_clear:
213
- if param in st.query_params:
214
- del st.query_params[param]
215
- st.rerun()
216
-
217
- with col2:
218
- if st.button("Minta Link Baru", use_container_width=True):
219
- if hasattr(st, 'query_params'):
220
- params_to_clear = ['type', 'access_token', 'refresh_token', 'expires_in', 'expires_at', 'token_type']
221
- for param in params_to_clear:
222
- if param in st.query_params:
223
- del st.query_params[param]
224
- st.rerun()
225
-
226
- return # Exit early, don't show other tabs
227
 
228
- # NORMAL MODE - Login/Register/Forgot Password Tabs
229
  tab_login, tab_register, tab_forgot = st.tabs(["Login", "Register", "Forgot Password"])
230
 
 
231
  with tab_login:
232
- st.markdown("### Selamat Datang Kembali")
233
-
234
- # Quick tips untuk login
235
- with st.expander("Tips Login", expanded=False):
236
- st.markdown("""
237
- - Pastikan email dan password sudah benar
238
- - Jika lupa password, gunakan tab "Forgot Password"
239
- - Untuk akun baru, gunakan tab "Register"
240
- """)
241
-
242
  with st.form("login_form"):
243
- email = st.text_input("Email", placeholder="contoh@email.com")
244
  password = st.text_input("Password", type="password")
245
- remember_me = st.checkbox("Ingat saya")
246
- submitted = st.form_submit_button("Login", use_container_width=True)
247
-
248
  if submitted:
249
- if not email or not password:
250
- st.error("Email dan password harus diisi.")
251
- return
252
-
253
- # Demo password fallback
254
  shared_pw = os.getenv("APP_DEMO_PASSWORD")
255
  if shared_pw and password == shared_pw:
256
  st.session_state["user"] = {"id": "demo-user", "email": email or "demo@example.com"}
257
  st.success("Login demo berhasil")
258
  st.rerun()
259
- return
260
-
261
  try:
262
- with st.spinner("Memverifikasi kredensial..."):
263
- auth_res = supabase.auth.sign_in_with_password({
264
- "email": email,
265
- "password": password,
266
- })
267
- user = getattr(auth_res, "user", None)
268
- if user:
269
- st.session_state["user"] = {"id": user.id, "email": getattr(user, "email", email)}
270
- session_obj = getattr(auth_res, "session", None)
271
- if session_obj:
272
- st.session_state["sb_session"] = {
273
- "access_token": getattr(session_obj, "access_token", None),
274
- "refresh_token": getattr(session_obj, "refresh_token", None),
275
- }
276
- st.success("Login berhasil! Selamat datang!")
277
- st.rerun()
278
- else:
279
- st.error("Email atau password salah.")
280
- st.info("Cek kembali email dan password Anda, atau gunakan 'Forgot Password' jika lupa.")
281
  except Exception as e:
282
- st.error(f"Gagal login: {str(e)}")
283
 
 
284
  with tab_register:
285
- st.markdown("### Daftar Akun Baru")
286
- st.caption("Buat akun untuk mulai menggunakan PNP Bot")
287
-
288
  with st.form("register_form"):
289
- r_email = st.text_input("Email", key="reg_email", placeholder="contoh@email.com")
290
- r_password = st.text_input("Password", type="password", key="reg_password", help="Minimal 6 karakter, gunakan kombinasi huruf dan angka")
291
  r_password2 = st.text_input("Konfirmasi Password", type="password", key="reg_password2")
292
-
293
- # Password strength untuk register juga
294
- if r_password:
295
- strength_score = 0
296
- if len(r_password) >= 6: strength_score += 1
297
- if any(c.isupper() for c in r_password): strength_score += 1
298
- if any(c.islower() for c in r_password): strength_score += 1
299
- if any(c.isdigit() for c in r_password): strength_score += 1
300
-
301
- if strength_score <= 1:
302
- st.error("Password terlalu lemah")
303
- elif strength_score <= 2:
304
- st.warning("Password cukup")
305
- elif strength_score <= 3:
306
- st.info("Password baik")
307
- else:
308
- st.success("Password sangat kuat!")
309
-
310
- submitted_r = st.form_submit_button("Daftar Sekarang", use_container_width=True)
311
-
312
  if submitted_r:
313
- if not r_email or not r_password:
314
- st.error("Email dan password harus diisi.")
315
- elif len(r_password) < 6:
316
- st.error("Password minimal 6 karakter.")
317
- elif r_password != r_password2:
318
- st.error("Konfirmasi password tidak sama.")
319
  else:
320
  try:
321
- with st.spinner("Mendaftarkan akun..."):
322
- redirect_url = os.getenv("SUPABASE_EMAIL_REDIRECT")
323
- if not redirect_url:
324
- redirect_url = os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/")
325
-
326
- supabase.auth.sign_up({
327
- "email": r_email,
328
- "password": r_password,
329
- "options": {"email_redirect_to": redirect_url}
330
- })
331
- st.success("Registrasi berhasil!")
332
- st.info("Email konfirmasi telah dikirim ke " + r_email)
333
- st.info("**Langkah selanjutnya:**")
334
- st.info("1. Buka email Anda")
335
- st.info("2. Klik link konfirmasi")
336
- st.info("3. Kembali ke halaman ini untuk login")
337
- st.warning("Periksa folder spam jika email tidak ditemukan dalam 5 menit")
338
  except Exception as e:
339
- st.error(f"Gagal registrasi: {str(e)}")
340
 
 
341
  with tab_forgot:
342
- st.markdown("### Reset Password")
343
- st.caption("Masukkan email Anda untuk menerima link reset password")
344
-
345
- # Instruksi yang jelas tanpa copy-paste
346
- st.info("""
347
- **Cara kerja reset password:**
348
- 1. Masukkan email dan klik "Kirim Link Reset"
349
- 2. Buka email dari PNP Bot
350
- 3. Klik link di email (link akan membuka halaman ini)
351
- 4. Jika halaman reset tidak muncul, ganti `#` dengan `?` di URL browser
352
- 5. Set password baru Anda
353
- """)
354
-
355
  with st.form("forgot_form"):
356
- f_email = st.text_input("Email", key="forgot_email", placeholder="contoh@email.com")
357
- submitted_f = st.form_submit_button("Kirim Link Reset", use_container_width=True)
358
-
359
  if submitted_f:
360
- if not f_email:
361
- st.error("Email harus diisi.")
362
- else:
363
- try:
364
- with st.spinner("Mengirim email..."):
365
- redirect_url = os.getenv("SUPABASE_EMAIL_REDIRECT")
366
- if not redirect_url:
367
- redirect_url = os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/")
368
-
369
- supabase.auth.reset_password_for_email(
370
- f_email,
371
- {"redirect_to": redirect_url}
372
- )
373
- st.success("Link reset password telah dikirim!")
374
- st.info(f"Silakan cek email di **{f_email}**")
375
-
376
- # Tips tanpa copy paste
377
- with st.expander("Troubleshooting", expanded=False):
378
- st.markdown("""
379
- **Jika mengalami masalah:**
380
-
381
- **Email tidak masuk?**
382
- Tunggu 2-3 menit, periksa folder spam
383
-
384
- **Link tidak berfungsi?**
385
- Di browser, ganti tanda `#` dengan `?` pada URL
386
-
387
- **Halaman kosong setelah klik link?**
388
- Refresh halaman atau coba browser lain
389
-
390
- **Link sudah expired?**
391
- Minta link baru (berlaku 1 jam)
392
- """)
393
- except Exception as e:
394
- st.error(f"Gagal mengirim email: {str(e)}")
 
9
  # Wrapper (centered) for all auth content
10
  left, center, right = st.columns([1, 2, 1])
11
  with center:
12
+ # Header: PNP logo + title
13
  logo_path = os.path.join("assets", "pnp-logo.png")
14
 
 
15
  def get_base64_image(path):
16
  with open(path, "rb") as f:
17
  return base64.b64encode(f.read()).decode()
18
 
19
  encoded_logo = get_base64_image(logo_path)
20
 
 
21
  st.markdown(
22
  f"""
23
  <div style="text-align:center;">
 
28
  unsafe_allow_html=True
29
  )
30
 
31
+ # --- FIX: Auto convert hash (#) to query (?) ---
32
+ st.markdown(
33
+ """
34
+ <script>
35
+ (function() {
36
+ const hash = window.location.hash;
37
+ if (hash && hash.length > 1 && !sessionStorage.getItem("hash_migrated")) {
38
+ // ambil param setelah #
39
+ const query = hash.substring(1);
40
+ const newUrl = window.location.pathname + "?" + query;
 
41
 
42
+ // tandai sudah migrasi supaya tidak infinite loop
43
+ sessionStorage.setItem("hash_migrated", "true");
44
+
45
+ // ganti URL tanpa hash
46
+ window.history.replaceState(null, "", newUrl);
47
+
48
+ // reload supaya Streamlit bisa baca query param
49
+ window.location.reload();
50
+ }
51
+ })();
52
+ </script>
53
+ """,
54
+ unsafe_allow_html=True
55
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ # --- Recovery flow ---
58
+ if hasattr(st, "query_params"):
59
+ qp = st.query_params
60
+ get_q = lambda k: qp.get(k)
61
+ else:
62
+ qp = st.experimental_get_query_params()
63
+ get_q = lambda k: (qp.get(k, [None])[0] if isinstance(qp.get(k, None), list) else qp.get(k))
64
 
65
+ q_type = get_q("type")
66
  if q_type == "recovery":
67
+ st.info("Reset password: silakan masukkan password baru Anda.")
68
+ access_token = get_q("access_token")
69
+ refresh_token = get_q("refresh_token")
70
+ with st.form("reset_password_form"):
71
+ npw = st.text_input("Password Baru", type="password")
72
+ npw2 = st.text_input("Konfirmasi Password Baru", type="password")
73
+ submit_reset = st.form_submit_button("Set Password Baru")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  if submit_reset:
75
+ if not npw or len(npw) < 6:
 
 
 
76
  st.error("Password minimal 6 karakter.")
77
+ elif npw != npw2:
78
  st.error("Konfirmasi password tidak sama.")
79
+ elif not access_token or not refresh_token:
80
+ st.error("Token pemulihan tidak ditemukan. Coba klik ulang tautan dari email.")
81
  else:
 
82
  try:
83
+ supabase.auth.set_session(access_token, refresh_token)
84
+ supabase.auth.update_user({"password": npw})
85
+ st.success("Password berhasil diubah. Anda akan diarahkan ke login...")
86
+ # bersihkan query params setelah reset
87
+ st.experimental_set_query_params()
88
+ st.session_state.clear()
89
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  except Exception as e:
91
+ st.error(f"Gagal mengubah password: {e}")
92
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
+ # --- Auth tabs ---
95
  tab_login, tab_register, tab_forgot = st.tabs(["Login", "Register", "Forgot Password"])
96
 
97
+ # LOGIN
98
  with tab_login:
 
 
 
 
 
 
 
 
 
 
99
  with st.form("login_form"):
100
+ email = st.text_input("Email")
101
  password = st.text_input("Password", type="password")
102
+ submitted = st.form_submit_button("Login")
 
 
103
  if submitted:
 
 
 
 
 
104
  shared_pw = os.getenv("APP_DEMO_PASSWORD")
105
  if shared_pw and password == shared_pw:
106
  st.session_state["user"] = {"id": "demo-user", "email": email or "demo@example.com"}
107
  st.success("Login demo berhasil")
108
  st.rerun()
 
 
109
  try:
110
+ auth_res = supabase.auth.sign_in_with_password({
111
+ "email": email,
112
+ "password": password,
113
+ })
114
+ user = getattr(auth_res, "user", None)
115
+ if user:
116
+ st.session_state["user"] = {"id": user.id, "email": getattr(user, "email", email)}
117
+ session_obj = getattr(auth_res, "session", None)
118
+ if session_obj:
119
+ st.session_state["sb_session"] = {
120
+ "access_token": getattr(session_obj, "access_token", None),
121
+ "refresh_token": getattr(session_obj, "refresh_token", None),
122
+ }
123
+ st.success("Login berhasil")
124
+ st.rerun()
125
+ else:
126
+ st.error("Email atau password salah.")
 
 
127
  except Exception as e:
128
+ st.error(f"Gagal login: {e}")
129
 
130
+ # REGISTER
131
  with tab_register:
132
+ st.caption("Buat akun baru. Anda akan menerima email konfirmasi.")
 
 
133
  with st.form("register_form"):
134
+ r_email = st.text_input("Email", key="reg_email")
135
+ r_password = st.text_input("Password", type="password", key="reg_password")
136
  r_password2 = st.text_input("Konfirmasi Password", type="password", key="reg_password2")
137
+ submitted_r = st.form_submit_button("Register")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  if submitted_r:
139
+ if r_password != r_password2:
140
+ st.error("Password tidak sama.")
 
 
 
 
141
  else:
142
  try:
143
+ redirect_url = os.getenv(
144
+ "SUPABASE_EMAIL_REDIRECT",
145
+ os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/"),
146
+ )
147
+ supabase.auth.sign_up({
148
+ "email": r_email,
149
+ "password": r_password,
150
+ "options": {"email_redirect_to": redirect_url}
151
+ })
152
+ st.success("Registrasi berhasil. Silakan cek email untuk konfirmasi.")
 
 
 
 
 
 
 
153
  except Exception as e:
154
+ st.error(f"Gagal registrasi: {e}")
155
 
156
+ # FORGOT PASSWORD
157
  with tab_forgot:
158
+ st.caption("Kirim tautan reset password ke email Anda.")
 
 
 
 
 
 
 
 
 
 
 
 
159
  with st.form("forgot_form"):
160
+ f_email = st.text_input("Email", key="forgot_email")
161
+ submitted_f = st.form_submit_button("Kirim Link Reset")
 
162
  if submitted_f:
163
+ try:
164
+ redirect_url = os.getenv(
165
+ "SUPABASE_EMAIL_REDIRECT",
166
+ os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space/"),
167
+ )
168
+ supabase.auth.reset_password_for_email(f_email, {"redirect_to": redirect_url})
169
+ st.success("Email reset password telah dikirim. Periksa kotak masuk Anda.")
170
+ except Exception as e:
171
+ st.error(f"Gagal mengirim email reset password: {e}")