Skip to content

feat(identity,tenancy): invite-only login, companies and forced RLS #4

Description

@Fluory

Goal

Staff log in with e-mail and password (invite-only). Every user belongs to a company (Better Auth organization), and every tenant-owned query runs inside withTenant under forced row-level security.

Acceptance criteria

  • Given no invitation, when someone signs up, then sign-up is rejected
  • Given an invited user, when they log in, then the session carries their active company
  • withTenant(companyId, fn) sets app.company_id transaction-locally; repositories cannot be called without a tenant context
  • Given companies A and B, when a user of A lists requests, then no row of B is returned – via repository and via raw SQL as app_rw
  • Given app_rw, when a row carrying the id of another company is inserted, then RLS rejects it
  • Roles admin and clerk; authorize() denies admin-only actions to clerks
  • Better Auth pinned to >= 1.7.5 with @better-auth/drizzle-adapter; built-in rate limit uses database storage; only organization + admin plugins

Not part of this task

  • SSO / Entra ID, magic link, role-admin UI (invite form + seed script only)

Affected areas

  • src/features/identity/
  • src/features/tenancy/
  • src/db/

Test plan

Criterion Check
sign-up and login integration test
cross-tenant read/write integration test with two tenants, repository and raw SQL
authorize() unit test

Security/Privacy affected?

Yes – authentication, authorization and tenant isolation. Run security-review before ready-for-review.

Epic: #2 · Architecture: docs/decisions/ADR-0001-pilot-architecture.md

Activity

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

Metadata

Metadata

Assignees

Labels

featureNew capabilityreadyDefinition of Ready met – may be claimedsecuritySecurity or privacy relevant

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions