This repository now includes the Milestone 1 foundation baseline for the monorepo, runtime contracts, and CI/CD scaffolding.
apps/web- frontend workspace baselineapps/api- backend workspace baselinepackages/config- shared TypeScript, ESLint, and Prettier configuration
- TypeScript strict mode is centralized via
packages/config/tsconfig.base.json. - ESLint config is centralized in
packages/config/eslint.base.cjsand consumed by root,apps/web, andapps/api. - Prettier config is centralized in
packages/config/prettier.base.cjsand consumed by root,apps/web, andapps/api. - Root and app-level command surfaces run the actual toolchain (
tsc,eslint,vitest, andprettier) rather than placeholder scripts.
pnpm build- recursively runs each app workspace build command.pnpm lint- recursively runs each app workspace ESLint command.pnpm typecheck- recursively runs each app workspace TypeScript no-emit check.pnpm test- recursively runs each app workspacevitestcommand.pnpm format- formats root workspace files plusapps/*andpackages/*.pnpm format:check- checks formatting for the same workspace surfaces without writing changes.
apps/webandapps/apieach exposebuild,lint,typecheck,test,format, andformat:check.- Each app inherits shared TypeScript, ESLint, and Prettier settings from
packages/configwhile executing its own local source-file commands.
apps/webis a Next.js App Router frontend shell for the Milestone 2 public landing experience, auth-entry routes, and authenticated-shell foundation.- Styling stays within the Milestone 1 architecture baseline: CSS Modules for component/page styles plus shared global CSS custom-property tokens in
apps/web/app/globals.css. - Public-facing routes include the branded homepage (
/), branded404, branded runtime error surface, returning-user sign-in (/login), and provider-first registration (/register) with local email/password fallback. - The authenticated shell includes
/app,/profile,/settings, and/onboarding/username;/app,/profile, and/settingsall preserve destination intent for unauthenticated users with/login?next=<route>, and all authenticated routes redirectuser.onboardingRequiredsessions into username completion before normal authenticated use. - Frontend health endpoints are available at
/health/liveand/health/ready. - Frontend code targets the shared
/apipath contract.NEXT_PUBLIC_API_BASE_PATHdefaults to/api, development rewrites forward toWEB_API_ORIGIN(http://localhost:3001by default), and non-development containerized routing can targetWEB_API_INTERNAL_URL.
- Milestone 2 Subtask 1 adds the first persistence-layer identity and authorization foundation to
apps/apiwhile keeping the current frontend shell public-only. - Reviewed migration
1714435200000-identity-authorization-foundation.tsintroduces theusers,auth_identities,password_authenticators,auth_sessions,email_verifications,totp_secrets,totp_recovery_codes, andauthorization_grantstables with MySQL 5.7-compatible DDL. UsersModuleowns user persistence throughUserEntityandUsersService.AuthModuleimportsUsersModuleand owns auth persistence throughAuthIdentityEntity,PasswordAuthenticatorEntity,AuthSessionEntity,EmailVerificationEntity,TotpSecretEntity,TotpRecoveryCodeEntity, plusAuthControllerandAuthServicefor local auth and provider-backed (Google/GitHub) auth flows.AuthorizationModuleowns reusable authorization grant persistence throughAuthorizationGrantEntityandAuthorizationService.AuthorizationServicenow provides reusable global-role + ACL authorization decisions (read/write/admin) over generic resources (resourceType,resourceId,ownerUserId,visibility, optionalprojectId) so later content milestones can reuse one authz contract.AppModulenow composesDatabaseModule,UsersModule,AuthModule,AuthorizationModule, andHealthModuleso the API can bootstrap the shared identity/authz foundation as one application surface.- Local password auth stores Argon2id password hashes after appending the required password pepper, and local login stays blocked until a primary-email verification token has been consumed successfully.
- Email verification tokens are generated at registration time, hashed before persistence with
AUTH_SESSION_TOKEN_PEPPER, checked for expiry at verification time, and consumed once so the same token cannot activate the account twice. - Session lifecycle is server-managed through the
sfus_sessionHTTP-only cookie: login issues a new session,GET /api/auth/sessionresolves and refreshes the active session timestamp, idle or absolute expiry revokes the record, and logout revokes the current session and clears the cookie. - MFA is implemented with TOTP plus recovery codes: authenticated users start enrollment at
POST /api/auth/mfa/enroll, confirm atPOST /api/auth/mfa/enroll/verify, and receive one-time recovery codes only after successful verification. Recovery codes are single-use during challenge or proof flows, and regeneration (POST /api/auth/mfa/recovery/regenerate) replaces the previous set after authenticated MFA proof. Disable (POST /api/auth/mfa/disable) also requires authenticated MFA proof. - Password and external-provider login flows now return an MFA challenge whenever a verified TOTP secret exists. Challenge completion at
POST /api/auth/mfa/challengeissues the session cookie, and challenge tokens are signed and single-use. - External-provider auth is provider-agnostic via an adapter registry boundary, with deterministic account linking in this order: existing
(provider, subject)identity match, then existing user by normalized email, then new pending-onboarding user creation. - First-time external users are marked
onboarding_requireduntilPOST /api/auth/onboarding/usernamesets a valid unique username;GET /api/auth/sessionnow returnsuser.onboardingRequiredso the web shell can gate authenticated routes. - Auth API contract for frontend session-awareness:
POST /api/auth/registerPOST /api/auth/verify-emailPOST /api/auth/loginPOST /api/auth/mfa/challengePOST /api/auth/mfa/enrollPOST /api/auth/mfa/enroll/verifyPOST /api/auth/mfa/recovery/regeneratePOST /api/auth/mfa/disablePOST /api/auth/logoutGET /api/auth/sessionGET /api/auth/external/:provider/startGET /api/auth/external/:provider/callbackPOST /api/auth/onboarding/usernameGET /api/auth/profilePATCH /api/auth/profileGET /api/auth/settingsPATCH /api/auth/settings- authenticated
loginresponses return either{ user, session }or{ mfa }when a challenge is required;sessionremains stable{ user, session } PATCH /api/auth/profileaccepts profile-display-name updates only and returns{ username, email, displayName }PATCH /api/auth/settingsaccepts username updates only, enforces uniqueness, and returns{ username, email, emailVerified, mfaEnabled }GET|PATCH /api/auth/profileandGET|PATCH /api/auth/settingsnow run through the shared authorization layer for account-scoped access, including global-role and ACL grant checks when?userId=<targetId>is supplied for representative cross-account authorization coverage.
Milestone 1 local development is hybrid by default:
webruns on the host atlocalhost:3000apiruns on the host atlocalhost:3001mysqlruns throughcicd/docker/compose.dev.yml
The same local Compose file also supports full-stack container validation with the fullstack profile, while cicd/docker/compose.prod.yml is the single production Compose definition for long-lived web and api services. Production routing stays behind the existing reverse-proxy integration, so production-oriented Compose does not bind host ports for either app service.
The production migrate service is independently runnable as a one-off pre-rollout step and does not depend on starting api.
Copy the example env files before running local Compose validation:
cp .env.example .env
cp apps/web/.env.example apps/web/.env
cp apps/api/.env.example apps/api/.envOwnership is split by runtime boundary:
.env.example- platform/deployment-owned values used by Compose metadata and local MySQL scaffoldingapps/web/.env.example- web-owned values, including/apipath targeting and container-internal API routingapps/api/.env.example- API-owned values, including the database contract plus auth foundation inputs for password peppering, session-token hashing, verification/session lifetimes, TOTP issuer naming, and recovery-code generation used by the API container and explicit migration flow
The API environment contract now validates these auth settings at startup:
AUTH_PASSWORD_PEPPERis required and must be at least 16 characters.AUTH_SESSION_TOKEN_PEPPERis required, must be at least 16 characters, and is used when hashing persisted session and email-verification tokens.AUTH_SESSION_TTL_MINUTESmust be an integer from 5 to 43200.AUTH_SESSION_IDLE_TIMEOUT_MINUTESmust be an integer from 5 to 10080 and cannot exceedAUTH_SESSION_TTL_MINUTES.AUTH_EMAIL_VERIFICATION_TTL_MINUTESmust be an integer from 5 to 10080 and controls how long a newly issued verification token remains usable.AUTH_EXTERNAL_STATE_TTL_MINUTESmust be an integer from 5 to 60 and controls OAuth callback-state expiry.AUTH_TOTP_ISSUERis required and names the issuer presented by TOTP authenticators.AUTH_RECOVERY_CODE_COUNTmust be an integer from 6 to 20.AUTH_RECOVERY_CODE_LENGTHmust be an integer from 8 to 16.AUTH_GOOGLE_CLIENT_ID,AUTH_GOOGLE_CLIENT_SECRET, andAUTH_GOOGLE_CALLBACK_URLare required for Google sign-in.AUTH_GITHUB_CLIENT_ID,AUTH_GITHUB_CLIENT_SECRET, andAUTH_GITHUB_CALLBACK_URLare required for GitHub sign-in.
The example files are templates only. Production secrets and the external production MySQL connection are managed on the host outside the repository checkout.
cicd/docs/local-pipeline.md- hybrid local development, full-stack Compose validation, and explicit production migration flowcicd/docs/cicd.md- CI validation entrypoints, smoke validation usage, and runtime contract artifactsdocs/website-launch-guide.md- website container startup, required local env files, runtime URLs, migrations, and test commandsdocs/architecture/milestone-1-foundation-decisions.md- locked Milestone 1 architecture and deployment decisions
bash cicd/scripts/run-validations.sh cicd/config/validation-config.yml- lint, typecheck, test, smoke validation, and shared CI/CD contract checksbash cicd/scripts/smoke-validate.sh- build the apps, start the full local stack, run the explicit migration command, and verify homepage plus API health