diff --git a/docs/integration-guide.md b/docs/integration-guide.md index 45e975d..f9ca93d 100644 --- a/docs/integration-guide.md +++ b/docs/integration-guide.md @@ -286,11 +286,60 @@ No shared secret. The webhook sender proves its identity with a device-bound sig --- +## Local Development + +Use the same `amesh.fetch()` and `amesh.verify()` code paths in local development as in production. No `if (NODE_ENV === 'development') { skipAuth() }` hacks. + +### Setup + +Use the `encrypted-file` backend with a simple passphrase for local services: + +```bash +# Create identities for local services +AUTH_MESH_DIR=/tmp/amesh-a amesh init --name "local-service-a" --backend encrypted-file --passphrase "dev" +AUTH_MESH_DIR=/tmp/amesh-b amesh init --name "local-service-b" --backend encrypted-file --passphrase "dev" + +# Start the relay (needed only for pairing) +bunx @authmesh/relay + +# Pair them (same as production) +AUTH_MESH_DIR=/tmp/amesh-b amesh listen # on service-b (target) +AUTH_MESH_DIR=/tmp/amesh-a amesh invite 482916 # on service-a (controller) +``` + +### Same code in dev and production + +Your application code is identical everywhere: + +```typescript +// This is the SAME code in dev and production. No environment checks. +import { amesh } from '@authmesh/sdk'; + +const res = await amesh.fetch('http://localhost:4000/api/data', { + method: 'POST', + body: JSON.stringify({ query: 'test' }), +}); +``` + +The only difference is the key storage backend: +- **Production:** macOS Keychain, Secure Enclave, or TPM 2.0 +- **Local dev:** `--backend encrypted-file --passphrase "dev"` + +### Tips + +- Use a shared passphrase like `"dev"` for all local identities. Security is not the goal — dev/prod parity is. +- Use `AUTH_MESH_DIR` to isolate each service's identity directory. +- For Docker Compose, set `AUTH_MESH_PASSPHRASE=dev` and mount `AUTH_MESH_DIR` as a volume so identities persist across restarts. +- The relay is only needed during initial pairing. Once devices are paired, stop it. + +--- + ## Environment Variables | Variable | Description | Default | |----------|-------------|---------| | `AUTH_MESH_DIR` | Directory for identity and keys | `~/.amesh/` | +| `AUTH_MESH_PASSPHRASE` | Passphrase for encrypted-file backend | (optional) | | `AMESH_BOOTSTRAP_TOKEN` | Bootstrap token for automated pairing | (optional) | | `RELAY_URL` | WebSocket relay URL | `wss://relay.authmesh.dev/ws` | | `REDIS_URL` | Redis URL for nonce store | (optional) | diff --git a/landpage/src/lib/components/Footer.svelte b/landpage/src/lib/components/Footer.svelte index 0668565..97ddc42 100644 --- a/landpage/src/lib/components/Footer.svelte +++ b/landpage/src/lib/components/Footer.svelte @@ -42,6 +42,8 @@
Microservices Webhooks + Cron Jobs + Internal Tools
diff --git a/landpage/src/lib/components/Nav.svelte b/landpage/src/lib/components/Nav.svelte index f8a0739..8c3fd68 100644 --- a/landpage/src/lib/components/Nav.svelte +++ b/landpage/src/lib/components/Nav.svelte @@ -6,6 +6,8 @@ const useCases = [ { slug: 'microservices', title: 'Microservices', desc: 'Service-to-service identity' }, { slug: 'webhooks', title: 'Webhooks', desc: 'Prove sender identity' }, + { slug: 'cron-jobs', title: 'Cron Jobs', desc: 'Scheduled task identity' }, + { slug: 'internal-tools', title: 'Internal Tools', desc: 'Per-developer audit trail' }, ]; let dropdownOpen = $state(false); diff --git a/landpage/src/routes/docs/integration/+page.svelte b/landpage/src/routes/docs/integration/+page.svelte index 339d506..992144a 100644 --- a/landpage/src/routes/docs/integration/+page.svelte +++ b/landpage/src/routes/docs/integration/+page.svelte @@ -188,12 +188,48 @@ app.post('/webhooks', amesh.verify(), (req + +
+

Local Development

+

Use the same code paths in local dev as production. No environment checks needed.

+ +

Setup

+
+ # Create local identities (encrypted-file backend for dev) +AUTH_MESH_DIR=/tmp/amesh-a amesh init --name "local-service-a" --backend encrypted-file --passphrase "dev" +AUTH_MESH_DIR=/tmp/amesh-b amesh init --name "local-service-b" --backend encrypted-file --passphrase "dev" + +# Pair them (same ceremony as production) +AUTH_MESH_DIR=/tmp/amesh-b amesh listen # on service-b (target) +AUTH_MESH_DIR=/tmp/amesh-a amesh invite 482916 # on service-a (controller)`} /> +
+ +

Same code, dev and production

+
+ // Same code in dev and production. No environment checks. +import {'{'} amesh {'}'} from '@authmesh/sdk'; + +const res = await amesh.fetch('http://localhost:4000/api/data', {'{'} + method: 'POST', + body: JSON.stringify({'{'} query: 'test' {'}'}) +{'}'});`} /> +
+ +
+

+ Production: macOS Keychain, Secure Enclave, or TPM 2.0
+ Local dev: --backend encrypted-file --passphrase "dev" +

+
+
+

Environment Variables

{#each [ { name: 'AUTH_MESH_DIR', desc: 'Directory for identity and keys', def: '~/.amesh/' }, + { name: 'AUTH_MESH_PASSPHRASE', desc: 'Passphrase for encrypted-file backend', def: 'optional' }, { name: 'AMESH_BOOTSTRAP_TOKEN', desc: 'Bootstrap token for automated pairing', def: 'optional' }, { name: 'RELAY_URL', desc: 'WebSocket relay URL', def: 'wss://relay.authmesh.dev/ws' }, { name: 'REDIS_URL', desc: 'Redis URL for nonce store', def: 'optional' }, diff --git a/landpage/src/routes/use-cases/+page.svelte b/landpage/src/routes/use-cases/+page.svelte index a6647e2..92cd64c 100644 --- a/landpage/src/routes/use-cases/+page.svelte +++ b/landpage/src/routes/use-cases/+page.svelte @@ -1,18 +1,20 @@ Use Cases — amesh - + - + diff --git a/landpage/src/routes/use-cases/cron-jobs/+page.svelte b/landpage/src/routes/use-cases/cron-jobs/+page.svelte new file mode 100644 index 0000000..2f19d11 --- /dev/null +++ b/landpage/src/routes/use-cases/cron-jobs/+page.svelte @@ -0,0 +1,62 @@ + + + + Cron Job Authentication Without API Keys — amesh + + + + + + + +// Cron script — signs every request with device identity +import { amesh } from '@authmesh/sdk'; + +const res = await amesh.fetch( + 'https://api.internal/billing/sync', + { method: 'POST', body: JSON.stringify({ date: '2026-04-01' }) } +); + +console.log(\`Sync complete: \${res.status}\`);` }, + { filename: 'api-server.ts', code: `// API server — verifies which device called +import { amesh } from '@authmesh/sdk'; + +app.use(amesh.verify()); + +app.post('/billing/sync', (req, res) => { + console.log(\`Sync by: \${req.authMesh.friendlyName}\`); + // "Sync by: cron-server-1" — not "someone with BILLING_KEY" + res.json({ synced: true }); +});` }, + { filename: 'Terminal', code: `# On the API server (target): +$ amesh init --name "billing-api" +$ amesh listen + +# On the cron server (controller): +$ amesh init --name "cron-server-1" +$ amesh invite 482916 + +# Server compromised? Revoke just that one device: +$ amesh revoke am_7f2e8a1b` }, + ]} + changes={[ + { before: 'API key in .env or crontab', after: 'No key. Device identity signs the request.' }, + { before: 'Can\'t tell cron job from attacker', after: 'req.authMesh.deviceId on every request' }, + { before: 'Compromised server = key works anywhere', after: 'amesh revoke kills that device only' }, + ]} +/> diff --git a/landpage/src/routes/use-cases/internal-tools/+page.svelte b/landpage/src/routes/use-cases/internal-tools/+page.svelte new file mode 100644 index 0000000..6a05d58 --- /dev/null +++ b/landpage/src/routes/use-cases/internal-tools/+page.svelte @@ -0,0 +1,67 @@ + + + + Internal Tools & Audit Trail — amesh + + + + + + + +// Admin script — identity comes from the developer's laptop +import { amesh } from '@authmesh/sdk'; + +// No API key. The developer's device IS the credential. +const res = await amesh.fetch( + 'https://admin.internal/cache/purge', + { method: 'POST', body: JSON.stringify({ pattern: 'users:*' }) } +); + +console.log(\`Purged: \${(await res.json()).count} keys\`);` }, + { filename: 'admin-api.ts', code: `// Admin API — knows exactly who called +import { amesh } from '@authmesh/sdk'; + +app.use('/admin', amesh.verify()); + +app.post('/admin/cache/purge', (req, res) => { + // "alice-macbook (am_3d9f1a2e) purged users:* at 10:15 AM" + audit.log({ + who: req.authMesh.friendlyName, + device: req.authMesh.deviceId, + action: 'cache.purge', + params: req.body, + }); + res.json({ count: 847 }); +});` }, + { filename: 'Terminal', code: `# Each developer's laptop gets its own identity (uses macOS Keychain): +$ amesh init --name "alice-macbook" +$ amesh invite 482916 + +# Alice leaves the team? Revoke her device. Bob is unaffected: +$ amesh revoke am_3d9f1a2e + Removed. Access revoked immediately. + +# No shared key to rotate. No other developer's access changes.` }, + ]} + changes={[ + { before: 'Shared admin key in 1Password', after: 'Each developer\'s laptop IS their identity' }, + { before: '"Someone ran the reset-db script"', after: '"Alice ran it from alice-macbook at 10:15 AM"' }, + { before: 'Offboarding = rotate shared key', after: 'amesh revoke . Others unaffected.' }, + ]} +/>