- New, clearer dashboard showing the status of each document - The administrator can create a list of expected signatures for a given document. - The administrator can manage the list of users who must confirm that they have read the document
12 KiB
🔐 Ackify
Proof of Read. Compliance made simple.
Secure document reading validation service with cryptographic traceability and irrefutable proof.
Visite our website here : https://www.ackify.eu
🎯 Why Ackify?
Problem: How to prove that a collaborator has actually read and understood an important document?
Solution: Ed25519 cryptographic signatures with immutable timestamps and complete traceability.
Real-world use cases
- ✅ Security policy validation
- ✅ Mandatory training attestations
- ✅ GDPR acknowledgment
- ✅ Contractual acknowledgments
- ✅ Quality and compliance procedures
📸 Vidéos
Click to GIFs for open videos WebM in your browser.
1) Create sign
|
2) User sign flow
|
📸 Screenshots
Home page
|
Signing request
|
Signature confirmed
|
Signatures list
|
Outline integration
|
Google Docs integration
|
⚡ Quick Start
With Docker (recommended)
git clone https://github.com/btouchard/ackify-ce.git
cd ackify-ce
# Minimal configuration
cp .env.example .env
# Edit .env with your OAuth2 settings
# Start
docker compose up -d
# Test
curl http://localhost:8080/health # alias: /health
Required variables
ACKIFY_BASE_URL="https://your-domain.com"
ACKIFY_OAUTH_CLIENT_ID="your-oauth-client-id" # Google/GitHub/GitLab
ACKIFY_OAUTH_CLIENT_SECRET="your-oauth-client-secret"
ACKIFY_DB_DSN="postgres://user:password@localhost/ackify?sslmode=disable"
ACKIFY_OAUTH_COOKIE_SECRET="$(openssl rand -base64 32)"
Optional: Email notifications (SMTP)
ACKIFY_MAIL_HOST="smtp.gmail.com" # SMTP server
ACKIFY_MAIL_PORT="587" # SMTP port (default: 587)
ACKIFY_MAIL_USERNAME="your-email@gmail.com" # SMTP username
ACKIFY_MAIL_PASSWORD="your-app-password" # SMTP password
ACKIFY_MAIL_FROM="noreply@company.com" # Sender address
ACKIFY_MAIL_FROM_NAME="Ackify" # Sender name
# If ACKIFY_MAIL_HOST is not set, email service is disabled (no error)
🚀 Simple Usage
1. Request a signature
https://your-domain.com/sign?doc=security_procedure_2025
→ User authenticates via OAuth2 and validates their reading
2. Verify signatures
# JSON API - Complete list
curl "https://your-domain.com/status?doc=security_procedure_2025"
# PNG Badge - Individual status
curl "https://your-domain.com/status.png?doc=security_procedure_2025&user=john.doe@company.com"
3. Integrate into your pages
<!-- Embeddable widget -->
<iframe src="https://your-domain.com/embed?doc=security_procedure_2025"
width="500" height="300"></iframe>
<!-- Via oEmbed -->
<script>
fetch('/oembed?url=https://your-domain.com/embed?doc=security_procedure_2025')
.then(r => r.json())
.then(data => document.getElementById('signatures').innerHTML = data.html);
</script>
🔧 OAuth2 Configuration
Supported providers
| Provider | Configuration |
|---|---|
ACKIFY_OAUTH_PROVIDER=google |
|
| GitHub | ACKIFY_OAUTH_PROVIDER=github |
| GitLab | ACKIFY_OAUTH_PROVIDER=gitlab + ACKIFY_OAUTH_GITLAB_URL |
| Custom | Custom endpoints |
Custom provider
# Leave ACKIFY_OAUTH_PROVIDER empty
ACKIFY_OAUTH_AUTH_URL="https://auth.company.com/oauth/authorize"
ACKIFY_OAUTH_TOKEN_URL="https://auth.company.com/oauth/token"
ACKIFY_OAUTH_USERINFO_URL="https://auth.company.com/api/user"
ACKIFY_OAUTH_SCOPES="read:user,user:email"
Domain restriction
ACKIFY_OAUTH_ALLOWED_DOMAIN="@company.com" # Only @company.com emails
Log level setup
ACKIFY_LOG_LEVEL="info" # can be debug, info, warn(ing), error. default: info
🛡️ Security & Architecture
Cryptographic security
- Ed25519: State-of-the-art digital signatures
- SHA-256: Payload hashing against tampering
- Immutable timestamps: PostgreSQL triggers
- Encrypted sessions: Secure cookies
- CSP headers: XSS protection
Go architecture
cmd/ackapp/ # Entry point
internal/
domain/ # Business logic
models/ # Entities
repositories/ # Persistence interfaces
application/ # Use cases
services/ # Business implementations
infrastructure/ # Adapters
auth/ # OAuth2
database/ # PostgreSQL
email/ # SMTP service
config/ # Configuration
presentation/ # HTTP
handlers/ # Controllers + interfaces
templates/ # HTML views
pkg/ # Shared utilities
Technology stack
- Go 1.24.5: Performance and simplicity
- PostgreSQL: Integrity constraints
- OAuth2: Multi-provider
- SMTP: Email signature reminders (optional)
- Docker: Simplified deployment
- Traefik: HTTPS reverse proxy
📊 Database
-- Main signatures table
CREATE TABLE signatures (
id BIGSERIAL PRIMARY KEY,
doc_id TEXT NOT NULL, -- Document ID
user_sub TEXT NOT NULL, -- OAuth user ID
user_email TEXT NOT NULL, -- User email
signed_at TIMESTAMPTZ NOT NULL, -- Signature timestamp
payload_hash TEXT NOT NULL, -- Cryptographic hash
signature TEXT NOT NULL, -- Ed25519 signature
nonce TEXT NOT NULL, -- Anti-replay
created_at TIMESTAMPTZ DEFAULT now(), -- Immutable
referer TEXT, -- Source (optional)
prev_hash TEXT,
UNIQUE (doc_id, user_sub) -- One signature per user/doc
);
-- Expected signers table (for tracking)
CREATE TABLE expected_signers (
id BIGSERIAL PRIMARY KEY,
doc_id TEXT NOT NULL,
email TEXT NOT NULL,
added_at TIMESTAMPTZ NOT NULL DEFAULT now(),
added_by TEXT NOT NULL, -- Admin who added
notes TEXT,
UNIQUE (doc_id, email) -- One expectation per email/doc
);
Guarantees:
- ✅ Uniqueness: One user = one signature per document
- ✅ Immutability:
created_atprotected by trigger - ✅ Integrity: SHA-256 hash to detect modifications
- ✅ Non-repudiation: Ed25519 signature cryptographically provable
- ✅ Tracking: Expected signers for completion monitoring
🚀 Production Deployment
compose.yml
version: '3.8'
services:
ackapp:
image: btouchard/ackify-ce:latest
environment:
ACKIFY_BASE_URL: https://ackify.company.com
ACKIFY_DB_DSN: postgres://user:pass@postgres:5432/ackdb?sslmode=require
ACKIFY_OAUTH_CLIENT_ID: ${ACKIFY_OAUTH_CLIENT_ID}
ACKIFY_OAUTH_CLIENT_SECRET: ${ACKIFY_OAUTH_CLIENT_SECRET}
ACKIFY_OAUTH_COOKIE_SECRET: ${ACKIFY_OAUTH_COOKIE_SECRET}
labels:
- "traefik.enable=true"
- "traefik.http.routers.ackify.rule=Host(`ackify.company.com`)"
- "traefik.http.routers.ackify.tls.certresolver=letsencrypt"
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: ackdb
POSTGRES_USER: ackuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
Production variables
# Enhanced security
ACKIFY_OAUTH_COOKIE_SECRET="$(openssl rand 64 | base64 -w 0)"
ACKIFY_ED25519_PRIVATE_KEY="$(openssl rand 64 | base64 -w 0)"
# HTTPS mandatory
ACKIFY_BASE_URL="https://ackify.company.com"
# Secure PostgreSQL
ACKIFY_DB_DSN="postgres://user:pass@postgres:5432/ackdb?sslmode=require"
# Optional: SMTP for signature reminders
ACKIFY_MAIL_HOST="smtp.company.com"
ACKIFY_MAIL_FROM="noreply@company.com"
ACKIFY_MAIL_USERNAME="${SMTP_USERNAME}"
ACKIFY_MAIL_PASSWORD="${SMTP_PASSWORD}"
📋 Complete API
Authentication
GET /login?next=<url>- OAuth2 loginGET /logout- LogoutGET /oauth2/callback- OAuth2 callback
Signatures
GET /sign?doc=<id>- Signature interfacePOST /sign- Create signatureGET /signatures- My signatures (auth required)
Consultation
GET /status?doc=<id>- JSON all signaturesGET /status.png?doc=<id>&user=<email>- PNG badge
Integration
GET /oembed?url=<embed_url>- oEmbed metadataGET /embed?doc=<id>- HTML widget
Monitoring
GET /health- Health check
Admin
GET /admin- Dashboard (restricted)GET /admin/docs/{docID}- Document details with expected signers managementPOST /admin/docs/{docID}/expected- Add expected signersPOST /admin/docs/{docID}/expected/remove- Remove an expected signerGET /admin/docs/{docID}/status.json- Document status as JSON (AJAX)GET /admin/api/chain-integrity/{docID}- Chain integrity verification JSON
Access control: set ACKIFY_ADMIN_EMAILS with a comma-separated list of admin emails (exact match, case-insensitive). Example:
ACKIFY_ADMIN_EMAILS="alice@company.com,bob@company.com"
Expected Signers Feature
Administrators can define and track expected signers for each document:
- Add expected signers: Paste emails separated by newlines, commas, or semicolons
- Track completion: Visual progress bar with completion percentage
- Monitor status: See who signed (✓) vs. who is pending (⏳)
- Detect unexpected signatures: Identify users who signed but weren't expected
- Share easily: One-click copy of document signature link
- Bulk management: Add/remove signers individually or in batch
🔍 Development & Testing
Local build
# Dependencies
go mod tidy
# Build
go build ./cmd/community
# Linting
go fmt ./...
go vet ./...
# Tests (TODO: add tests)
go test -v ./...
Docker development
# Build image
docker build -t ackify-ce:dev .
# Run with local database
docker run -p 8080:8080 --env-file .env ackify-ce:dev
# Optional: static analysis
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...
🤝 Support
Help & Documentation
- 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
License (AGPLv3)
Distributed under the GNU Affero General Public License v3.0. See LICENSE for details.
Developed with ❤️ by Benjamin TOUCHARD





