Creating the owner account
On first launch, visit/setup to create the owner account with an email and password. Once an owner exists, the setup page closes and /login becomes the entry point.
There is no self-service sign-up. The public
/api/auth/sign-up/email route is blocked; only the owner-bootstrap and invite flows create accounts.Two-factor authentication (email OTP)
Two-factor is opt-in per user and strongly recommended. Enable it under Settings → Security, or during onboarding at the Security step. When 2FA is on, each sign-in is:- Enter email + password.
- A 6-digit code is emailed to you (via Resend).
- Enter the code to finish signing in.
Inviting team members
From Settings → Team, the owner invites teammates by email and role. Each invite:- Creates an account with a temporary password (emailed to them).
- Forces a password reset on first login before they can use the dashboard.
Password reset
Users who forget their password use Forgot password? on the login page (/forgot-password) to receive an emailed reset link.
Email delivery
Sign-in codes, invites, and reset links are sent through the Resend integration (configure it under Integrations → Resend). If Resend isn’t configured, these messages are printed to the server logs instead — useful for local development:Rate limiting
Auth endpoints are rate-limited by better-auth (e.g. sign-in is capped at 3 attempts per 10 seconds, password reset at 3 per minute), and 2FA codes are invalidated after 5 wrong attempts. Counters are stored in the database (therate_limit table) so the limits hold across serverless instances — the in-memory default would reset on every cold start. Rate limiting is active in production automatically; no configuration is needed.
Demo mode
SetIS_DEMO_SITE=true to run a public, throwaway demo. Never enable this on a real deployment.
In demo mode:
- A demo merchant is seeded automatically —
[email protected]/123456— along with sample products, payment links, and transactions, so the dashboard looks populated. You can also seed manually withpnpm db:seed:demo. - The login page pre-fills the demo credentials and accepts a fixed confirmation code (
123456) instead of real 2FA. No email is sent. - Payout wallets are fixed and locked. They’re defined in
src/lib/auth/demo.ts(DEMO_PAYOUTS); the settings API rejects changes to them and the UI renders the fields read-only.