Skip to content

encryption4all/postguard-business

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

201 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PostGuard for Business

Business portal for organizations to manage PostGuard identity-based email signing. Built with SvelteKit, PostgreSQL, and Yivi/IRMA attribute-based authentication.

Production: business.postguard.eu Staging: business.staging.postguard.eu

Features

  • Landing page with pricing and organization registration
  • Portal — API key management, organization info, email audit log, DNS verification
  • Admin panel — organization management, audit log, impersonation
  • Yivi authentication — attribute-based login for both org users and admins
  • Feature flags — every feature toggleable via environment variables

Tech stack

  • SvelteKit with adapter-node (server-side rendering)
  • Svelte 5 with runes ($state, $derived, $props)
  • Drizzle ORM with postgres.js driver
  • PostgreSQL 18
  • SCSS with CSS custom properties (purple colorway)
  • svelte-i18n (en-US, nl-NL)

Quick start

Prerequisites

Running locally

# 1. Clone and install dependencies
git clone git@github.com:encryption4all/postguard-business.git
cd postguard-business
npm install

# 2. Copy the example environment file
cp .env.example .env

# 3. Start everything
docker compose up

This starts:

Service URL Purpose
App http://localhost:8080 SvelteKit dev server (via nginx)
Adminer http://localhost:8081 Database admin UI
MailCrab http://localhost:1080 Email capture UI
IRMA server http://localhost:8088 Yivi/IRMA dev server

The db-setup service automatically runs migrations and seeds a demo admin account + example organization on first start.

Demo credentials

The seed script creates demo accounts that work with irma-demo attributes:

Role Attribute Value
Admin Email admin@postguard.eu
Full name Jan de Admin
Phone 0612345678
Org user Email info@acme.example.nl

Admin login is at /auth/login/admin. Org login is at /auth/login.

Override admin credentials via ADMIN_EMAIL, ADMIN_FULL_NAME, ADMIN_PHONE in .env.

Feature flags

Toggle features via environment variables in .env:

Flag Controls
FF_PRICING_PAGE Pricing page visibility
FF_REGISTRATION Organization registration form
FF_PORTAL_API_KEYS API key management in portal
FF_PORTAL_ORG_INFO Organization info page
FF_PORTAL_EMAIL_LOG Email audit log
FF_PORTAL_DNS DNS verification page
FF_ADMIN_PANEL Entire admin panel
FF_ADMIN_ORG_STATUS Activate/suspend org buttons
FF_ADMIN_AUDIT_LOG Admin audit log page
FF_ADMIN_IMPERSONATION Admin impersonation feature

In development mode, flags can also be toggled at runtime from the admin settings page.

Database

Schema

Defined in src/lib/server/db/schema.ts using Drizzle's pgTable. Tables:

  • organizations — registered organizations
  • business_api_keys — API keys (named business_ to avoid collision with PKG's api_keys table)
  • sessions — server-side sessions (hashed tokens)
  • change_requests — org data change requests pending admin approval
  • email_audit_log — signed email audit trail
  • dns_verifications — domain verification records
  • admin_accounts — admin users
  • admin_audit_log — admin action audit trail

Migrations

We use file-based SQL migrations (not drizzle-kit push). Migration files live in drizzle/migrations/ and are version-controlled.

Creating a new migration:

# 1. Edit the schema in src/lib/server/db/schema.ts

# 2. Generate the SQL migration
npm run db:generate

# 3. Review the generated SQL file in drizzle/migrations/

# 4. Commit the migration file alongside the schema change

Running migrations locally:

npm run db:migrate

Migration safety rules:

All migrations are checked for backward compatibility (both in a pre-commit hook and in CI). The following patterns are blocked:

  • DROP TABLE / DROP COLUMN / RENAME COLUMN / RENAME TABLE
  • SET NOT NULL without prior backfill
  • ADD COLUMN NOT NULL without a DEFAULT
  • TRUNCATE

Use the expand/contract pattern for breaking changes:

  1. Release N: Add new column (nullable or with default). Old code ignores it.
  2. Release N+1: Code starts using the new column.
  3. Release N+2: Drop the old column (only after old code is fully gone).

Run the checker manually:

npm run db:check

Shared database

The business portal and the PKG server share the same PostgreSQL instance. To avoid table name collisions, business portal API keys are stored in business_api_keys (not api_keys).

Development

Available scripts

Command Description
npm run dev Start SvelteKit dev server
npm run build Production build
npm run check TypeScript + Svelte type checking
npm run lint Prettier + ESLint
npm run format Auto-format with Prettier
npm run test Run unit + E2E tests
npm run test:unit Vitest unit tests
npm run test:e2e Playwright E2E tests
npm run db:generate Generate SQL migration from schema changes
npm run db:migrate Run pending migrations
npm run db:push Push schema directly (dev only)
npm run db:seed Seed demo data
npm run db:studio Open Drizzle Studio
npm run db:check Check migrations for dangerous patterns

Pre-commit hooks

Husky runs two checks on every commit:

  1. svelte-check --threshold warning — TypeScript + Svelte type checking
  2. scripts/check-migrations.ts — migration safety rules

Project structure

src/
  routes/
    (marketing)/        # Public pages (landing, pricing, register)
    (portal)/portal/    # Authenticated org portal
    (admin)/admin/      # Admin panel
    auth/               # Login/logout
    api/                # JSON API endpoints
    irma/[...path]/     # IRMA server proxy (adds auth token)
    health/             # Kubernetes health endpoint
  lib/
    server/
      db/               # Drizzle schema + client
      auth/             # Session + Yivi helpers
      services/         # Business logic
    components/         # Svelte components
    stores/             # Svelte 5 rune-based stores
    locales/            # i18n (en.json, nl.json)
    feature-flags.ts    # Feature flag system
    global.scss         # Design tokens (purple colorway)
drizzle/
  migrations/           # SQL migration files (version-controlled)
scripts/
  migrate.ts            # Standalone migration runner
  seed.ts               # Demo data seeder
  check-migrations.ts   # Migration safety checker
docker/
  Dockerfile            # Production image
  dev.Dockerfile        # Dev image (hot reload)
  nginx.dev.conf        # Dev reverse proxy
  entrypoint.sh         # Production entrypoint (seed + start)

CI/CD

Pipeline (.github/workflows/ci.yml)

Runs on every push to main and on pull requests:

  1. Migration Safety — checks SQL migrations for backward-incompatible patterns
  2. Svelte Check — TypeScript + Svelte type checking
  3. Tests — Vitest + Playwright (with PostgreSQL service container)
  4. Release Please — creates GitHub releases with semantic versioning (main only)
  5. Build — multi-platform Docker images (amd64 + arm64)
  6. Finalize — merges into a multi-platform manifest on GHCR

Docker image tags

Trigger Tag Example
Push to main edge ghcr.io/encryption4all/postguard-business:edge
Pull request pr-N ghcr.io/encryption4all/postguard-business:pr-42
Release X.Y.Z ghcr.io/encryption4all/postguard-business:1.2.0

Releases

This project uses Release Please for automated semantic versioning based on Conventional Commits.

Commit message format

Prefix Version bump Example
fix: Patch (1.0.x) fix: resolve login redirect loop
feat: Minor (1.x.0) feat: add email revocation
feat!: or BREAKING CHANGE: Major (x.0.0) feat!: change API key format

Other prefixes (chore:, docs:, refactor:, test:) do not trigger a release.

Release workflow

  1. Write code using conventional commit messages
  2. Push to main — CI builds the edge image
  3. Release Please automatically opens/updates a release PR that:
    • Bumps the version in package.json
    • Updates CHANGELOG.md
    • Collects all commits since the last release
  4. Merge the release PR — this triggers:
    • A GitHub release + git tag
    • A Docker image tagged with the version (e.g., 1.2.0)
  5. Deploy — update the image tag in postguard-ops and apply Terraform

Deploying to staging

Staging automatically tracks the edge tag. After pushing to main:

# In postguard-ops/
terraform apply -var-file=environments/dev.tfvars

Terraform runs the migration Job first, then rolls out the new deployment.

Deploying to production

After merging a release PR:

# In postguard-ops/
# Update postguard_business_image_tag in environments/prod.tfvars to the new version
terraform apply -var-file=environments/prod.tfvars

Kubernetes migration Job

Migrations run as a Kubernetes Job (business-migrate-{tag}) before the deployment starts. The Job:

  • Uses the same Docker image as the app
  • Runs scripts/migrate.ts against the production database
  • Must complete successfully before the deployment proceeds
  • Retries up to 3 times, times out after 2 minutes
  • Auto-cleans up after 5 minutes

If the migration fails, Terraform stops and the deployment does not proceed.

Note: For edge deploys, the Job name is business-migrate-edge and won't be recreated on subsequent applies with the same tag. Delete the old Job first: kubectl delete job business-migrate-edge -n <namespace>

Infrastructure

Managed in postguard-ops via Terraform.

Key Terraform variables

Variable Description
deploy_business Enable/disable business portal
postguard_business_image_tag Docker image tag to deploy
business_host Public hostname
business_database_user PostgreSQL user (key in K8s postgres secret)
business_admin_secret_id Scaleway secret with admin credentials

Architecture

Internet → Ingress → business-svc:3000 → business-deployment
                                              ↓
                              PostgreSQL (Scaleway Managed)
                                              ↑
                              business-migrate Job (pre-deploy)

The IRMA/Yivi server is accessed through SvelteKit's backend proxy (/irma/[...path]), which adds the authentication token. The browser never communicates directly with the IRMA server.

License

MIT © Encryption 4 All

About

No description, website, or topics provided.

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors