Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 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
36bc777
Lint fixes
AaronPlave Jun 8, 2026
7ce5334
Merge branch 'develop' into feature/oidc-support
AaronPlave Jun 8, 2026
6e8265a
test fix
AaronPlave Jun 18, 2026
6396c63
add backend-service smoke tests to the OIDC e2e suite
AaronPlave Jul 1, 2026
58f9559
Split OIDC tests into their own runner
AaronPlave Jul 2, 2026
b7ec9d8
Merge remote-tracking branch 'origin/develop' into feature/oidc-support
AaronPlave Jul 6, 2026
8f26f4d
test: dedupe snapshot helper onto develop's createPlanSnapshot
AaronPlave Jul 6, 2026
e2b3667
fix(e2e): skip templating preview server on OIDC runs
AaronPlave Jul 6, 2026
4e0cc50
fix(oidc): treat empty OIDC_ALGORITHMS as default RS256
AaronPlave Jul 6, 2026
ff50761
test(oidc): warm up dev server before OIDC suite to de-flake first login
AaronPlave Jul 6, 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=
37 changes: 37 additions & 0 deletions .env.test.oidc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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
69 changes: 69 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@
- 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 @@ -121,6 +123,73 @@
docker ps -a
docker compose -f docker-compose-test.yml down
docker ps -a

- name: Prune Volumes (PlanDev)
if: always()
run: docker volume prune --force

# Runs in parallel with e2e-test on its own runner. OIDC needs Hasura on RS256+jwk_url and a
# running Keycloak, whose config and ports conflict with the HS256 stack — so it can't share a
# runner with e2e-test. Kept independent (no `needs:`) so OIDC status is reported even when the
# regular suite fails, and so the two phases run concurrently rather than back-to-back.
e2e-oidc-test:
runs-on: ubuntu-latest
environment: ${{ inputs.env || 'test-workflow' }}
steps:
- name: Checkout Repo (UI)
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Node (UI)
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '22.22.0'
cache: npm
- name: Extract PlanDev backend docker tag from PR body
if: ${{ contains(env.PR_BODY, '___REQUIRES_AERIE_PR___=') }}
run: |
echo "AERIE_IMAGE_TAG=pr-$(echo "$PR_BODY" | sed -n 's/.*___REQUIRES_AERIE_PR___=\"\([0-9]\+\)\".*/\1/p')" >> "$GITHUB_ENV"

Check failure

Code scanning / CodeQL

Environment variable built from user-controlled sources Critical test

Potential environment variable injection in
echo "AERIE_IMAGE_TAG=pr-$(echo "$PR_BODY" | sed -n 's/.*___REQUIRES_AERIE_PR___=\"\([0-9]\+\)\".*/\1/p')" >> "$GITHUB_ENV"
, which may be controlled by an external user (
workflow_dispatch
).
Potential environment variable injection in
echo "AERIE_IMAGE_TAG=pr-$(echo "$PR_BODY" | sed -n 's/.*___REQUIRES_AERIE_PR___=\"\([0-9]\+\)\".*/\1/p')" >> "$GITHUB_ENV"
, which may be controlled by an external user (
workflow_dispatch
).
Comment on lines +148 to +149
- name: Extract PlanDev gateway docker tag from PR body
if: ${{ contains(env.PR_BODY, '___REQUIRES_GATEWAY_PR___=') }}
run: |
echo "GATEWAY_IMAGE_TAG=pr-$(echo "$PR_BODY" | sed -n 's/.*___REQUIRES_GATEWAY_PR___=\"\([0-9]\+\)\".*/\1/p')" >> "$GITHUB_ENV"

Check failure

Code scanning / CodeQL

Environment variable built from user-controlled sources Critical test

Potential environment variable injection in
echo "GATEWAY_IMAGE_TAG=pr-$(echo "$PR_BODY" | sed -n 's/.*___REQUIRES_GATEWAY_PR___=\"\([0-9]\+\)\".*/\1/p')" >> "$GITHUB_ENV"
, which may be controlled by an external user (
workflow_dispatch
).
Potential environment variable injection in
echo "GATEWAY_IMAGE_TAG=pr-$(echo "$PR_BODY" | sed -n 's/.*___REQUIRES_GATEWAY_PR___=\"\([0-9]\+\)\".*/\1/p')" >> "$GITHUB_ENV"
, which may be controlled by an external user (
workflow_dispatch
).
Comment on lines +152 to +153
- name: Install Dependencies (UI)
run: npm ci

Check warning

Code scanning / SonarCloud

JavaScript package manager scripts should not be executed during installation Medium test

Omitting "--ignore-scripts" allows lifecycle scripts to run during package installation. See more on SonarQube Cloud

Check warning on line 155 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--ignore-scripts" allows lifecycle scripts to run during package installation.

See more on https://sonarcloud.io/project/issues?id=NASA-AMMOS_aerie-ui&issues=AZ84NOIACSuz0nVHYF-_&open=AZ84NOIACSuz0nVHYF-_&pullRequest=1746
- name: Install Playwright Dependencies (Test - e2e)
run: npx playwright install chromium --with-deps

Check warning

Code scanning / SonarCloud

JavaScript package manager scripts should not be executed during installation Medium test

"npx" can install packages on-demand and run their lifecycle scripts. See more on SonarQube Cloud

Check warning

Code scanning / SonarCloud

JavaScript dependencies should be locked to verified versions Medium test

Define exact package version to avoid installing unverified releases. See more on SonarQube Cloud

Check warning on line 157 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define exact package version to avoid installing unverified releases.

See more on https://sonarcloud.io/project/issues?id=NASA-AMMOS_aerie-ui&issues=AZ84NOIACSuz0nVHYF_B&open=AZ84NOIACSuz0nVHYF_B&pullRequest=1746

Check warning on line 157 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"npx" can install packages on-demand and run their lifecycle scripts.

See more on https://sonarcloud.io/project/issues?id=NASA-AMMOS_aerie-ui&issues=AZ84NOIACSuz0nVHYF_A&open=AZ84NOIACSuz0nVHYF_A&pullRequest=1746
# No `npm run build`: the OIDC Playwright project starts the UI via `npm run dev`
# (playwright.config.ts webServer), unlike the regular suite which uses the built preview.
- name: Start Services (OIDC)
run: |
echo "AERIE_IMAGE_TAG: $AERIE_IMAGE_TAG"
echo "GATEWAY_IMAGE_TAG: $GATEWAY_IMAGE_TAG"
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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
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 (OIDC)
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