Skip to content

SecPal API: Multi-tenant security, field encryption & blind indexes, Sanctum & Spatie Teams — TDD/PEST, DRY, best practices #50

@kevalyq

Description

@kevalyq

Goal

Establish a secure, API-only Laravel 12 (PHP 8.4) foundation for SecPal: multi-tenant isolation, field encryption, blind indexes, Sanctum auth, Spatie Teams RBAC; CI with strict formatting, static analysis, and TDD via Pest. DRY by reusing org-wide .github.

Non-goals

Final user/identity domain decisions (login identifiers, MFA, invites). These will follow later PRs after business clarification.

Repository Scope & Communication

  • Scope: Work only in SecPal/api. Do not modify other repos (except referencing org-wide .github via workflow_call).
  • Language: GitHub (issues/PRs/commits/docs) English-only. Direct questions to the owner in German (VS Code) are allowed.

Principles

  • TDD, Quality First, DRY, Clean over quick
  • PEST-only (no direct PHPUnit); coverage ≥80%
  • Small PRs (~400–600 LOC) with Conventional Commits
  • Sanctum (guard api), Spatie Teams (tenant_id scope)
  • Field encryption (Eloquent encrypted casts), blind indexes (HMAC) for equality search
  • No LIKE/range/sort on encrypted fields; FTS only via tsvector
  • Envelope key management without external KMS (KEK file, per-tenant DEK/idx_key, rotation)
  • DRY: Reuse org-wide .github workflows/templates (fail CI if local duplicates appear)

Acceptance Criteria

  • All PRs green in CI (Pint PSR-12, Larastan, Pest ≥80% coverage)
  • Reuse of org .github via reusable workflows (no local copies; duplication causes CI failure)
  • API-only with Sanctum (stateless PATs) and Spatie Teams fully wired
  • Multi-tenant scope enforced; no cross-tenant access (tests)
  • No plaintext in DB/logs/responses (tests)
  • Rotation/rebuild commands exist & are tested
  • Each PR ≤ ~400–600 LOC where feasible; else split
  • PR template checklist completed; linked to this issue and to Project

Work Plan (each item = its own PR; reference this issue)

  • PR-0: Org .github DRY & Preflight
    - Reference org reusable CI via workflow_call (Laravel CI); add only minimal repo glue.
    - .editorconfig, .gitattributes (LF) if not inherited.
    - README (preflight checks, KEK creation).
    - PEST smoke tests (env/config).
  • PR-1: Migrations & Base Schema
    - Tables: tenant_keys (dek_wrapped/nonce, idx_wrapped/nonce, key_version, created_at); person (tenant_id, *_enc, *_idx, created_at).
    - Indexes: (tenant_id, email_idx), (tenant_id, phone_idx). Optional FTS: note_tsv + GIN.
    - PEST schema tests.
  • PR-2: Services — KeyStore & BlindIndex
    - config/keys.php; KeyStore (libsodium xchacha20-poly1305); BlindIndex (normalize/HMAC).
    - PEST tests: KEK load, unwrap, HMAC determinism; no plaintext.
  • PR-3: Tenant Middleware & RBAC Wiring
    - Middleware SetTenant (path or X-Tenant), membership check (Spatie), PermissionRegistrar::setPermissionsTeamId($tenantId).
    - Tests: missing tenant 400/403, valid tenant, isolation.
  • PR-4: Model/Observer & Repository
    - Person model: encrypted casts; $hidden protects *_enc/*_idx; observer computes _idx from transient email_plain/phone_plain.
    - PersonRepository: findByEmail, createOrUpdate.
    - Tests: encryption cast, hidden, blind-index search.
  • PR-5: API Endpoints (JSON)
    - Routes under [auth:api, SetTenant]: POST /tenants/{tenant}/persons, GET /tenants/{tenant}/persons/by-email.
    - Spatie middleware (permission:person.write/read).
    - Feature tests: 201/200/403; no _enc/_idx exposure.
  • PR-6: Rotation & Maintenance
    - Commands: keys:generate-tenant, keys:rotate-kek, keys:rotate-dek, idx:rebuild.
    - Tests: re-wrap/re-encrypt/index rebuild; search/decrypt still OK.
  • PR-7: Hardening & Cleanups
    - Fix static analysis/CS; README security notes (KEK, rotation, FTS leakage).
    - Tests: no plaintext in logs/DB.

Technical Guardrails

  • Auth: Sanctum PATs (guard api), stateless
  • RBAC: Spatie Teams (team_foreign_key='tenant_id')
  • Encryption: Eloquent encrypted casts for *_enc; blind index *_idx = HMAC_SHA256(normalized_value, idx_key_of_tenant) stored as BYTEA
  • Normalization: email lower(trim(v)); phone/badge: digits-only
  • Envelope keys: KEK file (KEK_PATH, mode 0600); per-tenant DEK + idx_key wrapped and stored in tenant_keys
  • CI: Reuse org .github reusable workflows; fail on Pint/Larastan errors and coverage <80%

Links

  • Org reusable workflows & templates: SecPal/.github
  • This repo: SecPal/api
  • Project: link this issue to the Roadmap/Project board

Notes

  • Do not open “question” issues. Ask the owner directly in VS Code (German) for clarifications.
  • Keep each PR small (~400–600 LOC). Follow Conventional Commits.

Branch & PR Conventions

  • Branches: feat/<scope>-<desc>, chore/<scope>-<desc>, fix/<scope>-<desc>
  • PR titles/descriptions: English-only, Conventional Commits

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions