Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
0d2e406
:hammer: Publish database module
turtton Oct 10, 2024
faa5a58
:hammer: Prepare axum server
turtton Oct 10, 2024
d42fe23
:construction: Add get accounts api
turtton Nov 7, 2024
6aa47ab
:memo: Add keycloak start command
turtton Nov 28, 2024
bbf7d7e
:construction: Add basic permission system
turtton Dec 5, 2024
e8afaff
:pushpin: Share destructure/dotenvy
turtton Jan 25, 2025
f83646e
:memo: Update keycloak script
turtton Jan 25, 2025
c7a2c45
:recycle: Change stellar related structure as auth
turtton Jan 25, 2025
ed37f2c
:hammer: Add some functions
turtton Jan 25, 2025
13ed900
:hammer: Add created_at field in Account
turtton Jan 25, 2025
ee00562
:bug: Fix invalid param
turtton Jan 25, 2025
cb0b393
:hammer: Implement get_all_accounts
turtton Jan 25, 2025
812bf33
:white_check_mark: Fix test codes
turtton Jan 25, 2025
f4c70ac
:hammer: Add get account path
turtton Jan 25, 2025
9369963
:bug: Add missed case
turtton Jan 27, 2025
b8f8dde
:art: Format codes
turtton Jan 27, 2025
2e1d3bd
:wrench: Automate keycloak initialization
turtton Feb 7, 2025
bf1c9be
:see_no_evil: Ignore sqlx log folder
turtton Feb 7, 2025
f18ef05
:truck: Update keycloak files
turtton Feb 13, 2025
981f1fc
:arrow_up: Update flake.lock
turtton Feb 13, 2025
33aacdf
:heavy_plus_sign: Add test_with
turtton Feb 13, 2025
8e2e0f5
:wrench: Add server as package
turtton Feb 13, 2025
be3f432
:wrench: Add keycloak env defaults
turtton Feb 13, 2025
e413412
:recycle: Extract closure to function and improve response
turtton Feb 20, 2025
afd85f2
:bulb: I tried to use mold but gained no advantages...(1sec slower th…
turtton Feb 27, 2025
2bf4ac8
:hammer: Add defaults
turtton Feb 27, 2025
eba1b84
:construction: Add auth_account treatments
turtton Feb 27, 2025
3fc173d
:art: Format codes
turtton Mar 13, 2025
ca4cd4d
:hammer: Implement get_auth_account
turtton Mar 13, 2025
30397c3
:hammer: Add redis database
turtton Mar 23, 2025
72418da
:hammer: Implement DatabaseConnection for RedisConnection
turtton Mar 27, 2025
14f3290
:hammer: Add Signal system
turtton Mar 27, 2025
5af3c72
:hammer: Add persist_and_emit in command handler
turtton Apr 12, 2025
edeb399
:hammer: Implement event applier system
turtton Apr 12, 2025
840b160
:lock: Implement RSA-2048 key pair generation with encryption
turtton Dec 31, 2025
e1f99db
:hammer: Implement create/get/delete account methods
turtton Jan 1, 2026
e0c768c
:wrench: Add agent settings
turtton Jan 1, 2026
5ecbd87
:hammer: Add default generator
turtton Jan 1, 2026
cbd06b0
:recycle: Add adapter layer and improve crypto security
turtton Jan 2, 2026
dfe4ce3
:recycle: Replace Account creation event sourcing with direct INSERT
turtton Feb 16, 2026
f38a1d1
:sparkles: Add PUT /accounts/:id endpoint for updating account is_bot…
turtton Feb 17, 2026
6f27c9a
:recycle: Return 204 No Content from PUT /accounts/:id
turtton Feb 18, 2026
43bee15
:recycle: Add AccountRepository adapter to unify Query/Modifier access
turtton Mar 8, 2026
70157f5
:recycle: Introduce AccountReadModel and AccountEventStore for proper…
turtton Mar 9, 2026
93d17db
:white_check_mark: Fix event store tests and add optimistic concurren…
turtton Mar 9, 2026
e80e878
:recycle: Rename Transaction to Executor in trait definitions
turtton Mar 9, 2026
343bf98
:recycle: Introduce adapter layer with AccountCommandProcessor/QueryP…
turtton Mar 9, 2026
a90d265
:hammer: Update lock file
turtton Mar 9, 2026
90d1040
:recycle: Apply CQRS pattern to AuthAccount and remove generic Event …
turtton Mar 9, 2026
364ceb8
:bug: Fix AuthAccount CQRS issues found in code review
turtton Mar 9, 2026
3e9a5e4
:memo: Fix CLAUDE.md discrepancies found by code review
turtton Mar 9, 2026
149875a
:recycle: Apply CQRS to Profile/Metadata and Repository pattern to Fo…
turtton Mar 9, 2026
21867bd
:recycle: Replace Keycloak with Ory Kratos + Hydra for authentication
turtton Mar 12, 2026
ebeb9ac
:white_check_mark: Add OAuth2 Login/Consent integration tests with wi…
turtton Mar 12, 2026
26d1bbd
:recycle: Skip OAuth2 tests when DATABASE_URL is unavailable
turtton Mar 12, 2026
06120c2
:wrench: Use test_with::env to skip OAuth2 tests when DATABASE_URL is…
turtton Mar 12, 2026
17ec372
:sparkles: Introduce Ory Keto permission system with Relation Tuple a…
turtton Mar 12, 2026
aa95db1
:recycle: Refactor Account deletion to deactivation with cascade cleanup
turtton Mar 12, 2026
6ed0d4e
:sparkles: Replace individual GET endpoints with batch fetch APIs
turtton Mar 12, 2026
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
16 changes: 16 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read file_path; if [[ \"$file_path\" == *.rs ]]; then cargo fmt 2>/dev/null || true; fi; }",
"timeout": 30
}
]
}
]
}
}
20 changes: 16 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
DATABASE_URL=postgres://user:password@localhost:5432/dbname
DATABASE_URL=postgres://postgres:develop@localhost:5432/postgres
# or
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_NAME=dbname
DATABASE_USER=postgres
DATABASE_PASSWORD=develop
DATABASE_NAME=postgres

HYDRA_ISSUER_URL=http://localhost:4444/
HYDRA_ADMIN_URL=http://localhost:4445/
KRATOS_PUBLIC_URL=http://localhost:4433/
EXPECTED_AUDIENCE=account

KETO_READ_URL=http://localhost:4466
KETO_WRITE_URL=http://localhost:4467

REDIS_URL=redis://localhost:6379
# or
REDIS_HOST=localhost
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
.idea
.direnv
/migrations/dbml-error.log
.env
.env
logs
master-key-password
183 changes: 183 additions & 0 deletions .review/keycloak-to-ory-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
---
feature: keycloak-to-ory-migration
started: 2026-03-11
phase: implementing
---

# Keycloak to Ory Migration (Phase 1: Kratos + Hydra)

## Requirements

### Purpose

Replace Keycloak with Ory Kratos (identity management) + Ory Hydra (OAuth2/OIDC) to reduce infrastructure weight. Enable self-service registration. Login/Consent UI is ShuttlePub frontend's responsibility.

### Scope - Do

- Remove all Keycloak dependencies from server layer (`axum-keycloak-auth`, `KeycloakAuthLayer`, `KeycloakToken`, `expect_role!` macro)
- Implement JWT validation middleware for Hydra-issued tokens
- Validate: `iss`, `aud`, `exp`, `sub`
- JWKS: auto-resolve via OIDC Discovery, in-memory cache, re-fetch on kid miss (rate-limited), lazy init on startup failure
- Audience: configurable via `EXPECTED_AUDIENCE` env var
- Implement Login/Consent Provider backend in server layer (Hydra login/consent accept/reject API)
- Value mapping: `AuthHost.url` = Hydra issuer URL, `AuthAccount.client_id` = Kratos identity UUID (JWT `sub`)
- Simplify authorization to auth-check only (JWT validity). Role-based authz deferred to Phase 2 (Ketos)
- Dev environment: Keycloak -> Kratos + Hydra containers, self-service registration enabled
- Kratos identity schema: minimal (email + password)
- Seed data: test user (email: testuser@example.com / password: testuser) via Kratos import
- Existing AuthHost/AuthAccount data: discard (dev only, no migration)
- Env vars: `HYDRA_ISSUER_URL`, `HYDRA_ADMIN_URL`, `EXPECTED_AUDIENCE`
- Update server-layer auth tests. JWT unit tests use test RSA keypair + mock JWKS. Integration tests use `test_with::env` pattern

### Scope - Don't

- Ory Ketos introduction (Phase 2)
- Authorization checker trait in kernel (Phase 2)
- Multi-AuthAccount permission separation (Phase 2+)
- Entity structure changes (AuthAccount, AuthHost, etc.)
- kernel / adapter / application / driver layer changes
- Login UI implementation (ShuttlePub frontend responsibility)

## Design

### A. JWT Validation Middleware

Replace `axum-keycloak-auth` with `jsonwebtoken` + `reqwest`.

```
server/src/auth.rs (new, replaces keycloak.rs)
├── OidcConfig — issuer URL, expected audience, jwks_refetch_interval_secs (configurable for tests)
├── JwksCache — in-memory (Arc<RwLock<JwkSet>>), kid miss → re-fetch (rate-limited), lazy init
├── AuthClaims — standard OIDC claims (iss, sub, aud, exp)
├── OidcAuthInfo — from AuthClaims: issuer → AuthHost.url, subject → AuthAccount.client_id
├── auth_middleware() — axum middleware: Bearer token → validate → Extension<AuthClaims>
└── resolve_auth_account_id() — rewritten with OidcAuthInfo (same find-or-create logic)
```

Value mapping: JWT `iss` → AuthHost.url, JWT `sub` (= Kratos identity UUID) → AuthAccount.client_id.
Hydra login accept sets `subject = Kratos identity.id`, so JWT `sub` = Kratos identity UUID.

### B. Login/Consent Provider

```
server/src/route/oauth2.rs (new) — NOT under auth_middleware
├── GET /oauth2/login — login_challenge → Kratos session check → Hydra login accept/redirect
├── GET /oauth2/consent — consent_challenge → skip check → unified JSON response
│ skip: { action: "redirect", redirect_to: "..." }
│ non-skip: { action: "show_consent", consent_challenge, client_name, requested_scope }
├── POST /oauth2/consent — consent result → Hydra consent accept/reject
```

### C. Hydra Admin API Client

```
server/src/hydra.rs (new, reqwest-based)
├── HydraAdminClient
│ ├── get_login_request / accept_login / reject_login
│ └── get_consent_request / accept_consent / reject_consent
```

### D. Kratos Client

```
server/src/kratos.rs (new)
├── KratosClient
│ └── whoami(cookie) -> Option<KratosSession>
```

Domain premise: Kratos and Emumet on same domain (subdomain) for cookie reachability.

### E. Route Handler Changes

account.rs, profile.rs, metadata.rs: KeycloakToken → AuthClaims, remove KeycloakAuthLayer, remove expect_role!.
route.rs: remove `to_permission_strings` and its test, add oauth2 module.

Router structure in main.rs:
```
Router::new()
.route_account/profile/metadata(...)
.layer(auth_middleware(...)) // JWT required
.route_oauth2(...) // NO auth_middleware
.layer(CorsLayer)
.with_state(app)
```

### F. Handler / AppModule

Handler gets: HydraAdminClient, KratosClient, JwksCache, OidcConfig as fields.
Access via `#[derive(References)]` auto-generated getters. No DependOn* traits needed (server-layer only).

### G. Dev Environment

podman-compose (docker-compose.yml). Helm charts for production separately later.

```
ory/
├── kratos/
│ ├── kratos.yml, identity.schema.json, seed-users.json
└── hydra/
└── hydra.yml
docker-compose.yml — postgres, redis, kratos, hydra
```

Env vars: HYDRA_ISSUER_URL, HYDRA_ADMIN_URL, KRATOS_PUBLIC_URL, EXPECTED_AUDIENCE

### H. Data Cleanup

Dev switch: TRUNCATE auth_hosts, auth_accounts, auth_account_events. No schema migration needed.

### I. Files NOT Changed/Deleted

- permission.rs: kept (potential Phase 2 reference)
- kernel / adapter / application / driver: no changes

### J. Testing

- JWT validation: test RSA keypair + in-memory JwkSet injection (configurable refetch interval = 0)
- Login/Consent: test_with::env(HYDRA_ADMIN_URL) skip
- Route handlers: Extension<AuthClaims> directly set, bypass middleware

## Tasks

- [x] 1. Dev environment (Ory Kratos + Hydra)
- [x] 1.1 Kratos config files (kratos.yml, identity.schema.json, seed-users.json) (P)
- [x] 1.2 Hydra config file (hydra.yml) (P)
- [x] 1.3 docker-compose.yml (postgres, redis, kratos, hydra; replaces standalone podman run; shared postgres with DB name separation)
- [x] 1.4 .env.example update (add HYDRA_ISSUER_URL, HYDRA_ADMIN_URL, KRATOS_PUBLIC_URL, EXPECTED_AUDIENCE; remove KEYCLOAK_SERVER, KEYCLOAK_REALM)
- [x] 1.5 Startup verification (podman-compose up, health endpoints respond)
- [x] 2. Cargo.toml deps and type definitions
- [x] 2.1 Add jsonwebtoken / reqwest to Cargo.toml (keep axum-keycloak-auth for now)
- [x] 2.2 OidcConfig / AuthClaims / OidcAuthInfo type definitions (server/src/auth.rs new)
- [x] 3. JWT validation and external clients
- [x] 3.1 JwksCache (OIDC Discovery, in-memory cache, kid miss re-fetch, lazy init) (P)
- [x] 3.2 HydraAdminClient types and methods (server/src/hydra.rs) (P)
- [x] 3.3 KratosClient and whoami (server/src/kratos.rs) (P)
- [x] 3.4 auth_middleware (Bearer extraction, JWT validation, Extension<AuthClaims>)
- [x] 3.5 JWT validation unit tests (test RSA keypair + JwkSet injection)
- [x] R1. Code review: auth foundation (auth.rs, hydra.rs, kratos.rs)
- [x] 4. Login/Consent Provider endpoints
- [x] 4.1 OAuth2Router trait and oauth2 module in route.rs
- [x] 4.2 GET /oauth2/login endpoint
- [x] 4.3 GET /oauth2/consent endpoint (skip check, unified JSON response)
- [x] 4.4 POST /oauth2/consent endpoint (consent result -> Hydra accept/reject)
- [x] R2. Code review: OAuth2 flow (route/oauth2.rs)
- [x] 5. Keycloak removal and route handler rewrite
- [x] 5.1 Rewrite resolve_auth_account_id with OidcAuthInfo (in auth.rs)
- [x] 5.2 account.rs rewrite (KeycloakToken -> AuthClaims, remove KeycloakAuthLayer/expect_role!) (P)
- [x] 5.3 profile.rs rewrite (same) (P)
- [x] 5.4 metadata.rs rewrite (same) (P)
- [x] 5.5 Remove to_permission_strings + test from route.rs, remove expect_role! macro from keycloak.rs
- [x] 5.6 Route handler unit test updates (Extension<AuthClaims> direct injection)
- [x] 6. Handler / AppModule / main.rs integration
- [x] 6.1 Handler: add HydraAdminClient / KratosClient / JwksCache / OidcConfig fields + AppModule accessors + init
- [x] 6.2 main.rs rewrite (remove KeycloakAuthInstance, add auth_middleware + OAuth2 routes, middleware scoping)
- [x] 6.3 Remove axum-keycloak-auth from Cargo.toml
- [x] R3. Code review: integration (handler.rs, main.rs, route handlers consistency)
- [x] 7. Cleanup and documentation
- [x] 7.1 Delete keycloak.rs, keycloak-data/, remove keycloak-data lines from .gitignore (P)
- [x] 7.2 Update CLAUDE.md / README.md (podman-compose setup instructions) (P)
- [x] 7.3 Document data cleanup procedure (TRUNCATE auth_hosts / auth_accounts / auth_account_events)
- [x] 8. Integration tests and verification
- [ ] 8.1 Login/Consent flow integration test (test_with::env(HYDRA_ADMIN_URL) skip)
- [x] 8.2 E2E verification with podman-compose (register -> login -> JWT -> API access)
- [x] R4. Final review: full alignment check (requirements/design coverage, missing items, code quality)
1 change: 1 addition & 0 deletions AGENTS.md
Loading
Loading