Files
ackify/README.md
Benjamin 5e74921ee7 feat: admin dashboard document request signatures
- 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
2025-10-06 23:34:01 +02:00

12 KiB

🔐 Ackify

Proof of Read. Compliance made simple.

Secure document reading validation service with cryptographic traceability and irrefutable proof.

Build Security Go License

🇫🇷 Version française disponible ici

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
Initialisation d’une signature
2) User sign flow
Parcours de signature utilisateur

📸 Screenshots

Home page
Home page
Signing request
Signing request
Signature confirmed
Signature confirmed
Signatures list
Signatures list
Outline integration
Outline integration
Google Docs integration
Google Docs integration

Quick Start

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
Google 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_at protected 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 login
  • GET /logout - Logout
  • GET /oauth2/callback - OAuth2 callback

Signatures

  • GET /sign?doc=<id> - Signature interface
  • POST /sign - Create signature
  • GET /signatures - My signatures (auth required)

Consultation

  • GET /status?doc=<id> - JSON all signatures
  • GET /status.png?doc=<id>&user=<email> - PNG badge

Integration

  • GET /oembed?url=<embed_url> - oEmbed metadata
  • GET /embed?doc=<id> - HTML widget

Monitoring

  • GET /health - Health check

Admin

  • GET /admin - Dashboard (restricted)
  • GET /admin/docs/{docID} - Document details with expected signers management
  • POST /admin/docs/{docID}/expected - Add expected signers
  • POST /admin/docs/{docID}/expected/remove - Remove an expected signer
  • GET /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

License (AGPLv3)

Distributed under the GNU Affero General Public License v3.0. See LICENSE for details.


Developed with ❤️ by Benjamin TOUCHARD