feat: Allow OAuth users to add passwords

This commit is contained in:
gpt-engineer-app[bot]
2025-10-14 14:45:51 +00:00
parent 6430777dc0
commit 8594291ad2
2 changed files with 69 additions and 21 deletions

View File

@@ -19,14 +19,16 @@ interface PasswordSetupDialogProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
onSuccess: () => void; onSuccess: () => void;
provider: OAuthProvider; provider?: OAuthProvider;
mode?: 'standalone' | 'disconnect';
} }
export function PasswordSetupDialog({ export function PasswordSetupDialog({
open, open,
onOpenChange, onOpenChange,
onSuccess, onSuccess,
provider provider,
mode = 'standalone'
}: PasswordSetupDialogProps) { }: PasswordSetupDialogProps) {
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState('');
@@ -73,8 +75,17 @@ export function PasswordSetupDialog({
<DialogTitle>Set Up Password</DialogTitle> <DialogTitle>Set Up Password</DialogTitle>
</div> </div>
<DialogDescription> <DialogDescription>
You need to set a password before disconnecting your {provider} account. {mode === 'disconnect' && provider ? (
This ensures you can still access your account. <>
You need to set a password before disconnecting your {provider} account.
This ensures you can still access your account.
</>
) : (
<>
Add a password to your account for increased security. You'll be able to
sign in using either your password or your connected social accounts.
</>
)}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>

View File

@@ -26,6 +26,8 @@ export function SecurityTab() {
const [loadingIdentities, setLoadingIdentities] = useState(true); const [loadingIdentities, setLoadingIdentities] = useState(true);
const [disconnectingProvider, setDisconnectingProvider] = useState<OAuthProvider | null>(null); const [disconnectingProvider, setDisconnectingProvider] = useState<OAuthProvider | null>(null);
const [passwordSetupProvider, setPasswordSetupProvider] = useState<OAuthProvider | null>(null); const [passwordSetupProvider, setPasswordSetupProvider] = useState<OAuthProvider | null>(null);
const [hasPassword, setHasPassword] = useState(false);
const [addPasswordMode, setAddPasswordMode] = useState<'standalone' | 'disconnect'>('standalone');
// Load user identities on mount // Load user identities on mount
useEffect(() => { useEffect(() => {
@@ -33,10 +35,24 @@ export function SecurityTab() {
}, []); }, []);
const loadIdentities = async () => { const loadIdentities = async () => {
setLoadingIdentities(true); try {
const fetchedIdentities = await getUserIdentities(); setLoadingIdentities(true);
setIdentities(fetchedIdentities); const fetchedIdentities = await getUserIdentities();
setLoadingIdentities(false); setIdentities(fetchedIdentities);
// Check if user has email/password auth
const hasEmailProvider = fetchedIdentities.some(i => i.provider === 'email');
setHasPassword(hasEmailProvider);
} catch (error) {
console.error('Failed to load identities:', error);
toast({
title: 'Error',
description: 'Failed to load connected accounts',
variant: 'destructive',
});
} finally {
setLoadingIdentities(false);
}
}; };
const handleSocialLogin = async (provider: OAuthProvider) => { const handleSocialLogin = async (provider: OAuthProvider) => {
@@ -63,6 +79,7 @@ export function SecurityTab() {
if (!safetyCheck.canDisconnect) { if (!safetyCheck.canDisconnect) {
if (safetyCheck.reason === 'no_password_backup') { if (safetyCheck.reason === 'no_password_backup') {
// Show password setup dialog // Show password setup dialog
setAddPasswordMode('disconnect');
setPasswordSetupProvider(provider); setPasswordSetupProvider(provider);
toast({ toast({
title: "Password Required", title: "Password Required",
@@ -103,21 +120,30 @@ export function SecurityTab() {
}; };
const handlePasswordSetupSuccess = async () => { const handlePasswordSetupSuccess = async () => {
toast({
title: "Password Set",
description: "You can now disconnect your social login."
});
// Refresh identities to show email provider // Refresh identities to show email provider
await loadIdentities(); await loadIdentities();
// Proceed with disconnect if (addPasswordMode === 'disconnect' && passwordSetupProvider) {
if (passwordSetupProvider) { toast({
title: "Password Set",
description: "You can now disconnect your social login."
});
await handleUnlinkSocial(passwordSetupProvider); await handleUnlinkSocial(passwordSetupProvider);
setPasswordSetupProvider(null); setPasswordSetupProvider(null);
} else {
toast({
title: 'Password Added',
description: 'You can now sign in using your email and password.',
});
setPasswordSetupProvider(null);
} }
}; };
const handleAddPassword = () => {
setAddPasswordMode('standalone');
setPasswordSetupProvider('google' as OAuthProvider);
};
// Get connected accounts with identity data // Get connected accounts with identity data
const connectedAccounts = [ const connectedAccounts = [
{ {
@@ -150,27 +176,38 @@ export function SecurityTab() {
onOpenChange={(open) => !open && setPasswordSetupProvider(null)} onOpenChange={(open) => !open && setPasswordSetupProvider(null)}
onSuccess={handlePasswordSetupSuccess} onSuccess={handlePasswordSetupSuccess}
provider={passwordSetupProvider} provider={passwordSetupProvider}
mode={addPasswordMode}
/> />
)} )}
<div className="space-y-8"> <div className="space-y-8">
{/* Change Password */} {/* Password Section - Conditional based on auth method */}
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Key className="w-5 h-5" /> <Key className="w-5 h-5" />
<h3 className="text-lg font-medium">Change Password</h3> <h3 className="text-lg font-medium">{hasPassword ? 'Change Password' : 'Add Password'}</h3>
</div> </div>
<Card> <Card>
<CardHeader> <CardHeader>
<CardDescription> <CardDescription>
Update your password to keep your account secure. {user?.identities?.some(i => i.provider === 'totp') && 'Two-factor authentication will be required.'} {hasPassword ? (
<>Update your password to keep your account secure. {user?.identities?.some(i => i.provider === 'totp') && 'Two-factor authentication will be required.'}</>
) : (
'Add password authentication to your account for increased security and backup access.'
)}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Button onClick={() => setPasswordDialogOpen(true)}> {hasPassword ? (
Change Password <Button onClick={() => setPasswordDialogOpen(true)}>
</Button> Change Password
</Button>
) : (
<Button onClick={handleAddPassword}>
Add Password
</Button>
)}
</CardContent> </CardContent>
</Card> </Card>
</div> </div>