FauziIsyrinApridal commited on
Commit
0a5edbb
·
1 Parent(s): 4d7e1af
Files changed (3) hide show
  1. .env.example +0 -3
  2. app/auth.py +94 -11
  3. pages/reset_password.py +0 -162
.env.example CHANGED
@@ -4,9 +4,6 @@ SUPABASE_URL=
4
  SUPABASE_KEY=
5
  SUPABASE_STORAGE_BUCKET=
6
 
7
- # Email redirect URL for Supabase auth (registration & password reset)
8
- SUPABASE_EMAIL_REDIRECT=https://yozora721-pnp-chatbot-v1.hf.space/reset_password
9
- NEXT_PUBLIC_SITE_URL=https://yozora721-pnp-chatbot-v1.hf.space
10
 
11
  HUGGINGFACEHUB_API_TOKEN=
12
  LANGSMITH_TRACING=
 
4
  SUPABASE_KEY=
5
  SUPABASE_STORAGE_BUCKET=
6
 
 
 
 
7
 
8
  HUGGINGFACEHUB_API_TOKEN=
9
  LANGSMITH_TRACING=
app/auth.py CHANGED
@@ -31,17 +31,100 @@ def auth_view():
31
  unsafe_allow_html=True
32
  )
33
 
34
- # Hide sidebar and hamburger while on auth screens
 
35
  st.markdown(
36
  """
37
- <style>
38
- [data-testid="stSidebar"] { display: none !important; }
39
- [data-testid="collapsedControl"] { display: none !important; }
40
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  """,
42
  unsafe_allow_html=True,
43
  )
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  # Auth tabs inside wrapper
47
  tab_login, tab_register, tab_forgot = st.tabs(["Login", "Register", "Forgot Password"])
@@ -91,11 +174,11 @@ def auth_view():
91
  st.error("Password tidak sama.")
92
  else:
93
  try:
94
- # Prefer explicit env, then generic site URL, then production URL
95
  redirect_url = os.getenv(
96
  "SUPABASE_EMAIL_REDIRECT",
97
- os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space"),
98
- ) + "/reset_password"
99
  supabase.auth.sign_up({
100
  "email": r_email,
101
  "password": r_password,
@@ -112,11 +195,11 @@ def auth_view():
112
  submitted_f = st.form_submit_button("Kirim Link Reset")
113
  if submitted_f:
114
  try:
115
- # Prefer explicit env, then generic site URL, then production URL
116
  redirect_url = os.getenv(
117
  "SUPABASE_EMAIL_REDIRECT",
118
- os.getenv("NEXT_PUBLIC_SITE_URL", "https://yozora721-pnp-chatbot-v1.hf.space"),
119
- ) + "/reset_password"
120
  supabase.auth.reset_password_for_email(f_email, {"redirect_to": redirect_url})
121
  st.success("Email reset password telah dikirim. Periksa kotak masuk Anda.")
122
  except Exception as e:
 
31
  unsafe_allow_html=True
32
  )
33
 
34
+ # --- Password recovery handler (Supabase redirect) ---
35
+ # 1) Move hash params to query params on first load, then reload once
36
  st.markdown(
37
  """
38
+ <script>
39
+ (function(){
40
+ // Check if we have hash parameters and haven't migrated yet
41
+ const hash = window.location.hash;
42
+ if (hash && hash.length > 1 && !sessionStorage.getItem('hash_migrated')) {
43
+ // Remove the # and parse as URLSearchParams
44
+ const hashParams = new URLSearchParams(hash.substring(1));
45
+ const queryParams = new URLSearchParams(window.location.search);
46
+
47
+ // Copy all hash params to query params
48
+ let hasParams = false;
49
+ for (const [key, value] of hashParams.entries()) {
50
+ queryParams.set(key, value);
51
+ hasParams = true;
52
+ }
53
+
54
+ if (hasParams) {
55
+ // Build new URL with query params
56
+ const newUrl = window.location.pathname + '?' + queryParams.toString();
57
+
58
+ // Mark as migrated to prevent infinite loops
59
+ sessionStorage.setItem('hash_migrated', 'true');
60
+
61
+ // Replace current URL and clear hash
62
+ window.history.replaceState(null, '', newUrl);
63
+ window.location.hash = '';
64
+
65
+ // Reload to let Streamlit process the new query params
66
+ window.location.reload();
67
+ }
68
+ }
69
+ })();
70
+ </script>
71
  """,
72
  unsafe_allow_html=True,
73
  )
74
 
75
+ # 2) Read query params for recovery flow
76
+ try:
77
+ qp = st.query_params # Streamlit >= 1.30
78
+ get_q = lambda k: qp.get(k, None)
79
+ except Exception:
80
+ qp = st.experimental_get_query_params()
81
+ get_q = lambda k: (qp.get(k, [None])[0] if isinstance(qp.get(k, None), list) else qp.get(k, None))
82
+
83
+ q_type = get_q("type")
84
+ if q_type == "recovery":
85
+ st.info("Reset password: silakan masukkan password baru Anda.")
86
+ access_token = get_q("access_token")
87
+ refresh_token = get_q("refresh_token")
88
+ with st.form("reset_password_form"):
89
+ npw = st.text_input("Password Baru", type="password")
90
+ npw2 = st.text_input("Konfirmasi Password Baru", type="password")
91
+ submit_reset = st.form_submit_button("Set Password Baru")
92
+ if submit_reset:
93
+ if not npw or len(npw) < 6:
94
+ st.error("Password minimal 6 karakter.")
95
+ elif npw != npw2:
96
+ st.error("Konfirmasi password tidak sama.")
97
+ elif not access_token or not refresh_token:
98
+ st.error("Token pemulihan tidak ditemukan. Coba klik ulang tautan dari email.")
99
+ else:
100
+ try:
101
+ # Set current session using tokens from redirect
102
+ supabase.auth.set_session(access_token, refresh_token)
103
+ # Update user password
104
+ supabase.auth.update_user({"password": npw})
105
+ st.success("Password berhasil diubah. Silakan login kembali.")
106
+ # Optional: clear recovery query params
107
+ try:
108
+ # Best-effort to clear params
109
+ st.markdown(
110
+ """
111
+ <script>
112
+ (function(){
113
+ const qp = new URLSearchParams(window.location.search);
114
+ ["type","access_token","refresh_token","expires_in","expires_at","token_type"].forEach(k=>qp.delete(k));
115
+ const newUrl = window.location.pathname + (qp.toString()?('?'+qp.toString()):'');
116
+ window.history.replaceState(null, '', newUrl);
117
+ })();
118
+ </script>
119
+ """,
120
+ unsafe_allow_html=True,
121
+ )
122
+ except Exception:
123
+ pass
124
+ except Exception as e:
125
+ st.error(f"Gagal mengubah password: {e}")
126
+ # When in recovery mode, do not render login/register tabs
127
+ return
128
 
129
  # Auth tabs inside wrapper
130
  tab_login, tab_register, tab_forgot = st.tabs(["Login", "Register", "Forgot Password"])
 
174
  st.error("Password tidak sama.")
175
  else:
176
  try:
177
+ # Prefer explicit env, then generic site URL, then localhost for dev
178
  redirect_url = os.getenv(
179
  "SUPABASE_EMAIL_REDIRECT",
180
+ os.getenv("NEXT_PUBLIC_SITE_URL", "http://localhost:8501"),
181
+ )
182
  supabase.auth.sign_up({
183
  "email": r_email,
184
  "password": r_password,
 
195
  submitted_f = st.form_submit_button("Kirim Link Reset")
196
  if submitted_f:
197
  try:
198
+ # Prefer explicit env, then generic site URL, then localhost for dev
199
  redirect_url = os.getenv(
200
  "SUPABASE_EMAIL_REDIRECT",
201
+ os.getenv("NEXT_PUBLIC_SITE_URL", "http://localhost:8501"),
202
+ )
203
  supabase.auth.reset_password_for_email(f_email, {"redirect_to": redirect_url})
204
  st.success("Email reset password telah dikirim. Periksa kotak masuk Anda.")
205
  except Exception as e:
pages/reset_password.py DELETED
@@ -1,162 +0,0 @@
1
- import os
2
- import base64
3
- import streamlit as st
4
- from app.db import supabase
5
-
6
-
7
- def main():
8
- """Dedicated reset password page."""
9
-
10
- st.set_page_config(
11
- page_title="Reset Password - PNP Bot",
12
- page_icon="assets/favicon.ico",
13
- initial_sidebar_state="collapsed",
14
- )
15
- # Hide sidebar and hamburger completely
16
- st.markdown(
17
- """
18
- <style>
19
- [data-testid="stSidebar"] { display: none !important; }
20
- [data-testid="collapsedControl"] { display: none !important; }
21
- </style>
22
- """,
23
- unsafe_allow_html=True,
24
- )
25
-
26
- # Center the content
27
- left, center, right = st.columns([1, 2, 1])
28
- with center:
29
- # Header with logo
30
- logo_path = os.path.join("assets", "pnp-logo.png")
31
-
32
- def get_base64_image(path):
33
- with open(path, "rb") as f:
34
- return base64.b64encode(f.read()).decode()
35
-
36
- encoded_logo = get_base64_image(logo_path)
37
-
38
- st.markdown(
39
- f"""
40
- <div style="text-align:center;">
41
- <img src="data:image/png;base64,{encoded_logo}" width="100">
42
- <h1 style="margin-top:0.25rem;">Reset Password</h1>
43
- <p style="color: #666; margin-bottom: 2rem;">Masukkan password baru Anda</p>
44
- </div>
45
- """,
46
- unsafe_allow_html=True
47
- )
48
-
49
- # Get query parameters for recovery tokens
50
- try:
51
- qp = st.query_params # Streamlit >= 1.30
52
- get_q = lambda k: qp.get(k, None)
53
- except Exception:
54
- qp = st.experimental_get_query_params()
55
- get_q = lambda k: (qp.get(k, [None])[0] if isinstance(qp.get(k, None), list) else qp.get(k, None))
56
-
57
- access_token = get_q("access_token")
58
- refresh_token = get_q("refresh_token")
59
- q_type = get_q("type")
60
-
61
- # Validate that this is a recovery request
62
- if q_type != "recovery" or not access_token or not refresh_token:
63
- st.error("⚠️ Link reset password tidak valid atau sudah kedaluwarsa.")
64
- st.info("Silakan kembali ke halaman login dan minta link reset password yang baru.")
65
-
66
- if st.button("← Kembali ke Login", type="primary"):
67
- # Redirect to root (main app)
68
- st.markdown(
69
- """
70
- <script>
71
- window.location.href = window.location.origin +
72
- (window.location.pathname.replace(/\/?pages\/?reset_password\/?$/,'/') || '/');
73
- </script>
74
- """,
75
- unsafe_allow_html=True,
76
- )
77
- return
78
-
79
- # Reset password form
80
- with st.form("reset_password_form", clear_on_submit=False):
81
- st.markdown("### 🔐 Password Baru")
82
-
83
- new_password = st.text_input(
84
- "Password Baru",
85
- type="password",
86
- placeholder="Minimal 6 karakter",
87
- help="Password harus minimal 6 karakter"
88
- )
89
-
90
- confirm_password = st.text_input(
91
- "Konfirmasi Password Baru",
92
- type="password",
93
- placeholder="Ulangi password baru"
94
- )
95
-
96
- col1, col2 = st.columns([1, 1])
97
- with col1:
98
- submit_reset = st.form_submit_button("🔄 Update Password", type="primary", use_container_width=True)
99
- with col2:
100
- cancel_reset = st.form_submit_button("❌ Batal", use_container_width=True)
101
-
102
- if cancel_reset:
103
- st.markdown(
104
- """
105
- <script>
106
- window.location.href = window.location.origin +
107
- (window.location.pathname.replace(/\/?pages\/?reset_password\/?$/,'/') || '/');
108
- </script>
109
- """,
110
- unsafe_allow_html=True,
111
- )
112
-
113
- if submit_reset:
114
- # Validation
115
- if not new_password:
116
- st.error("❌ Password tidak boleh kosong.")
117
- return
118
-
119
- if len(new_password) < 6:
120
- st.error("❌ Password minimal 6 karakter.")
121
- return
122
-
123
- if new_password != confirm_password:
124
- st.error("❌ Konfirmasi password tidak sama.")
125
- return
126
-
127
- # Update password
128
- try:
129
- with st.spinner("Mengupdate password..."):
130
- # Set current session using tokens from redirect
131
- supabase.auth.set_session(access_token, refresh_token)
132
-
133
- # Update user password
134
- result = supabase.auth.update_user({"password": new_password})
135
-
136
- if result:
137
- st.success("✅ Password berhasil diubah!")
138
- st.info("Silakan login dengan password baru Anda.")
139
-
140
- # Show countdown and auto-redirect to main app
141
- st.markdown(
142
- """
143
- <script>
144
- setTimeout(function(){
145
- window.location.href = window.location.origin +
146
- (window.location.pathname.replace(/\/?pages\/?reset_password\/?$/,'/') || '/');
147
- }, 1500);
148
- </script>
149
- """,
150
- unsafe_allow_html=True,
151
- )
152
- st.caption("Mengalihkan ke halaman login...")
153
- else:
154
- st.error("❌ Gagal mengubah password. Silakan coba lagi.")
155
-
156
- except Exception as e:
157
- st.error(f"❌ Gagal mengubah password: {str(e)}")
158
- st.info("Link mungkin sudah kedaluwarsa. Silakan minta link reset password yang baru.")
159
-
160
-
161
- if __name__ == "__main__":
162
- main()