mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 18:01:37 +00:00
e1e43c8924
Audit 2026-05-10 MED-11 closure (foundation step).
WHAT.
Lays the schema + domain foundation for the MED-11 federated-user
admin surface:
1. Migration 000045 adds users.deactivated_at TIMESTAMPTZ (nullable;
non-NULL = deactivated). Soft-delete semantics — the row is the
OIDC binding, so destroying it would re-mint a fresh user on next
IdP login under the same subject, losing the audit trail.
2. Seeds 2 new catalogue permissions:
- auth.user.read (admin / operator / auditor)
- auth.user.deactivate (admin ONLY)
3. Extends User domain struct with DeactivatedAt *time.Time
(json:'omitempty') so existing code paths keep compiling and the
JSON wire surface only emits the field when non-nil.
WHY.
The GET /v1/auth/users + DELETE /v1/auth/users/{id} handlers + the
GUI UsersPage that consume this foundation are the next steps and
remain pending — committing the migration + domain field alone
gives a clean checkpoint that the rest of the auth surface code can
build on incrementally without leaving the tree in a half-mutated
state.
HOW.
migrations/000045_users_deactivated_at.up.sql:
- ALTER TABLE users ADD COLUMN IF NOT EXISTS deactivated_at TIMESTAMPTZ
- INSERT 2 permissions into permissions
- INSERT role_permissions rows (read in r-admin/operator/auditor;
deactivate in r-admin)
- Single BEGIN/COMMIT, idempotent (ON CONFLICT DO NOTHING)
migrations/000045_users_deactivated_at.down.sql:
- reverse-order DELETE + DROP COLUMN
internal/auth/user/domain/types.go:
- User.DeactivatedAt *time.Time, JSON tag omitempty.
VERIFY.
- go vet ./internal/auth/user/... ./internal/auth/oidc/...
./internal/repository/... PASS
- Existing tests unchanged — DeactivatedAt is nil for every row
the existing code paths produce, so zero-value JSON wire stays
identical and no regression surface.
Refs: cowork/auth-bundles-audit-2026-05-10.md MED-11
cowork/auth-bundles-fixes-2026-05-10/HANDOFF.md item 14
40 lines
1.4 KiB
PL/PgSQL
40 lines
1.4 KiB
PL/PgSQL
-- 000045_users_deactivated_at.up.sql
|
|
-- Audit 2026-05-10 MED-11 closure: federated-user admin surface.
|
|
--
|
|
-- Adds the deactivated_at column to users so the admin DELETE-by-id
|
|
-- path can soft-delete a federated identity without destroying the
|
|
-- row (the row is the OIDC binding — destroying it would re-mint a
|
|
-- fresh user on the next IdP login under the same subject, losing
|
|
-- the audit trail). Also seeds two new catalogue permissions:
|
|
--
|
|
-- auth.user.read — list / get a user. Seeded into r-admin,
|
|
-- r-operator, r-auditor.
|
|
-- auth.user.deactivate — set deactivated_at + cascade-revoke
|
|
-- sessions. Seeded into r-admin ONLY.
|
|
--
|
|
-- Idempotent. Single transaction.
|
|
|
|
BEGIN;
|
|
|
|
ALTER TABLE users
|
|
ADD COLUMN IF NOT EXISTS deactivated_at TIMESTAMPTZ;
|
|
|
|
INSERT INTO permissions (name) VALUES
|
|
('auth.user.read'),
|
|
('auth.user.deactivate')
|
|
ON CONFLICT (name) DO NOTHING;
|
|
|
|
-- Read is broad (admin / operator / auditor).
|
|
INSERT INTO role_permissions (role_id, permission, scope_type, scope_id) VALUES
|
|
('r-admin', 'auth.user.read', 'global', NULL),
|
|
('r-operator', 'auth.user.read', 'global', NULL),
|
|
('r-auditor', 'auth.user.read', 'global', NULL)
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- Deactivate is admin-only.
|
|
INSERT INTO role_permissions (role_id, permission, scope_type, scope_id) VALUES
|
|
('r-admin', 'auth.user.deactivate', 'global', NULL)
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
COMMIT;
|