diff --git a/lib/auth.ts b/lib/auth.ts index fc8a38e..f7d9968 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -2,13 +2,15 @@ * EncryptID Auth Store for rfunds-online * * Provides passkey-based identity for space creation and editing. - * Uses Zustand with localStorage persistence. + * Uses Zustand with localStorage persistence, delegates WebAuthn ceremony to @encryptid/sdk. */ import { create } from 'zustand' import { persist } from 'zustand/middleware' +import { EncryptIDClient } from '@encryptid/sdk/client' const ENCRYPTID_SERVER = process.env.NEXT_PUBLIC_ENCRYPTID_SERVER_URL || 'https://encryptid.jeffemmett.com' +const client = new EncryptIDClient(ENCRYPTID_SERVER) interface AuthState { isAuthenticated: boolean @@ -22,17 +24,6 @@ interface AuthState { logout: () => void } -function toBase64url(buffer: ArrayBuffer): string { - return btoa(String.fromCharCode(...new Uint8Array(buffer))) - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/=+$/, '') -} - -function fromBase64url(str: string): Uint8Array { - return Uint8Array.from(atob(str.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)) -} - export const useAuthStore = create()( persist( (set) => ({ @@ -45,48 +36,8 @@ export const useAuthStore = create()( login: async () => { set({ loading: true }) try { - const startRes = await fetch(`${ENCRYPTID_SERVER}/api/auth/start`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({}), - }) - const { options } = await startRes.json() - - const publicKeyOptions: PublicKeyCredentialRequestOptions = { - challenge: fromBase64url(options.challenge).buffer as ArrayBuffer, - rpId: options.rpId, - userVerification: options.userVerification as UserVerificationRequirement, - timeout: options.timeout, - allowCredentials: options.allowCredentials?.map((c: { type: string; id: string; transports: string[] }) => ({ - type: c.type as PublicKeyCredentialType, - id: fromBase64url(c.id).buffer as ArrayBuffer, - transports: c.transports as AuthenticatorTransport[], - })), - } - - const assertion = await navigator.credentials.get({ publicKey: publicKeyOptions }) as PublicKeyCredential - if (!assertion) throw new Error('Authentication cancelled') - - const response = assertion.response as AuthenticatorAssertionResponse - - const completeRes = await fetch(`${ENCRYPTID_SERVER}/api/auth/complete`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - challenge: options.challenge, - credential: { - credentialId: assertion.id, - authenticatorData: toBase64url(response.authenticatorData), - clientDataJSON: toBase64url(response.clientDataJSON), - signature: toBase64url(response.signature), - userHandle: response.userHandle ? toBase64url(response.userHandle) : null, - }, - }), - }) - - const result = await completeRes.json() - if (!result.success) throw new Error(result.error || 'Authentication failed') - + const result = await client.authenticate() + document.cookie = `encryptid_token=${result.token};path=/;max-age=900;SameSite=Lax` set({ isAuthenticated: true, token: result.token, @@ -103,52 +54,8 @@ export const useAuthStore = create()( register: async (username: string) => { set({ loading: true }) try { - const startRes = await fetch(`${ENCRYPTID_SERVER}/api/register/start`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, displayName: username }), - }) - const { options, userId } = await startRes.json() - - const publicKeyOptions: PublicKeyCredentialCreationOptions = { - challenge: fromBase64url(options.challenge).buffer as ArrayBuffer, - rp: options.rp, - user: { - id: fromBase64url(options.user.id).buffer as ArrayBuffer, - name: options.user.name, - displayName: options.user.displayName, - }, - pubKeyCredParams: options.pubKeyCredParams, - authenticatorSelection: options.authenticatorSelection, - timeout: options.timeout, - attestation: options.attestation as AttestationConveyancePreference, - } - - const credential = await navigator.credentials.create({ publicKey: publicKeyOptions }) as PublicKeyCredential - if (!credential) throw new Error('Registration cancelled') - - const response = credential.response as AuthenticatorAttestationResponse - - const completeRes = await fetch(`${ENCRYPTID_SERVER}/api/register/complete`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - challenge: options.challenge, - userId, - username, - credential: { - credentialId: credential.id, - publicKey: toBase64url(response.getPublicKey?.() || response.attestationObject), - attestationObject: toBase64url(response.attestationObject), - clientDataJSON: toBase64url(response.clientDataJSON), - transports: (response as AuthenticatorAttestationResponse & { getTransports?: () => string[] }).getTransports?.() || [], - }, - }), - }) - - const result = await completeRes.json() - if (!result.success) throw new Error(result.error || 'Registration failed') - + const result = await client.register(username) + document.cookie = `encryptid_token=${result.token};path=/;max-age=900;SameSite=Lax` set({ isAuthenticated: true, token: result.token, @@ -163,6 +70,7 @@ export const useAuthStore = create()( }, logout: () => { + document.cookie = 'encryptid_token=;path=/;max-age=0;SameSite=Lax' set({ isAuthenticated: false, token: null,