Allow non-admin document owners to add and remove expected signers for their documents via new endpoints:
- POST /users/me/documents/{docId}/signers
- DELETE /users/me/documents/{docId}/signers/{email}
Closes#17
fix: escape database names in SQL and add owner-based document management
- Quote PostgreSQL identifiers with double-quotes in migrations to support database names containing special characters like hyphens (fixes#16)
- Add CanManageDocument to Authorizer interface for ownership checks
- Add owner-based document management
- Non-admin users can now manage documents they created when ACKIFY_ONLY_ADMIN_CAN_CREATE=false (fixes#17)
- Return 403 for owner routes when ACKIFY_ONLY_ADMIN_CAN_CREATE=true
Closes#16
refactor: move TenantProvider interface to pkg/providers
- Centralize all provider interfaces in pkg/providers package for consistency with Authorizer and other capability provider interfaces.
MagicLinkRepository was bypassing RLS by using r.db directly instead of
dbctx.GetQuerier(ctx, r.db). This meant queries ran outside the
transaction with app.tenant_id set, causing RLS policies to not apply.
All methods now use dbctx.GetQuerier to properly participate in the
RLS transaction context.
- Move service initialization (MagicLink, Email, i18n) to main.go
- Change signature lookup from user_sub to email for cross-auth consistency
- Remove OauthService wrapper, simplify auth layer
- Pass parent context to workers for graceful shutdown
- Fix IP extraction from RemoteAddr with port
- Add compact mode to SignatureList component
- Update Cypress tests with new data-testid attributes
- Create unified AuthProvider interface in pkg/providers/interfaces.go
supporting OIDC, MagicLink, and session management
- Implement DynamicAuthProvider that reads config from ConfigService
on each request, enabling hot-reload of auth settings
- Simplify ServerBuilder by removing separate oauthProvider and flags
- Consolidate auth handlers into single handler.go using unified interface
- Remove obsolete providers (oauth_provider.go, magiclink_provider.go)
- Remove separate magic_link_handler.go and reminder_auth_handler.go
- Update tests with new mockAuthProvider implementing full interface
- Fix config_service_test.go SMTP validation (requires Host + From)
BREAKING CHANGE: ACKIFY_APP_PASSWORD environment variable is now required for RLS support. The migrate tool creates the ackify_app role before running migrations, ensuring compatibility with existing deployments.
Changes:
- Add ensureAppRole() in cmd/migrate to create/update ackify_app role
- Remove docker/init-scripts/01-create-app-user.sh (no longer needed)
- Update compose.yml: add ACKIFY_APP_PASSWORD, backend connects as ackify_app
- Update migration 0016: remove conditional role creation
- Add RLS documentation (docs/en/configuration/rls.md, docs/fr/configuration/rls.md)
- Update configuration docs with RLS section and security checklist
Migration path for existing deployments:
1. Set ACKIFY_APP_PASSWORD in .env
2. Run docker compose up (migrate will create the role automatically)
Apply Clean Architecture principles throughout the codebase to eliminate tight coupling between layers. Handlers now depend exclusively on services through private interfaces, never directly on repositories.
Introduce a ServerBuilder pattern with pluggable capability providers.
refactor(auth): introduce injectable AuthorizerService
Replace hardcoded AdminEmails and OnlyAdminCanCreate config fields
with an injectable AuthorizerService. This improves testability and
follows the dependency injection pattern used elsewhere in the codebase.
- Create AuthorizerService in application/services/
- Define minimal Authorizer interfaces in consuming packages
- Update middleware, handlers, and router to use injected authorizer
- Update all affected tests with mock implementations
refactor(build): move go.mod to backend directory
Move Go module files from project root to backend/ directory while keeping the module name as github.com/btouchard/ackify-ce.
This improves project structure by keeping Go-specific files within the Go codebase directory.
# Conflicts:
# backend/internal/application/services/checksum_service_test.go
# backend/internal/application/services/document_service.go
# backend/internal/application/services/document_service_duplicate_test.go
# backend/internal/application/services/document_service_test.go
# backend/internal/presentation/api/documents/handler.go
# backend/internal/presentation/api/documents/handler_test.go
# backend/internal/presentation/api/router.go
# backend/pkg/web/server.go
- Add instance_metadata table with unique UUID per instance
- Add tenant_id column to all business tables (documents, signatures, expected_signers, webhooks, reminder_logs, email_queue, checksum_verifications, webhook_deliveries)
- Backfill existing data with instance tenant UUID
- Create TenantProvider interface and SingleTenantProvider implementation
- Update all repositories to filter by tenant_id
- Add immutability triggers to prevent tenant_id modification after creation
Migration 0015 includes:
- Schema changes with indexes for tenant_id columns
- SQL backfill for existing data
- Trigger functions for data integrity
- Add instance_metadata table with unique UUID per instance
- Add tenant_id column to all business tables (documents, signatures, expected_signers, webhooks, reminder_logs, email_queue, checksum_verifications, webhook_deliveries)
- Backfill existing data with instance tenant UUID
- Create TenantProvider interface and SingleTenantProvider implementation
- Update all repositories to filter by tenant_id
- Add immutability triggers to prevent tenant_id modification after creation
Migration 0015 includes:
- Schema changes with indexes for tenant_id columns
- SQL backfill for existing data
- Trigger functions for data integrity
Backend:
- Add Search() method to DocumentRepository for ILIKE pattern matching on doc_id, title, url, description
- Add Count() method to accurately count total matching documents (used for pagination)
- Update admin handler to support search query parameter with proper pagination
- Implement full public documents API with search support (previously stub)
- Update test mocks to include new repository methods
Frontend:
- Replace client-side filtering with server-side search in admin dashboard
- Add debounced search input (300ms delay) to reduce API calls
- Separate loading states: initial page load vs. search/pagination (prevents input focus loss)
- Add visual feedback: spinning loader icon in search field during active search
- Enable pagination during search (previously disabled)
- Pass search parameter to API service
Add authentication tokens embedded in reminder emails allowing users to
authenticate and sign documents in one click.
Changes:
- Add 'purpose' (login/reminder_auth) and 'doc_id' columns to magic_link_tokens
- Implement CreateReminderAuthToken (24h validity) and VerifyReminderAuthToken
- Create reminder auth handler and route (/api/v1/auth/reminder-link/verify)
- Update ReminderService and ReminderAsyncService to generate auth tokens
- Fix table name mismatch: magic_links → magic_link_tokens throughout
- Reorder service initialization in server.go for proper dependencies
Token validity:
- Magic Link: 15 minutes (login)
- Reminder Auth: 24 hours (document signature)
The reminder auth flow:
1. Admin sends reminder
2. User receives email with auth link
3. User clicks link → auto-authenticated if not logged in
4. User redirected to document signature page
5. If already authenticated with correct account, skip auth step
- Now can activate OIDC and/or MagicLink for user authentication.
- Add page to choose authentication method (if only OIDC is enabled, auto redirecting to login screen)
- Envoi des événements vers des URLs configurées
- Signature HMAC-SHA256 via en-tête X-Signature (secret partagé)
- Retentatives avec backoff exponentiel et jitter
- Timeout réseau et gestion des erreurs/transitoires
- Idempotence par event_id et journalisation structurée
- Paramètres: WEBHOOK_URLS, WEBHOOK_SECRET, WEBHOOK_TIMEOUT_MS, WEBHOOK_MAX_RETRIES
- Replace nanosecond+pid with crypto/rand generated hex (16 chars)
- Fixes race condition where parallel tests starting at same nanosecond
would share the same database name
- Ensures true isolation even with hundreds of concurrent tests
- Resolves duplicate key constraint violations in CI (perf-doc, test-doc, etc.)
- Fix migration path lookup to check both './migrations' and './backend/migrations'
- Remove hardcoded test schema in admin handler tests
- Use database.SetupTestDB which applies all migrations automatically
- Ensures test schema matches production schema with all columns (deleted_at, doc_checksum, etc.)
- Fixes test failures in CI where admin handler tests returned empty responses
- Implement PKCE (Proof Key for Code Exchange) with S256 method
- Add crypto/pkce module with code verifier and challenge generation
- Modify OAuth flow to include code_challenge in authorization requests
- Update HandleCallback to validate code_verifier during token exchange
- Extend session lifetime from 7 to 30 days
- Add comprehensive unit tests for PKCE functions
- Maintain backward compatibility with fallback for non-PKCE sessions
- Add detailed logging for OAuth flow with PKCE tracking
PKCE enhances security by preventing authorization code interception
attacks, as recommended by OAuth 2.1 and OIDC standards.
feat: add encrypted refresh token storage with automatic cleanup
- Add oauth_sessions table for storing encrypted refresh tokens
- Implement AES-256-GCM encryption for refresh tokens using cookie secret
- Create OAuth session repository with full CRUD operations
- Add SessionWorker for automatic cleanup of expired sessions
- Configure cleanup to run every 24h for sessions older than 37 days
- Modify OAuth flow to store refresh tokens after successful authentication
- Track client IP and user agent for session security validation
- Link OAuth sessions to user sessions via session ID
- Add comprehensive encryption tests with security validations
- Integrate SessionWorker into server lifecycle with graceful shutdown
This enables persistent OAuth sessions with secure token storage,
reducing the need for frequent re-authentication from 7 to 30 days.
Major refactoring to modernize the application architecture:
Backend changes:
- Restructure API with v1 versioning and modular handlers
- Add comprehensive OpenAPI specification
- Implement RESTful endpoints for documents, signatures, admin
- Add checksum verification system for document integrity
- Add server-side runtime injection of ACKIFY_BASE_URL and meta tags
- Generate dynamic Open Graph/Twitter Card meta tags for unfurling
- Remove legacy HTML template handlers
- Isolate backend source on dedicated folder
- Improve tests suite
Frontend changes:
- Migrate from Go templates to Vue.js 3 SPA with TypeScript
- Add Tailwind CSS with shadcn/vue components
- Implement i18n support (fr, en, es, de, it)
- Add admin dashboard for document and signer management
- Add signature tracking with file checksum verification
- Add embed page with sign button linking to main app
- Implement dark mode and accessibility features
- Auto load file to compute checksum
Infrastructure:
- Update Dockerfile for SPA build process
- Simplify deployment with embedded frontend assets
- Add migration for checksum_verifications table
This enables better UX, proper link previews on social platforms,
and provides a foundation for future enhancements.