← Back
import { useState, useEffect, useCallback } from 'react';
import { useT } from '../i18n/LanguageContext';
import { useAccount, useSignMessage } from 'wagmi';
import { SiweContext } from './siweAuthContext';

/**
 * Wallet-based session auth (SIWE). Backend: /api/auth/{nonce,login,me,logout}.
 * Session = httpOnly cookie, so all fetches use credentials:'include'. The
 * connected wallet signs a server-issued nonce message (personal_sign).
 */
export function SiweAuthProvider({ children }: { children: React.ReactNode }) {
  const { t } = useT();
  const { address, isConnected } = useAccount();
  const { signMessageAsync } = useSignMessage();
  const [user, setUser] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Restore session on mount (cookie may already be valid).
  useEffect(() => {
    const t = setTimeout(async () => {
      try {
        const r = await fetch('/api/auth/me', { credentials: 'include' });
        if (r.ok) {
          const d = await r.json();
          if (d.success) setUser(d.address);
        }
      } catch { /* ignore */ }
    }, 0);
    return () => clearTimeout(t);
  }, []);

  const login = useCallback(async () => {
    if (!isConnected || !address) {
      setError(t('err.connectFirst'));
      return;
    }
    setLoading(true);
    setError(null);
    try {
      const nr = await fetch(`/api/auth/nonce?address=${address}`, { credentials: 'include' });
      const nd = await nr.json();
      if (!nd.success) throw new Error(nd.error || 'nonce failed');

      const signature = await signMessageAsync({ message: nd.message });

      const lr = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ address, signature }),
      });
      const ld = await lr.json();
      if (!lr.ok || !ld.success) throw new Error(ld.error || 'login failed');
      setUser(ld.address);
    } catch (e) {
      setError(e instanceof Error ? e.message : t('err.signinFailed'));
    } finally {
      setLoading(false);
    }
  }, [isConnected, address, signMessageAsync, t]);

  const logout = useCallback(async () => {
    try {
      await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
    } catch { /* ignore */ }
    setUser(null);
  }, []);

  return (
    <SiweContext.Provider value={{ user, isConnected, address, loading, error, login, logout }}>
      {children}
    </SiweContext.Provider>
  );
}

📜 Git History

6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...