diff --git a/decisions/0029-authentik-user-registration-workflow.md b/decisions/0029-authentik-user-registration-workflow.md
new file mode 100644
index 0000000..2d50ed1
--- /dev/null
+++ b/decisions/0029-authentik-user-registration-workflow.md
@@ -0,0 +1,340 @@
+# Authentik User Registration and Approval Workflow
+
+* Status: accepted
+* Date: 2026-02-04
+* Deciders: Billy
+* Technical Story: Control access to homelab applications through vetted user registration
+
+## Context and Problem Statement
+
+The homelab hosts applications that should only be accessible to trusted users (family, close friends). While Authentik provides SSO, we need a controlled registration process where:
+1. Users can self-register
+2. An admin reviews and approves registrations
+3. Approved users get access only to specific applications based on their role
+
+How do we implement a user registration workflow with approval and conditional application access?
+
+## Decision Drivers
+
+* Self-service registration - users shouldn't need admin to create accounts
+* Admin approval - prevent unauthorized access
+* Granular access control - different users get different applications
+* Simple to manage - single admin shouldn't be overwhelmed
+* Audit trail - know who approved whom and when
+
+## Considered Options
+
+1. **Authentik enrollment flow with admin approval + group-based access**
+2. **Invite-only registration (no self-service)**
+3. **Open registration with post-hoc restriction**
+
+## Decision Outcome
+
+Chosen option: **Option 1 - Authentik enrollment flow with admin approval + group-based access**
+
+Authentik's enrollment stages allow custom registration flows. Combined with group-based application access, this provides a complete workflow:
+- Users self-register → account created but inactive/limited
+- Admin receives notification → reviews and assigns groups
+- User gets access only to applications their groups permit
+
+### Positive Consequences
+
+* Self-service reduces admin burden for initial registration
+* Approval step prevents unauthorized access
+* Group-based access scales well as applications grow
+* Full audit log of approvals
+* Can add MFA requirement during enrollment
+
+### Negative Consequences
+
+* Admin must actively monitor registration requests
+* Users experience delay between registration and access
+* Requires proper group/application mapping setup
+
+## Architecture
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ REGISTRATION FLOW │
+│ │
+│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
+│ │ User │───▶│ Enrollment │───▶│ Account │───▶│ Pending │ │
+│ │ Requests │ │ Form │ │ Created │ │ Approval │ │
+│ └──────────┘ └──────────────┘ └──────────────┘ └──────┬───────┘ │
+│ │ │
+│ ┌────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ ┌─────────────────────┐ │
+│ │ Admin Reviews │ │
+│ │ (Email/Dashboard) │ │
+│ └──────────┬──────────┘ │
+│ │ │
+│ ┌─────────────┼─────────────┐ │
+│ │ │ │ │
+│ ▼ ▼ ▼ │
+│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
+│ │ Reject │ │ Approve │ │ Approve │ │
+│ │ │ │ Basic │ │ Full │ │
+│ └──────────┘ └────┬─────┘ └────┬─────┘ │
+│ │ │ │
+│ ▼ ▼ │
+│ ┌──────────┐ ┌──────────┐ │
+│ │ homelab- │ │ homelab- │ │
+│ │ guests │ │ users │ │
+│ └──────────┘ └──────────┘ │
+└─────────────────────────────────────────────────────────────────────────────┘
+
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ APPLICATION ACCESS BY GROUP │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ homelab-admins │ │
+│ │ All applications + Authentik admin + Grafana admin │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ homelab-users │ │
+│ │ Affine, Kavita, Nextcloud, Immich, Kasm, ntfy, Vaultwarden │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ homelab-guests │ │
+│ │ Kavita only │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ pending-approval │ │
+│ │ No applications (can only access Authentik profile) │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+## Group Structure
+
+| Group | Purpose | Applications |
+|-------|---------|--------------|
+| `homelab-admins` | Full administrative access | All + Gitea, Flux UI, Authentik admin |
+| `homelab-users` | Trusted users (family) | Affine, Immich, Kasm, Kavita, Nextcloud, ntfy, Vaultwarden |
+| `homelab-guests` | Limited access (friends) | Kavita only |
+| `pending-approval` | Newly registered, not yet vetted | None (Authentik profile only) |
+
+## Application Access Matrix
+
+External applications exposed via `envoy-external` gateway:
+
+| Application | admins | users | guests | pending |
+|-------------|--------|-------|--------|---------|
+| Authentik (admin) | ✅ | ❌ | ❌ | ❌ |
+| Authentik (profile) | ✅ | ✅ | ✅ | ✅ |
+| Affine | ✅ | ✅ | ❌ | ❌ |
+| Gitea | ✅ | ❌ | ❌ | ❌ |
+| Immich | ✅ | ✅ | ❌ | ❌ |
+| Kasm | ✅ | ✅ | ❌ | ❌ |
+| Kavita | ✅ | ✅ | ✅ | ❌ |
+| Nextcloud | ✅ | ✅ | ❌ | ❌ |
+| ntfy | ✅ | ✅ | ❌ | ❌ |
+| Vaultwarden | ✅ | ✅ | ❌ | ❌ |
+| Flux UI | ✅ | ❌ | ❌ | ❌ |
+
+## Implementation
+
+### Step 1: Create Groups
+
+In Authentik Admin → Directory → Groups, create:
+
+1. **homelab-admins**
+ - Superuser status: Yes (for Authentik admin)
+
+2. **homelab-users**
+ - Superuser status: No
+
+3. **homelab-guests**
+ - Superuser status: No
+
+4. **pending-approval**
+ - Superuser status: No
+ - This is the default group for new registrations
+
+### Step 2: Create Enrollment Flow
+
+In Authentik Admin → Flows & Stages:
+
+#### 2a. Create Stages
+
+1. **enrollment-invitation-check** (Invitation Stage)
+ - Continue without invitation: Yes
+
+2. **enrollment-user-form** (Prompt Stage)
+ - Fields: username, email, name, password, password_repeat
+
+3. **enrollment-user-write** (User Write Stage)
+ - Create users as inactive: No
+ - Group: `pending-approval`
+
+4. **enrollment-user-login** (User Login Stage)
+ - Session duration: default
+
+#### 2b. Create Flow
+
+1. **Name:** `homelab-enrollment`
+2. **Slug:** `homelab-enrollment`
+3. **Designation:** Enrollment
+4. **Bind stages in order:**
+ - enrollment-invitation-check
+ - enrollment-user-form
+ - enrollment-user-write
+ - enrollment-user-login
+
+### Step 3: Configure Application Policies
+
+For each application, create a policy binding that requires group membership:
+
+#### 3a. Create Group Membership Policies
+
+In Authentik Admin → Policies:
+
+**Policy: require-homelab-users**
+```python
+# Expression Policy
+return ak_is_group_member(request.user, name="homelab-users") or \
+ ak_is_group_member(request.user, name="homelab-admins")
+```
+
+**Policy: require-homelab-guests-or-higher**
+```python
+# Expression Policy
+return ak_is_group_member(request.user, name="homelab-guests") or \
+ ak_is_group_member(request.user, name="homelab-users") or \
+ ak_is_group_member(request.user, name="homelab-admins")
+```
+
+**Policy: require-homelab-admins**
+```python
+# Expression Policy
+return ak_is_group_member(request.user, name="homelab-admins")
+```
+
+#### 3b. Bind Policies to Applications
+
+For each application in Applications → Applications:
+
+| Application | Policy to Bind |
+|-------------|----------------|
+| Affine | require-homelab-users |
+| Gitea | require-homelab-admins |
+| Immich | require-homelab-users |
+| Kasm | require-homelab-users |
+| Kavita | require-homelab-guests-or-higher |
+| Nextcloud | require-homelab-users |
+| ntfy | require-homelab-users |
+| Vaultwarden | require-homelab-users |
+| Flux UI | require-homelab-admins |
+
+### Step 4: Admin Notification
+
+Since no SMTP is configured (per ADR-0016), admin notification options:
+
+#### Option A: Dashboard Monitoring
+
+1. Periodically check Authentik Admin → Directory → Users
+2. Filter by group: `pending-approval`
+3. Review and move to appropriate group
+
+#### Option B: Webhook to Discord/Slack (Future Enhancement)
+
+Configure Authentik event to send webhook on user creation:
+
+```python
+# Event Rule: new-user-notification
+# Trigger: Model Created (User)
+# Action: Webhook to Discord
+
+webhook_url = "https://discord.com/api/webhooks/..."
+message = f"New user registration: {model.username} ({model.email})"
+```
+
+### Step 5: Approval Process
+
+When a new user registers:
+
+1. Admin receives notification (or checks dashboard)
+2. Admin reviews user information:
+ - Do I know this person?
+ - What access level should they have?
+3. Admin action:
+ - **Approve as user:** Remove from `pending-approval`, add to `homelab-users`
+ - **Approve as guest:** Remove from `pending-approval`, add to `homelab-guests`
+ - **Reject:** Delete user account
+4. User can now access their permitted applications
+
+## User Experience
+
+### Registration Flow (User Perspective)
+
+1. User visits `https://auth.daviestechlabs.io/if/flow/homelab-enrollment/`
+2. Fills out registration form (username, email, password)
+3. Account created, logged in automatically
+4. Sees Authentik user dashboard with no applications
+5. Message displayed: "Your account is pending approval"
+6. After admin approval, applications appear on dashboard
+
+### Custom Pending Message
+
+Create a custom user interface text in Authentik:
+
+In Admin → Flows → Default Interface → Customization:
+
+```html
+
+
+
Account Pending Approval
+
Your registration is being reviewed. You'll receive access to applications once approved.
+
Contact the homelab admin if you need expedited access.
+
+```
+
+## MFA Enforcement
+
+See [ADR-0030: MFA and Yubikey Strategy](0030-mfa-yubikey-strategy.md) for MFA configuration details.
+
+## Audit Trail
+
+Authentik logs all changes:
+
+- User creation (with registration timestamp)
+- Group membership changes (who approved, when)
+- Application access attempts (granted/denied)
+
+View in Admin → Events → Logs
+
+## Security Considerations
+
+### Rate Limiting
+
+- Enrollment flow should have rate limiting to prevent spam registrations
+- Configure in Flow settings or via reverse proxy
+
+### Account Cleanup
+
+- Periodically review `pending-approval` group
+- Delete accounts that have been pending > 30 days without approval
+
+### Group Escalation
+
+- Only `homelab-admins` can modify group memberships
+- Consider requiring MFA for admin actions
+
+## Future Enhancements
+
+1. **Email notifications** - When SMTP is configured, notify users of approval
+2. **Invitation codes** - Pre-approve specific registrations with invite links
+3. **Self-service group requests** - Users can request access to additional apps
+4. **Time-limited access** - Guest access expires after N days
+5. **Application-specific approval** - Per-app access requests
+
+## References
+
+* [Authentik Flows Documentation](https://docs.goauthentik.io/docs/flow/)
+* [Authentik Policies](https://docs.goauthentik.io/docs/policies/)
+* [Authentik Groups](https://docs.goauthentik.io/docs/user-group-role/groups/)
diff --git a/decisions/0030-mfa-yubikey-strategy.md b/decisions/0030-mfa-yubikey-strategy.md
new file mode 100644
index 0000000..89f8c3d
--- /dev/null
+++ b/decisions/0030-mfa-yubikey-strategy.md
@@ -0,0 +1,339 @@
+# MFA and Yubikey Strategy
+
+* Status: proposed
+* Date: 2026-02-04
+* Deciders: Billy
+* Technical Story: Enable hardware security key (Yubikey) authentication across homelab applications
+
+## Context and Problem Statement
+
+Password-only authentication is vulnerable to phishing and credential theft. Hardware security keys (Yubikeys) provide phishing-resistant authentication through WebAuthn/FIDO2. The homelab uses multiple authentication points:
+
+1. **Authentik** - Central SSO for most applications
+2. **Vaultwarden** - Password manager (independent auth)
+
+How do we enable Yubikey support across these authentication points while maintaining usability?
+
+## Decision Drivers
+
+* Phishing-resistant authentication for sensitive applications
+* Flexibility - support both hardware keys and TOTP
+* User experience - single Yubikey works everywhere
+* Fallback options - don't lock users out if key is lost
+* Future-proof - support passkeys and newer standards
+
+## Considered Options
+
+1. **WebAuthn/FIDO2 for both Authentik and Vaultwarden**
+2. **Yubikey OTP only**
+3. **TOTP only (no hardware key support)**
+
+## Decision Outcome
+
+Chosen option: **Option 1 - WebAuthn/FIDO2 for both Authentik and Vaultwarden**
+
+WebAuthn provides the best security (phishing-resistant) and user experience (touch to authenticate). Both Authentik and Vaultwarden support WebAuthn natively.
+
+### Positive Consequences
+
+* Phishing-resistant authentication
+* Single Yubikey works for all applications
+* No external dependencies (unlike Yubikey OTP)
+* Supports passkeys for future passwordless auth
+* Hardware-bound credentials can't be copied
+
+### Negative Consequences
+
+* Requires modern browser with WebAuthn support
+* Users must have physical access to key
+* Key loss requires recovery process
+
+## Implementation Status
+
+| Application | WebAuthn Support | Current Status | Action Required |
+|-------------|------------------|----------------|-----------------|
+| Authentik | ✅ Native | ✅ Working | Configure enforcement policies |
+| Vaultwarden | ✅ Native | ⚠️ Partial | Enable in admin settings |
+
+## Authentik Configuration
+
+### Current State
+
+Authentik has WebAuthn support built-in. Users can already enroll devices via:
+- Settings → MFA Devices → Enroll WebAuthn Device
+
+### Required Configuration
+
+#### 1. Create WebAuthn Authenticator Stage
+
+In Admin → Flows & Stages → Stages:
+
+**Stage: authenticator-webauthn-setup**
+```
+Type: Authenticator WebAuthn Setup Stage
+Name: authenticator-webauthn-setup
+User verification: preferred
+Resident key requirement: preferred
+Authenticator attachment: cross-platform
+```
+
+**Stage: authenticator-webauthn-validation**
+```
+Type: Authenticator Validation Stage
+Name: authenticator-webauthn-validation
+Device classes: WebAuthn, TOTP (allow both)
+Not configured action: skip
+```
+
+#### 2. Bind to Authentication Flow
+
+Edit the default authentication flow to include MFA:
+
+```
+Flow: default-authentication-flow
+Stages (in order):
+1. identification (username/email)
+2. password
+3. authenticator-webauthn-validation ← Add this
+4. user-login
+```
+
+#### 3. Create MFA Enforcement Policies
+
+**Policy: enforce-mfa-admins**
+```python
+# Expression Policy
+# Requires WebAuthn for admins
+if ak_is_group_member(request.user, name="homelab-admins"):
+ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
+ if not WebAuthnDevice.objects.filter(user=request.user).exists():
+ ak_message("Administrators must enroll a hardware security key (Yubikey)")
+ return False
+return True
+```
+
+**Policy: enforce-mfa-users**
+```python
+# Expression Policy
+# Requires any MFA for users
+if ak_is_group_member(request.user, name="homelab-users"):
+ from authentik.stages.authenticator_totp.models import TOTPDevice
+ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
+ has_totp = TOTPDevice.objects.filter(user=request.user, confirmed=True).exists()
+ has_webauthn = WebAuthnDevice.objects.filter(user=request.user).exists()
+ if not (has_totp or has_webauthn):
+ ak_message("Please enroll an MFA device (authenticator app or security key)")
+ return False
+return True
+```
+
+#### 4. User Enrollment Flow
+
+1. User logs into Authentik
+2. Navigates to Settings → MFA Devices
+3. Clicks "Enroll WebAuthn Device"
+4. Inserts Yubikey and touches when prompted
+5. Names the device (e.g., "Yubikey 5 NFC - Primary")
+6. Optionally enrolls backup device or TOTP
+
+## Vaultwarden Configuration
+
+### Current State
+
+Vaultwarden deployment has WebAuthn enabled by default, but admin panel configuration may be needed.
+
+### Required Configuration
+
+#### 1. Enable WebAuthn in Admin Panel
+
+Access admin panel at `https://vaultwarden.daviestechlabs.io/admin`:
+
+```
+Settings → Advanced:
+ Enable Web Vault: true (already set)
+
+Two-Factor Authentication:
+ Enable WebAuthn: true (verify this is set)
+```
+
+#### 2. Optional: Enable Yubikey OTP
+
+If users want Yubikey OTP as an additional option (the 44-character string feature):
+
+**Step 1: Get Yubico API Credentials**
+
+Visit: https://upgrade.yubico.com/getapikey/
+
+**Step 2: Store credentials in Vault**
+
+```bash
+vault kv put kv/vaultwarden-yubico \
+ client_id="YOUR_CLIENT_ID" \
+ secret_key="YOUR_SECRET_KEY"
+```
+
+**Step 3: Create ExternalSecret**
+
+Add to `/home/billy/homelab-k8s2/kubernetes/apps/security/vaultwarden/app/externalsecret.yaml`:
+
+```yaml
+---
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+ name: vaultwarden-yubico
+spec:
+ refreshInterval: 1h
+ secretStoreRef:
+ kind: ClusterSecretStore
+ name: vault
+ target:
+ name: vaultwarden-yubico
+ creationPolicy: Owner
+ data:
+ - secretKey: YUBICO_CLIENT_ID
+ remoteRef:
+ key: kv/data/vaultwarden-yubico
+ property: client_id
+ - secretKey: YUBICO_SECRET_KEY
+ remoteRef:
+ key: kv/data/vaultwarden-yubico
+ property: secret_key
+```
+
+**Step 4: Update Deployment**
+
+Add to `/home/billy/homelab-k8s2/kubernetes/apps/security/vaultwarden/app/deployment.yaml`:
+
+```yaml
+envFrom:
+ - secretRef:
+ name: vaultwarden-db-credentials
+ - secretRef:
+ name: vaultwarden-yubico # Add this
+```
+
+**Status:** ⏳ NOT IMPLEMENTED - Requires Yubico API credentials
+
+#### 3. User Setup (WebAuthn - Already Available)
+
+1. Log into Vaultwarden web vault
+2. Go to Settings → Security → Two-step Login
+3. Click Manage next to "FIDO2 WebAuthn"
+4. Click "Register new key"
+5. Insert Yubikey and touch when prompted
+6. Name the key (e.g., "Yubikey 5 NFC")
+
+## MFA Requirements by User Group
+
+| Group | MFA Requirement | Allowed Methods |
+|-------|-----------------|-----------------|
+| homelab-admins | Required | WebAuthn only |
+| homelab-users | Required | WebAuthn or TOTP |
+| homelab-guests | Optional | WebAuthn or TOTP |
+| pending-approval | Not required | N/A |
+
+## Recovery Procedures
+
+### Lost Yubikey - Authentik
+
+1. Admin accesses Authentik Admin → Directory → Users
+2. Find affected user
+3. Go to user's MFA Devices tab
+4. Delete lost device
+5. User can enroll new device on next login
+
+### Lost Yubikey - Vaultwarden
+
+1. User uses backup recovery code (generated at 2FA setup)
+2. Or admin accesses Vaultwarden admin panel
+3. Users → Find user → Disable 2FA
+4. User can re-enable with new device
+
+### Best Practice: Backup Keys
+
+Recommend users enroll:
+- Primary Yubikey (carried daily)
+- Backup Yubikey (stored securely at home)
+- TOTP as fallback (if allowed by policy)
+
+## Supported Yubikey Models
+
+| Model | WebAuthn | NFC | USB-C | Recommended For |
+|-------|----------|-----|-------|-----------------|
+| Yubikey 5 NFC | ✅ | ✅ | ❌ | Mobile + Desktop (USB-A) |
+| Yubikey 5C NFC | ✅ | ✅ | ✅ | Modern laptops |
+| Yubikey 5 Nano | ✅ | ❌ | ❌ | Always-in desktop |
+| Security Key NFC | ✅ | ✅ | ❌ | Budget option |
+
+## Architecture
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ User Login │
+└──────────────────────────────┬──────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────────┐
+│ Authentik SSO │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ Password │───▶│ WebAuthn │───▶│ Session │ │
+│ │ Stage │ │ Validation │ │ Created │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+│ │ │
+│ ┌──────┴──────┐ │
+│ │ Yubikey │ │
+│ │ Touch │ │
+│ └─────────────┘ │
+└──────────────────────────────┬──────────────────────────────────┘
+ │ SSO (no MFA needed again)
+ ┌────────────────────┼────────────────────┐
+ ▼ ▼ ▼
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
+ │ Affine │ │ Nextcloud│ │ Immich │
+ └──────────┘ └──────────┘ └──────────┘
+
+┌─────────────────────────────────────────────────────────────────┐
+│ Vaultwarden (Separate) │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ Master │───▶│ WebAuthn │───▶│ Vault │ │
+│ │ Password │ │ (2FA) │ │ Unlocked │ │
+│ └─────────────┘ └──────┬──────┘ └─────────────┘ │
+│ │ │
+│ ┌──────┴──────┐ │
+│ │ Yubikey │ │
+│ │ Touch │ │
+│ └─────────────┘ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+## Testing Checklist
+
+### Authentik WebAuthn
+- [ ] Enroll Yubikey as admin user
+- [ ] Verify login requires Yubikey touch
+- [ ] Test SSO to Affine with Yubikey
+- [ ] Test recovery with backup device
+- [ ] Verify non-admin can use TOTP instead
+
+### Vaultwarden WebAuthn
+- [ ] Enable WebAuthn in admin panel
+- [ ] Enroll Yubikey for test user
+- [ ] Verify vault unlock requires Yubikey
+- [ ] Test with browser extension
+- [ ] Test recovery code flow
+
+## Future Enhancements
+
+1. **Passkeys** - Enable passwordless login with resident keys
+2. **Conditional access** - Require hardware key for sensitive operations only
+3. **Device attestation** - Verify Yubikey authenticity
+4. **Session binding** - Bind sessions to hardware key
+
+## References
+
+* [Authentik WebAuthn Documentation](https://docs.goauthentik.io/docs/flow/stages/authenticator_webauthn/)
+* [Vaultwarden 2FA Configuration](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-WebAuthn-authentication)
+* [WebAuthn Specification](https://www.w3.org/TR/webauthn-2/)
+* [FIDO Alliance](https://fidoalliance.org/)
+* [Yubico Developer Program](https://developers.yubico.com/)