feat/identity-access #1
|
|
@ -0,0 +1,11 @@
|
||||||
|
## Change
|
||||||
|
|
||||||
|
Describe the user-visible behavior and relevant permission boundaries.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
List the checks run, migration upgrade tests, and any unavailable external verification.
|
||||||
|
|
||||||
|
## Release
|
||||||
|
|
||||||
|
Mention migrations, configuration changes and compatible rollback steps. Include no secrets.
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
name: Backend quality
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, 'feat/**', 'fix/**']
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
jobs:
|
||||||
|
quality:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:17
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: mani
|
||||||
|
POSTGRES_PASSWORD: ci_only_password
|
||||||
|
POSTGRES_DB: mani_ci
|
||||||
|
options: >-
|
||||||
|
--health-cmd "pg_isready -U mani -d mani_ci"
|
||||||
|
--health-interval 5s --health-timeout 5s --health-retries 10
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgresql://mani:ci_only_password@postgres:5432/mani_ci
|
||||||
|
TEST_DATABASE_URL: postgresql://mani:ci_only_password@postgres:5432/mani_ci
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '24'
|
||||||
|
- run: npm install --global pnpm@11.19.0
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm db:generate
|
||||||
|
- run: node scripts/verify-migrations.mjs
|
||||||
|
- run: pnpm check
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Mani Candles backend
|
# Mani Candles backend
|
||||||
|
|
||||||
Phase 1A implements the service foundation. It does not yet expose commerce or identity APIs.
|
Phase 1A provides the service foundation. Phase 1B adds staff account provisioning, authentication, sessions, recovery, configurable RBAC, and audit records. Commerce APIs follow in Phase 1C.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
|
@ -27,3 +27,5 @@ Development: run `pnpm dev` to compile on changes and `pnpm start:watch` in a se
|
||||||
- `docs`: delivery roadmap and engineering workflow.
|
- `docs`: delivery roadmap and engineering workflow.
|
||||||
|
|
||||||
Production must use a managed secret store, TLS termination, a restricted database account, backups, and the deployment migration command. Do not expose this foundation as a completed commerce platform.
|
Production must use a managed secret store, TLS termination, a restricted database account, backups, and the deployment migration command. Do not expose this foundation as a completed commerce platform.
|
||||||
|
|
||||||
|
Identity: see [API contract](docs/identity-api.md) and [setup/release guide](docs/identity-operations.md).
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Phase 1B: identity and access API
|
||||||
|
|
||||||
|
All paths below start with `/api/v1`. Protected endpoints require `Authorization: Bearer <accessToken>`. There are no public account-creation or owner-creation endpoints. Staff account provisioning is available to authorized administrators; storefront self-registration and verified email onboarding can be added with the customer milestone.
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
| Method | Path | Permission | Request / result |
|
||||||
|
| ------ | ---------------------- | ------------------ | ------------------------------------------------------------------- |
|
||||||
|
| POST | /auth/login | Public; throttled | organizationId, email, password → accessToken, tokenType, expiresAt |
|
||||||
|
| GET | /auth/me | Authenticated | userId, organizationId, sessionId, effective permissions |
|
||||||
|
| POST | /auth/logout | Authenticated | Revoke current session; 204 |
|
||||||
|
| POST | /auth/logout-all | Authenticated | Revoke all current user sessions; 204 |
|
||||||
|
| POST | /auth/recovery/request | Public; throttled | organizationId, email → generic 202; 503 if SMTP is unconfigured |
|
||||||
|
| POST | /auth/recovery/reset | Public; throttled | token, password → 204; token expires and can be consumed once |
|
||||||
|
| GET | /users | users.read | Paginated safe user records |
|
||||||
|
| POST | /users | users.create | name, email, password → PENDING account |
|
||||||
|
| PATCH | /users/:id/status | users.approve | status: ACTIVE or SUSPENDED |
|
||||||
|
| PATCH | /users/:id/roles | users.roles.assign | roleIds: UUID array; replaces assignments |
|
||||||
|
| GET | /roles/permissions | roles.read | Current permission catalogue |
|
||||||
|
| GET | /roles | roles.read | Paginated roles |
|
||||||
|
| POST | /roles | roles.manage | name, permissions |
|
||||||
|
| PATCH | /roles/:id | roles.manage | Complete replacement of name and permissions |
|
||||||
|
| GET | /audit-events | audit.read | Paginated append-only organization audit history |
|
||||||
|
|
||||||
|
List queries accept `limit` (1–100, default 25) and `offset` (0–10000, default 0). Unknown fields, malformed UUIDs, duplicate role assignments/permissions and unknown permissions are rejected. Errors use Nest's JSON error contract: 400 invalid input, 401 unauthenticated, 403 denied, 404 missing/out-of-scope target, 409 duplicate record, 429 throttled, 503 unavailable.
|
||||||
|
|
||||||
|
Example login:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"organizationId": "<UUID returned by bootstrap>",
|
||||||
|
"email": "owner@example.com",
|
||||||
|
"password": "<your passphrase>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Access rules
|
||||||
|
|
||||||
|
Organization scope comes from the persisted session, never from an administration request body. Email uniqueness is per organization and normalized to lowercase. New accounts have no roles and cannot sign in until approved. Multiple roles combine their permissions. Permissions are reloaded on every request and rechecked inside administration transactions.
|
||||||
|
|
||||||
|
Administrators cannot grant permissions they do not hold, modify a more privileged role, change their own role assignments, assign the system Owner role, or change the owner's approval state. A single installation owner is created by the CLI; API-supplied owner flags are rejected. Suspending a user revokes sessions and recovery tokens; reactivation does not restore them.
|
||||||
|
|
||||||
|
An organization represents an access boundary, not a customer or supplier by default. No organization onboarding API, SaaS subscription isolation, MFA, SSO or social login is included in this milestone.
|
||||||
|
|
||||||
|
## Passwords, sessions and recovery
|
||||||
|
|
||||||
|
Passwords require 15–128 characters and use salted asynchronous scrypt (N=32768, r=8, p=3). Session and recovery secrets use 256 bits of randomness; only SHA-256 token digests are persisted. Sessions expire after the configured absolute lifetime. No refresh-token flow is needed for this opaque-session design; clients sign in again after expiry.
|
||||||
|
|
||||||
|
Recovery links put the token in the URL fragment; the future frontend must read it, submit it to the reset endpoint and remove it from browser history. Never log request bodies or authorization headers at the reverse proxy. Recovery resets invalidate all sessions and other recovery tokens in the same transaction. A failed SMTP send discards that token and logs a sanitized failure; users can request again after the throttle window.
|
||||||
|
|
||||||
|
Recovery delivery is synchronous in this milestone. The response body does not reveal account existence, but response timing is not constant. Durable queued delivery and retries belong to the notification milestone before a public storefront launch. No real emails are sent by the test suite.
|
||||||
|
|
||||||
|
## Audit and abuse protection
|
||||||
|
|
||||||
|
Successful logins, known-account login failures, logout, password recovery/reset, user provisioning/approval/suspension and role changes are audited. Mutations and audit inserts share a transaction. Audit rows accept no arbitrary metadata and database triggers reject updates/deletes. Deploy with a restricted application database role; database owners can bypass database controls.
|
||||||
|
|
||||||
|
Auth routes share a database-backed IP limit of 30 requests/minute. Login also permits 10 attempts/account/15 minutes; recovery requests permit 3/account/15 minutes. Limits persist across instances. Proxy trust is disabled: behind a proxy, IP throttling groups requests under its address until an explicitly trusted proxy policy is configured. Use an edge rate limiter as well before public launch.
|
||||||
|
|
||||||
|
Expired sessions, recovery tokens and rate-limit rows can be removed by a controlled housekeeping job using their indexed expiry columns. Audit retention requires an explicit archival policy and privileged maintenance procedure; the API never deletes audits.
|
||||||
|
|
||||||
|
References: [Node crypto](https://nodejs.org/api/crypto.html), [Nodemailer SMTP](https://nodemailer.com/smtp).
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Identity setup and release
|
||||||
|
|
||||||
|
## Database and owner bootstrap
|
||||||
|
|
||||||
|
1. Set DATABASE_URL for the intended development PostgreSQL database.
|
||||||
|
2. Install with the lockfile, run `pnpm db:generate`, then `pnpm db:deploy`.
|
||||||
|
3. Run `pnpm check` and `pnpm build`.
|
||||||
|
4. Supply ORGANIZATION_NAME, OWNER_NAME, OWNER_EMAIL and OWNER_PASSWORD through environment variables. The password must be 15–128 characters. Do not pass secrets in CLI arguments or commit them.
|
||||||
|
5. Run `pnpm bootstrap:owner`. It prints only organizationId and userId. Remove bootstrap environment secrets after use.
|
||||||
|
6. Start the application and use the returned organizationId to log in.
|
||||||
|
|
||||||
|
Bootstrap is a one-time installation operation protected by a transaction advisory lock and a unique owner index. A repeat invocation fails without creating partial records. Never bootstrap a production owner using test credentials. There is no default password.
|
||||||
|
|
||||||
|
## Optional recovery email
|
||||||
|
|
||||||
|
Set all of SMTP_HOST, SMTP_USER, SMTP_PASSWORD, SMTP_FROM and RECOVERY_URL together. SMTP_PORT defaults to 587; port 465 uses implicit TLS. TLS is mandatory. RECOVERY_URL must be an HTTPS frontend reset page without credentials, a query string or a fragment.
|
||||||
|
|
||||||
|
Until SMTP is configured, recovery requests return 503 for every account. The rest of identity remains usable. SESSION_TTL_MINUTES defaults to 480 (5–10080 allowed); RECOVERY_TTL_MINUTES defaults to 15 (5–60 allowed). SMTP settings are validated at startup but connectivity is only exercised on delivery.
|
||||||
|
|
||||||
|
No production SMTP account, reset frontend page, deployment environment or live owner was provisioned in this development task.
|
||||||
|
|
||||||
|
## Migrations and release checks
|
||||||
|
|
||||||
|
- 202609080002_identity_access adds users, roles, organization-safe assignments, sessions, recovery tokens, audit events and durable rate-limit buckets.
|
||||||
|
- 202609080003_identity_integrity adds normalized-email and single-owner constraints plus append-only audit triggers.
|
||||||
|
- Existing organization rows are retained. The migrations add no default user or credential.
|
||||||
|
- Custom SQL constraints/triggers are intentional and cannot all be represented in the Prisma model. Their migration tests must remain enabled.
|
||||||
|
- Run `node scripts/verify-migrations.mjs` against a disposable PostgreSQL database to verify deploy, status and model drift.
|
||||||
|
- Back up the target database, run deploy once per release, then verify readiness and the identity smoke flow. Do not use destructive down migrations to remove identity data. Roll back application binaries only when schema-compatible; otherwise apply a forward fix.
|
||||||
|
|
||||||
|
## Automated checks
|
||||||
|
|
||||||
|
`pnpm check` runs formatting, schema validation, strict type checks, unit tests, integration tests with coverage and production compilation. Tests use an embedded PostgreSQL engine by default and apply every checked-in migration, including upgrading a seeded foundation organization.
|
||||||
|
|
||||||
|
For native PostgreSQL tests, set TEST_DATABASE_URL to a disposable PostgreSQL administrator connection. Each fixture creates and drops a randomly named test database. Never supply production credentials. The concurrent recovery test runs only on native PostgreSQL because the embedded engine does not reproduce independent PostgreSQL connection concurrency.
|
||||||
|
|
||||||
|
The Gitea workflow targets a Docker-capable act runner labelled ubuntu-latest, with Actions enabled and internet access for dependency installation. It provides PostgreSQL 17, runs native tests and verifies migrations. Runner availability, remote CI results and branch-protection rules must be checked on the repository host; committing a workflow does not enable these settings automatically.
|
||||||
|
|
||||||
|
Before public launch, complete the reset frontend, SMTP smoke test, queued notification delivery, trusted proxy policy, operational housekeeping, and native database CI. Customer self-registration is separate from staff provisioning.
|
||||||
|
|
@ -4,8 +4,8 @@ Source: Mani Candles Commerce Platform specification and project pack created in
|
||||||
|
|
||||||
## Phase 1: Foundation and core commerce
|
## Phase 1: Foundation and core commerce
|
||||||
|
|
||||||
- 1A: service bootstrap, configuration, database lifecycle, initial organization migration, health API, test/build baseline, team workflow.
|
- 1A (implemented): service bootstrap, configuration, database lifecycle, initial organization migration, health API, test/build baseline, team workflow.
|
||||||
- 1B: authentication, sessions, recovery, users, configurable RBAC, organization access, approval status, audit events. Test denied access and cross-organization access.
|
- 1B (implemented; native CI and deployment configuration pending): authentication, sessions, recovery, users, configurable RBAC, organization access, approval status, audit events. Test denied access and cross-organization access.
|
||||||
- 1C: catalog, variants, collections, addresses, inventory ledger and reservations. Test concurrent reservation and stock reconciliation.
|
- 1C: catalog, variants, collections, addresses, inventory ledger and reservations. Test concurrent reservation and stock reconciliation.
|
||||||
- 1D: cart, checkout, orders, coupons and pricing snapshots. Test money precision, discount eligibility, retries and transaction rollback.
|
- 1D: cart, checkout, orders, coupons and pricing snapshots. Test money precision, discount eligibility, retries and transaction rollback.
|
||||||
- 1E: verified payments/refunds, shipping/tracking, returns, notifications and operational dashboard. Test signatures, replay, partial fulfillment and reconciliation.
|
- 1E: verified payments/refunds, shipping/tracking, returns, notifications and operational dashboard. Test signatures, replay, partial fulfillment and reconciliation.
|
||||||
|
|
@ -32,4 +32,4 @@ Small cohesive files; no duplicated business rules; unit tests for success, fail
|
||||||
|
|
||||||
## Decisions still needed
|
## Decisions still needed
|
||||||
|
|
||||||
Git author identity; remote connectivity; provider accounts; deployment target and PostgreSQL service; tax/invoice and retention rules; stock location model; approval thresholds; Etsy sync direction; launch feature cut. Resolve each before its dependent milestone.
|
Provider accounts; deployment target and PostgreSQL service; tax/invoice and retention rules; stock location model; approval thresholds; Etsy sync direction; launch feature cut. Resolve each before its dependent milestone.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
# Verification record
|
# Verification record — Phase 1B
|
||||||
|
|
||||||
Phase 1A: 19 tests pass across configuration, database lifecycle, and HTTP health/security behavior. Formatting, Prisma schema validation, TypeScript checking and production compilation pass. Coverage excludes generated client, module declarations and bootstrap: 100% statements/lines/functions, 85.71% branches.
|
Completed locally:
|
||||||
|
|
||||||
Tests use mocked database connectivity. A real PostgreSQL instance is not installed/configured in this workspace. Run `node scripts/verify-migrations.mjs` with DATABASE_URL pointing to a disposable PostgreSQL database; it applies migrations, checks status and checks schema drift. Repeat against a restored prior-release snapshot for future releases. This script applies migrations and must never target an unapproved production database.
|
- 86 passing tests across 13 suites; one native PostgreSQL concurrency test is intentionally skipped without TEST_DATABASE_URL.
|
||||||
|
- 100% statements, lines and functions; 86.11% branches for hand-written application code. Generated code, Nest module declarations and CLI/HTTP entrypoint wrappers are excluded. Bootstrap business logic is tested.
|
||||||
|
- Formatting, Prisma schema validation, strict TypeScript/unused-code checks and production compilation pass.
|
||||||
|
- All three migrations execute through Prisma migrate deploy; migrate status reports up to date and schema diff reports no drift against a disposable embedded PostgreSQL engine.
|
||||||
|
- Migration tests preserve a pre-existing organization row, reject cross-organization role assignments, enforce normalized email and one owner, and prevent audit updates/deletes.
|
||||||
|
- API tests cover pending/suspended accounts, revoked/expired sessions, current permissions, privilege escalation, recovery replay, throttling and transaction rollback.
|
||||||
|
|
||||||
SSH remote access failed host-key verification. No remote history was fetched and no commits were pushed. The feature branch is provisional until remote history is checked. Git author is configured locally as mihir <motiyanimihir@gmail.com>. Do not merge an unrelated root history into an existing repository; fetch and base the feature work on its default branch first.
|
Native PostgreSQL concurrency and the remote Gitea workflow have not been verified locally. The workflow provisions PostgreSQL 17 and enables the native test path. A Docker-capable Gitea runner and Actions access are required.
|
||||||
|
|
||||||
|
Recovery email is tested using a mock SMTP adapter; no external email was sent. Live SMTP, a frontend recovery page and production deployment/owner bootstrap remain environment setup tasks. Recovery email is synchronous pending the later notification queue milestone.
|
||||||
|
|
||||||
|
Git identity: mihir <motiyanimihir@gmail.com>. Work is based on the fetched main branch and lives on feat/identity-access.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue