Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
46a3e3b
Add OIDC authentication support with Arctic library
jmorton Dec 8, 2025
5c16379
Replace PageData.user with reactive userStore across application
jmorton Dec 8, 2025
be6e135
Add server-side rule enforcement infrastructure
jmorton Dec 8, 2025
47959b4
Add E2E tests for OIDC authentication flow
jmorton Dec 8, 2025
48790cc
Update non-OIDC auth routes to use event-based cookie handling
jmorton Dec 8, 2025
2dba59a
Fix race condition in OIDC well-known configuration fetch
jmorton Dec 8, 2025
7e958b8
Add OIDC nonce parameter for replay attack protection
jmorton Dec 8, 2025
34d49f1
Add audience claim validation to JWT verification
jmorton Dec 8, 2025
4848409
Validate audience only on ID token per OIDC spec
jmorton Dec 9, 2025
684a9df
Fix open redirect vulnerability in back parameter
jmorton Dec 9, 2025
216a19b
Add secure flag to all authentication cookies
jmorton Dec 9, 2025
623b39e
Add SameSite=Lax to all authentication cookies
jmorton Dec 9, 2025
5148894
Add configurable JWT signing algorithms via OIDC_ALGORITHMS env var
jmorton Dec 9, 2025
10f35c9
Remove sensitive data from OIDC logs
jmorton Dec 9, 2025
d9f266e
Standardize OIDC logging and fix additional sensitive data leaks
jmorton Dec 9, 2025
b4b1beb
Add Content Security Policy headers (report-only mode)
jmorton Dec 9, 2025
da15bd8
Verify ID token before using as logout hint
jmorton Dec 9, 2025
96f8604
Add configurable JWT claim paths for OIDC
jmorton Dec 9, 2025
23c109c
Use dynamically loaded env vars for OIDC related values set at launch…
jmorton Dec 10, 2025
6bf7260
Fix WebSocket lifecycle and token refresh for OIDC
AaronPlave Feb 17, 2026
7e56f90
Linting, fixes, and small cleanup
AaronPlave Feb 17, 2026
0f45665
Handle expired refresh tokens gracefully instead of infinite retry loop
AaronPlave Feb 18, 2026
f3dcf98
Merge remote-tracking branch 'origin/develop' into feature/oidc-support
AaronPlave May 12, 2026
cd44bee
Revert reqHasura auto-logout, dedupe OIDC claims helper, anchor non-p…
AaronPlave May 13, 2026
74bf56b
Add OIDC role-switch, multi-tab, and tab-backgrounding e2e tests and …
AaronPlave May 13, 2026
26716c4
Surface auto-logout reason on /login after OIDC roundtrip and suppres…
AaronPlave May 28, 2026
30893e5
Move OIDC user provisioning to gateway and add OIDC e2e test stack
AaronPlave May 28, 2026
12005d2
Harden OIDC role-switch e2e and refresh stale test comments
AaronPlave May 29, 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
14 changes: 14 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,17 @@ PUBLIC_LIBRARY_SEQUENCES_ENABLED=false
PUBLIC_COMMAND_EXPANSION_MODE=typescript
# VITE_HOST=localhost.jpl.nasa.gov
# VITE_HTTPS=true

PUBLIC_AUTH_OIDC_ENABLED=false
OIDC_WELL_KNOWN_URL=
OIDC_AUTHORIZATION_URL=
OIDC_TOKEN_URL=
OIDC_LOGOUT_URL=
OIDC_JWKS_URL=
OIDC_SCOPES=
OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=
OIDC_REDIRECT_URI=
OIDC_AUDIENCE=
OIDC_ISSUER=
OIDC_ALGORITHMS=
38 changes: 38 additions & 0 deletions .env.test.oidc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# All env vars needed to run OIDC e2e tests end-to-end (CI and local).
# Backend container creds match plandev's local-dev defaults; not real secrets.
# Loaded into the shell by `npm run test:e2e:oidc` and consumed by
# `docker compose --env-file .env.test.oidc` in the setup/teardown scripts.

# --- Backend container credentials (used by docker-compose-test.yml) ---
AERIE_USERNAME=aerie_admin
AERIE_PASSWORD=aerie_admin
GATEWAY_USERNAME=gateway_user
GATEWAY_PASSWORD=gateway_user
MERLIN_USERNAME=merlin_user
MERLIN_PASSWORD=merlin_user
SCHEDULER_USERNAME=scheduler_user
SCHEDULER_PASSWORD=scheduler_user
SEQUENCING_USERNAME=sequencing_user
SEQUENCING_PASSWORD=sequencing_user
POSTGRES_USER=postgres_user
POSTGRES_PASSWORD=postgres_user
HASURA_GRAPHQL_ADMIN_SECRET=aerie
ACTION_COOKIE_NAMES=
ACTION_CORS_ALLOWED_ORIGIN=

# --- Hasura JWT validation against Keycloak (RS256 via JWKS) ---
HASURA_GRAPHQL_JWT_SECRET='{"type":"RS256","jwk_url":"http://aerie_keycloak:8000/realms/aerie-dev/protocol/openid-connect/certs","claims_namespace":"https://hasura.io/jwt/claims"}'

# --- UI-side OIDC config (read by SvelteKit's $env/dynamic/{public,private}) ---
PUBLIC_AUTH_OIDC_ENABLED=true
OIDC_WELL_KNOWN_URL=http://localhost:8000/realms/aerie-dev/.well-known/openid-configuration
OIDC_AUTHORIZATION_URL=http://localhost:8000/realms/aerie-dev/protocol/openid-connect/auth
OIDC_TOKEN_URL=http://localhost:8000/realms/aerie-dev/protocol/openid-connect/token
OIDC_LOGOUT_URL=http://localhost:8000/realms/aerie-dev/protocol/openid-connect/logout
OIDC_JWKS_URL=http://localhost:8000/realms/aerie-dev/protocol/openid-connect/certs
OIDC_SCOPES='openid profile email'
OIDC_CLIENT_ID=aerie
OIDC_REDIRECT_URI=http://localhost:3000/oidc/callback
OIDC_AUDIENCE=aerie
OIDC_ISSUER=http://localhost:8000/realms/aerie-dev
GATEWAY_IMAGE_TAG=oidc-local
38 changes: 38 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ jobs:
- name: Install Playwright Dependencies (Test - e2e)
run: npx playwright install chromium --with-deps
- name: Test (e2e)
# `npm run test:e2e` runs everything EXCEPT the `oidc tests` project — see package.json.
# OIDC needs Hasura on RS256+jwk_url and a running Keycloak; it runs in its own phase below.
run: npm run test:e2e
- name: Upload Results
if: always()
Expand All @@ -118,6 +120,42 @@ jobs:
docker ps -a
docker compose -f docker-compose-test.yml down
docker ps -a

# --- OIDC phase ---
# The HS256 stack is down. Bring everything back up with Hasura on RS256+jwk_url
# and Keycloak as the IdP, then run the OIDC-only Playwright project. All env
# config lives in .env.test.oidc (single source of truth for backend + UI).
- name: Start Services (OIDC phase)
run: |
npm run test:e2e:oidc:setup
docker ps -a --no-trunc
- name: Wait for Keycloak readiness
run: |
for i in {1..30}; do
if curl -sf http://localhost:8000/realms/aerie-dev/.well-known/openid-configuration > /dev/null; then
echo "Keycloak ready"; exit 0
fi
sleep 2
done
echo "Keycloak did not become ready within 60s"; exit 1
- name: Test (e2e OIDC)
run: npm run test:e2e:oidc
- name: Upload OIDC Results
if: always()
uses: actions/upload-artifact@v4
with:
name: E2E OIDC Test Results
path: |
**/e2e-test-results
- name: Print Logs for OIDC Services
if: always()
run: docker compose --env-file .env.test.oidc -f docker-compose-test.yml --profile oidc logs -t
- name: Stop OIDC Services
if: always()
run: |
docker ps -a
npm run test:e2e:oidc:teardown
docker ps -a
- name: Prune Volumes (PlanDev)
if: always()
run: docker volume prune --force
16 changes: 16 additions & 0 deletions docker-compose-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,22 @@ services:
restart: always
volumes:
- postgres_data:/var/lib/postgresql/data
keycloak:
# Only started when running the OIDC test phase: `docker compose --profile oidc up`.
# Imports the canned realm from e2e-tests/oauth/realm-export.json, which defines the
# `aerie` OIDC client (PKCE) plus the three test users (AerieAdmin/AerieUser/AerieViewer).
profiles: ['oidc']
container_name: aerie_keycloak
image: 'quay.io/keycloak/keycloak:latest'
ports: ['8000:8000']
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: kcadmin
KC_BOOTSTRAP_ADMIN_PASSWORD: kcadmin
KC_FEATURES: scripts
KC_HTTP_PORT: 8000
command: ['start-dev', '--import-realm']
volumes:
- ./e2e-tests/oauth/realm-export.json:/opt/keycloak/data/import/realm-export.json
aerie_workspace:
container_name: aerie_workspace
depends_on: ['postgres']
Expand Down
6 changes: 6 additions & 0 deletions e2e-tests/fixtures/AppNav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export class AppNav {
await this.pageLoadingLocator.waitFor({ state: 'detached' });
}

async show() {
await this.appMenuButton.click();
await this.appMenu.waitFor({ state: 'attached' });
await this.appMenu.waitFor({ state: 'visible' });
}

updatePage(page: Page): void {
this.aboutModal = page.locator(`.modal:has-text("About")`);
this.aboutModalCloseButton = page.locator(`.modal:has-text("About") >> button:has-text("Close")`);
Expand Down
Loading
Loading