Merge develop into main (Phase 2: Identity + CRM + Groups) - #139
Closed
derpixler wants to merge 5 commits into
Closed
Merge develop into main (Phase 2: Identity + CRM + Groups)#139derpixler wants to merge 5 commits into
derpixler wants to merge 5 commits into
Conversation
- Package-level doc comments for all 12 packages - Exported types, functions, methods documented - Inline comments for non-obvious code paths - RequirePermission: explicit TODO for Phase 2 RBAC - ActorMiddleware: explained as future request-scoped init - ScheduledJobs: documented as placeholder for future phases 19 files, no functional changes.
Docs: godoc comments on all exported symbols
* Phase 2: auth foundations — password hashing, JWT, mail service - internal/auth: bcrypt password hash/verify; HS256 JWT issue/verify (access + 2FA-pending tokens, alg-confusion/expiry/issuer checks) - internal/core/mail: Mailer interface, NoopMailer, stdlib SMTP mailer - deps: add golang-jwt/jwt/v5; promote x/crypto to direct * Phase 2: add secrets (AES-256-GCM) and require ENCRYPTION_KEY - internal/core/secrets: Cipher with SHA-256 key derivation, base64 nonce||ciphertext; round-trip, tamper, wrong-key tests - config: ENCRYPTION_KEY now required (.env.example + config tests) * Phase 2: add dbexec actor transaction wrapper - internal/core/dbexec: WithActor/WithActorOptions run work in a tx that sets app.actor_user_id (audit) and optionally app.allow_delete - testcontainers integration test: audit actor recorded, rollback on error, soft-delete guard enforced and bypassed * Phase 2: bootstrap sqlc query layer (internal/db) - fix sqlc.yaml paths (were not resolved relative to the config dir, so generation never worked): queries, schema, out - add sqlc/queries/users.sql (GetUserByID, CountActiveUsers) - generate internal/db: db.go, models.go (all tables), querier.go, users.sql.go * Phase 2: add HTML email support to core mail (#22) - mail.Message gains HTML field; SMTPMailer builds multipart/alternative (text+html), or text/html when only HTML is set - add Renderer (html/template) for rendering HTML email bodies - tests: multipart parsing, html-only, renderer render/escape/errors * Phase 2: add generic EAV metadata handler (#23) - internal/core/metadata: Store over the 12 *_meta tables (allow-listed), Get/Set(upsert)/Delete/All; DBTX works on pool or actor tx - .golangci.yml: exclude gosec for metadata (dynamic table name from a fixed allow-list), matching the database/ precedent - tests: allow-list unit test + testcontainers CRUD integration * Phase 2: real JWT auth middleware + permission checks (#127) - middleware: Actor gains Permissions + HasRole/HasPermission; replace AuthSkeleton with Authenticate(Verifier) (injected, no import cycle); RequirePermission checks real permissions (admin = wildcard); ActorMiddleware propagates the actor into the request context - app/router + cmd/api: NewRouter takes a middleware.Verifier; main builds it from auth.TokenManager (access tokens only) - tests: authenticate none/valid/invalid, permission grant/deny, context propagation; router/main updated to the new signature * Phase 2: add German full-text search helper (#24) - internal/core/search: Searcher over the 7 search_vector tables (allow-listed); websearch_to_tsquery('german', ...) + ts_rank ranking, deleted_at filter, limit clamping, empty-query short-circuit - .golangci.yml: exclude gosec for search (dynamic table from allow-list) - tests: allow-list + empty-query unit; testcontainers integration (match, case-insensitive, no-match, soft-delete exclusion) * Phase 2: auth user repository + queries; fix audit trigger (#12) - internal/auth/repository.go: Repository with user methods; reads on pool, writes via dbexec.WithActor (audit + soft-delete enforcement) - sqlc/queries/users.sql: CreateUser, GetUserByID/Email, ListUsers, CountActiveUsers, UpdateUser, SoftDeleteUser, UserExistsByEmail (+ regen db) - schema.sql: audit_trigger_func() reads OLD/NEW fields via to_jsonb(...)->>'col' (NULL-safe), fixing 42703 on UPDATE for audited tables without status/deleted_at/anonymized_at (e.g. users) - testcontainers integration: user CRUD + audit actor attribution Partial #12 (users); roles/permissions to follow. * Phase 2: auth roles, permissions & resolution repository (#12) - sqlc/queries/roles.sql: ListRoles, GetRole, AssignRole (idempotent), RemoveRole, ListUserRoles, UserHasRole - sqlc/queries/permissions.sql: ListPermissions, GetPermissionsForUser (user_roles -> role_permissions, active roles), GetPermissionsForRole, AddRolePermission, RemoveRolePermission (+ regen db) - internal/auth/repository.go: role/user-role + permission methods (join tables are not audited/soft-deletable -> direct pool writes) - testcontainers integration: role assign/remove/list, permission resolution (mitglied=7, admin=47, DISTINCT), role_permissions CRUD Completes #12 (users + roles + permissions + user_roles + role_permissions). * Phase 2: role-assignment endpoints + OpenAPI contract (#17) - internal/auth: Service, Handler, DTOs, RegisterRoutes; establishes the handler/service/repository HTTP pattern. Endpoints GET/POST /api/users/:id/roles and DELETE /api/users/:id/roles/:slug (RequirePermission users.read / users.write) - internal/app/router.go: mount auth routes; serve GET /api/openapi.yaml - api/openapi.yaml + api/embed.go: spec-first OpenAPI 3.1 contract (health + role endpoints), embedded via go:embed - internal/app/openapi_test.go: kin-openapi spec validation + route-parity (every mounted route documented, every operation mounted) - testcontainers HTTP integration: assign/list/remove + 401/403/404/422 - deps: add getkin/kin-openapi Completes #17. * Phase 2: permission check with admin bypass (#18) - sqlc/queries/permissions.sql: UserHasPermission (role_permissions join) - internal/auth: Repository.UserHasPermission; Service.CheckPermission (admin role grants all permissions implicitly, else resolve via roles) - testcontainers integration: kassierer grant/deny, admin bypass (incl. a non-catalog permission), role-less user denied, seed 47 perms / 5 roles Completes #18. * Phase 2: groups CRUD module + endpoints + OpenAPI (#20) - sqlc/queries/groups.sql: Create/Get/List/ListByType/Update/SoftDelete - internal/groups: Repository (writes via dbexec.WithActor), Service (group_type validation, NotFound mapping), Handler + RegisterRoutes; endpoints GET/POST /api/groups, GET/PATCH/DELETE /api/groups/:id (RequirePermission groups.read/groups.write), optional ?group_type filter - internal/app/router.go: mount group routes - api/openapi.yaml: /groups + /groups/{id} paths and schemas - testcontainers integration: repo CRUD/FindByType/soft-delete/audit; HTTP create/list/filter/get/update/delete + 401/403/404/422 Completes #20 (group CRUD; members tracked in #21). * Phase 2: group members module + endpoints + OpenAPI (#21) - sqlc/queries/group_members.sql: AddMember (upsert role), RemoveMember, ListMembers, ListUserGroups - internal/groups: member repository (direct pool; not audited), Service.AddMember/ListMembers/RemoveMember/ListUserGroups (role_in_group validation, group existence -> 404), handler + member routes (GET/POST /api/groups/:id/members, DELETE .../:user_id, GET /api/users/:id/groups) - api/openapi.yaml: member + user-groups paths and schemas - testcontainers integration: member repo (add/upsert-role/remove/list); HTTP add/list/user-groups/remove + 401/403/404/422 Completes #21. * Phase 2: CRM module (address, contacts, preferences) + OpenAPI (#19) - sqlc/queries/{crm,contacts}.sql: address & preferences upsert/get; contacts list/get/create/update/soft-delete + clear-primary-per-type - internal/crm: Repository (address/preferences direct pool; contacts via dbexec.WithActor with clear-primary tx), Service (validation, NotFound, is_primary orchestration, belongs-to-user check), Handler + RegisterRoutes; endpoints GET/PUT /api/users/:id/address, GET/PUT .../preferences, GET/POST .../contacts, PATCH/DELETE .../contacts/:cid - internal/app/router.go: mount CRM routes - api/openapi.yaml: address/preferences/contacts paths and schemas - testcontainers integration: address 1:1, preferences, contacts CRUD + is_primary/SetPrimary/audit; HTTP CRUD + errors Completes #19. * Phase 2: auth & users HTTP - login, register, user CRUD, search (#128) - internal/auth: NewVerifier (token->Actor, moved from main); Service gains TokenManager + Login (verify password -> token with roles+permissions), Register (admin-gated; 409 on duplicate email), ListUsers/GetUser/UpdateUser/ DeleteUser, SearchUsers (core/search #24 + GetUsersByIDs, rank-ordered) - routes: POST /api/auth/login (public), POST /api/auth/register (users.write), GET /api/users, GET/PATCH/DELETE /api/users/:id, GET /api/search/users - wiring: NewRouter takes *auth.TokenManager; auth.RegisterRoutes(rg, pool, tm) - sqlc: GetUsersByIDs (regen db) - api/openapi.yaml: login/register/users/search paths and schemas - testcontainers integration: login (valid/invalid/claims), register (create/dup/validation), user CRUD, search + permission/401/403/404 Completes #128. * Phase 2: TOTP 2FA service core + secrets cipher wiring (#15, #16) - deps: add pquerna/otp (TOTP codes + provisioning URIs) - sqlc/queries/totp.sql: Get/Upsert/DeleteTOTPSecret; regen db - internal/auth: TOTP repo methods; Service extended with Setup2FA (secret + recovery codes), Confirm2FA, Verify2FA (lockout after 5 failures), ConsumeRecoveryCode (single-use), Disable2FA, IssueAccessForUser; cipher is passed through NewRouter/RegisterRoutes/ NewService for TOTP secret encryption - wiring: main builds secrets.Cipher from ENCRYPTION_KEY, passes through NewRouter -> auth.RegisterRoutes -> Service Partial #15, #16 (TOTP foundation; endpoints + login integration to follow). * Phase 2: 2FA endpoints + login integration (#15, #16) - Login service: checks TOTP state; returns a pending token and requires_2fa flag when 2FA is active instead of a full access token - Handler: Login responds {requires_2fa, temp_token} when 2FA active - 2FA endpoints: setup (provisioning URI + recovery codes), confirm (activates TOTP), verify (temp_token + code -> access token), recovery (redeem single-use code), disable (code required) - loginResponse gained Requires2FA / TempToken fields - api/openapi.yaml: 2FA paths and schemas - testcontainers integration: full 2FA flow (setup/confirm/login-2fa/ verify/recovery/disable) with real TOTP, #15/#16 Completes #15 and #16. * Phase 2: E2E registration flow test (#25) - internal/auth/e2e_test.go: full HTTP integration exercising register -> role assign -> login -> profile -> 2FA setup+confirm -> login-2fa -> verify -> profile, end-to-end via the public API Completes #25 (and Sprint 02). * Phase 2: hybrid test suite + API documentation (#129) - scripts/lib/common.sh: extracted harness (colors, helpers, global steps: prereqs, build, lint, coverage, compose, docker-build, makefile, full-check) - scripts/tests/tp2.sh: curated TP2 unit (§§ 1.1–1.12) + integration steps (§§ 2.1–2.20) across all modules (auth/crm/groups) - scripts/test.sh: combined TP1+TP2 dispatcher (menu, --ci mode, backward- compatible step numbers 01–10; 04/05 aggregate both phases) - docs/testing.md: test suite usage, CI, per-package test commands - docs/api/{auth,crm,groups}.md: endpoint overviews with examples - docs/README.md: documentation index Completes #129. * Phase 2: add timestamped log files to test suite (#129) - scripts/test.sh: each run writes a timestamped log to logs/test-<YYYYmmdd-HHMMSS>.log via exec > >(tee ...) 2>&1 - .gitignore: exclude logs/ - fix CI_MODE label display * Phase 2: promote pquerna/otp to direct dependency (#15, #16) * Phase 2: improve test coverage to ~80% (#130) - auth (74.8->80.5%): error paths for RemoveRole (404), DeleteUser (404), UpdateUser (422/404), pagination query params - 2FA: Confirm2FA wrong-code (422), Disable2FA wrong-code (401) - groups (74.4->79.0%): Delete nonexistent (404), RemoveMember nonexistent group (404), pagination happy/invalid paths - crm (76.1->79.6%): DeleteContact nonexistent (404), GetPreferences nonexistent user (404) - search: limit>maxLimit clamp branch Completes #130. * fix: change default port from 8080 to 8088 (port conflict) 8080 was occupied by another service (WordPress) on the dev machine. 8088 is free in the 80xx range. - .env.example, config.go: default APP_PORT -> 8088 - config_test.go: update default port assertion - docker-compose.prod.yml: port mapping, env, healthcheck -> 8088 - scripts/lib/common.sh: health check curl -> localhost:8088 - README.md: quick-start example -> localhost:8088 * fix: support 'help' and '--help' in test.sh (#131) * fix: show detailed failure list in test suite summary - FAILED_STEPS array collects failed check descriptions - run_cmd, run_go_test and direct FAILED increments populate it - summary() lists each failure below the count line * fix: coverage step no longer marks FAIL when threshold is met go test -cover exits non-zero if any package has 0% coverage (cmd/api), which caused a cosmetic FAIL. The threshold check now handles PASS/FAIL independently. * Phase 2: password reset (forgot + reset via email) (#133) - sqlc/queries/users.sql: UpdatePassword query (+ regen db) - internal/auth: Repository.UpdatePassword, Service.ForgotPassword (bcrypt token + users_meta store + email via mailer, no enumeration), Service.ResetPassword (token verify, expiry, single-use, password update) - routes: POST /auth/password/forgot + POST /auth/password/reset (public) - wiring: mail.Mailer passed through NewRouter/RegisterRoutes/Service (also unblocks Email-OTP for #15/#16 follow-up) - api/openapi.yaml: password reset paths and schemas - test: forgot flow, wrong token, invalid id, missing fields (#133) Completes #133. * feat: add self-hosted Redoc API docs at GET /api/docs - api/redoc.js: vendored redoc.standalone.js (~1MB, MIT license) - api/redoc.html: minimal HTML loading Redoc against /api/openapi.yaml - api/embed.go: embed both redoc files - router: serve GET /api/docs + GET /api/docs/redoc.js - openapi_test: exempt /docs + /docs/redoc.js from route parity * test: cover password reset expiry + used-flag edge cases (#133) * docs: add RBAC and password reset architecture docs * feat: email-2FA as a standalone second factor (#134) Email-OTP second factor parallel to TOTP: setup/confirm/verify/resend/disable. 6-digit codes (bcrypt-hashed, single-use, 10m expiry, 5-failure lockout) stored in users_meta. Login requires 2FA if TOTP or email is enabled. Adds OpenAPI paths + TempTokenRequest schema and auth API docs. * test: detailed gated logging + email-2FA tests doReq logs request/response; tstep/tlog/assertStatus helpers (HTTP in auth_test, white-box in package auth via testlog_test.go). Password redacted, JWT collapsed to eyJ…, gated behind -v. Adds email-2FA flow + lockout tests. * test: add Email-2FA step 2.21 to TP2 integration suite * fix: provide ENCRYPTION_KEY in docker-compose.prod.yml App config requires ENCRYPTION_KEY; add it (dev default, overridable) so the prod-compose health check no longer panics on startup. * feat(core): add module SDK skeleton (Module interface + Registry) * feat(core): add EventBus, Cache, Searcher seams + wire into module Deps * refactor(app): module-driven router via module.Registry + module adapters * refactor(auth): introduce identity Provider seam (local default, OIDC-ready) * refactor(app): composition-root module assembly + registry lifecycle
derpixler
force-pushed
the
merge/develop-to-main
branch
from
June 29, 2026 13:32
f616b79 to
f4318c8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge-branch from main, bringing develop's Phase 2 (Sprint 02 — #132) into main. Conflicts (5 files: godoc #126 vs Phase-2 rewrites) auto-resolved toward develop (Phase 2 superset, develop already contains the godoc via #125). Automatically merged with
-X theirs.