Authentication
Authentication runs on Better Auth inside the Worker, stored in D1 through the Drizzle adapter. Its endpoints live under /api/auth; the full list is in the Auth REST API reference.
| Client | How it signs in |
|---|---|
| Web and PWA | Session cookie on gymgym.club. Google, GitHub, passkeys, or a guest account that can be upgraded later by linking a provider. |
| CLI | Device code flow: gymgym login shows a code, you approve it at /device while signed in, and the CLI keeps a bearer token. Only first-party client ids (DEVICE_CLIENTS in packages/api/src/auth/plugins.ts) may start this flow. |
| Native apps and watches (later) | Bearer token from the same device flow or handed over by the phone app. |
| Telegram Mini App (later) | A Better Auth plugin that checks Telegram’s signed initData. |
Providers
Section titled “Providers”Set these Worker secrets to enable a provider. Without them, the button is hidden.
| Provider | Secrets | Callback URL |
|---|---|---|
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET |
https://gymgym.club/api/auth/callback/google |
|
| GitHub | GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET |
https://gymgym.club/api/auth/callback/github |
| Cloudflare (optional) | ENABLE_CLOUDFLARE_LOGIN=1, CLOUDFLARE_CLIENT_ID, CLOUDFLARE_CLIENT_SECRET |
https://gymgym.club/api/auth/callback/cloudflare |
GitHub allows one callback URL per OAuth app, so staging and local development each need their own app. Google accepts several callback URLs on one client.
Passkeys use relying party id gymgym.club (PASSKEY_RP_ID), so they also work on subdomains such as staging.gymgym.club.
Admins and invites
Section titled “Admins and invites”Accounts whose email is in ADMIN_EMAILS, or whose user id is in ADMIN_USER_IDS (both comma-separated), get the admin role when they sign up or sign in, so listing an existing account works on its next sign-in. Taking someone off a list does not remove the role. With INVITE_ONLY=1, new accounts need an invite code created by an admin.
Instance switches
Section titled “Instance switches”| Variable | Effect |
|---|---|
PASSWORD_LOGIN=1 |
Staging or production also accept an email and password. Sign-up by password stays off: a signed-in user adds a password in Settings (the setPassword mutation) and changes it with /api/auth/change-password. So nobody can register an address they don’t control, and Google or GitHub linking by email can’t hand such an account to whoever registered the address first. Someone who forgets the password signs in the way they first did and sets a new one. |
ALLOW_GUEST=0 |
Turns off guest accounts. New passkey-only accounts start as guests, so they go too; existing passkeys keep working. |
DEFAULT_LANG=de |
Language for visitors and new accounts whose browser asks for none of the supported ones (English otherwise). |
Locally (ENVIRONMENT=development) email and password sign-in and sign-up are always on, for the seed accounts and testing.
In Settings, each passkey can be renamed or removed; the last way into a guest account can’t be removed.
Sign-in log
Section titled “Sign-in log”Every new session adds a row to sign_in: when, how (google, github, passkey, password, guest, cli), the country from Cloudflare’s cf-ipcountry and a short device label such as “Firefox · macOS”. IP addresses and full user agents are not stored. Admins read it on the dashboard (admin.signIns); rows older than 90 days are pruned by the hourly cron.
Rate limits
Section titled “Rate limits”Better Auth’s rate limiter is on in staging and production and off locally, so tests and development aren’t throttled.