diff --git a/src/lib/api.ts b/src/lib/api.ts index 1d2dabb..0921860 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -185,6 +185,16 @@ export const api = { }), organizations: () => request('/users/me/organizations'), + + changePassword: (currentPassword: string, newPassword: string, newPasswordConfirm: string) => + request<{ message: string }>('/users/me/password', { + method: 'POST', + body: JSON.stringify({ + current_password: currentPassword, + new_password: newPassword, + new_password_confirm: newPasswordConfirm, + }), + }), }, }; diff --git a/src/pages/user/SecurityPage.tsx b/src/pages/user/SecurityPage.tsx index 218d0bb..7fbf7c6 100644 --- a/src/pages/user/SecurityPage.tsx +++ b/src/pages/user/SecurityPage.tsx @@ -1,15 +1,27 @@ import { useState } from "react"; -import { Lock, Fingerprint, Smartphone, Shield, Plus, CheckCircle } from "lucide-react"; +import { Lock, Fingerprint, Smartphone, Shield, Plus, CheckCircle, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { AddPasskeyWizard } from "@/components/security/AddPasskeyWizard"; +import { PasswordStrengthMeter, isPasswordValid } from "@/components/auth/PasswordStrengthMeter"; +import { api, ApiError } from "@/lib/api"; +import { useToast } from "@/hooks/use-toast"; export default function SecurityPage() { const [showPasswordForm, setShowPasswordForm] = useState(false); const [showAddPasskey, setShowAddPasskey] = useState(false); + + // Password form state + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [isChangingPassword, setIsChangingPassword] = useState(false); + const [passwordError, setPasswordError] = useState(null); + + const { toast } = useToast(); // Mock security data const security = { @@ -27,6 +39,68 @@ export default function SecurityPage() { }, }; + const resetPasswordForm = () => { + setCurrentPassword(""); + setNewPassword(""); + setConfirmPassword(""); + setPasswordError(null); + }; + + const handlePasswordChange = async () => { + setPasswordError(null); + + // Client-side validation + if (!currentPassword) { + setPasswordError("Current password is required"); + return; + } + + if (!isPasswordValid(newPassword)) { + setPasswordError("New password does not meet strength requirements"); + return; + } + + if (newPassword !== confirmPassword) { + setPasswordError("Passwords do not match"); + return; + } + + setIsChangingPassword(true); + + try { + await api.users.changePassword(currentPassword, newPassword, confirmPassword); + + toast({ + title: "Password updated", + description: "Your password has been changed successfully.", + }); + + resetPasswordForm(); + setShowPasswordForm(false); + } catch (err) { + console.error("Password change failed:", err); + + if (err instanceof ApiError) { + if (err.type === "INVALID_CREDENTIALS" || err.code === 401) { + setPasswordError("Current password is incorrect"); + } else if (err.type === "VALIDATION_ERROR") { + setPasswordError(err.message); + } else { + setPasswordError(err.message); + } + } else { + setPasswordError("An unexpected error occurred. Please try again."); + } + } finally { + setIsChangingPassword(false); + } + }; + + const handleCancelPasswordChange = () => { + resetPasswordForm(); + setShowPasswordForm(false); + }; + return (
@@ -74,24 +148,58 @@ export default function SecurityPage() { {showPasswordForm && ( + {passwordError && ( +
+ {passwordError} +
+ )}
- + setCurrentPassword(e.target.value)} + disabled={isChangingPassword} + />
- -

- Minimum {security.policyRequirements.minPasswordLength} characters required by organization -

+ setNewPassword(e.target.value)} + disabled={isChangingPassword} + /> +
- + setConfirmPassword(e.target.value)} + disabled={isChangingPassword} + /> + {confirmPassword && newPassword !== confirmPassword && ( +

Passwords do not match

+ )}
- - +