Skip to content

ik-labs/keysmith

Repository files navigation

Keysmith - Open Source License & Entitlements Server

Keysmith is a modern, open-source licensing server that provides secure license validation with offline verification capabilities, entitlements management, and seamless integration with billing providers like Stripe and Polar.

πŸš€ Features

  • Secure License Generation: Ed25519-based license signing with JWT tokens
  • Offline Verification: Client libraries can validate licenses without internet connectivity
  • Device Management: Activation limits and device tracking
  • Entitlements System: Flexible feature flags and usage limits
  • Webhook Integration: Automated license lifecycle via Stripe/Polar webhooks
  • Automated License Management: Cron jobs for expiry and email reminders
  • Customer Self-Service Portal: Magic link authentication with device management
  • Admin Interface: Complete web UI for license, product, and template management
  • Software Updates: Versioned release management with channel support
  • RESTful API: Simple, well-documented REST endpoints
  • Type-Safe: Built with TypeScript for end-to-end type safety

πŸ—οΈ Architecture

Built on Encore.ts with:

  • Backend: TypeScript, PostgreSQL, Pub/Sub, Cron Jobs
  • Crypto: Ed25519 signatures for maximum security and performance
  • Database: PostgreSQL with optimized schemas and indexes
  • Deployment: Cloud-native with built-in scaling and monitoring

πŸ“– Quick Start

Prerequisites

Local Development

  1. Clone and setup:
git clone <your-repo>
cd keysmith
encore app create keysmith
  1. Start development server:
encore run
  1. Run the automated setup:
chmod +x examples/curl/setup-example.sh
./examples/curl/setup-example.sh

This will:

  • Generate Ed25519 key pairs
  • Set the required secrets
  • Seed demo data
  • Test the basic functionality

Manual Setup (Alternative)

  1. Generate Ed25519 keys:
# Start the development server first
encore run

# Generate a key pair
curl -X POST http://localhost:4000/v1/setup/generate-key
  1. Set the private key secret:
# Copy the private key from the previous response
encore secret set --type dev Ed25519PrivateKey your-private-key-here
  1. Seed demo data:
# Option 1: Use the built-in seed endpoint (recommended)
curl -X POST http://localhost:4000/v1/setup/seed

# Option 2: Use the comprehensive demo data script
chmod +x create-demo-licenses.sh
./create-demo-licenses.sh

# Option 3: Add additional demo data with different license statuses
chmod +x create-more-demo-data.sh
./create-more-demo-data.sh
  1. Test the API:
chmod +x examples/curl/api-examples.sh
./examples/curl/api-examples.sh

🌱 Demo Data & Admin Panel

Getting Started with Demo Data

Keysmith includes comprehensive demo data to help you explore all features immediately after setup:

Option 1: Built-in Seed Endpoint (Quickest)

# Seeds products, customers, templates, and Stripe mappings
curl -X POST http://localhost:4000/v1/setup/seed

Option 2: Comprehensive Demo Script (Recommended)

# Creates varied demo licenses with realistic data
chmod +x create-demo-licenses.sh
./create-demo-licenses.sh

Option 3: Advanced Demo Data (Full Testing)

# Adds licenses with all statuses: active, expired, suspended, revoked
chmod +x create-more-demo-data.sh
./create-more-demo-data.sh

What Gets Created

After seeding, you'll have:

Products (2)

  • DevTool Pro - Professional development tools (devtool-pro)
  • API Gateway Plus - Advanced API management (api-gateway-plus)

Demo Customers (5+)

  • Alice Smith (alice@example.com) - Pro user
  • Bob Johnson (bob@company.com) - Team user
  • Charlie Brown (charlie@startup.io) - Trial user
  • Demo User (demo@keysmith.dev) - Long-term demo
  • Various status-specific test customers

Demo Licenses (5+)

  • Active licenses with different expiration dates (30 days, 90 days, 1 year)
  • Trial license (7 days) for testing short-term scenarios
  • Expired license for testing expiration handling
  • Suspended license for payment failure simulation
  • Revoked license for policy violation testing
  • Various activation limits (1, 3, 5, 10 devices)

Entitlement Templates

// DevTool Pro Template
{
  "features": {
    "code_analysis": true,
    "team_collaboration": true,
    "advanced_debugging": true,
    "custom_integrations": false
  },
  "limits": {
    "projects": 10,
    "team_members": 5,
    "api_calls_per_month": 50000,
    "storage_gb": 10
  },
  "ui": {
    "theme_customization": true,
    "white_labeling": false
  }
}

// API Gateway Template  
{
  "features": {
    "request_routing": true,
    "rate_limiting": true,
    "analytics": true,
    "custom_middleware": true,
    "webhook_support": true
  },
  "limits": {
    "requests_per_second": 1000,
    "endpoints": 50,
    "webhook_destinations": 10
  },
  "monitoring": {
    "real_time_metrics": true,
    "alerting": true,
    "log_retention_days": 30
  }
}

Stripe Product Mappings

  • Monthly and yearly billing cycles
  • Different activation limits per plan
  • Realistic license durations (1 month, 12 months)

Admin Panel Access

With demo data loaded, you can immediately explore the admin interface:

  1. Start the frontend (if not already running):

    cd frontend
    bun install && bun dev
  2. Access the admin panel: http://localhost:5173/admin

  3. Login: Use admin key test-admin-key-for-mvp

  4. Explore features:

    • Dashboard: View license statistics and recent activity
    • Licenses Tab: Browse, search, filter, and manage all licenses
    • Templates Tab: View and edit entitlement templates
    • Create License: Test the full license creation workflow
    • Edit Licenses: Update status, expiration, limits, metadata

Verification Commands

# View all products
curl -H "X-Admin-Key: test-admin-key-for-mvp" http://localhost:4000/v1/admin/products

# List all licenses (paginated)
curl -H "X-Admin-Key: test-admin-key-for-mvp" http://localhost:4000/v1/admin/licenses

# Filter active licenses only
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/licenses?status=active"

# Search licenses by customer email
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/licenses?search=alice"

# View entitlement templates
curl -H "X-Admin-Key: test-admin-key-for-mvp" http://localhost:4000/v1/admin/entitlements/templates

# Check recent events
curl -H "X-Admin-Key: test-admin-key-for-mvp" http://localhost:4000/v1/admin/events

Testing License Validation

# Test online license validation
LICENSE_KEY="UQX5-DU7V-G7KV-NSPM"  # Alice's license from demo data
curl "http://localhost:4000/v1/validate?license_key=$LICENSE_KEY&device_id=test-device-001"

# Test device activation
curl -X POST http://localhost:4000/v1/activate \
  -H "Content-Type: application/json" \
  -d '{
    "license_key": "'$LICENSE_KEY'",
    "device_id": "test-laptop-001",
    "device_name": "Development Laptop"
  }'

# Get entitlements for a license
curl "http://localhost:4000/v1/entitlements/$LICENSE_KEY"

Custom Demo Data

You can also create your own demo data:

# Create a custom license
curl -X POST http://localhost:4000/v1/admin/licenses \
  -H "X-Admin-Key: test-admin-key-for-mvp" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "d49e5716-4046-44c2-bd52-dde6db50ad3e",
    "customer_email": "yourname@company.com",
    "customer_name": "Your Name",
    "expires_at": "2025-12-31T23:59:59Z",
    "max_activations": 5,
    "metadata": {"plan": "enterprise", "custom": true}
  }'

πŸ”‘ Customer Portal

Keysmith includes a self-service customer portal where your users can view their licenses and manage device activations.

How to Access

  1. Start the frontend server:

    cd frontend
    bun dev
  2. Navigate to the portal: Open your browser to http://localhost:5173/portal

Magic Link Authentication

The portal uses secure, passwordless magic links for authentication:

  1. A customer enters their email address.
  2. A unique, short-lived login link is sent to their email.
    • Note: In local development, if you haven't configured email sending with Resend, the magic link will be printed in your backend terminal (where encore run is active).
  3. Clicking the link authenticates the user and creates a secure session.

What Customers Can Do

  • View all licenses associated with their email address.
  • See license status, expiration dates, and activation limits.
  • Download the latest version of their purchased software.
  • (Coming soon) Manage device activations for each license.

Testing the Portal

  1. Use a demo customer email: After running the seed script, use an email like alice@example.com.
  2. Request a magic link from the portal login page.
  3. Check your backend terminal for the logged magic link URL.
  4. Copy and paste the URL into your browser to log in.

⏰ Automated License Management (Cron Jobs)

Keysmith includes a robust cron job system that automatically manages the license lifecycle, ensuring your customers are always informed about their license status.

Available Cron Jobs

1. License Expiry Processing (processExpiredLicenses)

  • Schedule: Every 6 hours (00:00, 06:00, 12:00, 18:00 UTC)
  • Function: Automatically transitions expired licenses to 'expired' status
  • Benefits: Ensures expired licenses are immediately deactivated

2. Expiry Reminder Emails (sendExpiryReminders)

  • Schedule: Daily at 09:00 UTC
  • Function: Sends reminder emails to customers whose licenses expire within 7 days
  • Email Templates: Professional HTML emails with license details and renewal links

Testing Cron Jobs Manually

During development, you can manually trigger the cron jobs to test their functionality:

# Manually process expired licenses
curl -X POST http://localhost:4000/v1/cron/process-expired

# Manually send expiry reminder emails
curl -X POST http://localhost:4000/v1/cron/send-expiry-reminders

# Check recent cron job results in events
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/events?search=cron"

Email Configuration

To enable automated email reminders in production, configure your Resend integration:

  1. Set Resend secrets:

    encore secret set --type production ResendApiKey your-resend-api-key
    encore secret set --type production ResendDomain your-verified-domain.com
  2. Test email functionality:

    # Test if email can be sent (check your terminal for email content in dev mode)
    curl -X POST http://localhost:4000/v1/cron/send-expiry-reminders

Creating Test Data for Cron Jobs

To test the cron functionality, create licenses that will expire soon:

# Create a license expiring in 3 days (perfect for testing reminders)
TOMORROW=$(date -d "+3 days" -u +"%Y-%m-%dT%H:%M:%SZ")
curl -X POST http://localhost:4000/v1/admin/licenses \
  -H "X-Admin-Key: test-admin-key-for-mvp" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "d49e5716-4046-44c2-bd52-dde6db50ad3e",
    "customer_email": "test-expiring@example.com",
    "customer_name": "Test Expiring User",
    "expires_at": "'$TOMORROW'",
    "max_activations": 1,
    "metadata": {"test": "expiry_reminder"}
  }'

# Create an already expired license (for testing expired processing)
YESTERDAY=$(date -d "-1 day" -u +"%Y-%m-%dT%H:%M:%SZ")
curl -X POST http://localhost:4000/v1/admin/licenses \
  -H "X-Admin-Key: test-admin-key-for-mvp" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "d49e5716-4046-44c2-bd52-dde6db50ad3e",
    "customer_email": "test-expired@example.com",
    "customer_name": "Test Expired User",
    "expires_at": "'$YESTERDAY'",
    "max_activations": 1,
    "metadata": {"test": "already_expired"}
  }'

Monitoring Cron Jobs

Cron job activities are logged in the events system:

# View recent cron job activities
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/events?search=cron"

# View licenses updated by expiry processing
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/events?search=expired"

# Check for email-related events
curl -H "X-Admin-Key: test-admin-key-for-mvp" "http://localhost:4000/v1/admin/events?search=reminder"

Customizing Email Templates

The expiry reminder emails use professional HTML templates. You can customize them by modifying the email templates in the cron service:

  • Subject: "Your [Product Name] license expires in [X] days"
  • Content: HTML email with license details, expiration date, and renewal call-to-action
  • Styling: Professional layout with your branding

πŸš€ Deployment

Step 1: Set Production Secrets

# Generate keys (if you haven't already)
curl -X POST http://localhost:4000/v1/setup/generate-key

# Set the private key for production (use the key from above)
encore secret set --type production Ed25519PrivateKey YOUR_PRIVATE_KEY_HERE

# Set Stripe webhook secret for production (get from Stripe dashboard)
encore secret set --type production StripeWebhookSecret YOUR_STRIPE_WEBHOOK_SECRET_HERE

# Set a secret for signing magic links
encore secret set --type production MagicLinkSecret YOUR_RANDOM_SECRET_STRING_HERE

# Set Resend API key for sending emails
encore secret set --type production ResendApiKey YOUR_RESEND_API_KEY_HERE

# Set Resend domain for sending emails
encore secret set --type production ResendDomain your-verified-domain.com

Step 2: Deploy

# Deploy to production
encore deploy --env=production

# Or deploy to staging first
encore deploy --env=staging

Alternative: Set Secrets via Dashboard

  1. Go to your app in the Encore Cloud Dashboard
  2. Navigate to the "Infrastructure" tab
  3. Click on "Secrets"
  4. Add Ed25519PrivateKey with your private key value
  5. Add StripeWebhookSecret with your Stripe webhook secret
  6. Add MagicLinkSecret with a random secure string
  7. Add ResendApiKey with your key from Resend
  8. Add ResendDomain with your verified domain from Resend

πŸ”§ Core API Endpoints

License Management

  • POST /v1/licenses - Create a new license
  • GET /v1/validate?license_key=XXX - Validate license online
  • POST /v1/licenses/{id}/revoke - Revoke a license
  • GET /v1/public_key - Get public key for offline verification

Device Activation

  • POST /v1/activate - Activate a device
  • POST /v1/deactivate - Deactivate a device

Entitlements

  • GET /v1/entitlements/{license_key} - Get signed entitlements

Webhooks

  • POST /webhooks/stripe - Stripe webhook handler

Admin Panel API

  • GET /v1/admin/dashboard - Get dashboard statistics
  • GET /v1/admin/licenses - List/search/filter licenses (paginated)
  • POST /v1/admin/licenses - Create new license manually
  • PUT /v1/admin/licenses/:id - Update existing license
  • GET /v1/admin/products - List all products
  • GET /v1/admin/entitlements/templates - List entitlement templates
  • POST /v1/admin/entitlements/templates - Create new template
  • PUT /v1/admin/entitlements/templates/:id - Update existing template
  • GET /v1/admin/events - List system events (audit trail)

Setup & Administration

  • POST /v1/setup/generate-key - Generate Ed25519 key pair
  • POST /v1/setup/seed - Seed database with demo data

Cron Jobs (Manual Testing)

  • POST /v1/cron/process-expired - Manually trigger expired license processing
  • POST /v1/cron/send-expiry-reminders - Manually trigger expiry reminder emails

πŸ’» Client Integration

Offline License Verification (TypeScript)

import { LicenseVerifier } from './examples/typescript/verify-license';

// Get public key from your server
const response = await fetch('https://your-server.com/v1/public_key');
const { public_key } = await response.json();

// Initialize verifier
const verifier = new LicenseVerifier(public_key);

// Verify license offline
try {
  const license = await verifier.verifyLicense(licenseToken);
  console.log('License is valid:', license);
  
  // Check entitlements
  const entitlements = await verifier.verifyEntitlements(entitlementsToken);
  const hasFeature = verifier.hasFeature(entitlements.entitlements, 'features.advanced_mode');
  const apiLimit = verifier.getLimit(entitlements.entitlements, 'limits.api_calls');
  
} catch (error) {
  console.error('License verification failed:', error);
}

Online Validation

// Validate license with server
const response = await fetch(`https://your-server.com/v1/validate?license_key=${licenseKey}&device_id=${deviceId}`);
const validation = await response.json();

if (validation.valid) {
  console.log('License is valid');
  console.log('Activations remaining:', validation.activations_remaining);
} else {
  console.log('License invalid:', validation.reason);
}

πŸ” Security

  • Ed25519 Signatures: Modern, fast, and secure cryptographic signatures
  • JWT Tokens: Industry-standard token format with built-in expiration
  • Webhook Verification: Secure webhook signature validation
  • Rate Limiting: Built-in protection against abuse
  • Audit Logging: Complete event trail for license operations

🚨 Troubleshooting

"Secret key(s) not defined: Ed25519PrivateKey" during deployment

This error means you haven't set the required secret for your deployment environment:

  1. Generate a key pair (if you haven't already):

    curl -X POST http://localhost:4000/v1/setup/generate-key
  2. Set the secret for your target environment:

    # For production
    encore secret set --type production Ed25519PrivateKey YOUR_PRIVATE_KEY
    
    # For staging  
    encore secret set --type staging Ed25519PrivateKey YOUR_PRIVATE_KEY
  3. Verify the secret is set:

    encore secret list
  4. Deploy again:

    encore deploy --env=production

Other Common Issues

  • Key generation fails: Ensure @noble/ed25519 dependency is installed
  • API calls fail: Check that the server is running and secrets are set
  • Database connection issues: Ensure PostgreSQL is available in your deployment environment

πŸ“‹ Database Schema

The system uses PostgreSQL with the following main tables:

  • products - Your software products
  • customers - License holders
  • licenses - Individual licenses with status and limits
  • activations - Device activations and tracking
  • entitlement_templates - Feature and limit definitions
  • events - Audit log of all license operations
  • versions - Software update metadata
  • stripe_product_mappings - Maps Stripe products to Keysmith products
  • failed_webhook_events - Failed webhook events for manual intervention

πŸ”— Webhook Integration

Stripe Integration

  1. Configure your Stripe webhook endpoint to point to /webhooks/stripe:
{
  "events": [
    "customer.subscription.created",
    "customer.subscription.updated", 
    "customer.subscription.deleted",
    "invoice.payment_succeeded",
    "invoice.payment_failed"
  ],
  "url": "https://your-keysmith-server.com/webhooks/stripe"
}
  1. Set up product mappings in your database:
INSERT INTO stripe_product_mappings (
  stripe_product_id, 
  stripe_price_id, 
  keysmith_product_id, 
  license_duration_months,
  max_activations
) VALUES (
  'prod_stripe_id_here',
  'price_stripe_id_here', 
  'your-keysmith-product-uuid',
  12,  -- 12 months license duration
  5    -- 5 device activations
);
  1. Test webhook integration:
chmod +x examples/webhooks/test-webhook.sh
./examples/webhooks/test-webhook.sh

Supported Events

  • customer.subscription.created β†’ Creates new license
  • invoice.payment_succeeded β†’ Renews/extends license
  • invoice.payment_failed β†’ Suspends license
  • customer.subscription.deleted β†’ Revokes license

Error Handling

Failed webhook events are automatically stored in failed_webhook_events table for manual review:

  • Retryable errors: Temporary issues (DB connection, etc.) - auto-retry
  • Manual errors: Business logic issues - require human intervention
  • Permanent errors: Invalid event types - logged and ignored

πŸ§ͺ Testing

# Run all tests
encore test

# Run with watch mode
npm run test:watch

# Run with coverage
npm run test:coverage

# Run specific test file
npx vitest backend/licensing/crypto.test.ts

Test Structure

  • Unit Tests: Individual function and component testing
  • Integration Tests: Full API endpoint testing with database
  • Crypto Tests: Ed25519 signing/verification validation
  • Activation Tests: Device limit and quota management
  • Validation Tests: License status and expiration logic
  • Webhook Tests: Stripe event processing and signature verification
  • Cron Job Tests: Automated license expiry and email reminder testing
  • Email Tests: Email sending and template validation (using test mode)

Testing Cron Jobs

# Test expired license processing
npm run test -- --grep "expired license"

# Test email reminder functionality
npm run test -- --grep "expiry reminder"

# Test cron job scheduling and execution
npm run test -- --grep "cron"

# Run all cron-related tests
npm run test -- backend/cron/

πŸ“š Examples

Check the examples/ directory for:

  • TypeScript offline verification client
  • curl API examples
  • Setup automation scripts
  • Webhook testing scripts and sample payloads
  • Integration examples for popular languages

🀝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Ensure all tests pass: npm test
  6. Submit a pull request

πŸ“„ License

Apache License 2.0 - see LICENSE file for details

πŸ†˜ Support


Keysmith - Secure, scalable, and simple license management for modern software teams.

About

Created by Leap: https://leap.new

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages