# Login Page
The login page uses a responsive split-panel layout that adapts between OIDC (SSO) and local (email/password) authentication modes.
---
## Architecture
```
src/app/login/
├── page.tsx # Server component — reads NEXT_PUBLIC_AUTH_PROVIDER env var
└── login-form.tsx # Client component — all UI and auth logic
```
**`page.tsx`** is a server component with `export const dynamic = 'force-dynamic'` to ensure the auth provider env var is read at runtime (critical for Docker deployments where the env var is not available during build).
**`login-form.tsx`** receives `authProvider` as a prop and renders the appropriate form based on whether the value is `"oidc"` or `"local"` (default).
---
## Layout
### Desktop (lg and above)
```
┌─────────────────────────────┬──────────────────────┐
│ │ │
│ Left Panel (55%) │ Right Panel (45%) │
│ │ │
│ ┌─ Logo ─────────────┐ │ ┌──────────────┐ │
│ │ 🔲 LibreDB Studio │ │ │ Welcome back │ │
│ └────────────────────┘ │ │ │ │
│ │ │ [Form] │ │
│ Hero text │ │ │ │
│ "The open-source SQL │ │ OIDC: SSO │ │
│ IDE for cloud-native │ │ button │ │
│ teams" │ │ │ │
│ │ │ Local: │ │
│ ┌─────────┐ ┌─────────┐ │ │ email/pass │ │
│ │Feature 1│ │Feature 2│ │ │ │ │
│ └─────────┘ └─────────┘ │ │ │ │
│ ┌─────────┐ ┌─────────┐ │ └──────────────┘ │
│ │Feature 3│ │Feature 4│ │ │
│ └─────────┘ └─────────┘ │ │
│ │ │
│ Supported Databases │ │
│ [PG] [MySQL] [MongoDB].. │ │
│ │ │
└─────────────────────────────┴──────────────────────┘
```
### Mobile (below lg)
```
┌──────────────────────┐
│ 🔲 LibreDB Studio │ ← Compact branding
│ Open-source SQL IDE │
│ │
│ ┌──────────────┐ │
│ │ Sign in │ │
│ │ │ │
│ │ [Form] │ │
│ │ │ │
│ └──────────────┘ │
│ │
│ [PG] [MySQL] ... │ ← DB badges
└──────────────────────┘
```
- Left branding panel is hidden (`hidden lg:flex`)
- Mobile branding appears above the card (`lg:hidden`)
- Card title: "Welcome back" (desktop) / "Sign in" (mobile)
- Card description adapts per viewport
- Accessibility: mobile branding uses `
` to avoid duplicate `` tags
---
## Authentication Modes
### OIDC Mode (`NEXT_PUBLIC_AUTH_PROVIDER=oidc`)
When OIDC is active, the right panel shows:
1. **ShieldCheck icon** with "Single Sign-On" label
2. **"Login with SSO" button** — triggers a full-page redirect to `/api/auth/oidc/login`
3. **Security badges** — "Encrypted" and "OIDC Protected"
The SSO flow uses standard browser redirect (not popup). The OIDC login route handles PKCE, and the callback route creates a local JWT session before redirecting to `/` or `/admin` based on the mapped role.
### Local Mode (`NEXT_PUBLIC_AUTH_PROVIDER=local`, default)
When local auth is active, the right panel shows:
1. **Email/password form** with icon-prefixed inputs
2. **"Sign In" button** — calls `POST /api/auth/login` with JSON body
On successful login, the user is redirected based on their role:
- `admin` → `/admin`
- `user` → `/`
On failure, the form surfaces the API's `message` via a toast instead of a
generic error:
- Wrong credentials → `"Invalid email or password"` (401).
- Server not configured (missing `ADMIN_PASSWORD`, or a missing/too-short
`JWT_SECRET`) → the actionable `AuthConfigError` message (503), e.g. *"Login
is unavailable: this server has no administrator password configured. Set
the ADMIN_PASSWORD environment variable and restart the server."* — never a
misleading "Invalid email or password" (PR #106).
**Zero-config first run:** if `ADMIN_PASSWORD`/`JWT_SECRET` are missing, they
are generated at boot and the admin password is printed once to the server
log instead of the 503 above; see [DISTRIBUTION.md](../DISTRIBUTION.md) for
the full behavior. Set `AUTH_BOOTSTRAP=off` to disable generation and exercise
the 503 path with explicit credentials.
---
## Design System
The login page follows the app's premium dark aesthetic:
| Element | Value | Notes |
|---------|-------|-------|
| Left panel background | `bg-zinc-950` | Matches app background (`--background: #09090b`) |
| Gradient overlay | `from-blue-950/20 to-cyan-950/10` | Subtle blue tint for depth |
| Accent color | `text-blue-400` | App's primary accent |
| Feature cards | `bg-white/[0.03] border-white/[0.05]` | Glassmorphism, matching admin dashboard |
| Feature icon bg | `bg-blue-500/10 border-blue-500/10` | Blue-tinted icon containers |
| Ambient orbs | `bg-blue-500/[0.07]`, `bg-cyan-500/[0.05]` | Soft glow, same pattern as admin dashboard |
| Dot grid | `opacity-[0.04]`, 32px spacing | Decorative texture |
| Panel separator | `bg-white/[0.06]` | 1px right edge line |
| Text hierarchy | `text-white` → `text-zinc-200` → `text-zinc-400` → `text-zinc-500` → `text-zinc-600` | 5-level opacity scale |
| Mobile icon | `bg-zinc-900 border-white/[0.08]` | Dark container with blue glow shadow |
| Form card | `border-muted-foreground/10 shadow-2xl` | Shadcn Card with elevated shadow |
---
## Files
| File | Purpose |
|------|---------|
| `src/app/login/page.tsx` | Server component, reads auth provider env var, forces dynamic rendering |
| `src/app/login/login-form.tsx` | Client component, split-panel layout, OIDC/local form rendering |
| `tests/components/LoginPage.test.tsx` | Component tests — rendering, form submission, OIDC mode |
---
## Environment Variables
| Variable | Default | Effect on Login |
|----------|---------|-----------------|
| `NEXT_PUBLIC_AUTH_PROVIDER` | `local` | `"oidc"` → SSO button, `"local"` → email/password form |
| `NEXT_PUBLIC_APP_VERSION` | — | Displayed in footer as `v{version}` |
| `AUTH_BOOTSTRAP` | on | `off`/`false`/`0` (case-insensitive) disables zero-config credential generation, so a missing `ADMIN_PASSWORD`/`JWT_SECRET` surfaces the 503 error above instead |
---
## Customization
### Changing branding text
Edit the `features` array and hero text in `login-form.tsx`:
```tsx
const features = [
{ icon: Globe, title: '7+ Database Engines', desc: 'PostgreSQL, MySQL, ...' },
{ icon: Zap, title: 'AI-Native Queries', desc: 'Natural language to SQL...' },
// ...
];
```
Hero text is in the `` element. The gradient word uses `from-blue-400 to-cyan-400`.
### Changing database badges
Both desktop and mobile lists are separate arrays. Keep them in sync:
```tsx
// Desktop (left panel, line ~142)
{['PostgreSQL', 'MySQL', 'MongoDB', 'Oracle', 'SQL Server'].map(...)}
// Mobile (bottom pills, line ~297)
{['PostgreSQL', 'MySQL', 'MongoDB', 'Oracle', 'SQL Server'].map(...)}
```
### Changing colors
To align with a different brand, update these Tailwind classes:
- **Accent**: Replace `blue-400`, `blue-500`, `blue-950` with your color
- **Gradient text**: `from-blue-400 to-cyan-400` on the hero heading
- **Feature icons**: `bg-blue-500/10 border-blue-500/10` and `text-blue-400`
- **Mobile icon**: `bg-blue-500/20` glow and `text-blue-400` icon