Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ JWT_SECRET=your_32_character_random_string_here
# AUTHENTICATION PROVIDER
# ============================================
# "local" (default) = email/password login (ADMIN_EMAIL/ADMIN_PASSWORD, USER_EMAIL/USER_PASSWORD)
# "oidc" = OpenID Connect SSO (Auth0, Keycloak, Okta, Azure AD, etc.)
# "oidc" = OpenID Connect SSO (Auth0, Keycloak, Okta, Azure AD, Zitadel, etc.)
NEXT_PUBLIC_AUTH_PROVIDER=local

# ============================================
Expand All @@ -43,6 +43,7 @@ NEXT_PUBLIC_AUTH_PROVIDER=local

# Scopes to request (default: openid profile email)
# OIDC_SCOPE=openid profile email
# if using Zitadel, add this scope: urn:zitadel:iam:org:project:roles

# Role mapping (optional) — claim path for determining admin vs user role
# Supports dot-notation for nested claims (e.g. "realm_access.roles")
Expand All @@ -56,6 +57,7 @@ NEXT_PUBLIC_AUTH_PROVIDER=local
# Keycloak: OIDC_ROLE_CLAIM=realm_access.roles
# Okta: OIDC_ROLE_CLAIM=groups
# Azure AD: OIDC_ROLE_CLAIM=roles
# Zitadel: OIDC_ROLE_CLAIM=urn:zitadel:iam:org:project:roles

# ============================================
# STORAGE PROVIDER (Optional)
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,5 @@ Thumbs.db


!.claude/skills
.orchids/
.codegraph/
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ The demo runs in **Demo Mode** with simulated data. No real database required!

### Authentication & SSO
- **Dual Auth Modes**: Local email/password login or OpenID Connect (OIDC) Single Sign-On — switchable via environment variable.
- **Vendor-Agnostic OIDC**: Works with any OIDC-compliant provider — Auth0, Keycloak, Okta, Azure AD, Google, and more.
- **Vendor-Agnostic OIDC**: Works with any OIDC-compliant provider — Auth0, Keycloak, Okta, Azure AD, Zitadel, Google, and more.
- **PKCE Security**: Authorization Code Flow with Proof Key for Code Exchange (S256) for secure authentication.
- **Auto Role Mapping**: Configurable claim-based role mapping with dot-notation for nested claims (e.g., `realm_access.roles`).
- **Provider Logout**: Logout clears both local JWT session and identity provider session.
Expand Down Expand Up @@ -406,7 +406,7 @@ LibreDB Studio is optimized for K8s with:
- [x] **Phase 12**: Advanced Charting (Scatter, Histogram, Stacked Charts, Aggregation, Date Grouping, Chart Save/Load, Chart Dashboard).
- [x] **Phase 13**: Monitoring Enhancement (Time-Series Trends, Threshold Alerting, Connection Pool Stats, Configurable Polling).
- [x] **Phase 14**: Enterprise Database Support (Oracle Database via oracledb Thin mode, Microsoft SQL Server via mssql/tedious).
- [x] **Phase 15**: SSO Integration — Vendor-agnostic OIDC authentication (Auth0, Keycloak, Okta, Azure AD) with PKCE, role mapping, and provider logout.
- [x] **Phase 15**: SSO Integration — Vendor-agnostic OIDC authentication (Auth0, Keycloak, Okta, Azure AD, Zitadel) with PKCE, role mapping, and provider logout.
- [ ] **Phase 16**: DBA & Monitoring (Lock Dependency Graph, Vacuum Scheduler, Prometheus Export).
- [ ] **Phase 17**: Enterprise Collaboration (User Identity, Shared Workspaces, SAML 2.0).

Expand All @@ -419,7 +419,7 @@ LibreDB Studio is optimized for K8s with:
| [DeepWiki](https://deepwiki.com/libredb/libredb-studio) | AI-powered documentation — always up-to-date with the codebase |
| [SonarCloud](https://sonarcloud.io/project/overview?id=libredb_libredb-studio) | Code quality, security analysis, and technical debt tracking |
| [API Docs](docs/API_DOCS.md) | Complete REST API reference |
| [OIDC Setup Guide](docs/OIDC_SETUP.md) | SSO configuration for Auth0, Keycloak, Okta, Azure AD |
| [OIDC Setup Guide](docs/OIDC_SETUP.md) | SSO configuration for Auth0, Keycloak, Okta, Azure AD, Zitadel |
| [OIDC Architecture](docs/OIDC_ARCH.md) | OIDC subsystem internals, security model, extension points |
| [Theming Guide](docs/THEMING.md) | CSS theming, dark mode, and styling customization |
| [Architecture](docs/ARCHITECTURE.md) | System architecture and design patterns |
Expand Down
28 changes: 28 additions & 0 deletions docs/OIDC_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,34 @@ Navigate to `/login` and click **"Login with SSO"**.
OIDC_ADMIN_ROLES=Admin,admin
```

### Zitadel

1. **Create Project & Application** in Zitadel Console → Projects → Create New Project → Add Application (Web)
- Auth Method: PKCE

2. **Settings:**
```
Redirect URIs: http://localhost:3000/api/auth/oidc/callback
Post Logout URIs: http://localhost:3000/login
```

3. **Environment Variables:**
```env
NEXT_PUBLIC_AUTH_PROVIDER=oidc
OIDC_ISSUER=https://your-instance.zitadel.cloud
OIDC_CLIENT_ID=your_client_id
OIDC_CLIENT_SECRET=your_client_secret
```

4. **Role Mapping:**

Zitadel includes roles if requested via scopes. Ensure `OIDC_SCOPE` includes `urn:zitadel:iam:org:project:roles`.
```env
OIDC_SCOPE=openid profile email urn:zitadel:iam:org:project:roles
OIDC_ROLE_CLAIM=urn:zitadel:iam:org:project:roles
OIDC_ADMIN_ROLES=admin
```

### Google Workspace

1. **Create OAuth Client** in Google Cloud Console → APIs & Services → Credentials → Create OAuth Client ID → Web Application
Expand Down
9 changes: 9 additions & 0 deletions src/lib/oidc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ export function buildLogoutUrl(returnTo: string): string | null {
try {
const config = getOIDCConfig();
const issuerUrl = new URL(config.issuer);
const roleClaim = config.roleClaim;

// Auth0 uses /v2/logout
if (issuerUrl.hostname.includes('auth0.com')) {
Expand All @@ -237,6 +238,14 @@ export function buildLogoutUrl(returnTo: string): string | null {
return logoutUrl.toString();
}

// Zitadel RP-Initiated Logout
if (roleClaim.includes('zitadel')) {
const logoutUrl = new URL('/oidc/v1/end_session', config.issuer);
logoutUrl.searchParams.set('client_id', config.clientId);
logoutUrl.searchParams.set('post_logout_redirect_uri', returnTo);
return logoutUrl.toString();
}

// Generic OIDC (Keycloak, Okta, Azure AD, etc.) — RP-Initiated Logout
const logoutUrl = new URL('/protocol/openid-connect/logout', config.issuer);
logoutUrl.searchParams.set('client_id', config.clientId);
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/lib/oidc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,16 @@ describe('buildLogoutUrl', () => {
expect(url).toContain('post_logout_redirect_uri=');
});

test('builds Zitadel logout URL', () => {
process.env.OIDC_ISSUER = 'https://my-instance.zitadel.cloud';
process.env.OIDC_ROLE_CLAIM = 'urn:zitadel:iam:org:project:roles';
const url = buildLogoutUrl('http://localhost:3000/login');
expect(url).not.toBeNull();
expect(url).toContain('/oidc/v1/end_session');
expect(url).toContain('client_id=test-client-id');
expect(url).toContain('post_logout_redirect_uri=');
});

test('returns null when OIDC config is missing', () => {
delete process.env.OIDC_ISSUER;
const url = buildLogoutUrl('http://localhost:3000/login');
Expand Down