@cedros/login-react
v0.0.55
Published
React component library for authentication with email/password, Google OAuth, and Solana wallet sign-in
Maintainers
Readme
@cedros/login-react
Warning: Development Preview
This package is in early development (v0.0.x) and is not ready for production use. APIs may change without notice. Use at your own risk.
React component library for authentication with email/password, Google OAuth, Apple Sign In, Solana wallet, instant links, and multi-tenancy support. Supports embedded Solana wallets through SSS and private deposits and account credits through Privacy Cash.
Installation
npm install @cedros/login-react
# or
yarn add @cedros/login-react
# or
pnpm add @cedros/login-reactOptional: Solana Wallet Support
For Solana wallet authentication, install the wallet adapter packages:
npm install @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets @solana/web3.jsChoose The Right Entrypoint
| Use case | Import path |
|----------|-------------|
| Full auth UI, including optional Solana login | @cedros/login-react |
| Admin/host apps that should not bundle wallet adapter packages | @cedros/login-react/non-wallet |
| Shared admin plugin only | @cedros/login-react/admin-only |
Admin-Safe / Non-Wallet Entrypoint
If your app does not use Solana login and should not bundle the wallet adapter packages, import the non-wallet subpath instead of the root package:
import {
CedrosLoginProvider,
LoginForm,
useCedrosLogin,
} from '@cedros/login-react/non-wallet';This entrypoint omits the Solana login button and avoids the @solana/wallet-adapter-*
bundle edge entirely.
Quick Start
Full Auth App
import { CedrosLoginProvider, LoginButton, useCedrosLogin } from '@cedros/login-react';
import '@cedros/login-react/style.css';
function App() {
return (
<CedrosLoginProvider
config={{
serverUrl: 'http://localhost:8080',
features: {
email: true,
google: true,
apple: true,
solana: true,
},
}}
>
<AuthStatus />
</CedrosLoginProvider>
);
}
function AuthStatus() {
const { user, isAuthenticated, logout } = useCedrosLogin();
if (!isAuthenticated) {
return <LoginButton />;
}
return (
<div>
<p>Welcome, {user?.name || user?.email}</p>
<button onClick={logout}>Logout</button>
</div>
);
}Features
- Multiple Auth Methods: Email/password, Google OAuth, Apple Sign In, Solana wallet, instant links, WebAuthn/Passkeys
- WebAuthn/Passkeys: Passwordless login with TouchID, FaceID, Windows Hello, or security keys
- Username-less Login: Discoverable credentials allow login without entering email first
- Multi-Tenancy: Organization management, member invites, role-based access
- Session Management: View and revoke active sessions
- Credential Management: View and manage all authentication methods
- MFA Support: TOTP two-factor authentication with QR setup, recovery codes, and verification
- i18n Ready: Built-in translations with customization support
- Theming: Light/dark mode with CSS variable customization
- TypeScript: Full type definitions included
- Tree-Shakeable: Import only what you need
Components
Authentication
| Component | Description |
|-----------|-------------|
| LoginButton | Button that opens the login modal |
| LoginModal | Full authentication modal with all methods |
| LoginForm | Standalone login form |
| EmailLoginForm | Email/password login form |
| EmailRegisterForm | Registration form with password validation |
| GoogleLoginButton | Google sign-in button |
| AppleLoginButton | Apple Sign In button |
| SolanaLoginButton | Solana wallet connect button |
| PasswordInput | Password field with visibility toggle and strength meter |
| ForgotPasswordForm | Password reset request form |
| ResetPasswordForm | Password reset form with token |
| PasskeyPrompt | WebAuthn passkey registration/authentication prompt |
WebAuthn / Passkeys
| Component | Description |
|-----------|-------------|
| PasskeyPrompt | Modal for passkey registration or authentication |
| PasskeyList | List registered passkeys with management options |
Embedded Wallet (Server-Side Signing)
| Component | Description |
|-----------|-------------|
| WalletEnrollment | Full enrollment flow with auth method selection |
| WalletStatus | Shows wallet state (not enrolled/locked/unlocked) |
| WalletAddressRow | Wallet address display with copy + explorer link |
| WalletUnlock | Unlock prompt for session-based signing |
| WalletManager | Orchestrates status/enroll/unlock/recover in one flow |
| RecoveryPhraseDisplay | Shows 24-word phrase with copy/confirm |
| RecoveryPhraseInput | Input for recovery phrase entry |
| CapabilityWarning | Browser capability check (WebCrypto, etc.) |
Organizations
| Component | Description |
|-----------|-------------|
| OrgSelector | Dropdown to select active organization |
| OrgSwitcher | Modal to switch organizations or create new ones |
Members & Invites
| Component | Description |
|-----------|-------------|
| MemberList | List org members with role management |
| InviteForm | Form to invite new members by email |
| InviteList | List and manage pending invites |
Sessions
| Component | Description |
|-----------|-------------|
| SessionList | List active sessions with revoke option |
Two-Factor Authentication (TOTP)
| Component | Description |
|-----------|-------------|
| TotpSettings | Settings panel to enable/disable 2FA, regenerate recovery codes |
| TotpSetup | Step-by-step wizard for setting up TOTP |
| TotpVerify | Verification prompt during login |
| OtpInput | 6-digit code input component |
User Withdrawals
| Component | Description |
|-----------|-------------|
| WithdrawalFlow | Multi-step withdrawal wizard (token select, amount, confirm, success) |
| WithdrawalHistory | Paginated withdrawal history with Solana explorer links |
Privacy Cash (Deposits & Credits)
| Component | Description |
|-----------|-------------|
| DepositForm | Create new privacy deposits (amount input, wallet signing) |
| CreditBalance | Display current SOL credit balance |
| CreditHistory | Transaction history (deposits, spends) with pagination |
| DepositStatus | Single deposit status card with progress indicator |
| DepositList | User's deposit history with status filtering |
Privacy Cash Admin (System Admin Only)
| Component | Description |
|-----------|-------------|
| AdminDepositStats | Aggregate statistics (totals, pending, withdrawn, failed) |
| AdminDepositList | All deposits across users with status filtering |
| AdminWithdrawalQueue | Deposits ready for withdrawal processing |
| PrivacyCashAdminPanel | Combined tabbed admin interface |
Credentials
| Component | Description |
|-----------|-------------|
| CredentialList | List all authentication methods (passwords, passkeys, OAuth) |
| CredentialCard | Individual credential display with management options |
Compliance (KYC & Accreditation)
| Component | Description |
|-----------|-------------|
| KycBanner | Banner prompting KYC identity verification, with status-aware messaging |
| KycCallback | Post-redirect landing page that polls KYC status until resolved |
| AccreditationWizard | Multi-step accredited investor verification wizard (method select, details, review) |
| AccreditationBanner | Banner prompting accreditation verification, with status-aware messaging |
Rewards
| Component | Description |
|-----------|-------------|
| RewardsPanel | Self-contained rewards dashboard with summary cards, payout wallet editor, and paginated history |
Admin
| Component | Description |
|-----------|-------------|
| SetupWizard | First-run setup wizard — creates the first admin account (email, password, name, org). Render it on your admin route before loading AdminShell when useSetup().status?.needsSetup is true. |
| cedrosLoginPlugin | Admin plugin for use with the shared AdminShell from @cedros/admin-react |
| AdminPanel | Legacy admin dashboard with tabs for members, invites, sessions, system settings |
| SystemSettings | System settings editor (privacy, withdrawal, rate limits) - system admin only |
Admin Plugin System
The admin dashboard supports a plugin architecture for creating unified dashboards that combine sections from multiple Cedros packages (e.g., cedros-login + cedros-pay). The shared host now lives in @cedros/admin-react.
| Export | Description |
|--------|-------------|
| cedrosLoginPlugin | Plugin definition for cedros-login admin sections |
| AdminShell | Import from @cedros/admin-react |
| useAdminShell | Import from @cedros/admin-react |
Shared
| Component | Description |
|-----------|-------------|
| LoadingSpinner | Loading indicator |
| ErrorMessage | Error display component |
LoginButton Customization
The LoginButton component shows a "Sign in" button when logged out, and a user menu with dropdown when authenticated. You can customize the dropdown menu items:
import { LoginButton, LoginModal } from '@cedros/login-react';
function Navbar() {
return (
<nav>
<LoginButton
menuItems={[
{
label: 'Account settings',
onClick: () => navigate('/settings'),
},
{
label: 'Billing',
onClick: () => navigate('/billing'),
},
]}
/>
<LoginModal />
</nav>
);
}Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| variant | 'default' \| 'outline' \| 'ghost' | 'default' | Button style variant |
| size | 'sm' \| 'md' \| 'lg' | 'md' | Button size |
| menuItems | MenuItemConfig[] | [] | Custom menu items above "Sign out" |
| hideSignOut | boolean | false | Hide the default "Sign out" item |
| className | string | '' | Additional CSS classes |
| children | ReactNode | 'Sign in' | Custom button text (logged out state) |
MenuItemConfig:
interface MenuItemConfig {
label: string; // Display text
onClick: () => void; // Click handler
icon?: ReactNode; // Optional icon element
}Dark navbar styling:
// Use the built-in dark navbar class
<LoginButton className="cedros-button-dark-navbar" />Hooks
Authentication
// Main authentication hook
const {
user, // Current user or null
isAuthenticated, // Boolean auth status
isLoading, // Loading state
login, // Login function
register, // Registration function
logout, // Logout function
refreshToken, // Refresh access token
} = useCedrosLogin();
// Email-specific auth
const {
login,
register,
isLoading,
error,
} = useEmailAuth();
// Google OAuth
const {
login,
isLoading,
error,
} = useGoogleAuth();
// Solana wallet
const {
login,
challenge,
isLoading,
error,
} = useSolanaAuth();
// Apple Sign In
const {
login,
isLoading,
error,
} = useAppleAuth();
// Account deletion
const {
deleteAccount,
requestDeletionEmail,
accountDeletionUrl,
isLoading,
error,
} = useAccountDeletion();
// WebAuthn / Passkeys
const {
registerPasskey, // Start passkey registration
authenticatePasskey, // Start passkey authentication
isSupported, // Browser supports WebAuthn
isLoading,
error,
} = useWebAuthn();
// Password reset
const {
requestReset, // Send password reset email
resetPassword, // Reset with token
isLoading,
error,
} = usePasswordReset();
// TOTP / Two-Factor Authentication
const {
status, // { enabled: boolean, recoveryCodesRemaining: number } or null
isLoading,
error,
getStatus, // Fetch current 2FA status
beginSetup, // Initiate setup (returns otpauthUri + secret + recovery codes)
enableTotp, // Enable with verification code
disableTotp, // Disable with password confirmation
regenerateBackupCodes, // Get new recovery codes (requires TOTP code)
clearError,
} = useTotp();
// TOTP Verification (during login)
const {
verifyTotp, // Complete login MFA (submit TOTP code or recovery code)
isLoading,
error,
} = useTotpVerify();
// Wallet status and capabilities
const {
status, // 'not_enrolled' | 'enrolled_locked' | 'enrolled_unlocked'
solanaPubkey, // Base58 public key (if enrolled)
authMethod, // 'password' | 'passkey'
hasExternalWallet, // User logged in with Solana wallet (not SSS)
isUnlocked, // Whether SSS wallet is unlocked
capabilities, // Browser capability check
refresh, // Refresh wallet status
error,
} = useWallet();
// Wallet enrollment
const {
startEnrollment, // Begin enrollment flow
completeEnrollment, // Finish with shares
isLoading,
error,
} = useWalletEnrollment();
// Wallet material API (low-level operations)
const {
getMaterial, // Get wallet material
getStatus, // Get wallet status
enroll, // Enroll new wallet
signTransaction, // Sign transaction
unlock, // Unlock for session-based signing (returns { unlocked, ttlSeconds })
lock, // Lock wallet (clear cached key)
rotateUserSecret, // Re-encrypt Share A with new credential
isLoading,
error,
} = useWalletMaterial();
// Wallet signing (high-level)
const {
signTransaction, // Sign transaction (prompts for credential if needed)
isSigning,
error,
} = useWalletSigning();
// Unified transaction signing (routes to external or SSS wallet)
const {
signTransaction, // Auto-routes to correct wallet type
signingMethod, // 'external' | 'sss' | 'none'
canSign, // Whether user can sign transactions
publicKey, // Solana public key
hasExternalWallet,
hasSssWallet,
isSssUnlocked,
isSigning,
error,
} = useTransactionSigning({
// For external wallet users, provide adapter callback
onExternalSign: (tx) => walletAdapter.signTransaction(tx),
});Organizations
const {
orgs, // List of user's organizations
activeOrg, // Currently selected organization
isLoading,
error,
createOrg, // Create new organization
updateOrg, // Update organization details
deleteOrg, // Delete organization
switchOrg, // Switch active organization
refetch, // Refresh org list
} = useOrgs();Members
const {
members, // List of organization members
isLoading,
error,
updateRole, // Change member's role
removeMember, // Remove member from org
refetch,
} = useMembers(orgId);Invites
const {
invites, // List of pending invites
isLoading,
error,
createInvite, // Send new invite
cancelInvite, // Cancel pending invite
resendInvite, // Resend invite email
acceptInvite, // Accept invite (by invitee)
refetch,
} = useInvites(orgId);Sessions
const {
sessions, // List of active sessions
isLoading,
error,
revokeAll, // Revoke all sessions (logout everywhere)
refetch,
} = useSessions();User Withdrawals
// Withdraw SOL/SPL tokens to external addresses
const {
getBalances, // Fetch SOL + SPL token balances from wallet
withdrawSol, // Withdraw native SOL (destination, amountLamports)
withdrawSpl, // Withdraw SPL token (destination, tokenMint, amount)
getHistory, // Paginated withdrawal history (limit, offset)
isSubmitting, // True while withdrawal is in flight
error, // Error message or null
clearError, // Clear error state
lastResult, // Most recent WithdrawalResponse
} = useWithdrawal();Privacy Cash (Deposits & Credits)
// User deposit operations
const {
deposit, // Execute a deposit (returns DepositResponse)
getStatus, // Get deposit status by session ID
getConfig, // Get deposit configuration (min amount, etc.)
listDeposits, // List user's deposits with pagination
isLoading,
error,
clearError,
} = useDeposit();
// User credit operations
const {
getBalance, // Get SOL credit balance
getAllBalances, // Get all currency balances
getHistory, // Get transaction history with pagination
isLoading,
error,
clearError,
} = useCredits();
// Admin operations (requires system admin privileges)
const {
listDeposits, // List all deposits across users
getStats, // Get aggregate statistics
listPendingWithdrawals, // List deposits ready for withdrawal
processWithdrawal, // Process single withdrawal (with optional force for early)
processAllWithdrawals, // Process all ready withdrawals
isLoading,
error,
clearError,
} = useAdminDeposits();
// System settings (requires system admin privileges)
const {
settings, // Settings grouped by category { privacy: [...], withdrawal: [...], rate_limit: [...] }
isLoading,
isUpdating,
error,
fetchSettings, // Refresh settings from server
updateSettings, // Update multiple settings: [{ key, value }, ...]
getValue, // Get single setting value by key
} = useSystemSettings();
// Process a single withdrawal
const result = await processWithdrawal(sessionId, { force: false });
// result: { success, sessionId, txSignature?, error?, earlyWithdrawal }
// Force early withdrawal (before privacy period - shows warning in UI)
const result = await processWithdrawal(sessionId, { force: true });
// Process all ready withdrawals (past privacy period only)
const batchResult = await processAllWithdrawals();
// batchResult: { totalProcessed, totalSucceeded, totalFailed, results }Credentials
const {
credentials, // List of all auth methods (password, passkeys, OAuth)
isLoading,
error,
updateCredential, // Update credential label
unlinkCredential, // Remove credential (if not last one)
refetch,
} = useCredentials();Referrals
const {
getReferral, // Fetch referral code and count (returns ReferralInfo)
regenerateCode, // Regenerate referral code (returns new code string)
isLoading,
error,
} = useReferral();
// Usage
const info = await getReferral();
console.log(info.referralCode, info.referralCount);KYC (Identity Verification)
const {
status, // 'none' | 'pending' | 'verified' | 'failed' | 'expired' | 'canceled' or null
verifiedAt, // ISO 8601 timestamp or null
expiresAt, // ISO 8601 timestamp or null
isRequired, // True when enforcement mode is not "none"
enforcementMode, // Current enforcement mode from server
fetchStatus, // Fetch user's KYC status
startVerification, // Start a session (returns redirect URL)
isLoading,
error,
} = useKyc();
// Usage
useEffect(() => { fetchStatus(); }, [fetchStatus]);
if (isRequired && status !== 'verified') {
const url = await startVerification();
window.location.href = url;
}Accreditation (Investor Verification)
const {
status, // 'none' | 'pending' | 'approved' | 'rejected' | 'expired' or null
verifiedAt, // ISO 8601 timestamp or null
expiresAt, // ISO 8601 timestamp or null
isRequired, // True when enforcement mode is not "none"
enforcementMode, // Current enforcement mode from server
fetchStatus, // Fetch user's accreditation status
submitVerification, // Submit verification (method, data) -> { submissionId }
uploadDocument, // Upload document (submissionId, file, documentType) -> { documentId }
listSubmissions, // List user's submissions
isLoading,
error,
} = useAccreditation();
// Usage
useEffect(() => { fetchStatus(); }, [fetchStatus]);
const { submissionId } = await submitVerification('income', { statedAmountUsd: 250000 });
await uploadDocument(submissionId, taxFile, 'tax_return');Rewards
const {
rewards, // RewardsInfo summary (totalEarned, pendingAmount, currency, etc.) or null
history, // RewardHistoryItem[]
historyTotal, // Total history count for pagination
fetchRewards, // Fetch rewards summary
fetchHistory, // Fetch history (limit, offset)
setPayoutWallet, // Set payout wallet address (string | null)
isLoading,
error,
} = useRewards();
// Usage
useEffect(() => {
fetchRewards();
fetchHistory(10, 0);
}, [fetchRewards, fetchHistory]);Authorization
const {
checkPermission, // Check if action is allowed
isLoading,
error,
} = useAuthorize();
// Usage
const canInvite = await checkPermission({
orgId: 'org-123',
action: 'member:invite',
resourceType: 'member',
});Configuration
Apple Sign In (Web)
This library uses Apple's JS SDK in popup mode and initializes it with:
redirectURI: window.location.origin
To avoid popup flows hanging or failing to return an id_token, configure your Apple Services ID
with a Return URL that exactly matches your site's origin.
Example:
- If your login page is served from
https://login.example.com/..., set a Return URL ofhttps://login.example.com.
Note: Apple Sign In generally requires HTTPS and a verified domain.
WebAuthn / Passkeys (Server-managed)
This library supports server-managed passkeys (WebAuthn) for authentication via:
POST /webauthn/auth/options->navigator.credentials.get(...)->POST /webauthn/auth/verifyPOST /webauthn/register/options->navigator.credentials.create(...)->POST /webauthn/register/verify
Server requirements:
WEBAUTHN_ENABLED=trueWEBAUTHN_RP_ID=<domain>(e.g.login.example.comorexample.com)WEBAUTHN_RP_ORIGIN=<origin>(e.g.https://login.example.com)
Browser requirements:
- HTTPS (secure context)
The LoginForm includes a "Continue with Passkey" button by default. Disable it with:
features: { webauthn: false }<CedrosLoginProvider
config={{
// Required
serverUrl: 'http://localhost:8080',
// Feature flags
features: {
email: true, // Email/password auth
google: true, // Google OAuth
solana: false, // Solana wallet
},
// Google OAuth
googleClientId: 'your-google-client-id',
// Apple Sign In (Services ID)
appleClientId: 'com.your.app.service',
// Solana wallet
solana: {
network: 'mainnet-beta', // 'mainnet-beta' | 'devnet' | 'testnet'
},
// Session storage
session: {
// Recommended: cookie storage (tokens in httpOnly cookies)
storage: 'cookie', // 'cookie' | 'localStorage' | 'sessionStorage' | 'memory'
// If you must use web storage (XSS-vulnerable), you must explicitly opt in:
// storage: 'localStorage',
// allowWebStorage: true,
persistKey: 'cedros_auth',
},
// Two-factor authentication
totp: {
enabled: true, // Show 2FA options
required: false, // Require all users to enable 2FA
issuer: 'MyApp', // Name shown in authenticator apps
},
// Embedded wallet (server-side signing)
wallet: {
exposeAvailability: true, // Expose via window global for cedros-pay
// Server config: WALLET_ENABLED, WALLET_RECOVERY_MODE, WALLET_UNLOCK_TTL
},
// Theming
theme: 'auto', // 'light' | 'dark' | 'auto'
themeOverrides: {
'--cedros-primary': '#6366f1',
'--cedros-destructive': '#ef4444',
},
// Callbacks
callbacks: {
onLoginSuccess: (user) => console.log('User logged in:', user),
onLogout: () => console.log('User logged out'),
onLoginError: (error) => console.error('Auth error:', error),
},
}}
>
{children}
</CedrosLoginProvider>Package Feature Flags
Package feature flags are defined in one registry: ui/src/featureFlags.ts.
Resolution precedence:
config.featuresCEDROS_FEATURE_*environment variables- Registry defaults
Consumer config example:
<CedrosLoginProvider
config={{
serverUrl: 'https://auth.example.com',
features: {
instantLink: true,
},
}}
>
<App />
</CedrosLoginProvider>Environment variable example:
CEDROS_FEATURE_INSTANT_LINK=true
CEDROS_FEATURE_WEBAUTHN=offIf your app runtime does not expose process.env to the package, pass the env
object through config:
<CedrosLoginProvider
config={{
serverUrl: 'https://auth.example.com',
featureFlagEnv: import.meta.env,
}}
>
<App />
</CedrosLoginProvider>Supported boolean env values:
- truthy:
1,true,yes,on - falsy:
0,false,no,off
Inspect available flags:
import { FEATURE_FLAG_REGISTRY, getFeatureFlagDefinitions } from '@cedros/login-react';
console.log(FEATURE_FLAG_REGISTRY.instantLink.defaultEnabled);
console.table(getFeatureFlagDefinitions());Define a new flag:
export const FEATURE_FLAG_REGISTRY = {
...,
newParser: {
name: 'newParser',
description: 'Enable the new parser implementation.',
defaultEnabled: false,
status: 'experimental',
envVar: 'CEDROS_FEATURE_NEW_PARSER',
autoDiscoverable: false,
},
} as const;Flip a feature from opt-in to on-by-default:
newParser: {
...,
defaultEnabled: true,
}That rollout pattern keeps the flag name stable. Consumers can opt in while the feature is experimental, and later opt out temporarily after the default flips, without any flag rename or call-site rewrite.
Bundle Optimization
Import only the auth methods you need to reduce bundle size:
// Email only (smallest bundle)
import { EmailLoginForm, useEmailAuth } from '@cedros/login-react/email-only';
// Google only
import { GoogleLoginButton, useGoogleAuth } from '@cedros/login-react/google-only';
// Solana only
import { SolanaLoginButton, useSolanaAuth } from '@cedros/login-react/solana-only';Styling
@cedros/login-react follows the same styling contract as Cedros Pay:
- wrap your app in
CedrosLoginProvider - import
@cedros/login-react/style.cssonce - customize through
theme,themeOverrides, andunstyled
Using Default Styles
import '@cedros/login-react/style.css';<CedrosLoginProvider
config={{
serverUrl: '/api/auth',
theme: 'dark',
themeOverrides: {
'--cedros-primary': '#0f766e',
'--cedros-card': '#042f2e',
'--cedros-ring': '#14b8a6',
},
}}
>
<App />
</CedrosLoginProvider>Set unstyled: true to opt out of all default Cedros theme application and supply your own design system.
Theme Tokens
The shipped stylesheet and built-in templates all read from the same Cedros theme tokens:
:root {
--cedros-primary: #111827;
--cedros-primary-foreground: #f9fafb;
--cedros-background: #ffffff;
--cedros-foreground: #111827;
--cedros-card: #ffffff;
--cedros-card-foreground: #111827;
--cedros-muted: #f3f4f6;
--cedros-muted-foreground: #6b7280;
--cedros-accent: #e5e7eb;
--cedros-accent-foreground: #111827;
--cedros-border: #e5e7eb;
--cedros-input: #e5e7eb;
--cedros-ring: #111827;
--cedros-radius: 0.5rem;
--cedros-destructive: #dc2626;
--cedros-destructive-foreground: #f9fafb;
--cedros-warning: #f59e0b;
--cedros-success: #22c55e;
--cedros-link: #2563eb;
}
.cedros-dark {
--cedros-background: #09090b;
--cedros-foreground: #fafafa;
--cedros-card: #09090b;
--cedros-card-foreground: #fafafa;
--cedros-muted: #27272a;
--cedros-muted-foreground: #a1a1aa;
--cedros-border: #27272a;
--cedros-input: #27272a;
}If you build host components around Cedros UI, useCedrosTheme() exposes the same className and style pair used internally by the provider.
Internationalization
Using Built-in Translations
import { I18nProvider, CedrosLoginProvider } from '@cedros/login-react';
<I18nProvider locale="en">
<CedrosLoginProvider config={config}>
{children}
</CedrosLoginProvider>
</I18nProvider>Custom Translations
import { I18nProvider, mergeTranslations, defaultTranslations } from '@cedros/login-react';
const customTranslations = mergeTranslations(defaultTranslations, {
en: {
login: {
title: 'Welcome Back',
submit: 'Sign In',
},
},
});
<I18nProvider translations={customTranslations} locale="en">
{children}
</I18nProvider>Using Translations in Components
import { useTranslations, useLocale } from '@cedros/login-react';
function MyComponent() {
const t = useTranslations();
const { locale, setLocale } = useLocale();
return (
<div>
<h1>{t.login.title}</h1>
<button onClick={() => setLocale('es')}>
Switch to Spanish
</button>
</div>
);
}Examples
Admin / Non-Wallet Login Page
import { CedrosLoginProvider, LoginForm } from '@cedros/login-react/non-wallet';
import '@cedros/login-react/style.css';
export function AdminLoginPage() {
return (
<CedrosLoginProvider
config={{
serverUrl: '/api/auth',
features: { email: true, google: true, solana: false },
}}
>
<LoginForm />
</CedrosLoginProvider>
);
}Use this entrypoint for host/admin apps that only need CedrosLoginProvider,
LoginForm, and useCedrosLogin without any wallet-adapter package requirement.
Basic Login Page
import { CedrosLoginProvider, LoginForm } from '@cedros/login-react/non-wallet';
import '@cedros/login-react/style.css';
export function LoginPage() {
return (
<CedrosLoginProvider
config={{
serverUrl: '/api/auth',
features: { email: true, google: true },
}}
>
<div className="login-container">
<h1>Sign In</h1>
<LoginForm
onSuccess={(user) => {
window.location.href = '/dashboard';
}}
/>
</div>
</CedrosLoginProvider>
);
}Organization Switcher
import { useOrgs, OrgSwitcher } from '@cedros/login-react';
function Header() {
const { activeOrg } = useOrgs();
const [showSwitcher, setShowSwitcher] = useState(false);
return (
<header>
<button onClick={() => setShowSwitcher(true)}>
{activeOrg?.name || 'Select Organization'}
</button>
{showSwitcher && (
<OrgSwitcher
onClose={() => setShowSwitcher(false)}
onSwitch={(org) => {
console.log('Switched to:', org.name);
setShowSwitcher(false);
}}
/>
)}
</header>
);
}Team Management
import { useMembers, useInvites, MemberList, InviteForm, InviteList } from '@cedros/login-react';
import { useOrgs } from '@cedros/login-react';
function TeamSettings() {
const { activeOrg } = useOrgs();
const orgId = activeOrg?.id;
if (!orgId) return <p>Select an organization</p>;
return (
<div>
<h2>Team Members</h2>
<MemberList orgId={orgId} />
<h2>Invite New Member</h2>
<InviteForm
orgId={orgId}
onSuccess={() => console.log('Invite sent!')}
/>
<h2>Pending Invites</h2>
<InviteList orgId={orgId} />
</div>
);
}Session Management
import { useSessions, SessionList } from '@cedros/login-react';
function SecuritySettings() {
const { revokeAll, isLoading } = useSessions();
return (
<div>
<h2>Active Sessions</h2>
<SessionList />
<button
onClick={revokeAll}
disabled={isLoading}
>
Logout from all devices
</button>
</div>
);
}Two-Factor Authentication
Scope: TOTP 2FA applies to email/password sign-in only. OAuth providers (Google, Apple) and passkeys handle their own multi-factor verification, so TOTP is not prompted for those methods.
import { TotpSettings, TotpSetup, TotpVerify } from '@cedros/login-react';
// Settings page - enable/disable 2FA
function SecuritySettings() {
return (
<div>
<h2>Two-Factor Authentication</h2>
<TotpSettings
onStatusChange={(enabled) => {
console.log('2FA is now', enabled ? 'enabled' : 'disabled');
}}
/>
</div>
);
}
// Standalone setup wizard (if you need custom placement)
function TwoFactorSetupPage() {
return (
<TotpSetup
onSuccess={() => {
window.location.href = '/settings';
}}
onCancel={() => {
window.location.href = '/settings';
}}
/>
);
}
// Custom verification UI during login
function TwoFactorChallenge({ onSuccess, email }) {
return (
<TotpVerify
email={email}
onSuccess={onSuccess}
onCancel={() => window.location.href = '/login'}
/>
);
}Embedded Wallet
import { useWallet, useWalletSigning, WalletStatus } from '@cedros/login-react';
function WalletPage() {
const { status, solanaPubkey, authMethod } = useWallet();
const { unlock, signTransaction, isUnlocked, isLoading } = useWalletSigning();
const handleSign = async () => {
// If not unlocked, prompt for credential first
if (!isUnlocked) {
await unlock({ password: userPassword }); // or { prfOutput }
}
// Sign transaction (server-side)
const { signature } = await signTransaction(transactionBytes);
console.log('Signed:', signature);
};
return (
<div>
<WalletStatus
status={status}
publicKey={solanaPubkey}
onEnroll={() => navigate('/wallet/enroll')}
onUnlock={() => setShowUnlockModal(true)}
onLock={() => lock()}
/>
<button onClick={handleSign} disabled={isLoading}>
Sign Transaction
</button>
</div>
);
}Permission-Based UI
import { useAuthorize } from '@cedros/login-react';
function InviteButton({ orgId }) {
const { checkPermission } = useAuthorize();
const [canInvite, setCanInvite] = useState(false);
useEffect(() => {
checkPermission({
orgId,
action: 'member:invite',
resourceType: 'member',
}).then(setCanInvite);
}, [orgId]);
if (!canInvite) return null;
return <button>Invite Member</button>;
}KYC Verification
import { KycBanner, KycCallback, useKyc } from '@cedros/login-react';
// Display a KYC prompt banner
function Dashboard() {
const { status, isRequired, startVerification, fetchStatus } = useKyc();
useEffect(() => { fetchStatus(); }, [fetchStatus]);
if (!isRequired || status === 'verified') return <MainContent />;
return <KycBanner status={status ?? 'none'} startVerification={startVerification} />;
}
// Handle the post-redirect callback page
function KycReturnPage() {
const { fetchStatus } = useKyc();
return <KycCallback fetchStatus={fetchStatus} onComplete={(s) => navigate('/dashboard')} />;
}KycBanner Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| status | string | — | Current KYC status (none, pending, verified, failed, expired, canceled) |
| startVerification | () => Promise<string> | — | Function that starts verification and returns redirect URL |
| className | string | '' | Additional CSS classes |
KycCallback Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| fetchStatus | () => Promise<{ status: string }> | — | Function that fetches KYC status (from useKyc().fetchStatus) |
| onComplete | (status: string) => void | — | Called when polling resolves to a non-pending status |
| className | string | '' | Additional CSS classes |
Accreditation Verification
import {
AccreditationBanner,
AccreditationWizard,
useAccreditation,
} from '@cedros/login-react';
function InvestorGate() {
const { status, isRequired, fetchStatus } = useAccreditation();
const [showWizard, setShowWizard] = useState(false);
useEffect(() => { fetchStatus(); }, [fetchStatus]);
if (!isRequired || status === 'approved') return <ProtectedContent />;
if (showWizard) return <AccreditationWizard onComplete={() => setShowWizard(false)} />;
return (
<AccreditationBanner
status={status ?? 'none'}
onStartVerification={() => setShowWizard(true)}
/>
);
}AccreditationWizard Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| onComplete | (submissionId: string) => void | — | Called after successful submission |
| onCancel | () => void | — | Called when user cancels or navigates back from step 1 |
| className | string | '' | Additional CSS classes |
AccreditationBanner Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| status | string | — | Current accreditation status (none, pending, approved, rejected, expired) |
| onStartVerification | () => void | — | Called when user clicks the verification action button |
| className | string | '' | Additional CSS classes |
Rewards Dashboard
import { RewardsPanel } from '@cedros/login-react';
function RewardsPage() {
return <RewardsPanel explorerUrl="https://explorer.solana.com" />;
}RewardsPanel Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| explorerUrl | string | 'https://explorer.solana.com' | Solana explorer URL base for transaction links |
| className | string | '' | Additional CSS classes |
User Withdrawals
Withdraw SOL or SPL tokens from the user's embedded wallet to any external Solana address.
Requires feature_user_withdrawals to be enabled via system settings.
import { WithdrawalFlow, WithdrawalHistory, useWithdrawal } from '@cedros/login-react';
// Multi-step withdrawal wizard
function WithdrawPage() {
return (
<div>
<WithdrawalFlow
onSuccess={(result) => console.log('Sent:', result.txSignature)}
onCancel={() => console.log('Cancelled')}
/>
<WithdrawalHistory
pageSize={10}
onTransactionClick={(item) => console.log('Clicked:', item)}
/>
</div>
);
}
// Using the hook directly
function CustomWithdraw() {
const { getBalances, withdrawSol, withdrawSpl, getHistory, isSubmitting } = useWithdrawal();
const handleWithdraw = async () => {
const balances = await getBalances();
const result = await withdrawSol('DestinationAddress...', 100_000_000);
console.log('Tx:', result.txSignature, 'Fee:', result.feeLamports);
};
return <button onClick={handleWithdraw} disabled={isSubmitting}>Withdraw</button>;
}Privacy Cash
Deposits work in all wallet recovery modes, but private (privacy-preserving) deposits require the wallet to be configured in "no-recovery" mode for security reasons.
| Recovery Mode | Private Deposits | Public Deposits | Notes |
|--------------|-----------------|-----------------|-------|
| None | ✅ Available | ✅ Available | Maximum privacy, no key export |
| ShareCOnly | ❌ Blocked | ✅ Available | In-app recovery only |
| FullSeed | ❌ Blocked | ✅ Available | Full key portability |
Why private deposits require no-recovery mode: In recovery modes where users can export their private key, they could potentially front-run withdrawal transactions by extracting their key and submitting a competing transaction before the Privacy Cash relayer processes the batched withdrawal.
When privateDepositsEnabled is false (recovery mode enabled), the DepositFlow component automatically forces "receive" mode and shows public tier labels.
import {
DepositForm,
CreditBalance,
CreditHistory,
DepositList,
useDeposit,
useCredits,
} from '@cedros/login-react';
// User's credit dashboard
function CreditDashboard() {
return (
<div>
<h2>Your Balance</h2>
<CreditBalance showRefresh />
<h2>Make a Deposit</h2>
<DepositForm
minAmountLamports={10_000_000} // 0.01 SOL
onSuccess={(response) => {
console.log('Deposit initiated:', response.sessionId);
}}
/>
<h2>Transaction History</h2>
<CreditHistory pageSize={10} />
<h2>Your Deposits</h2>
<DepositList
pageSize={10}
onDepositClick={(deposit) => {
console.log('View deposit:', deposit.sessionId);
}}
/>
</div>
);
}
// Using hooks directly
function CustomDepositUI() {
const { deposit, getStatus, isLoading, error } = useDeposit();
const { getBalance } = useCredits();
const handleDeposit = async (amountLamports: number) => {
const result = await deposit(amountLamports);
console.log('Deposit session:', result.sessionId);
// Poll for status updates
const status = await getStatus(result.sessionId);
console.log('Status:', status.status);
};
return (
<button onClick={() => handleDeposit(100_000_000)} disabled={isLoading}>
Deposit 0.1 SOL
</button>
);
}Privacy Cash Admin
import {
PrivacyCashAdminPanel,
AdminDepositStats,
useAdminDeposits,
} from '@cedros/login-react';
// Full admin dashboard
function AdminDashboard() {
return (
<PrivacyCashAdminPanel
pageSize={20}
refreshInterval={30000} // Auto-refresh every 30s
onDepositClick={(deposit) => {
console.log('View deposit:', deposit.id, 'User:', deposit.userId);
}}
onWithdrawalClick={(item) => {
console.log('Process withdrawal:', item.id);
}}
/>
);
}
// Using admin hooks directly
function AdminStatsWidget() {
const { getStats, isLoading, error } = useAdminDeposits();
const [stats, setStats] = useState(null);
useEffect(() => {
getStats().then(setStats);
}, []);
if (!stats) return null;
return (
<div>
<p>Total Deposits: {stats.totalDeposits}</p>
<p>Total Volume: {stats.totalDepositedSol} SOL</p>
<p>Pending Withdrawals: {stats.pendingWithdrawalCount}</p>
</div>
);
}First-Run Admin Setup
Cedros Login no longer ships a standalone admin shell. Build admin routes with AdminShell from @cedros/admin-react and cedrosLoginPlugin from @cedros/login-react/admin-only.
When no admin user exists yet, gate that route with useSetup() and render SetupWizard until setup completes.
import { useEffect } from 'react';
import { SetupWizard, useSetup } from '@cedros/login-react/non-wallet';
function AdminBootstrap() {
const { status, isLoading, checkStatus } = useSetup();
useEffect(() => { checkStatus(); }, [checkStatus]);
if (isLoading) return <div>Loading...</div>;
if (status?.needsSetup) {
return <SetupWizard onComplete={checkStatus} />;
}
return <AdminRoute />;
}Admin Shell Integration
When using both @cedros/login-react and @cedros/pay-react, you can create a unified admin dashboard that combines sections from both packages using the plugin architecture:
import { AdminShell, HOST_SERVICE_IDS } from '@cedros/admin-react';
import '@cedros/admin-react/styles.css';
import { useCedrosLogin, useOrgs } from '@cedros/login-react/non-wallet';
import { cedrosLoginPlugin } from '@cedros/login-react/admin-only';
import { cedrosPayPlugin } from '@cedros/pay-react';
function UnifiedAdminDashboard() {
const { user, getAccessToken } = useCedrosLogin();
const { activeOrg, role, permissions } = useOrgs();
// Build host context from all auth providers
const hostContext = {
services: {
[HOST_SERVICE_IDS.cedrosLogin]: {
user,
getAccessToken,
serverUrl: 'https://api.example.com/auth',
},
[HOST_SERVICE_IDS.cedrosPay]: {
serverUrl: 'https://api.example.com/pay',
// Add wallet/JWT context if using cedros-pay
},
},
org: activeOrg ? {
orgId: activeOrg.id,
role: role || 'member',
permissions: permissions || [],
} : undefined,
};
return (
<AdminShell
title="Admin Dashboard"
plugins={[cedrosLoginPlugin, cedrosPayPlugin]}
hostContext={hostContext}
defaultSection="cedros-login:users"
pageSize={20}
refreshInterval={30000}
/>
);
}Cedros Login plugin sections:
usersteamreferralsdepositswithdrawalscomplianceaccreditation-queuesanctionssignup-gatingsettings-authsettings-emailsettings-webhookssettings-walletsettings-creditssettings-compliancesettings-referralssettings-signupsettings-serversettings-images
settings-messaging from the old standalone dashboard is now represented by the
separate settings-email and settings-webhooks plugin sections.
How it works:
- Each package exports an
AdminPluginthat defines its sections and components AdminShellfrom@cedros/admin-reactaggregates sections from all registered plugins into a unified sidebar- Sections are grouped (Users, Store, Configuration) and sorted by order
- Each plugin's CSS is isolated via namespace scoping
Plugin Structure:
// Each plugin exports this structure
export const cedrosLoginPlugin: AdminPlugin = {
id: 'cedros-login',
name: 'Cedros Login',
version: '1.0.0',
sections: [
{ id: 'users', label: 'Users', icon: <UsersIcon />, group: 'Users', order: 0 },
{ id: 'team', label: 'Team', icon: <TeamIcon />, group: 'Users', order: 1 },
// ... more sections
],
components: {
users: UsersSection,
team: TeamSection,
// ... lazy-loaded components
},
createPluginContext: (hostContext) => ({
serverUrl: hostContext.cedrosLogin?.serverUrl || '',
userId: hostContext.cedrosLogin?.user?.id,
getAccessToken: hostContext.cedrosLogin?.getAccessToken || (() => null),
hasPermission: (p) => /* permission check */,
}),
checkPermission: (permission, hostContext) => /* ... */,
cssNamespace: 'cedros-dashboard',
};Sidebar grouping in unified mode:
| Group | Source | Sections | |-------|--------|----------| | Users | cedros-login | users, team, deposits, withdrawals | | Store | cedros-pay | products, subscriptions, transactions, coupons | | Configuration | both | All settings pages from both plugins |
TypeScript
All components and hooks are fully typed:
import type {
// Auth types
AuthUser,
AuthMethod,
TokenPair,
AuthState,
// Organization types
Organization,
Membership,
OrgRole,
// Member types
Member,
// Invite types
Invite,
CreateInviteRequest,
// Session types
Session,
// Wallet types
WalletStatus,
WalletMaterial,
WalletCapabilities,
UnlockCredential,
SignTransactionRequest,
SignTransactionResponse,
// User Withdrawal types
WithdrawalResponse,
WalletBalancesResponse,
UserWithdrawalHistoryItem,
UserWithdrawalHistoryResponse,
// Privacy Cash types
DepositRequest,
DepositResponse,
DepositStatusResponse,
DepositConfigResponse,
DepositItemResponse,
DepositListResponse,
CreditBalanceResponse,
CreditTransactionResponse,
CreditHistoryResponse,
AdminDepositItem,
AdminDepositListResponse,
AdminDepositStatsResponse,
// System settings types
SystemSetting,
UpdateSettingRequest,
ListSystemSettingsResponse,
UpdateSystemSettingsResponse,
UseSystemSettingsReturn,
// Accreditation types
AccreditationMethod,
AccreditationStatus,
AccreditationStatusResponse,
AccreditationSubmissionItem,
AccreditationDocumentItem,
// KYC / Compliance component props
KycBannerProps,
KycCallbackProps,
AccreditationWizardProps,
AccreditationBannerProps,
// Rewards types
RewardsPanelProps,
UseRewardsReturn,
UseKycReturn,
UseAccreditationReturn,
UseReferralReturn,
// Admin plugin types
AdminPlugin,
AdminSectionConfig,
AdminSectionProps,
PluginContext,
HostContext,
PluginRegistry,
// Config types
CedrosLoginConfig,
FeatureFlags,
ThemeOverrides,
} from '@cedros/login-react';Development
# Install dependencies
npm install
# Run dev server
npm run dev
# Run tests
npm test
# Build library
npm run build
# Type check
npm run typecheck
# Lint
npm run lintLicense
MIT
