All files / src/screens PasskeyRegister.tsx

27.27% Statements 6/22
37.5% Branches 6/16
50% Functions 1/2
27.27% Lines 6/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110                    3x 3x 3x 3x   3x                                                                       3x                                                                                                                    
import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { api } from '../api/client'
import {
  prepareRegistrationOptions,
  serializeRegistrationCredential,
} from '../lib/webauthn'
import { useAuth } from '../contexts/AuthContext'
 
export default function PasskeyRegister() {
  const navigate = useNavigate()
  const { refreshSession } = useAuth()
  const [error, setError] = useState('')
  const [submitting, setSubmitting] = useState(false)
 
  const webauthnSupported = typeof window !== 'undefined' && !!window.PublicKeyCredential
 
  async function handleRegister() {
    setError('')
    setSubmitting(true)
 
    try {
      const options = await api.passkey.registerOptions()
      const publicKeyOptions = prepareRegistrationOptions(options)
 
      const credential = await navigator.credentials.create({
        publicKey: publicKeyOptions,
      })
 
      if (!credential) {
        setError('Registration was cancelled.')
        return
      }
 
      await api.passkey.registerVerify(
        serializeRegistrationCredential(credential as PublicKeyCredential)
      )
 
      await refreshSession()
      navigate('/home')
    } catch (err) {
      if (err instanceof DOMException && err.name === 'NotAllowedError') {
        setError('Registration was cancelled.')
      } else {
        setError(err instanceof Error ? err.message : 'Registration failed')
      }
    } finally {
      setSubmitting(false)
    }
  }
 
  return (
    <div className="flex min-h-screen items-center justify-center bg-background p-4">
      <div className="w-full max-w-sm space-y-6">
        <div className="text-center">
          <h1 className="text-3xl font-bold text-foreground">Cookie</h1>
          <p className="mt-1 text-sm text-muted-foreground">
            Create your account with a passkey
          </p>
        </div>
 
        {error && (
          <div role="alert" className="rounded-md bg-red-50 p-3 text-sm text-red-800 dark:bg-red-900/20 dark:text-red-300">
            {error}
          </div>
        )}
 
        <div className="space-y-4">
          {webauthnSupported ? (
            <>
              <p className="text-center text-sm text-muted-foreground">
                No username, email, or password needed. Your device's biometrics (Face ID, Touch ID, or
                PIN) will be your login.
              </p>
 
              <button
                onClick={handleRegister}
                disabled={submitting}
                className="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow-sm hover:bg-primary/90 disabled:opacity-50"
              >
                {submitting ? 'Creating account...' : 'Create Account'}
              </button>
            </>
          ) : (
            <p className="text-center text-sm text-muted-foreground">
              Your browser does not support passkeys. Please use a modern browser or pair this device
              using a code from another device.
            </p>
          )}
        </div>
 
        <div className="text-center text-sm text-muted-foreground">
          <p>
            Already have an account?{' '}
            <Link to="/login" className="font-medium text-primary hover:underline">
              Sign In
            </Link>
          </p>
        </div>
 
        <div className="text-center">
          <a href="/privacy/" className="text-xs text-muted-foreground hover:text-foreground hover:underline">
            Privacy Policy
          </a>
        </div>
      </div>
    </div>
  )
}
 
← Back to Dashboard