npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cedros/login-react

v0.0.55

Published

React component library for authentication with email/password, Google OAuth, and Solana wallet sign-in

Readme

@cedros/login-react

npm version npm downloads license

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-react

Optional: 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.js

Choose 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 of https://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/verify
  • POST /webauthn/register/options -> navigator.credentials.create(...) -> POST /webauthn/register/verify

Server requirements:

  • WEBAUTHN_ENABLED=true
  • WEBAUTHN_RP_ID=<domain> (e.g. login.example.com or example.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:

  1. config.features
  2. CEDROS_FEATURE_* environment variables
  3. 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=off

If 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.css once
  • customize through theme, themeOverrides, and unstyled

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:

  • users
  • team
  • referrals
  • deposits
  • withdrawals
  • compliance
  • accreditation-queue
  • sanctions
  • signup-gating
  • settings-auth
  • settings-email
  • settings-webhooks
  • settings-wallet
  • settings-credits
  • settings-compliance
  • settings-referrals
  • settings-signup
  • settings-server
  • settings-images

settings-messaging from the old standalone dashboard is now represented by the separate settings-email and settings-webhooks plugin sections.

How it works:

  1. Each package exports an AdminPlugin that defines its sections and components
  2. AdminShell from @cedros/admin-react aggregates sections from all registered plugins into a unified sidebar
  3. Sections are grouped (Users, Store, Configuration) and sorted by order
  4. 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 lint

License

MIT