@concourse/concourse is the web application for Concourse. It contains authentication, tenant tasks, member administration, SSO configuration, and system administration.
Complete the repository quick start before you work in this app.
Run the complete local stack from the repository root:
pnpm devOpen http://localhost:3000. Use pnpm auth:create-admin if the database has no system administrator.
The app uses Next.js 16 with the App Router and native Turbopack. Cache Components and Partial Prefetching are enabled.
The main areas are:
| Path | Purpose |
|---|---|
/sign-in | Email, password, and SSO discovery |
/organizations | Organization selection |
/organizations/[slug]/tasks | Organization-owned tasks |
/organizations/[slug]/members | Members, roles, and invitations |
/organizations/[slug]/sso | OIDC or SAML provider configuration |
/account | Sessions and explicit SSO linking |
/admin | System users, organizations, SSO approval, impersonation, and audits |
/api/auth/[...all] | Better Auth HTTP handler |
/healthz | Deployment readiness response |
Every task belongs to one organization. Organization roles control tenant access. The separate systemAdmin role controls global administration.
The app creates authentication through @concourse/auth. It passes explicit database, email, origin, secret, and policy options.
The proxy performs an early navigation check for protected GET and HEAD requests. This preserves literal 403 and 404 responses before a streamed shell starts.
The proxy is not the authorization boundary. Every page, Route Handler, Server Action, and protected command performs an authoritative database-backed check.
The signed session-cookie cache lasts 60 seconds. The proxy can use that cache for ordinary navigation. System administration and all protected operations bypass it.
React request caching deduplicates the authoritative session and organization-context reads during one render. It does not share authentication data between requests.
The root .env.development file contains safe local values. Put local overrides in the ignored root .env.local file.
The server requires:
TURSO_DATABASE_URLAUTH_AUDIT_FINGERPRINT_SECRETAUTH_BASE_URLAUTH_EMAIL_FROMBETTER_AUTH_SECRETSEMAIL_PROVIDERRemote Turso URLs also require TURSO_AUTH_TOKEN. Resend requires RESEND_API_KEY and
RESEND_TEST_RECIPIENT.
Production also requires AUTH_TRUSTED_PROXY_IP_HEADERS. Configure only a header that the deployment proxy always overwrites.
AUTH_TRUSTED_ORIGINS is reserved for the pinned OIDC and SAML test-provider origins. Do not set it in development or production.
Read the development environment guide for local precedence and the deployment bootstrap for production values.
Apply the database migrations before administrator creation. Then run this command from the repository root:
pnpm auth:create-adminThe pinned Better Auth CLI reads this app configuration. It creates a verified account with the systemAdmin role.
Create all later users and organizations through the protected administration interface. System administrators cannot view or set another user password.
Use the preview bootstrap command after migrations in an automated preview environment:
DEPLOYMENT_ENV=preview pnpm auth:bootstrap-previewSet PREVIEW_ADMIN_NAME, PREVIEW_ADMIN_EMAIL, and PREVIEW_ADMIN_PASSWORD in the environment. Store the password as a secret.
The command creates a verified systemAdmin account when the email address is new. A repeated run keeps the existing password. It restores the systemAdmin role when the existing account has a different role.
The command stops before it connects to the database unless DEPLOYMENT_ENV is preview. Do not use this command for production administration.
Local email goes to Maildev. Open http://localhost:1080 to read verification, invitation, and reset messages.
The root pnpm dev command owns Maildev. Press Ctrl+C to stop Maildev with the application and other development processes.
Public password-reset and verification callbacks use Next.js after(). The HTTP response does not wait for those messages.
Creating an organization creates one pending owner invitation. It does not create the owner account or membership. Member and owner invitations use one email for verification and account setup. Membership still requires a matching sign-in and an explicit acceptance.
Invitation delivery waits for the provider response. Administration shows the last confirmed delivery, expiry, resend, and cancellation controls. Resending keeps the existing link and extends its expiry by 48 hours.
Password-reset links verify the destination email when Better Auth validates the callback. The token is consumed only after the password is saved. Successful invitation setup and password reset return to sign-in with the email filled. Invitation callbacks survive password and SSO sign-in.
Self-hosted processes need time to finish deferred work during shutdown. Use at least 10 seconds. Use 30 seconds when email delivery can be slow.
Use root commands for normal work:
| Command | Purpose |
|---|---|
pnpm dev | Start all apps, Drizzle Studio, and foreground Maildev |
pnpm --filter @concourse/concourse check-types | Generate route types and run both TypeScript configurations |
pnpm --filter @concourse/concourse test | Run app unit tests |
pnpm e2e | Run or restore the complete acceptance lifecycle |
pnpm e2e:full | Force the production build, services, integrations, and browser tests |
pnpm build | Build all deployable apps with native Turbopack |
pnpm verify | Run all merge gates |
The production build keeps Cache Components and Partial Prefetching enabled. Turbopack evaluates PostCSS plugins in worker threads, which removes its internal socket requirement.
Playwright defines three projects:
acceptance-setup runs integration tests and seeds the database and acceptance fixtures.chromium runs the browser specifications.acceptance-teardown attaches service logs.Playwright starts an application-owned server script before these projects. The script requires the
fixed test database, removes stale SQLite files, applies migrations, starts Next on localhost:3100,
and removes the database and its sidecars after Next stops. Playwright gives the script 10 seconds
to handle SIGTERM. Pre-run removal recovers files left by a forced stop.
Playwright also starts the package-owned Maildev and auth services before the projects. It stops all services after the projects finish.
The Playwright configuration uses @concourse/email and @concourse/auth commands. No acceptance-service implementation belongs to this app.
Do not use --no-deps. That option skips the setup and teardown projects.
The isolated services use:
| Service | Address |
|---|---|
| Maildev SMTP | 127.0.0.1:1125 |
| Maildev web API | http://127.0.0.1:1180 |
| OIDC provider | http://127.0.0.1:8180 |
| SAML provider | http://127.0.0.1:8280 |
Turbo caches only a successful, fully cleaned acceptance result. A cache hit restores the Playwright reports without starting services. Fixed service ports and the fixed database prohibit concurrent acceptance runs in one checkout.
Use the existing concourse Vercel project with root directory apps/concourse, the Next.js
framework preset, Node 24, and region iad1.
Configure the production values from the deployment runbook.
Vercel Git builds main. The protected production migration workflow applies migrations and runs
readiness checks before Vercel assigns the production alias.
Set AUTH_BASE_URL to https://concourse-conveyal.vercel.app. Configure Turso, Resend, and the
trusted proxy header before the first authentication request.
After deployment, verify /healthz, create the first administrator, and complete every post-release check in the root runbook.
@concourse/concourse is the web application for Concourse. It contains authentication, tenant tasks, member administration, SSO configuration, and system administration.
Complete the repository quick start before you work in this app.
Run the complete local stack from the repository root:
pnpm devOpen http://localhost:3000. Use pnpm auth:create-admin if the database has no system administrator.
The app uses Next.js 16 with the App Router and native Turbopack. Cache Components and Partial Prefetching are enabled.
The main areas are:
| Path | Purpose |
|---|---|
/sign-in | Email, password, and SSO discovery |
/organizations | Organization selection |
/organizations/[slug]/tasks | Organization-owned tasks |
/organizations/[slug]/members | Members, roles, and invitations |
/organizations/[slug]/sso | OIDC or SAML provider configuration |
/account | Sessions and explicit SSO linking |
/admin | System users, organizations, SSO approval, impersonation, and audits |
/api/auth/[...all] | Better Auth HTTP handler |
/healthz | Deployment readiness response |
Every task belongs to one organization. Organization roles control tenant access. The separate systemAdmin role controls global administration.
The app creates authentication through @concourse/auth. It passes explicit database, email, origin, secret, and policy options.
The proxy performs an early navigation check for protected GET and HEAD requests. This preserves literal 403 and 404 responses before a streamed shell starts.
The proxy is not the authorization boundary. Every page, Route Handler, Server Action, and protected command performs an authoritative database-backed check.
The signed session-cookie cache lasts 60 seconds. The proxy can use that cache for ordinary navigation. System administration and all protected operations bypass it.
React request caching deduplicates the authoritative session and organization-context reads during one render. It does not share authentication data between requests.
The root .env.development file contains safe local values. Put local overrides in the ignored root .env.local file.
The server requires:
TURSO_DATABASE_URLAUTH_AUDIT_FINGERPRINT_SECRETAUTH_BASE_URLAUTH_EMAIL_FROMBETTER_AUTH_SECRETSEMAIL_PROVIDERRemote Turso URLs also require TURSO_AUTH_TOKEN. Resend requires RESEND_API_KEY and
RESEND_TEST_RECIPIENT.
Production also requires AUTH_TRUSTED_PROXY_IP_HEADERS. Configure only a header that the deployment proxy always overwrites.
AUTH_TRUSTED_ORIGINS is reserved for the pinned OIDC and SAML test-provider origins. Do not set it in development or production.
Read the development environment guide for local precedence and the deployment bootstrap for production values.
Apply the database migrations before administrator creation. Then run this command from the repository root:
pnpm auth:create-adminThe pinned Better Auth CLI reads this app configuration. It creates a verified account with the systemAdmin role.
Create all later users and organizations through the protected administration interface. System administrators cannot view or set another user password.
Use the preview bootstrap command after migrations in an automated preview environment:
DEPLOYMENT_ENV=preview pnpm auth:bootstrap-previewSet PREVIEW_ADMIN_NAME, PREVIEW_ADMIN_EMAIL, and PREVIEW_ADMIN_PASSWORD in the environment. Store the password as a secret.
The command creates a verified systemAdmin account when the email address is new. A repeated run keeps the existing password. It restores the systemAdmin role when the existing account has a different role.
The command stops before it connects to the database unless DEPLOYMENT_ENV is preview. Do not use this command for production administration.
Local email goes to Maildev. Open http://localhost:1080 to read verification, invitation, and reset messages.
The root pnpm dev command owns Maildev. Press Ctrl+C to stop Maildev with the application and other development processes.
Public password-reset and verification callbacks use Next.js after(). The HTTP response does not wait for those messages.
Creating an organization creates one pending owner invitation. It does not create the owner account or membership. Member and owner invitations use one email for verification and account setup. Membership still requires a matching sign-in and an explicit acceptance.
Invitation delivery waits for the provider response. Administration shows the last confirmed delivery, expiry, resend, and cancellation controls. Resending keeps the existing link and extends its expiry by 48 hours.
Password-reset links verify the destination email when Better Auth validates the callback. The token is consumed only after the password is saved. Successful invitation setup and password reset return to sign-in with the email filled. Invitation callbacks survive password and SSO sign-in.
Self-hosted processes need time to finish deferred work during shutdown. Use at least 10 seconds. Use 30 seconds when email delivery can be slow.
Use root commands for normal work:
| Command | Purpose |
|---|---|
pnpm dev | Start all apps, Drizzle Studio, and foreground Maildev |
pnpm --filter @concourse/concourse check-types | Generate route types and run both TypeScript configurations |
pnpm --filter @concourse/concourse test | Run app unit tests |
pnpm e2e | Run or restore the complete acceptance lifecycle |
pnpm e2e:full | Force the production build, services, integrations, and browser tests |
pnpm build | Build all deployable apps with native Turbopack |
pnpm verify | Run all merge gates |
The production build keeps Cache Components and Partial Prefetching enabled. Turbopack evaluates PostCSS plugins in worker threads, which removes its internal socket requirement.
Playwright defines three projects:
acceptance-setup runs integration tests and seeds the database and acceptance fixtures.chromium runs the browser specifications.acceptance-teardown attaches service logs.Playwright starts an application-owned server script before these projects. The script requires the
fixed test database, removes stale SQLite files, applies migrations, starts Next on localhost:3100,
and removes the database and its sidecars after Next stops. Playwright gives the script 10 seconds
to handle SIGTERM. Pre-run removal recovers files left by a forced stop.
Playwright also starts the package-owned Maildev and auth services before the projects. It stops all services after the projects finish.
The Playwright configuration uses @concourse/email and @concourse/auth commands. No acceptance-service implementation belongs to this app.
Do not use --no-deps. That option skips the setup and teardown projects.
The isolated services use:
| Service | Address |
|---|---|
| Maildev SMTP | 127.0.0.1:1125 |
| Maildev web API | http://127.0.0.1:1180 |
| OIDC provider | http://127.0.0.1:8180 |
| SAML provider | http://127.0.0.1:8280 |
Turbo caches only a successful, fully cleaned acceptance result. A cache hit restores the Playwright reports without starting services. Fixed service ports and the fixed database prohibit concurrent acceptance runs in one checkout.
Use the existing concourse Vercel project with root directory apps/concourse, the Next.js
framework preset, Node 24, and region iad1.
Configure the production values from the deployment runbook.
Vercel Git builds main. The protected production migration workflow applies migrations and runs
readiness checks before Vercel assigns the production alias.
Set AUTH_BASE_URL to https://concourse-conveyal.vercel.app. Configure Turso, Resend, and the
trusted proxy header before the first authentication request.
After deployment, verify /healthz, create the first administrator, and complete every post-release check in the root runbook.