Skip to content

Commit e562d38

Browse files
committed
Enhance authentication pages with security and UX improvements
## Security Improvements - Fix user enumeration vulnerability in ForgotPassword (shows generic message) - Add OAuth health check before redirect to prevent browser errors - Add autocomplete attributes for password manager support ## UX Enhancements - Add real-time password match indicator to Signup and ResetPassword - Add password strength meter to ResetPassword page - Remove confusing social OAuth buttons from ForgotPassword page - Remove debug console.logs from production code ## Code Quality - Delete unused Login.tsx dead code (266 lines) - Create shared validation utilities (email, password) - Extract duplicate validation logic to utils/validation.ts - Reduce code duplication across auth pages ## Changes - **Created**: utils/validation.ts - Shared validation functions - **Deleted**: Login.tsx - Unused demo authentication (266 lines) - **Updated**: LoginForm.tsx - OAuth error handling, autocomplete, removed logs - **Updated**: Signup.tsx - Password match indicator, shared validation - **Updated**: ForgotPassword.tsx - Security fix, removed social buttons - **Updated**: ResetPassword.tsx - Password strength & match indicators
1 parent 43ebd96 commit e562d38

6 files changed

Lines changed: 186 additions & 448 deletions

File tree

packages/web/src/pages/ForgotPassword.tsx

Lines changed: 25 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
import { useState } from 'react';
22
import { Link } from 'react-router-dom';
3-
import { Mail, ArrowLeft, CheckCircle, XCircle, Github } from 'lucide-react';
3+
import { Mail, ArrowLeft, CheckCircle, XCircle } from 'lucide-react';
44
import { TlsStatusIndicator } from '../components/TlsStatusIndicator';
5+
import { isValidEmail } from '../utils/validation';
56

67
export function ForgotPassword() {
78
const [email, setEmail] = useState('');
89
const [loading, setLoading] = useState(false);
910
const [resetSent, setResetSent] = useState(false);
10-
const [userExists, setUserExists] = useState<boolean | null>(null);
1111
const [error, setError] = useState('');
1212
const [emailValid, setEmailValid] = useState<boolean | null>(null);
1313

14-
const isValidEmail = (email: string): boolean => {
15-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
16-
return emailRegex.test(email);
17-
};
18-
1914
const handleSubmit = async (e: React.FormEvent) => {
2015
e.preventDefault();
2116

@@ -24,8 +19,7 @@ export function ForgotPassword() {
2419
return;
2520
}
2621

27-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
28-
if (!emailRegex.test(email)) {
22+
if (!isValidEmail(email)) {
2923
setError('Please enter a valid email address');
3024
return;
3125
}
@@ -47,12 +41,10 @@ export function ForgotPassword() {
4741

4842
if (response.ok) {
4943
setResetSent(true);
50-
setUserExists(data.userExists);
5144
} else {
5245
setError(data.error || 'Failed to send reset link');
5346
}
5447
} catch (error) {
55-
console.error('Forgot password error:', error);
5648
setError('Failed to send reset link. Please try again.');
5749
} finally {
5850
setLoading(false);
@@ -76,119 +68,29 @@ export function ForgotPassword() {
7668

7769
{/* Reset Form */}
7870
<div className="bg-gray-800/50 backdrop-blur-xl border border-gray-700/50 rounded-2xl p-8 space-y-5 shadow-2xl animate-in fade-in slide-in-from-bottom-4 duration-700">
79-
{/* Social Login Buttons */}
80-
<div className="grid grid-cols-3 gap-3">
81-
<button
82-
type="button"
83-
onClick={() => window.location.href = `${import.meta.env.VITE_API_URL || 'https://localhost:4128'}/auth/google`}
84-
className="group relative flex items-center justify-center p-3 bg-gray-700/50 hover:bg-gray-600/80 backdrop-blur-sm border border-gray-600/50 hover:border-teal-500 hover:shadow-lg hover:shadow-teal-500/30 rounded-lg transition-all duration-200"
85-
aria-label="Sign in with Google"
86-
>
87-
<svg className="h-5 w-5 transition-all duration-200 group-hover:scale-110" viewBox="0 0 24 24">
88-
<path fill="#EA4335" d="M5.266 9.765A7.077 7.077 0 0 1 12 4.909c1.69 0 3.218.6 4.418 1.582L19.91 3C17.782 1.145 15.055 0 12 0 7.27 0 3.198 2.698 1.24 6.65l4.026 3.115Z"/>
89-
<path fill="#34A853" d="M16.04 18.013c-1.09.703-2.474 1.078-4.04 1.078a7.077 7.077 0 0 1-6.723-4.823l-4.04 3.067A11.965 11.965 0 0 0 12 24c2.933 0 5.735-1.043 7.834-3l-3.793-2.987Z"/>
90-
<path fill="#4A90E2" d="M19.834 21c2.195-2.048 3.62-5.096 3.62-9 0-.71-.109-1.473-.272-2.182H12v4.637h6.436c-.317 1.559-1.17 2.766-2.395 3.558L19.834 21Z"/>
91-
<path fill="#FBBC05" d="M5.277 14.268A7.12 7.12 0 0 1 4.909 12c0-.782.125-1.533.357-2.235L1.24 6.65A11.934 11.934 0 0 0 0 12c0 1.92.445 3.73 1.237 5.335l4.04-3.067Z"/>
92-
</svg>
93-
<span className="absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap text-teal-400 text-xs opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10">
94-
Sign in with Google
95-
</span>
96-
</button>
97-
98-
<button
99-
type="button"
100-
onClick={() => window.location.href = `${import.meta.env.VITE_API_URL || 'https://localhost:4128'}/auth/linkedin`}
101-
className="group relative flex items-center justify-center p-3 bg-gray-700/50 hover:bg-gray-600/80 backdrop-blur-sm border border-gray-600/50 hover:border-teal-500 hover:shadow-lg hover:shadow-teal-500/30 rounded-lg transition-all duration-200"
102-
aria-label="Sign in with LinkedIn"
103-
>
104-
<svg className="h-5 w-5 transition-all duration-200 group-hover:scale-110" viewBox="0 0 24 24" fill="#60A5FA">
105-
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
106-
</svg>
107-
<span className="absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap text-teal-400 text-xs opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10">
108-
Sign in with LinkedIn
109-
</span>
110-
</button>
111-
112-
<button
113-
type="button"
114-
onClick={() => window.location.href = `${import.meta.env.VITE_API_URL || 'https://localhost:4128'}/auth/github`}
115-
className="group relative flex items-center justify-center p-3 bg-gray-700/50 hover:bg-gray-600/80 backdrop-blur-sm border border-gray-600/50 hover:border-teal-500 hover:shadow-lg hover:shadow-teal-500/30 rounded-lg transition-all duration-200"
116-
aria-label="Sign in with GitHub"
117-
>
118-
<Github className="h-5 w-5 text-gray-300 transition-all duration-200 group-hover:scale-110" />
119-
<span className="absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap text-teal-400 text-xs opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10">
120-
Sign in with GitHub
121-
</span>
122-
</button>
123-
</div>
124-
125-
<div className="relative">
126-
<div className="absolute inset-0 flex items-center">
127-
<div className="w-full border-t border-gray-600" />
128-
</div>
129-
<div className="relative flex justify-center text-sm">
130-
<span className="px-2 bg-gray-800 text-gray-400">Or reset your password</span>
131-
</div>
132-
</div>
133-
13471
{resetSent ? (
135-
userExists ? (
136-
<div className="p-6 bg-teal-900/20 border border-teal-500/30 rounded-xl">
137-
<div className="flex items-center justify-center mb-3">
138-
<CheckCircle className="h-12 w-12 text-teal-400" />
139-
</div>
140-
<h3 className="text-lg font-semibold text-teal-300 text-center mb-2">Check Your Email!</h3>
141-
<p className="text-sm text-teal-200/80 text-center mb-4">
142-
We've sent a password reset link to <strong>{email}</strong>
143-
</p>
144-
<p className="text-xs text-teal-300/60 text-center mb-4">
145-
Click the link in the email to reset your password. The link expires in 1 hour.
146-
</p>
147-
<button
148-
type="button"
149-
onClick={() => {
150-
setResetSent(false);
151-
setEmail('');
152-
setUserExists(null);
153-
}}
154-
className="mt-2 w-full text-sm text-teal-400 hover:text-teal-300 transition-colors"
155-
>
156-
Try a different email
157-
</button>
72+
<div className="p-6 bg-teal-900/20 border border-teal-500/30 rounded-xl">
73+
<div className="flex items-center justify-center mb-3">
74+
<CheckCircle className="h-12 w-12 text-teal-400" />
15875
</div>
159-
) : (
160-
<div className="p-6 bg-red-900/20 border border-red-500/30 rounded-xl">
161-
<div className="flex items-center justify-center mb-3">
162-
<XCircle className="h-12 w-12 text-red-400" />
163-
</div>
164-
<h3 className="text-lg font-semibold text-red-300 text-center mb-2">Email Not Found</h3>
165-
<p className="text-sm text-red-200/80 text-center mb-4">
166-
The email <strong>{email}</strong> is not registered in our system.
167-
</p>
168-
<p className="text-xs text-red-300/60 text-center mb-4">
169-
Please check the email address or create a new account.
170-
</p>
171-
<div className="space-y-2">
172-
<button
173-
type="button"
174-
onClick={() => {
175-
setResetSent(false);
176-
setEmail('');
177-
setUserExists(null);
178-
}}
179-
className="mt-2 w-full text-sm text-red-400 hover:text-red-300 transition-colors"
180-
>
181-
Try a different email
182-
</button>
183-
<Link
184-
to="/signup"
185-
className="block w-full text-center text-sm text-red-400 hover:text-red-300 transition-colors"
186-
>
187-
Create a new account
188-
</Link>
189-
</div>
190-
</div>
191-
)
76+
<h3 className="text-lg font-semibold text-teal-300 text-center mb-2">Check Your Email!</h3>
77+
<p className="text-sm text-teal-200/80 text-center mb-4">
78+
If an account exists for <strong>{email}</strong>, you'll receive a password reset link shortly.
79+
</p>
80+
<p className="text-xs text-teal-300/60 text-center mb-4">
81+
Click the link in the email to reset your password. The link expires in 1 hour. If you don't receive an email, please check your spam folder or verify the email address.
82+
</p>
83+
<button
84+
type="button"
85+
onClick={() => {
86+
setResetSent(false);
87+
setEmail('');
88+
}}
89+
className="mt-2 w-full text-sm text-teal-400 hover:text-teal-300 transition-colors"
90+
>
91+
Try a different email
92+
</button>
93+
</div>
19294
) : (
19395
<form onSubmit={handleSubmit} className="space-y-5">
19496
{/* Email Field */}
@@ -215,6 +117,7 @@ export function ForgotPassword() {
215117
setEmailValid(isValidEmail(value));
216118
}
217119
}}
120+
autoComplete="email"
218121
autoFocus
219122
className={`w-full pl-10 py-3 bg-gray-700/50 backdrop-blur-sm border rounded-xl text-gray-100 focus:outline-none focus:ring-2 transition-all ${
220123
emailValid === false

0 commit comments

Comments
 (0)