Skip to content

feat: add a dev-only sign-in with test personas and a local database - #21

Open
toto04 wants to merge 1 commit into
mainfrom
feat/dev-login
Open

toto04 wants to merge 1 commit into
mainfrom
feat/dev-login

Conversation

@toto04

@toto04 toto04 commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Testing locally used to mean real provider credentials and a database everyone set up their own way. Now a fresh clone needs only Docker:

cp .env.local.example .env.local
vp run dev:setup   # PostgreSQL via compose.yaml, migrations, seed data
vp run dev         # then pick a persona under "Dev sign-in" on the login page

What's in it

  • Four personas. Each gets a real Better Auth session, so RBAC, the session hook and the API guards all run unchanged.

    Persona What it has
    Ada Admin Master Admin, through IDP_ADMIN_USER_IDS=dev-admin
    Sam Staff A dev-staff role with only idp:users:read and idp:roles:read
    Stella Student A verified Polimi student with Telegram linked
    Nico Newcomer Google only, nothing else

    Socio and Direttivo can't be personas: identity-subject.ts always checks them live against Entra and never trusts the database, and this PR doesn't change that.

  • GET /api/dev/login?as=<persona>&redirect=<path> signs in and redirects. /api/dev/login alone lists the personas as JSON, and curl -c jar 'localhost:3000/api/dev/login?as=admin' works for scripts and agents. Redirects are limited to local paths.

  • OpenID Connect sign-ins resume. When an application sent you to the login page, picking a persona continues to /consent.

  • vp run dev:seed adds 60 deterministic fake people with mixed accounts and statuses, so the user directory has something to show. Running it again updates them in place.

Why it can't reach production

  1. Compiled out. src/dev/ is imported only behind import.meta.env.DEV, which is false in vp build. The route file stays and answers 404.
  2. The build checks it. scripts/check-server-bundle.mjs now fails the build if the dev code's marker shows up anywhere in .output/server or .output/public. I confirmed it: with the guard removed, vp run build fails and names the chunk. With the guard in place, handleDevLogin, saveDevUser and the persona names are all absent from the output.
  3. Off unless asked for. The endpoint answers only when DEV_LOGIN=1, and scripts/security-config.mjs refuses to start with DEV_LOGIN set unless BETTER_AUTH_URL is localhost and NODE_ENV isn't production.
  4. The example secret only works locally. .env.local.example publishes a BETTER_AUTH_SECRET so a fresh clone runs as is, and startup refuses that value anywhere but localhost.

Verified

  • vp check passes and vp test passes (159 tests). New tests cover the startup rules, the leak check, and the redirect guard.
  • Against a fresh Compose database: migrate and seed ran cleanly.
    • Staff got exactly its role's permissions, plus the ones they imply.
    • Admin resolved to master-admin.
    • Student got the student state and its Telegram ID, and a 403 from the user directory.
    • Unknown personas return 400, and redirect=//evil.example falls back to /.
  • In the browser, clicking a persona landed on /users signed in.
  • An authorize request for a test client resumed to /consent after the dev sign-in.
  • I didn't run vp run dev:setup itself, since my own .env.local points elsewhere. I ran the same three steps (compose, migrate, seed) by hand with the example env.

🤖 Generated with Claude Code

Local testing needed real provider credentials and a hand-rolled database.
`vp run dev:setup` now starts PostgreSQL with Docker Compose, migrates it,
and seeds four personas plus 60 fake people. In `vp dev` with DEV_LOGIN=1,
the login page offers one-click sign-in as a persona, and
GET /api/dev/login?as=<persona>&redirect=<path> does the same for scripts
and agents. It creates a real Better Auth session, so RBAC and the API
guards run unchanged, and it resumes OpenID Connect sign-ins.

The dev code is imported only behind import.meta.env.DEV and the build
fails if it ever reaches a production bundle. Startup refuses DEV_LOGIN,
and the public example secret, unless BETTER_AUTH_URL is localhost.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 14 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 8629a906-eb8f-4cea-b3d5-724bb6c8e911

📥 Commits

Reviewing files that changed from the base of the PR and between b6e07a7 and 07002f5.

📒 Files selected for processing (18)
  • .env.example
  • .env.local.example
  • README.md
  • compose.yaml
  • package.json
  • scripts/check-server-bundle.mjs
  • scripts/check-server-bundle.test.mjs
  • scripts/security-config.mjs
  • src/auth/security-config.test.ts
  • src/components/dev-login.tsx
  • src/components/login-page.tsx
  • src/dev/login.ts
  • src/dev/personas.ts
  • src/dev/seed.ts
  • src/dev/shared.test.ts
  • src/dev/shared.ts
  • src/routeTree.gen.ts
  • src/routes/api/dev/login.ts
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lorenzocorallo lorenzocorallo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@toto04 I reviewed and tested commit 07002f5. Please fix the database safety boundary and OIDC continuation before merging. I also reproduced an account/evidence mismatch when refreshing fixtures after configuring an Entra tenant; the inline comments explain each change.

Validation against disposable databases:

  • vp check, vp run build, and the documented fresh Docker vp run dev:setup flow passed.
  • All four personas produced the expected identity claims and API access decisions.
  • The production bundle excluded the dev implementation, and /api/dev/login returned 404 in the running production build.
  • With all integration suites enabled, 220 tests passed and 2 failed. Both failures also reproduce on base commit b6e07a7, so I am not attributing them to this PR. They are denies escalation through managed and custom permission implications and does not leak role members through a write-only membership response.

The normal OIDC flow reaches consent, but reauthentication requests loop. The production build boundary works in the tested build; the standalone seed script needs its own safeguards.

Comment thread src/dev/seed.ts

const FAKE_PEOPLE = 60;

const local = ["localhost", "127.0.0.1", "[::1]"];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Validate the database target before migrations or seeding.

This guard checks the web origin rather than the database destination. A localhost BETTER_AUTH_URL permits any DB_HOST. It also permits NODE_ENV=production when DEV_LOGIN is unset and the secret is not the public example.

I reproduced this against an isolated PostgreSQL container: DB_HOST=172.18.0.2, NODE_ENV=production, DEV_LOGIN unset, a real secret, and BETTER_AUTH_URL=http://localhost:3000. vp run dev:seed completed and wrote the fixtures. The same path can write fake users, trusted student evidence, and staff role grants into a shared or production database if those credentials are configured. Compiling the endpoint out does not protect this standalone tsx script.

Please reject production execution and require an explicitly designated disposable development database. Apply that validation before drizzle-kit migrate in dev:setup as well, since that command currently migrates the configured database before this guard runs. Add coverage for unsafe database targets and production execution, including attempts with DEV_LOGIN unset.

{import.meta.env.DEV && (
<DevLoginPanel
redirect={
oauthFlow ? `/api/auth/oauth2/authorize?${searchStr.replace(/^\?/, "")}` : callbackURL

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Resume OIDC through the authenticated continuation.

This redirect replays the original authorization query, including prompt=login or max_age=0. The dev endpoint creates a session outside Better Auth's normal OAuth continuation hook, which normally clears the satisfied reauthentication requirement.

I reproduced both over HTTP with a registered PKCE client: an anonymous authorize request redirects to login; selecting the student persona creates a valid session; the replayed authorize request redirects back to login instead of /consent. A request without either parameter reaches consent.

Please resume through the provider's authenticated continuation and preserve validation of the signed request. Add integration coverage for prompt=login and max_age=0 so a successful persona sign-in reaches consent.

Comment thread src/dev/personas.ts
userId: entry.id,
updatedAt: now,
})
.onConflictDoNothing();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh fixture accounts together with their evidence.

The account has a fixed primary key, so onConflictDoNothing() leaves its issuer unchanged while the evidence below is refreshed using the current configured issuer.

I reproduced seeding without an Entra tenant and then refreshing after configuring one. The pn-entra account retained the zero-UUID tenant issuer; the new evidence used the configured tenant issuer. The account/evidence join kept reading the old email and evidence, contradicting the documented update-in-place behavior.

Please update existing fixture account issuer/subject fields consistently with their evidence, after checking that the account belongs to the expected fixture user. Cover changing from the placeholder tenant to a configured tenant in a reseeding test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants