{"id":51205574,"url":"https://github.com/omercnet/uar","last_synced_at":"2026-06-28T03:03:45.745Z","repository":{"id":366817976,"uuid":"1277989153","full_name":"omercnet/uar","owner":"omercnet","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-23T13:13:52.000Z","size":366,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T13:32:51.563Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://uar.preview.descope.org","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/omercnet.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-23T11:11:52.000Z","updated_at":"2026-06-23T13:14:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/omercnet/uar","commit_stats":null,"previous_names":["omercnet/uar"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/omercnet/uar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fuar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fuar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fuar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fuar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/omercnet","download_url":"https://codeload.github.com/omercnet/uar/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fuar/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34875369,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-28T03:03:45.153Z","updated_at":"2026-06-28T03:03:45.733Z","avatar_url":"https://github.com/omercnet.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# User Access Review (UAR)\n\nA multi-tenant compliance platform for running access-review campaigns: ingest access snapshots from connectors, assign reviewers, collect approve/revoke/exception decisions, finalize with a reproducible content-hash artifact, and export CSV evidence.\n\n## Quick start\n\n```bash\n# 1. Start Postgres\ndocker compose up -d postgres\n\n# 2. Migrate (creates all tables + uar_app RLS role)\nDATABASE_URL='postgres://uar:uar_dev_password@localhost:5433/uar' \\\n  pnpm --filter @uar/api db:migrate\n\n# 3. Seed a tenant (save the printed UUID)\ndocker exec uar-postgres psql -U uar -d uar -c \\\n  \"INSERT INTO tenants (tenant_id, slug, name)\n   VALUES (gen_random_uuid(), 'my-org', 'My Org')\n   RETURNING tenant_id;\"\n\n# 4. Start the API (stub auth — no Descope project needed)\nSTUB_AUTHZ=true \\\nUAR_STUB_TENANT_ID='\u003ctenant_uuid\u003e'  \\\nUAR_STUB_USER_ID='\u003cany_uuid\u003e'       \\\nUAR_STUB_ROLES='admin'              \\\nDATABASE_URL='postgres://uar:uar_dev_password@localhost:5433/uar' \\\nUAR_INGEST_CSV='./e2e/fixtures/access.csv' \\\nPORT=3001 \\\n  pnpm --filter @uar/api dev\n\n# 5. Start the web UI (separate terminal)\npnpm --filter @uar/web dev   # → http://localhost:3000\n# If :3000 is taken: PORT=3100 pnpm --filter @uar/web dev\n```\n\nOpen **http://localhost:3000**, create a campaign, trigger ingest, review items, finalize, and download the CSV evidence.\n\n### Smoke-test the API\n\n```bash\nnode -e \"fetch('http://localhost:3001/health').then(r=\u003er.json()).then(console.log)\"\n# → { status: 'ok' }\n```\n\n## Repo layout\n\n```\napps/\n  api/      node:http production API server (Drizzle/Postgres, Descope auth, RLS)\n  web/      Next.js 15 admin + reviewer UI\n  worker/   pg-boss background worker (CSV ingest pipeline)\n\npackages/\n  core/         Zod domain contracts, review lifecycle, connector contract\n  connectors/   CSV connector, GitHub connector, Descope outbound spike\n  reporting/    Content-hash finalization, EvidenceSink, CSV evidence renderer\n```\n\n## Running the full test suite\n\n```bash\n# Unit tests (no Postgres needed — DB-gated tests skip cleanly)\npnpm -r test\n\n# Unit tests against real Postgres (runs DB-gated tests)\nDATABASE_URL='postgres://uar:uar_dev_password@localhost:5433/uar' pnpm -r test\n\n# End-to-end (boots real API + web, requires Postgres on :5433)\npnpm e2e\n```\n\n\u003e **Note:** Before running unit tests with a live Postgres, kill any running API/web dev servers first. Leftover connections can deadlock the test-reset helpers.\n\n## Environment variables\n\n### API (`apps/api`)\n\n| Variable | Default | Description |\n|---|---|---|\n| `DATABASE_URL` | `postgres://uar:uar_dev_password@localhost:5433/uar` | Postgres connection string |\n| `PORT` | `3001` | HTTP listen port |\n| `STUB_AUTHZ` | `false` | Bypass Descope verification (dev/e2e only; forbidden in `NODE_ENV=production`) |\n| `UAR_STUB_TENANT_ID` | `local-dev-tenant` | Tenant UUID injected under stub auth |\n| `UAR_STUB_USER_ID` | `local-dev-user` | User UUID injected under stub auth |\n| `UAR_STUB_ROLES` | `admin` | Comma-separated roles under stub auth |\n| `DESCOPE_PROJECT_ID` | _(unset)_ | Real Descope project ID; required when `STUB_AUTHZ` is not set |\n| `UAR_INGEST_CSV` | `../../e2e/fixtures/access.csv` | Path to the CSV file used by the ingest endpoint |\n| `UAR_INGEST_APPLICATION_ID` | _(stable UUID)_ | Application ID written to the directory graph during ingest |\n\n\u003e `UAR_STUB_TENANT_ID` and `UAR_STUB_USER_ID` must be valid UUIDs (the stub user acts as both admin and reviewer).\n\n### Web (`apps/web`)\n\n| Variable | Default | Description |\n|---|---|---|\n| `NEXT_PUBLIC_API_URL` | `http://localhost:3001` | API base URL baked into the Next.js bundle at build time |\n\n## CSV connector format\n\nThe ingest endpoint reads a CSV with these columns:\n\n```\nexternalAccountId, email, displayName, grantId, accessId, accessLabel, accessType, observedAt\n```\n\nExample (`e2e/fixtures/access.csv`):\n\n```csv\nacct-001,alice@example.com,Alice Anderson,grant-001,role-admin,Production Administrator,role,2026-01-01T00:00:00.000Z\n```\n\n## Real Descope auth\n\n1. Create a Descope project at [app.descope.com](https://app.descope.com).\n2. Configure a login flow and set the session JWT to include your internal tenant UUID in the `dct` or `tenant_id` custom claim, and the user's internal UUID as the `sub`.\n3. Start the API with `DESCOPE_PROJECT_ID=\u003cyour_project_id\u003e` (drop `STUB_AUTHZ`).\n4. The web app reads the `DS` session cookie set by Descope's frontend SDK and forwards it as `Authorization: Bearer` to the API.\n\n## Architecture\n\nSee [`docs/architecture-notes.md`](docs/architecture-notes.md) for the full architecture.\n\nKey design decisions:\n- **Tenant isolation via Postgres RLS** — all 15 tenant-scoped tables have `FORCE ROW LEVEL SECURITY`; the app connects as the non-superuser `uar_app` role so policies actually enforce (migration `0005`).\n- **Immutable snapshots** — a DB trigger blocks mutation of frozen snapshot nodes/edges; re-ingest creates a new snapshot.\n- **Reproducible evidence** — `finalizeReviewExport` computes a deterministic SHA-256 over sorted nodes + edges + decisions + assignments; re-finalize is idempotent.\n- **Connector seam** — connectors implement an `AsyncIterable\u003cSyncResult\u003e` interface with resumable cursors; the CSV and GitHub connectors ship today.\n\n## Development\n\n```bash\n# Install dependencies\npnpm install\n\n# Build all packages\npnpm -r build\n\n# Lint\npnpm -r lint\n\n# Run a single package's tests\npnpm --filter @uar/api test\n```\n\n## Migrations\n\n```bash\n# Apply migrations (runs 0000–0005)\nDATABASE_URL='...' pnpm --filter @uar/api db:migrate\n\n# Generate a new migration after schema changes\npnpm --filter @uar/api db:generate\n```\n\n\u003e Migration `0004` adds RLS policies. Migration `0005` creates the `uar_app` application role. Both must be applied before the server will start successfully.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomercnet%2Fuar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fomercnet%2Fuar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomercnet%2Fuar/lists"}