revert passkey support and restore pre-passkey state

Co-authored-by: ASOwnerYT <[email protected]>
This commit is contained in:
v0
2026-03-22 23:38:40 +00:00
co-authored by ASOwnerYT
parent 1f5631552c
commit 60100e9c52
5 changed files with 24 additions and 338 deletions
+1 -51
View File
@@ -7,8 +7,7 @@ import { Label } from '@/components/ui/label'
import Link from 'next/link' import Link from 'next/link'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useState } from 'react' import { useState } from 'react'
import { Mail, Lock, Loader2, ArrowLeft, Key } from 'lucide-react' import { Mail, Lock, Loader2, ArrowLeft } from 'lucide-react'
import { isPasskeySupported, authenticateWithPasskey } from '@/lib/passkeys'
export default function LoginPage() { export default function LoginPage() {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
@@ -16,8 +15,6 @@ export default function LoginPage() {
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [isGoogleLoading, setIsGoogleLoading] = useState(false) const [isGoogleLoading, setIsGoogleLoading] = useState(false)
const [isPasskeyLoading, setIsPasskeyLoading] = useState(false)
const [passkeySupported] = useState(() => isPasskeySupported())
const router = useRouter() const router = useRouter()
const handleLogin = async (e: React.FormEvent) => { const handleLogin = async (e: React.FormEvent) => {
@@ -59,33 +56,6 @@ export default function LoginPage() {
} }
} }
const handlePasskeyLogin = async () => {
const supabase = createClient()
setIsPasskeyLoading(true)
setError(null)
try {
const credential = await authenticateWithPasskey()
if (!credential) {
throw new Error('Passkey authentication failed')
}
// For now, passkeys are registered as a factor in Supabase
// This is a placeholder - in production, you'd verify the credential server-side
console.log('[v0] Passkey authentication initiated:', credential.id)
// Since this is invite-only, users would have pre-registered their passkeys
// and we'd verify them here. For now, show success.
setError('Passkey authentication requires server-side verification setup')
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Passkey authentication failed'
setError(message)
} finally {
setIsPasskeyLoading(false)
}
}
return ( return (
<div className="min-h-screen bg-background flex items-center justify-center p-4"> <div className="min-h-screen bg-background flex items-center justify-center p-4">
{/* Background elements */} {/* Background elements */}
@@ -119,26 +89,6 @@ export default function LoginPage() {
</p> </p>
</div> </div>
{/* Passkey Button */}
{passkeySupported && (
<Button
type="button"
variant="outline"
className="w-full h-12 mb-4 relative"
onClick={handlePasskeyLogin}
disabled={isPasskeyLoading}
>
{isPasskeyLoading ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
<>
<Key className="w-5 h-5 mr-2" />
Sign in with passkey
</>
)}
</Button>
)}
{/* Google OAuth Button */} {/* Google OAuth Button */}
<Button <Button
type="button" type="button"
+23 -32
View File
@@ -3,7 +3,6 @@ import { redirect } from 'next/navigation'
import Link from 'next/link' import Link from 'next/link'
import { LogOut, User, ArrowLeft, Cloud, Globe, Wrench } from 'lucide-react' import { LogOut, User, ArrowLeft, Cloud, Globe, Wrench } from 'lucide-react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { PasskeyManager } from '@/components/passkey-manager'
async function signOut() { async function signOut() {
'use server' 'use server'
@@ -103,38 +102,30 @@ export default async function DashboardPage() {
</div> </div>
</div> </div>
{/* Account Settings */} {/* Account info */}
<div className="grid lg:grid-cols-2 gap-6"> <div className="bg-card border border-border rounded-2xl p-6">
{/* Account info */} <h2 className="text-xl font-bold mb-4">Account Information</h2>
<div className="bg-card border border-border rounded-2xl p-6"> <div className="space-y-3">
<h2 className="text-xl font-bold mb-4">Account Information</h2> <div className="flex justify-between py-2 border-b border-border">
<div className="space-y-3"> <span className="text-muted-foreground">Email</span>
<div className="flex justify-between py-2 border-b border-border"> <span className="font-medium">{user.email}</span>
<span className="text-muted-foreground">Email</span> </div>
<span className="font-medium">{user.email}</span> <div className="flex justify-between py-2 border-b border-border">
</div> <span className="text-muted-foreground">Account created</span>
<div className="flex justify-between py-2 border-b border-border"> <span className="font-medium">
<span className="text-muted-foreground">Account created</span> {new Date(user.created_at).toLocaleDateString('en-NZ', {
<span className="font-medium"> day: 'numeric',
{new Date(user.created_at).toLocaleDateString('en-NZ', { month: 'long',
day: 'numeric', year: 'numeric'
month: 'long', })}
year: 'numeric' </span>
})} </div>
</span> <div className="flex justify-between py-2">
</div> <span className="text-muted-foreground">Auth provider</span>
<div className="flex justify-between py-2"> <span className="font-medium capitalize">
<span className="text-muted-foreground">Auth provider</span> {user.app_metadata?.provider || 'Email'}
<span className="font-medium capitalize"> </span>
{user.app_metadata?.provider || 'Email'}
</span>
</div>
</div> </div>
</div>
{/* Passkey Management */}
<div className="bg-card border border-border rounded-2xl p-6">
<PasskeyManager />
</div> </div>
</div> </div>
</main> </main>
-137
View File
@@ -1,137 +0,0 @@
'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Key, Plus, Trash2, Loader2, AlertCircle } from 'lucide-react'
import { registerPasskey, isPasskeySupported } from '@/lib/passkeys'
export function PasskeyManager() {
const [isRegistering, setIsRegistering] = useState(false)
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState<string | null>(null)
const [passkeys, setPasskeys] = useState<Array<{ id: string; name: string; createdAt: string }>>([])
const passkeySupported = isPasskeySupported()
const handleRegisterPasskey = async () => {
if (!passkeySupported) {
setError('Your browser does not support passkeys')
return
}
setIsRegistering(true)
setError(null)
setSuccess(null)
try {
const credential = await registerPasskey('', 'My Passkey')
// In a real implementation, you'd send the credential to your server
// and store it securely associated with the user
const newPasskey = {
id: credential.id,
name: 'My Passkey',
createdAt: new Date().toLocaleDateString(),
}
setPasskeys([...passkeys, newPasskey])
setSuccess('Passkey registered successfully')
// Clear success message after 5 seconds
setTimeout(() => setSuccess(null), 5000)
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Failed to register passkey'
setError(message)
} finally {
setIsRegistering(false)
}
}
const handleDeletePasskey = (id: string) => {
setPasskeys(passkeys.filter(pk => pk.id !== id))
setSuccess('Passkey removed')
setTimeout(() => setSuccess(null), 5000)
}
if (!passkeySupported) {
return (
<div className="p-6 bg-card border border-border rounded-xl">
<div className="flex items-center gap-3 text-muted-foreground mb-2">
<AlertCircle className="w-5 h-5" />
<h3 className="font-semibold">Passkey Support Not Available</h3>
</div>
<p className="text-sm text-muted-foreground">
Your browser does not support passkeys. Please use a modern browser like Chrome, Safari, or Edge.
</p>
</div>
)
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Key className="w-5 h-5 text-primary" />
<h3 className="font-semibold">Passkeys</h3>
</div>
<Button
size="sm"
onClick={handleRegisterPasskey}
disabled={isRegistering}
className="bg-primary hover:bg-primary/90"
>
{isRegistering ? (
<Loader2 className="w-4 h-4 animate-spin mr-2" />
) : (
<Plus className="w-4 h-4 mr-2" />
)}
Add Passkey
</Button>
</div>
{error && (
<div className="p-3 bg-destructive/10 border border-destructive/50 rounded-lg">
<p className="text-sm text-destructive">{error}</p>
</div>
)}
{success && (
<div className="p-3 bg-primary/10 border border-primary/50 rounded-lg">
<p className="text-sm text-primary">{success}</p>
</div>
)}
{passkeys.length === 0 ? (
<div className="p-6 bg-card border border-border rounded-xl text-center">
<Key className="w-12 h-12 text-muted-foreground mx-auto mb-3 opacity-50" />
<p className="text-muted-foreground text-sm">
No passkeys registered yet. Add one to enable passwordless sign-in.
</p>
</div>
) : (
<div className="space-y-2">
{passkeys.map((passkey) => (
<div
key={passkey.id}
className="flex items-center justify-between p-4 bg-card border border-border rounded-lg"
>
<div>
<p className="font-medium">{passkey.name}</p>
<p className="text-xs text-muted-foreground">
Added on {passkey.createdAt}
</p>
</div>
<Button
size="sm"
variant="ghost"
onClick={() => handleDeletePasskey(passkey.id)}
className="text-destructive hover:text-destructive/90"
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
))}
</div>
)}
</div>
)
}
-83
View File
@@ -1,83 +0,0 @@
'use client'
import { createClient } from '@/lib/supabase/client'
export async function registerPasskey(userId: string, displayName: string) {
const supabase = createClient()
if (!window.PublicKeyCredential) {
throw new Error('Passkeys are not supported in your browser')
}
try {
// Get registration options from Supabase
const { data, error } = await supabase.auth.signInWithEnrollFactors({
factorType: 'totp',
})
if (error) throw error
// For now, we'll use a simplified approach with browser's WebAuthn API
// Create a passkey credential
const credential = await navigator.credentials.create({
publicKey: {
challenge: new Uint8Array(32),
rp: {
name: 'Cloudrite',
id: window.location.hostname,
},
user: {
id: new TextEncoder().encode(userId),
name: userId,
displayName: displayName,
},
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
timeout: 60000,
attestation: 'direct',
},
}) as PublicKeyCredential | null
if (!credential) {
throw new Error('Failed to create passkey')
}
return credential
} catch (error) {
console.error('[v0] Passkey registration failed:', error)
throw error
}
}
export async function authenticateWithPasskey() {
if (!window.PublicKeyCredential) {
throw new Error('Passkeys are not supported in your browser')
}
try {
const assertion = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array(32),
timeout: 60000,
userVerification: 'preferred',
},
}) as PublicKeyCredential | null
if (!assertion) {
throw new Error('Passkey authentication cancelled or failed')
}
return assertion
} catch (error) {
console.error('[v0] Passkey authentication failed:', error)
throw error
}
}
export function isPasskeySupported(): boolean {
return !!(
window.PublicKeyCredential &&
navigator.credentials &&
navigator.credentials.create &&
navigator.credentials.get
)
}
-35
View File
@@ -1,35 +0,0 @@
-- Create passkeys table for WebAuthn credential storage
CREATE TABLE IF NOT EXISTS public.passkeys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
credential_id TEXT NOT NULL UNIQUE,
public_key TEXT NOT NULL,
sign_count INTEGER NOT NULL DEFAULT 0,
transports TEXT[] DEFAULT ARRAY[]::TEXT[],
backup_eligible BOOLEAN DEFAULT FALSE,
backup_state BOOLEAN DEFAULT FALSE,
aaguid UUID,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_used TIMESTAMP WITH TIME ZONE,
friendly_name TEXT
);
-- Enable RLS on passkeys table
ALTER TABLE public.passkeys ENABLE ROW LEVEL SECURITY;
-- Create RLS policies for passkeys
CREATE POLICY "Users can view their own passkeys" ON public.passkeys
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert their own passkeys" ON public.passkeys
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can delete their own passkeys" ON public.passkeys
FOR DELETE USING (auth.uid() = user_id);
CREATE POLICY "Users can update their own passkeys" ON public.passkeys
FOR UPDATE USING (auth.uid() = user_id);
-- Create index for faster credential lookups
CREATE INDEX idx_passkeys_credential_id ON public.passkeys(credential_id);
CREATE INDEX idx_passkeys_user_id ON public.passkeys(user_id);