From 737382af01d09e9160c8c4dfdf25bf4659da61c7 Mon Sep 17 00:00:00 2001 From: MatchaDinosaur Date: Fri, 5 Sep 2025 00:28:54 +0800 Subject: [PATCH 01/87] page for verification, frontend see frontend/src/components/Login.vue, backend see apps/verifier/urls.py, apps/verifier/views.py, currently use ngrok for getting webhook from sjtu wj platform --- index.html | 2 + src/components/Login.vue | 543 ++++++++++++++++++++++++++++----------- vite.config.js | 6 + 3 files changed, 396 insertions(+), 155 deletions(-) diff --git a/index.html b/index.html index cef1ba3..7cfe695 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,8 @@ JI Course Review + + diff --git a/src/components/Login.vue b/src/components/Login.vue index 7551ba3..b150b07 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -1,195 +1,424 @@ + + diff --git a/vite.config.js b/vite.config.js index e684509..be3e0cb 100644 --- a/vite.config.js +++ b/vite.config.js @@ -11,6 +11,12 @@ export default defineConfig({ changeOrigin: false, // rewrite: (path) => path.replace(/^\/api/, ''), // Not needed if your Django URLs start with /api }, + // Proxy for the new verification API endpoints + "/verify/": { + target: "http://localhost:8000", + changeOrigin: false, + // rewrite: (path) => path.replace(/^\/verify/, ''), + }, }, }, build: { From d94b20a0a50a24a4bfed595bb546023d34bbbb45 Mon Sep 17 00:00:00 2001 From: alexis Date: Sat, 6 Sep 2025 00:00:50 +0800 Subject: [PATCH 02/87] fix:update with latest dev --- index.html | 6 +- src/components/Login.vue | 189 ++++++++++++++++++++++----------------- 2 files changed, 112 insertions(+), 83 deletions(-) diff --git a/index.html b/index.html index 7cfe695..66043d4 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,11 @@ JI Course Review - + diff --git a/src/components/Login.vue b/src/components/Login.vue index b150b07..87c2330 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -1,20 +1,25 @@ diff --git a/src/components/Auth.vue b/src/components/Auth.vue new file mode 100644 index 0000000..b9c4d4f --- /dev/null +++ b/src/components/Auth.vue @@ -0,0 +1,349 @@ + + + + + diff --git a/src/components/Login.vue b/src/components/Login.vue index 87c2330..7551ba3 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -1,442 +1,188 @@ - - diff --git a/src/main.js b/src/main.js index babf164..038f921 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,7 @@ import { createApp } from "vue"; import { createRouter, createWebHistory } from "vue-router"; import App from "./App.vue"; import Login from "./components/Login.vue"; +import Auth from "./components/Auth.vue"; import CourseDetail from "./components/CourseDetail.vue"; import Landing from "./components/Landing.vue"; import CourseReviewSearch from "./components/CourseReviewSearch.vue"; @@ -11,6 +12,7 @@ import "./style.css"; const routes = [ { path: "/", component: Landing }, { path: "/accounts/login", component: Login }, + { path: "/auth", component: Auth }, { path: "/course/:course_id", component: CourseDetail, props: true }, { path: "/course/:courseId/review_search", From cc4f84926b3d8dfc984f282824e8ce8f520ecd77 Mon Sep 17 00:00:00 2001 From: alexis Date: Fri, 12 Sep 2025 16:04:47 +0800 Subject: [PATCH 05/87] refactor: mv helpers to utils.py and improve return --- src/components/Login.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Login.vue b/src/components/Login.vue index 7551ba3..e21e5cd 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -165,7 +165,6 @@ const handleLogin = async () => { body: JSON.stringify({ email: email.value, password: password.value, - next: route.query.next || "/courses", }), }); @@ -175,7 +174,7 @@ const handleLogin = async () => { throw new Error(data.error || "Login failed"); } - window.location.href = data.next || "/courses"; + router.replace("/courses"); } catch (err) { error.value = err.message; } finally { From 9d642bf1573898be410425626d6195dc8347ffa8 Mon Sep 17 00:00:00 2001 From: alexis Date: Fri, 12 Sep 2025 21:20:19 +0800 Subject: [PATCH 06/87] refactor: mv config into development.yaml and fix some cq --- .env.example | 2 ++ src/components/Auth.vue | 41 +---------------------------------------- 2 files changed, 3 insertions(+), 40 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fe8ddce --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +# the env variable used for vite-processed frontend should begin with `VITE_` +VITE_TURNSTILE_SITE_KEY=0x4AAAAAABz2ci0ZN9OaO-dg \ No newline at end of file diff --git a/src/components/Auth.vue b/src/components/Auth.vue index b9c4d4f..de811db 100644 --- a/src/components/Auth.vue +++ b/src/components/Auth.vue @@ -139,41 +139,8 @@ const loading = ref(true); const error = ref(null); const initiating = ref(false); -// Configuration from backend -const config = ref({ - turnstile_site_key: null, - survey_url: null, -}); - let turnstileWidget = null; -// Fetch configuration from backend -const fetchConfig = async () => { - try { - const response = await fetch("/api/auth/config/", { - method: "GET", - credentials: "include", - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - config.value = { - turnstile_site_key: data.TURNSTILE_SITE_KEY, - survey_url: data.SURVEY_URL, - }; - - return true; - } catch (err) { - console.error("Failed to fetch configuration:", err); - error.value = - "Failed to load authentication configuration. Please refresh the page and try again."; - return false; - } -}; - // Load Cloudflare Turnstile script const loadTurnstile = () => { return new Promise((resolve, reject) => { @@ -204,12 +171,6 @@ const loadTurnstile = () => { // Initialize Turnstile widget const initializeTurnstile = async () => { try { - // First fetch configuration - const configLoaded = await fetchConfig(); - if (!configLoaded || !config.value.turnstile_site_key) { - return; - } - // Set loading to false so the Turnstile container is rendered in DOM loading.value = false; @@ -234,7 +195,7 @@ const initializeTurnstile = async () => { } turnstileWidget = window.turnstile.render("#turnstile-widget", { - sitekey: config.value.turnstile_site_key, + sitekey: import.meta.env.VITE_TURNSTILE_SITE_KEY, callback: (token) => { turnstileToken.value = token; }, From b9830d966fa5dc7e1bdb98da0e087e7881a020b6 Mon Sep 17 00:00:00 2001 From: alexis Date: Wed, 17 Sep 2025 14:00:18 +0800 Subject: [PATCH 07/87] refactor: apart signup and reset_password --- src/components/Login.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Login.vue b/src/components/Login.vue index e21e5cd..67e1d89 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -156,7 +156,7 @@ const handleLogin = async () => { loading.value = true; try { - const response = await fetch("/api/accounts/login/", { + const response = await fetch("/api/auth/login/", { method: "POST", headers: { "Content-Type": "application/json", From 64e50f4c8e597de874e0c08d17c909f910356ff3 Mon Sep 17 00:00:00 2001 From: arthurcai Date: Wed, 17 Sep 2025 20:14:42 +0800 Subject: [PATCH 08/87] feat: add frontend for authorization --- src/components/AuthCallback.vue | 523 ++++++++++++++++++++++ src/components/AuthInitiate.vue | 480 ++++++++++++++++++++ src/components/Login.vue | 167 +++---- src/components/SetPasswordForm.vue | 697 +++++++++++++++++++++++++++++ src/main.js | 11 +- src/views/AuthCallback.vue | 276 ++++++++++++ src/views/ResetPassword.vue | 593 ++++++++++++++++++++++++ src/views/Signup.vue | 500 +++++++++++++++++++++ src/views/Verify.vue | 254 +++++++++++ 9 files changed, 3421 insertions(+), 80 deletions(-) create mode 100644 src/components/AuthCallback.vue create mode 100644 src/components/AuthInitiate.vue create mode 100644 src/components/SetPasswordForm.vue create mode 100644 src/views/AuthCallback.vue create mode 100644 src/views/ResetPassword.vue create mode 100644 src/views/Signup.vue create mode 100644 src/views/Verify.vue diff --git a/src/components/AuthCallback.vue b/src/components/AuthCallback.vue new file mode 100644 index 0000000..20b90b4 --- /dev/null +++ b/src/components/AuthCallback.vue @@ -0,0 +1,523 @@ + + + + + diff --git a/src/components/AuthInitiate.vue b/src/components/AuthInitiate.vue new file mode 100644 index 0000000..5820c30 --- /dev/null +++ b/src/components/AuthInitiate.vue @@ -0,0 +1,480 @@ + + + + + diff --git a/src/components/Login.vue b/src/components/Login.vue index 67e1d89..6b7f684 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -1,92 +1,102 @@ + + + + diff --git a/src/main.js b/src/main.js index 038f921..50c1959 100644 --- a/src/main.js +++ b/src/main.js @@ -7,11 +7,20 @@ import CourseDetail from "./components/CourseDetail.vue"; import Landing from "./components/Landing.vue"; import CourseReviewSearch from "./components/CourseReviewSearch.vue"; import CourseList from "./components/CourseList.vue"; +import AuthCallback from "./views/AuthCallback.vue"; +import Signup from "./views/Signup.vue"; +import ResetPassword from "./views/ResetPassword.vue"; import "./style.css"; const routes = [ { path: "/", component: Landing }, - { path: "/accounts/login", component: Login }, + { path: "/login", component: Login }, + { path: "/accounts/login", component: Login }, // Legacy route for backward compatibility + { path: "/signup", component: Signup }, + { path: "/accounts/signup", component: Signup }, // Add accounts/signup route + { path: "/reset", component: ResetPassword }, + { path: "/accounts/reset", component: ResetPassword }, // Add accounts/reset for consistency + { path: "/callback", component: AuthCallback }, { path: "/auth", component: Auth }, { path: "/course/:course_id", component: CourseDetail, props: true }, { diff --git a/src/views/AuthCallback.vue b/src/views/AuthCallback.vue new file mode 100644 index 0000000..efffe47 --- /dev/null +++ b/src/views/AuthCallback.vue @@ -0,0 +1,276 @@ + + + diff --git a/src/views/ResetPassword.vue b/src/views/ResetPassword.vue new file mode 100644 index 0000000..76eee3e --- /dev/null +++ b/src/views/ResetPassword.vue @@ -0,0 +1,593 @@ + + + + + diff --git a/src/views/Signup.vue b/src/views/Signup.vue new file mode 100644 index 0000000..ec7aea1 --- /dev/null +++ b/src/views/Signup.vue @@ -0,0 +1,500 @@ + + + + + diff --git a/src/views/Verify.vue b/src/views/Verify.vue new file mode 100644 index 0000000..8231996 --- /dev/null +++ b/src/views/Verify.vue @@ -0,0 +1,254 @@ + + + From 9ded4cc0584c158d9fd8fcb4b00eed2d44cdb11f Mon Sep 17 00:00:00 2001 From: alexis Date: Thu, 18 Sep 2025 02:02:37 +0800 Subject: [PATCH 09/87] fix: rm reset action --- src/views/AuthCallback.vue | 16 +++------------- src/views/Verify.vue | 14 ++------------ 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/views/AuthCallback.vue b/src/views/AuthCallback.vue index efffe47..1613858 100644 --- a/src/views/AuthCallback.vue +++ b/src/views/AuthCallback.vue @@ -93,8 +93,8 @@ const parseUrlParams = () => { return null; } - // Validate action parameter - const validActions = ["signup", "login", "reset_password", "reset"]; // Accept both old and new reset actions + // Validate action parameter - only allow the three valid actions + const validActions = ["signup", "login", "reset_password"]; if (!validActions.includes(action)) { error.value = "Invalid action parameter. Please try the authentication process again."; @@ -102,17 +102,7 @@ const parseUrlParams = () => { return null; } - // Map old action values to new action values for consistency with backend - const actionMapping = { - reset: "reset_password", // Map old "reset" to new "reset_password" - signup: "signup", - login: "login", - reset_password: "reset_password", - }; - - const mappedAction = actionMapping[action] || action; - - return { account, answer_id, action: mappedAction }; + return { account, answer_id, action }; }; // Call backend verification API diff --git a/src/views/Verify.vue b/src/views/Verify.vue index 8231996..5c3fdb3 100644 --- a/src/views/Verify.vue +++ b/src/views/Verify.vue @@ -94,7 +94,7 @@ const parseUrlParams = () => { } // Validate action parameter - const validActions = ["signup", "login", "reset_password", "reset"]; // Accept both old and new reset actions + const validActions = ["signup", "login", "reset_password"]; if (!validActions.includes(action)) { error.value = "Invalid action parameter. Please try the authentication process again."; @@ -102,17 +102,7 @@ const parseUrlParams = () => { return null; } - // Map old action values to new action values for consistency with backend - const actionMapping = { - reset: "reset_password", // Map old "reset" to new "reset_password" - signup: "signup", - login: "login", - reset_password: "reset_password", - }; - - const mappedAction = actionMapping[action] || action; - - return { account, answer_id, action: mappedAction }; + return { account, answer_id, action }; }; // Call backend verification API From 7f91d50b3a573b4f6cf90c8b7519dc0d6309afc2 Mon Sep 17 00:00:00 2001 From: arthurcai Date: Thu, 18 Sep 2025 12:15:19 +0800 Subject: [PATCH 10/87] fix: fix failure in auth and refactor code for better cq --- src/components/AuthInitiate.vue | 166 +++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 5 deletions(-) diff --git a/src/components/AuthInitiate.vue b/src/components/AuthInitiate.vue index 5820c30..13a74ad 100644 --- a/src/components/AuthInitiate.vue +++ b/src/components/AuthInitiate.vue @@ -76,6 +76,7 @@ Redirecting in {{ countdown }}s... +

Expires in {{ formatTime(otpData.expires_at) }}

@@ -180,9 +181,11 @@ const redirecting = ref(false); const copyButtonText = ref("Copy Code & Continue"); const countdown = ref(0); const redirectUrl = ref(null); +const currentTime = ref(Date.now()); // For reactive countdown updates let turnstileWidget = null; let countdownInterval = null; +let timeUpdateInterval = null; // For updating currentTime // Check for existing OTP data in localStorage const checkExistingOTP = () => { @@ -201,6 +204,18 @@ const checkExistingOTP = () => { flowInfo.action === props.action && flowInfo.status === "pending" ) { + // Check if too much time has passed since generation (likely verification failed) + const timeSinceGeneration = + Date.now() - (otpInfo.expires_at - 2 * 60 * 1000); + if (timeSinceGeneration > 15 * 1000) { + // 15 seconds - more aggressive detection + console.log( + "🔧 Debug: OTP has been unused for too long, restarting verification flow", + ); + clearAuthData(); + return false; // Restart the flow + } + otpData.value = otpInfo; loading.value = false; return true; @@ -220,6 +235,7 @@ const checkExistingOTP = () => { const clearAuthData = () => { localStorage.removeItem("auth_otp"); localStorage.removeItem("auth_flow"); + localStorage.removeItem("auth_redirect_time"); }; // Load Turnstile script @@ -255,13 +271,24 @@ const initializeTurnstile = async () => { ); console.log("🔧 Debug: All env vars =", import.meta.env); + // First ensure we're in the right state for turnstile to be rendered + loading.value = false; + turnstileToken.value = null; + otpData.value = null; + error.value = null; + await loadTurnstile(); + + // Use multiple nextTicks to ensure DOM is fully updated + await nextTick(); + await nextTick(); await nextTick(); - // Wait for DOM element to be available with timeout + // Wait for DOM element to be available with longer timeout and more attempts let widgetContainer = null; let attempts = 0; - const maxAttempts = 10; + const maxAttempts = 30; // Further increased attempts + const waitTime = 300; // Increased wait time while (!widgetContainer && attempts < maxAttempts) { widgetContainer = document.getElementById("turnstile-widget"); @@ -269,7 +296,21 @@ const initializeTurnstile = async () => { console.log( `🔧 Debug: Waiting for turnstile-widget container (attempt ${attempts + 1}/${maxAttempts})`, ); - await new Promise((resolve) => setTimeout(resolve, 100)); + // Ensure we're still in the correct state + if ( + loading.value || + turnstileToken.value || + otpData.value || + error.value + ) { + console.log("🔧 Debug: State changed during wait, resetting state"); + loading.value = false; + turnstileToken.value = null; + otpData.value = null; + error.value = null; + await nextTick(); + } + await new Promise((resolve) => setTimeout(resolve, waitTime)); attempts++; } } @@ -373,6 +414,9 @@ const copyOTPAndRedirect = async () => { if (!otpData.value || !redirectUrl.value) return; try { + // Record the time when user leaves for questionnaire + localStorage.setItem("auth_redirect_time", Date.now().toString()); + // Copy to clipboard await navigator.clipboard.writeText(otpData.value.otp); copyButtonText.value = "Copied! ✓"; @@ -404,7 +448,10 @@ const copyOTPAndRedirect = async () => { // Format time remaining const formatTime = (expiresAt) => { - const remaining = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000)); + const remaining = Math.max( + 0, + Math.floor((expiresAt - currentTime.value) / 1000), + ); const minutes = Math.floor(remaining / 60); const seconds = remaining % 60; return `${minutes}:${seconds.toString().padStart(2, "0")}`; @@ -446,7 +493,7 @@ const resetAuth = () => { window.turnstile.reset(turnstileWidget); } - loading.value = true; + // Don't set loading.value = true here, let initializeTurnstile handle the state setTimeout(() => { initializeTurnstile(); }, 100); @@ -454,12 +501,110 @@ const resetAuth = () => { // Component lifecycle onMounted(async () => { + // Start time update interval for reactive countdown + timeUpdateInterval = setInterval(() => { + currentTime.value = Date.now(); + }, 1000); + + // Check if user just returned from signup or questionnaire + const urlParams = new URLSearchParams(window.location.search); + const hasOtpHint = urlParams.has("otp_hint"); + const referrer = document.referrer; + const isFromQuestionnaire = + referrer.includes("questionnaire") || + referrer.includes("sjtu") || + hasOtpHint; + + // If user is on signup page and has stored auth data, restart verification flow + const isOnSignupWithStoredAuth = + props.action === "signup" && localStorage.getItem("auth_otp"); + + // If user returned from signup or questionnaire, always restart verification flow + if (isFromQuestionnaire || isOnSignupWithStoredAuth) { + console.log( + "🔧 Debug: Detected", + isOnSignupWithStoredAuth + ? "signup page with stored auth data" + : "return from questionnaire", + "- restarting verification flow", + ); + clearAuthData(); + // Don't set loading.value = true, let initializeTurnstile handle the state + await nextTick(); + await initializeTurnstile(); + return; + } + + // Add visibility change listener to detect when user returns from questionnaire + const handleVisibilityChange = () => { + // Only check when page becomes visible + if (document.visibilityState === "visible") { + // Check if we have an OTP that's been around for a while + const storedOTP = localStorage.getItem("auth_otp"); + const redirectTime = localStorage.getItem("auth_redirect_time"); + + if (storedOTP) { + try { + const otpInfo = JSON.parse(storedOTP); + const now = Date.now(); + + // Check time since OTP generation + const timeSinceGeneration = + now - (otpInfo.expires_at - 2 * 60 * 1000); + + // If user left for questionnaire, check time since redirect + if (redirectTime) { + const timeSinceRedirect = now - parseInt(redirectTime); + // If user returned within 60 seconds, likely verification failed + if (timeSinceRedirect < 60 * 1000 && timeSinceRedirect > 5 * 1000) { + console.log( + "🔧 Debug: User returned quickly from questionnaire, likely verification failed", + ); + clearAuthData(); + localStorage.removeItem("auth_redirect_time"); + otpData.value = null; + // Don't set loading.value = true, let initializeTurnstile handle the state + setTimeout(async () => { + await initializeTurnstile(); + }, 100); + return; + } + } + + // If OTP exists for more than 30 seconds when user returns, likely verification failed + if (timeSinceGeneration > 30 * 1000) { + console.log( + "🔧 Debug: OTP has been unused for too long, restarting verification flow", + ); + clearAuthData(); + localStorage.removeItem("auth_redirect_time"); + otpData.value = null; + // Don't set loading.value = true, let initializeTurnstile handle the state + setTimeout(async () => { + await initializeTurnstile(); + }, 100); + } + } catch (e) { + console.error("Error checking OTP on visibility change:", e); + } + } + } + }; + + document.addEventListener("visibilitychange", handleVisibilityChange); + + // Store the handler for cleanup + window._authVisibilityHandler = handleVisibilityChange; + // Check for existing valid OTP first if (!checkExistingOTP()) { // No valid OTP, set loading to false first to render the turnstile container loading.value = false; await nextTick(); await initializeTurnstile(); + } else { + // OTP exists, just show it + loading.value = false; } }); @@ -467,9 +612,20 @@ onUnmounted(() => { if (countdownInterval) { clearInterval(countdownInterval); } + if (timeUpdateInterval) { + clearInterval(timeUpdateInterval); + } if (turnstileWidget && window.turnstile) { window.turnstile.remove(turnstileWidget); } + // Clean up visibility change event listener + if (window._authVisibilityHandler) { + document.removeEventListener( + "visibilitychange", + window._authVisibilityHandler, + ); + delete window._authVisibilityHandler; + } }); From 220383541d783300ac74f3c408e5d98136746214 Mon Sep 17 00:00:00 2001 From: arthurcai Date: Thu, 18 Sep 2025 12:23:01 +0800 Subject: [PATCH 11/87] fix: reset password failure --- src/components/AuthInitiate.vue | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/AuthInitiate.vue b/src/components/AuthInitiate.vue index 13a74ad..ae51638 100644 --- a/src/components/AuthInitiate.vue +++ b/src/components/AuthInitiate.vue @@ -515,17 +515,25 @@ onMounted(async () => { referrer.includes("sjtu") || hasOtpHint; - // If user is on signup page and has stored auth data, restart verification flow + // If user is on signup or reset_password page and has stored auth data, restart verification flow const isOnSignupWithStoredAuth = props.action === "signup" && localStorage.getItem("auth_otp"); - - // If user returned from signup or questionnaire, always restart verification flow - if (isFromQuestionnaire || isOnSignupWithStoredAuth) { + const isOnResetPasswordWithStoredAuth = + props.action === "reset_password" && localStorage.getItem("auth_otp"); + + // If user returned from signup/reset_password/questionnaire, always restart verification flow + if ( + isFromQuestionnaire || + isOnSignupWithStoredAuth || + isOnResetPasswordWithStoredAuth + ) { console.log( "🔧 Debug: Detected", isOnSignupWithStoredAuth ? "signup page with stored auth data" - : "return from questionnaire", + : isOnResetPasswordWithStoredAuth + ? "reset_password page with stored auth data" + : "return from questionnaire", "- restarting verification flow", ); clearAuthData(); From df8ba5a01fb13d0222c3b191154d27a6542a8869 Mon Sep 17 00:00:00 2001 From: arthurcai Date: Thu, 18 Sep 2025 12:41:05 +0800 Subject: [PATCH 12/87] fix: multiple login methods --- src/components/AuthInitiate.vue | 13 +++++++--- src/components/Login.vue | 46 +++++++++++++++++++++++++++++++-- src/views/AuthCallback.vue | 21 ++++++--------- 3 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/components/AuthInitiate.vue b/src/components/AuthInitiate.vue index ae51638..e674917 100644 --- a/src/components/AuthInitiate.vue +++ b/src/components/AuthInitiate.vue @@ -515,17 +515,20 @@ onMounted(async () => { referrer.includes("sjtu") || hasOtpHint; - // If user is on signup or reset_password page and has stored auth data, restart verification flow + // If user is on signup, reset_password, or login page and has stored auth data, restart verification flow const isOnSignupWithStoredAuth = props.action === "signup" && localStorage.getItem("auth_otp"); const isOnResetPasswordWithStoredAuth = props.action === "reset_password" && localStorage.getItem("auth_otp"); + const isOnLoginWithStoredAuth = + props.action === "login" && localStorage.getItem("auth_otp"); - // If user returned from signup/reset_password/questionnaire, always restart verification flow + // If user returned from signup/reset_password/login/questionnaire, always restart verification flow if ( isFromQuestionnaire || isOnSignupWithStoredAuth || - isOnResetPasswordWithStoredAuth + isOnResetPasswordWithStoredAuth || + isOnLoginWithStoredAuth ) { console.log( "🔧 Debug: Detected", @@ -533,7 +536,9 @@ onMounted(async () => { ? "signup page with stored auth data" : isOnResetPasswordWithStoredAuth ? "reset_password page with stored auth data" - : "return from questionnaire", + : isOnLoginWithStoredAuth + ? "login page with stored auth data" + : "return from questionnaire", "- restarting verification flow", ); clearAuthData(); diff --git a/src/components/Login.vue b/src/components/Login.vue index 6b7f684..e4d7594 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -21,6 +21,36 @@
+ +
+ +
+
@@ -35,7 +65,12 @@
-
+ +
@@ -119,6 +154,11 @@
+ + +
+ +

@@ -138,6 +178,7 @@ import { ref, onMounted } from "vue"; import { useRouter, useRoute } from "vue-router"; import { ExclamationTriangleIcon } from "@heroicons/vue/24/outline"; +import AuthInitiate from "./AuthInitiate.vue"; const router = useRouter(); const route = useRoute(); @@ -145,6 +186,7 @@ const email = ref(""); const password = ref(""); const error = ref(""); const loading = ref(false); +const showQuestionnaireLogin = ref(false); onMounted(async () => { try { diff --git a/src/views/AuthCallback.vue b/src/views/AuthCallback.vue index 1613858..2d23839 100644 --- a/src/views/AuthCallback.vue +++ b/src/views/AuthCallback.vue @@ -4,9 +4,6 @@ >

-
- Logo -

Verifying Authentication

@@ -32,22 +29,20 @@

{{ error }}

-
+
-
- - Return to {{ getActionDisplayName() }} - -
+ + Return to {{ getActionDisplayName() }} +
From 4da263144bcf870ed7f9c92eff0706e1de89532a Mon Sep 17 00:00:00 2001 From: arthurcai Date: Thu, 18 Sep 2025 20:21:35 +0800 Subject: [PATCH 13/87] fix: fix redirect interval and turnstile --- src/components/AuthCallback.vue | 6 +- src/components/AuthInitiate.vue | 92 +++++++++++++++++++++++------- src/components/SetPasswordForm.vue | 9 +-- 3 files changed, 81 insertions(+), 26 deletions(-) diff --git a/src/components/AuthCallback.vue b/src/components/AuthCallback.vue index 20b90b4..beacda2 100644 --- a/src/components/AuthCallback.vue +++ b/src/components/AuthCallback.vue @@ -170,7 +170,7 @@ export default { // Constants const isDevelopment = process.env.NODE_ENV === "development"; - const REDIRECT_DELAY = 3000; // 3 seconds + const REDIRECT_DELAY = 1000; // 1 second const PROGRESS_STEPS = [20, 50, 80, 100]; // Computed properties @@ -258,7 +258,7 @@ export default { clearInterval(countdownTimer); handleRedirect(); } - }, 1000); + }, 0); } async function verifyCallback() { @@ -360,6 +360,8 @@ export default { case "login": default: successMessage.value = "Login successful! Welcome back"; + // Trigger authentication state change event + window.dispatchEvent(new CustomEvent("auth-state-changed")); break; } diff --git a/src/components/AuthInitiate.vue b/src/components/AuthInitiate.vue index e674917..4f7ca30 100644 --- a/src/components/AuthInitiate.vue +++ b/src/components/AuthInitiate.vue @@ -183,7 +183,29 @@ const countdown = ref(0); const redirectUrl = ref(null); const currentTime = ref(Date.now()); // For reactive countdown updates +// Turnstile management let turnstileWidget = null; +let isInitializingTurnstile = false; // Prevent concurrent initialization + +// Safe cleanup of existing turnstile widget +const cleanupTurnstile = () => { + if (turnstileWidget && window.turnstile) { + try { + console.log("🔧 Debug: Removing existing turnstile widget"); + window.turnstile.remove(turnstileWidget); + } catch (e) { + console.warn("Error removing turnstile widget:", e); + } + } + turnstileWidget = null; + + // Also clear the DOM container to ensure clean state + const widgetContainer = document.getElementById("turnstile-widget"); + if (widgetContainer) { + widgetContainer.innerHTML = ""; + console.log("🔧 Debug: Cleared turnstile widget container"); + } +}; let countdownInterval = null; let timeUpdateInterval = null; // For updating currentTime @@ -264,12 +286,24 @@ const loadTurnstile = () => { // Initialize Turnstile widget const initializeTurnstile = async () => { + // Prevent concurrent initialization + if (isInitializingTurnstile) { + console.log( + "🔧 Debug: Turnstile initialization already in progress, skipping", + ); + return; + } + + isInitializingTurnstile = true; + try { console.log( "🔧 Debug: VITE_TURNSTILE_SITE_KEY =", import.meta.env.VITE_TURNSTILE_SITE_KEY, ); - console.log("🔧 Debug: All env vars =", import.meta.env); + + // Always cleanup any existing widget first + cleanupTurnstile(); // First ensure we're in the right state for turnstile to be rendered loading.value = false; @@ -318,10 +352,13 @@ const initializeTurnstile = async () => { if (!widgetContainer) { console.error("Turnstile widget container not found after waiting"); error.value = - "Failed to initialize security verification. Please refresh the page."; + "Failed to initialize security verification. Please try again."; return; } + // Clear container content to ensure clean state + widgetContainer.innerHTML = ""; + const siteKey = import.meta.env.VITE_TURNSTILE_SITE_KEY || "0x4AAAAAAABVPBtNKmaBqXhw"; console.log("🔧 Debug: Using site key =", siteKey); @@ -329,27 +366,34 @@ const initializeTurnstile = async () => { turnstileWidget = window.turnstile.render(widgetContainer, { sitekey: siteKey, callback: (token) => { + console.log("🔧 Debug: Turnstile callback received"); turnstileToken.value = token; loading.value = false; }, "error-callback": (errorCode) => { console.error("Turnstile error:", errorCode); - error.value = - "Security verification failed. Please refresh the page and try again."; + // Reset initialization flag so retry can work + isInitializingTurnstile = false; + error.value = "Security verification failed. Please try again."; loading.value = false; }, "expired-callback": () => { + console.log("🔧 Debug: Turnstile expired"); + // Reset initialization flag so retry can work + isInitializingTurnstile = false; turnstileToken.value = null; - error.value = - "Security verification expired. Please complete the check again."; + error.value = "Security verification expired. Please try again."; }, theme: "light", size: "normal", }); + + console.log("🔧 Debug: Turnstile widget created with ID:", turnstileWidget); } catch (err) { console.error("Failed to load Turnstile:", err); - error.value = - "Failed to load security verification. Please check your internet connection and refresh the page."; + error.value = "Failed to load security verification. Please try again."; + } finally { + isInitializingTurnstile = false; } }; @@ -423,7 +467,7 @@ const copyOTPAndRedirect = async () => { // Start countdown and redirect redirecting.value = true; - countdown.value = 3; + countdown.value = 1; countdownInterval = setInterval(() => { countdown.value--; @@ -475,12 +519,18 @@ const getCookie = (name) => { // Reset authentication state const resetAuth = () => { + console.log("🔧 Debug: Resetting auth state (try again)"); + + // Clear all state error.value = null; turnstileToken.value = null; otpData.value = null; redirecting.value = false; copyButtonText.value = "Copy Code & Continue"; + // Reset initialization flag to allow retry + isInitializingTurnstile = false; + if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; @@ -488,15 +538,16 @@ const resetAuth = () => { clearAuthData(); - // Reset turnstile widget - if (turnstileWidget && window.turnstile) { - window.turnstile.reset(turnstileWidget); - } + // Use cleanup function instead of reset to ensure complete removal + cleanupTurnstile(); + + // Set loading state briefly to show user we're working + loading.value = true; - // Don't set loading.value = true here, let initializeTurnstile handle the state - setTimeout(() => { - initializeTurnstile(); - }, 100); + // Add delay to ensure cleanup is complete before reinitializing + setTimeout(async () => { + await initializeTurnstile(); + }, 300); // Increased delay for error recovery }; // Component lifecycle @@ -628,9 +679,10 @@ onUnmounted(() => { if (timeUpdateInterval) { clearInterval(timeUpdateInterval); } - if (turnstileWidget && window.turnstile) { - window.turnstile.remove(turnstileWidget); - } + + // Use cleanup function for consistent turnstile cleanup + cleanupTurnstile(); + // Clean up visibility change event listener if (window._authVisibilityHandler) { document.removeEventListener( diff --git a/src/components/SetPasswordForm.vue b/src/components/SetPasswordForm.vue index ad34368..4f0876f 100644 --- a/src/components/SetPasswordForm.vue +++ b/src/components/SetPasswordForm.vue @@ -382,6 +382,7 @@ export default { let score = 0; // Length check - 12 characters is now the baseline + if (password.length < 10) return 1; if (password.length >= 12) score += 1; if (password.length >= 16) score += 1; @@ -471,10 +472,10 @@ export default { if (!password) { errors.value.password = "Please enter a password"; - } else if (password.length < 12) { - errors.value.password = "Password must be at least 12 characters"; - } else if (password.length > 128) { - errors.value.password = "Password cannot exceed 128 characters"; + } else if (password.length < 10) { + errors.value.password = "Password must be at least 10 characters"; + } else if (password.length > 32) { + errors.value.password = "Password cannot exceed 32 characters"; } else if (!/(?=.*[a-zA-Z])(?=.*[0-9])/.test(password)) { errors.value.password = "Password must contain both letters and numbers"; From 147ef807e1ffd0de43ab8c299de1d65f802e4e2e Mon Sep 17 00:00:00 2001 From: arthurcai Date: Thu, 18 Sep 2025 20:59:22 +0800 Subject: [PATCH 14/87] fix: fix user icon after login --- src/components/AppLayout.vue | 8 +++++++- src/components/Login.vue | 6 ++++++ src/views/AuthCallback.vue | 8 ++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/AppLayout.vue b/src/components/AppLayout.vue index fe05540..ef1da23 100644 --- a/src/components/AppLayout.vue +++ b/src/components/AppLayout.vue @@ -176,7 +176,7 @@ From 742e8bd4ea55233ae97043bd8edf4f30fb5fdac2 Mon Sep 17 00:00:00 2001 From: alexis Date: Fri, 19 Sep 2025 15:46:41 +0800 Subject: [PATCH 16/87] fix: add turnstile for password login --- src/components/Auth.vue | 310 -------------------------------- src/components/AuthInitiate.vue | 246 ++++--------------------- src/components/Login.vue | 39 +++- src/components/Turnstile.vue | 282 +++++++++++++++++++++++++++++ src/main.js | 6 +- 5 files changed, 357 insertions(+), 526 deletions(-) delete mode 100644 src/components/Auth.vue create mode 100644 src/components/Turnstile.vue diff --git a/src/components/Auth.vue b/src/components/Auth.vue deleted file mode 100644 index de811db..0000000 --- a/src/components/Auth.vue +++ /dev/null @@ -1,310 +0,0 @@ - - - - - diff --git a/src/components/AuthInitiate.vue b/src/components/AuthInitiate.vue index afdbab2..d304fad 100644 --- a/src/components/AuthInitiate.vue +++ b/src/components/AuthInitiate.vue @@ -90,19 +90,15 @@ v-else-if="!turnstileToken" class="bg-white py-8 px-4 shadow-sm sm:rounded-lg sm:px-10" > -
-
-

- Verify you're human -

-

- Complete the security check below to continue -

-
-
-
-
-
+
@@ -160,7 +156,8 @@ + + diff --git a/src/main.js b/src/main.js index 50c1959..e810a20 100644 --- a/src/main.js +++ b/src/main.js @@ -2,7 +2,6 @@ import { createApp } from "vue"; import { createRouter, createWebHistory } from "vue-router"; import App from "./App.vue"; import Login from "./components/Login.vue"; -import Auth from "./components/Auth.vue"; import CourseDetail from "./components/CourseDetail.vue"; import Landing from "./components/Landing.vue"; import CourseReviewSearch from "./components/CourseReviewSearch.vue"; @@ -17,11 +16,10 @@ const routes = [ { path: "/login", component: Login }, { path: "/accounts/login", component: Login }, // Legacy route for backward compatibility { path: "/signup", component: Signup }, - { path: "/accounts/signup", component: Signup }, // Add accounts/signup route + { path: "/accounts/signup", component: Signup }, { path: "/reset", component: ResetPassword }, - { path: "/accounts/reset", component: ResetPassword }, // Add accounts/reset for consistency + { path: "/accounts/reset", component: ResetPassword }, { path: "/callback", component: AuthCallback }, - { path: "/auth", component: Auth }, { path: "/course/:course_id", component: CourseDetail, props: true }, { path: "/course/:courseId/review_search", From 69e20fda853378f59eaeb27ceb32eb04d43c7efd Mon Sep 17 00:00:00 2001 From: arthurcai Date: Mon, 22 Sep 2025 13:03:35 +0800 Subject: [PATCH 17/87] refactor: frontend --- src/components/AppLayout.vue | 69 +-------- src/components/AuthInitiate.vue | 28 ++-- src/components/CourseDetail.vue | 209 +++++--------------------- src/components/CourseList.vue | 192 ++++++----------------- src/components/CourseReviewSearch.vue | 31 +--- src/components/Landing.vue | 17 +-- src/components/Login.vue | 41 +++-- src/components/ReviewCard.vue | 36 +---- src/components/Turnstile.vue | 44 +++++- src/composables/useAuth.js | 63 ++++++++ src/composables/useCourses.js | 146 ++++++++++++++++++ src/composables/useReviews.js | 115 ++++++++++++++ src/utils/api.js | 17 +++ src/utils/cookies.js | 17 +++ src/utils/sanitize.js | 13 ++ 15 files changed, 548 insertions(+), 490 deletions(-) create mode 100644 src/composables/useAuth.js create mode 100644 src/composables/useCourses.js create mode 100644 src/composables/useReviews.js create mode 100644 src/utils/api.js create mode 100644 src/utils/cookies.js create mode 100644 src/utils/sanitize.js diff --git a/src/components/AppLayout.vue b/src/components/AppLayout.vue index ef1da23..3549feb 100644 --- a/src/components/AppLayout.vue +++ b/src/components/AppLayout.vue @@ -176,7 +176,8 @@ diff --git a/src/components/AuthInitiate.vue b/src/components/AuthInitiate.vue index d304fad..2ae4503 100644 --- a/src/components/AuthInitiate.vue +++ b/src/components/AuthInitiate.vue @@ -157,6 +157,7 @@ diff --git a/src/components/CourseReviewSearch.vue b/src/components/CourseReviewSearch.vue index b2ab944..54d50a4 100644 --- a/src/components/CourseReviewSearch.vue +++ b/src/components/CourseReviewSearch.vue @@ -74,7 +74,8 @@ import { ref, onMounted, watch } from "vue"; import { useRoute, useRouter } from "vue-router"; import { MagnifyingGlassIcon } from "@heroicons/vue/20/solid"; import "md-editor-v3/lib/style.css"; -import DOMPurify from "dompurify"; +import { sanitize } from "../utils/sanitize"; +import { useAuth } from "../composables/useAuth"; import ReviewPagination from "./ReviewPagination.vue"; const props = defineProps({ @@ -84,16 +85,7 @@ const props = defineProps({ }, }); -// Sanitize function using DOMPurify with enhanced security configuration -const sanitize = (html) => - DOMPurify.sanitize(html, { - FORBID_TAGS: ["img", "svg", "math", "script", "iframe"], - FORBID_ATTR: ["onerror", "onload", "onclick", "onmouseover", "onmouseout"], - USE_PROFILES: { html: true }, // Only allow HTML, no SVG or MathML - SAFE_FOR_TEMPLATES: true, // Protect against template injection - SANITIZE_DOM: true, // Protect against DOM clobbering - KEEP_CONTENT: false, // Remove content of forbidden tags - }); +// sanitize imported from utils/sanitize.js const route = useRoute(); const router = useRouter(); @@ -105,7 +97,7 @@ const reviewsFullCount = ref(0); const remaining = ref(0); const courseShortName = ref(""); const query = ref(""); -const isAuthenticated = ref(false); +const { isAuthenticated, checkAuthentication } = useAuth(); const fetchReviews = async () => { loading.value = true; @@ -174,20 +166,7 @@ onMounted(async () => { await fetchReviews(); }); -const checkAuthentication = async () => { - try { - const response = await fetch("/api/user/status/"); - if (response.ok) { - const data = await response.json(); - isAuthenticated.value = data.isAuthenticated; - } else { - isAuthenticated.value = false; - } - } catch (e) { - console.error("Error checking authentication:", e); - isAuthenticated.value = false; - } -}; +// authentication handled by useAuth composable const updateReviewData = (updateData) => { const reviewIndex = reviews.value.findIndex( diff --git a/src/components/Landing.vue b/src/components/Landing.vue index c0d84d4..4e8be47 100644 --- a/src/components/Landing.vue +++ b/src/components/Landing.vue @@ -137,15 +137,16 @@ import { ref, onMounted } from "vue"; import { useRouter } from "vue-router"; import { MagnifyingGlassIcon } from "@heroicons/vue/24/outline"; +import { useAuth } from "../composables/useAuth"; const router = useRouter(); const reviewCount = ref(0); -const isAuthenticated = ref(false); +const { isAuthenticated } = useAuth(); const searchQuery = ref(""); onMounted(async () => { await fetchLandingData(); - await checkAuthentication(); + // useAuth performs initial authentication check }); const fetchLandingData = async () => { @@ -161,17 +162,7 @@ const fetchLandingData = async () => { } }; -const checkAuthentication = async () => { - try { - const response = await fetch("/api/user/status/"); - if (response.ok) { - const data = await response.json(); - isAuthenticated.value = data.isAuthenticated; - } - } catch (error) { - console.error("Error checking authentication:", error); - } -}; +// Authentication checking handled via shared util const performSearch = () => { if (searchQuery.value.trim().length >= 2) { diff --git a/src/components/Login.vue b/src/components/Login.vue index e4a2684..54798d5 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -190,12 +190,15 @@ diff --git a/src/components/ReviewCard.vue b/src/components/ReviewCard.vue index 10b78e2..1ddce8e 100644 --- a/src/components/ReviewCard.vue +++ b/src/components/ReviewCard.vue @@ -130,6 +130,7 @@ diff --git a/src/components/CourseList.vue b/src/components/CourseList.vue index 0ae18d0..dfff648 100644 --- a/src/components/CourseList.vue +++ b/src/components/CourseList.vue @@ -371,7 +371,7 @@ From d60cf07f0602272e55b08a912f5d73e7b6cac93c Mon Sep 17 00:00:00 2001 From: arthurcai Date: Wed, 24 Sep 2025 01:08:31 +0800 Subject: [PATCH 28/87] refactor: reset password and signup --- src/components/AuthFlow.vue | 524 ++++++++++++++++++++++++++++++++ src/views/ResetPassword.vue | 578 ++---------------------------------- src/views/Signup.vue | 433 ++------------------------- 3 files changed, 565 insertions(+), 970 deletions(-) create mode 100644 src/components/AuthFlow.vue diff --git a/src/components/AuthFlow.vue b/src/components/AuthFlow.vue new file mode 100644 index 0000000..e1983a1 --- /dev/null +++ b/src/components/AuthFlow.vue @@ -0,0 +1,524 @@ + + + + + diff --git a/src/views/ResetPassword.vue b/src/views/ResetPassword.vue index cc4ffae..346cba7 100644 --- a/src/views/ResetPassword.vue +++ b/src/views/ResetPassword.vue @@ -17,577 +17,37 @@ back to sign in - back to sign in -

-
- -
-
-
- -
-
- - - - 1 -
-
-
Identity
-
Verify your identity
-
-
- - -
- - -
-
- - - - 2 -
-
-
Password
-
Set new password
-
-
-
-
-
- - - - - - - - -
-
-
- - - -
-

- Password reset successful! -

-

- Your password has been reset successfully. You can now sign in with - your new password. -

-
- - - Back to home - -
-
- {{ - redirectCountdown > 0 - ? `Redirecting to sign in page in ${redirectCountdown} seconds` - : "" - }} -
-
-
-
- - -
-
-
-
- - - -
-
-

- Password Reset Instructions -

-
-
    -
  • First, verify your identity through SJTU authentication
  • -
  • - After successful verification, you can set a new password -
  • -
  • - Ensure your new password is secure (contains letters and - numbers, at least 12 characters) -
  • -
  • Keep your new password safe after resetting
  • -
-
-
-
-
-
- - -
-
-
-
- - - -
-
-

- Security Reminder -

-
-

- For your account security, please set a strong password and - change it regularly. Do not share your password with others. -

-
-
-
-
-
- - -
-
-
-
- - - -
-
-

- {{ errorToastMessage }} -

-
-
-
- -
-
-
-
-
+ - + diff --git a/src/views/Signup.vue b/src/views/Signup.vue index fadf235..03e92e0 100644 --- a/src/views/Signup.vue +++ b/src/views/Signup.vue @@ -24,250 +24,31 @@ -
- -
-
-
- -
-
- - - - 1 -
-
-
Identity
-
Verify your identity
-
-
- - -
- - -
-
- - - - 2 -
-
-
Password
-
Set your password
-
-
-
-
-
- - - - - - - - -
-
-
- - - -
-

- Registration successful! -

-

- Welcome to CourseReview! Your account has been created successfully. -

-
- - - Back to home - -
-
- {{ - redirectCountdown > 0 - ? `Redirecting to login page in ${redirectCountdown} seconds` - : "" - }} -
-
-
-
- - -
-
-
-
- - - -
-
-

- {{ errorToastMessage }} -

-
-
-
- -
-
-
-
-
+ - + From 5820a7f9608acd82b7bdca9253a8a7c1cca51d69 Mon Sep 17 00:00:00 2001 From: arthurcai Date: Thu, 25 Sep 2025 22:28:07 +0800 Subject: [PATCH 29/87] refactor: refactor frontend --- src/App.vue | 1 - src/components/AuthFlow.vue | 257 ++------------- src/components/AuthInitiate.vue | 293 ++++++----------- src/components/CourseList.vue | 45 +-- src/components/Icon.vue | 179 ++++++++++ src/components/ReviewCard.vue | 11 +- src/components/ReviewPagination.vue | 26 +- src/components/SetPasswordForm.vue | 448 +++++--------------------- src/components/ToastNotifications.vue | 101 ++++++ src/components/Turnstile.vue | 38 +-- src/composables/useCourses.js | 5 - src/composables/useNotifications.js | 128 ++++++++ src/composables/useReviews.js | 2 +- src/layout/AppLayout.vue | 7 - src/main.js | 2 +- src/styles/MarkdownContent.css | 21 -- src/utils/auth.js | 123 +++++++ src/utils/validation.js | 104 ++++++ src/views/AuthCallback.vue | 41 +-- src/views/CourseDetail.vue | 34 +- src/views/CourseReviewSearch.vue | 13 +- src/views/Home.vue | 12 +- src/views/Login.vue | 58 +--- src/views/ResetPassword.vue | 79 +++-- src/views/Signup.vue | 122 +++---- 25 files changed, 975 insertions(+), 1175 deletions(-) create mode 100644 src/components/Icon.vue create mode 100644 src/components/ToastNotifications.vue create mode 100644 src/composables/useNotifications.js create mode 100644 src/utils/auth.js create mode 100644 src/utils/validation.js diff --git a/src/App.vue b/src/App.vue index f180dde..6abacb4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -9,7 +9,6 @@ import AppLayout from "./layout/AppLayout.vue"; diff --git a/src/components/AuthInitiate.vue b/src/components/AuthInitiate.vue index 2f50ea4..cee1fc3 100644 --- a/src/components/AuthInitiate.vue +++ b/src/components/AuthInitiate.vue @@ -1,14 +1,10 @@