Login Page

August 13, 2026 · View on GitHub

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 <h2> to avoid duplicate <h1> 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 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:

ElementValueNotes
Left panel backgroundbg-zinc-950Matches app background (--background: #09090b)
Gradient overlayfrom-blue-950/20 to-cyan-950/10Subtle blue tint for depth
Accent colortext-blue-400App's primary accent
Feature cardsbg-white/[0.03] border-white/[0.05]Glassmorphism, matching admin dashboard
Feature icon bgbg-blue-500/10 border-blue-500/10Blue-tinted icon containers
Ambient orbsbg-blue-500/[0.07], bg-cyan-500/[0.05]Soft glow, same pattern as admin dashboard
Dot gridopacity-[0.04], 32px spacingDecorative texture
Panel separatorbg-white/[0.06]1px right edge line
Text hierarchytext-whitetext-zinc-200text-zinc-400text-zinc-500text-zinc-6005-level opacity scale
Mobile iconbg-zinc-900 border-white/[0.08]Dark container with blue glow shadow
Form cardborder-muted-foreground/10 shadow-2xlShadcn Card with elevated shadow

Files

FilePurpose
src/app/login/page.tsxServer component, reads auth provider env var, forces dynamic rendering
src/app/login/login-form.tsxClient component, split-panel layout, OIDC/local form rendering
tests/components/LoginPage.test.tsxComponent tests — rendering, form submission, OIDC mode

Environment Variables

VariableDefaultEffect on Login
NEXT_PUBLIC_AUTH_PROVIDERlocal"oidc" → SSO button, "local" → email/password form
NEXT_PUBLIC_APP_VERSIONDisplayed in footer as v{version}
AUTH_BOOTSTRAPonoff/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:

const features = [
  { icon: Globe, title: '7+ Database Engines', desc: 'PostgreSQL, MySQL, ...' },
  { icon: Zap, title: 'AI-Assisted SQL', desc: 'Plan explanations, safety checks...' },
  // ...
];

Hero text is in the <h1> 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:

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