ips/docs/ai/plans/2026-02-25-phase4-openidconnect-auth.md
Till Wegmueller 9814635a32
feat: Preserve manifest text through install pipeline, add architecture plans
Manifest text is now carried through the solver's ResolvedPkg and written
directly to disk during install, eliminating the redundant re-fetch from
the repository that could silently fail. save_manifest() is now mandatory
(fatal on error) since the .p5m file on disk is the authoritative record
for pkg verify and pkg fix.

Add ADRs for libips API layer (GUI sharing), OpenID Connect auth, and
SQLite catalog as query engine (including normalized installed_actions
table). Add phase plans for code hygiene, client completion, catalog
expansion, and OIDC authentication.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 17:28:10 +01:00

139 lines
3.4 KiB
Markdown

# Phase 4: OpenID Connect Authentication
**Date:** 2026-02-25
**Status:** Active
**Depends on:** Phase 1 (architecture)
**Implements:** ADR-002
## Goal
Secure the REST API with OIDC JWT validation. Allow CLI and GUI clients to authenticate via standard OIDC flows.
## Step 1: Server-side JWT validation (pkg6depotd)
### 1.1: Add dependencies
```toml
# pkg6depotd/Cargo.toml
jsonwebtoken = "9"
reqwest = { version = "0.12", features = ["json"] } # for JWKS fetch
serde_json = "1"
```
### 1.2: Configuration
```kdl
// depot.kdl
auth {
enabled true
oidc-issuer "https://keycloak.example.com/realms/ips"
required-scopes "ips:read" "ips:write"
// Optional: per-publisher access via JWT claims
publisher-claim "ips_publishers"
}
```
### 1.3: JWKS fetcher
Background task that fetches and caches JWKS from the OIDC provider:
```rust
pub struct JwksCache {
keys: RwLock<jwk::JwkSet>,
issuer: String,
jwks_uri: String,
}
impl JwksCache {
pub async fn new(issuer: &str) -> Result<Self> { /* fetch .well-known/openid-configuration */ }
pub async fn refresh(&self) -> Result<()> { /* re-fetch JWKS */ }
pub fn validate_token(&self, token: &str) -> Result<Claims> { /* decode + verify */ }
}
```
### 1.4: Auth middleware
Axum middleware that validates Bearer tokens on protected routes:
```rust
pub async fn require_auth(
State(jwks): State<Arc<JwksCache>>,
req: Request,
next: Next,
) -> Result<Response, DepotError> {
let token = extract_bearer_token(&req)?;
let claims = jwks.validate_token(&token)?;
// Check required scopes
// Inject claims into request extensions
next.run(req).await
}
```
Apply to: POST routes (publish, index rebuild). Leave GET routes (catalog, manifest, file, search) unauthenticated by default. Add optional `auth.require-read true` config to protect everything.
## Step 2: Client-side OIDC (libips RestBackend)
### 2.1: Add dependencies
```toml
# libips/Cargo.toml
openidconnect = "4"
```
### 2.2: CredentialProvider trait
```rust
pub trait CredentialProvider: Send + Sync {
fn get_token(&self) -> Result<String>;
fn refresh_if_needed(&self) -> Result<()>;
}
```
### 2.3: Device Code Flow (CLI)
```rust
pub struct DeviceCodeProvider {
issuer: String,
client_id: String,
token_path: PathBuf, // cached token on disk
}
```
Flow:
1. Call device authorization endpoint
2. Print "Open https://... and enter code: ABCD-EFGH"
3. Poll token endpoint until user completes
4. Cache token + refresh token to `{image}/.pkg/auth/{publisher}.json`
5. On subsequent calls, use refresh token if access token expired
### 2.4: Wire into RestBackend
```rust
impl RestBackend {
pub fn with_credentials(mut self, provider: Arc<dyn CredentialProvider>) -> Self {
self.credential_provider = Some(provider);
self
}
}
```
All HTTP requests check for credential provider and add `Authorization: Bearer {token}` header.
## Step 3: Token storage
Tokens stored in image metadata:
```
{image_root}/.pkg/auth/
{publisher}.json -- { "access_token": "...", "refresh_token": "...", "expires_at": "..." }
```
File permissions: `0600` (owner read/write only).
## Verification
- Start Keycloak/Dex in Docker for testing
- Verify unauthenticated GET requests still work
- Verify protected POST requires valid Bearer token
- Verify expired tokens are rejected
- Verify CLI device code flow obtains and caches token
- Verify token refresh works transparently