All Docs
FeaturesAuto Day PlannerUpdated March 10, 2026

Integration Data Model & Token Encryption — How It Works

Integration Data Model & Token Encryption

Introduced in v0.1.1, the integrations data layer is the foundation that all third-party connections in Focus Engine are built on. This page explains the schema design, how tokens are stored securely, and how to interact with integrations via the tRPC API.


Supported Providers

The following integration providers are supported:

ProviderType
githubCode / issue tracking
googleCalendar / email
outlookCalendar / email
gmailEmail
notionTasks
linearTasks / issue tracking
jiraTasks / issue tracking
asanaTasks
slackCommunication

Integration Statuses

Each integration carries a status field reflecting the current state of the connection:

StatusMeaning
activeConnected and operational
inactiveManually disconnected or disabled
errorSync or auth error encountered
expiredOAuth token has expired

Database Schema

The integrations table has a unique constraint on (userId, provider) — each user can have at most one active connection per provider.

ColumnTypeNotes
userIdFK → usersCascade deletes on user removal
providerenumOne of the supported providers above
providerAccountIdstringExternal account identifier from the provider
accessTokenEncryptedstringAES-256-GCM ciphertext
refreshTokenEncryptedstringAES-256-GCM ciphertext (nullable)
scopesstringSpace- or comma-separated OAuth scopes granted
expiresAttimestampToken expiry
statusenumCurrent connection status
lastSyncAttimestampTimestamp of last successful data sync
createdAt / updatedAttimestampAudit timestamps

Applying the Migration

npm run db:push
# or
npm run db:generate && npm run db:migrate

Token Encryption

OAuth tokens are never stored in plaintext. All access and refresh tokens are encrypted before being written to the database using AES-256-GCM.

How it works

  1. Encryption (encrypt(plaintext))

    • Generates a random 96-bit IV per call — each encrypted value is unique even for identical inputs
    • Produces a iv:authTag:ciphertext hex string
    • Requires ENCRYPTION_KEY (see below)
  2. Decryption (decrypt(ciphertext))

    • Reverses the process; returns the original plaintext
    • Returns null for empty or missing input
    • GCM authentication tag verification detects any tampering — a modified ciphertext will fail to decrypt
  3. Optional helper (encryptOptional(value))

    • Convenience wrapper that handles null/undefined refresh tokens gracefully

Security properties

  • Authenticated encryption — GCM mode ensures integrity; tampering is detected at decrypt time
  • Unique IV per write — prevents ciphertext comparison attacks across rows
  • Tokens only decrypted on demand — the getTokens procedure is the only path to plaintext tokens, and it is server-side only

Required environment variable

# Generate a 64-character hex string (32 bytes)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Set the result as ENCRYPTION_KEY in your environment. The app will not start without it.


tRPC Integrations Router

All integration management is exposed under the integrations tRPC namespace.

integrations.list

Returns all integrations for the authenticated user. Token fields are not returned; instead, boolean flags hasAccessToken and hasRefreshToken indicate whether tokens are stored.

const integrations = await trpc.integrations.list.query();
// [ { provider: 'github', status: 'active', hasAccessToken: true, ... } ]

integrations.getByProvider

Fetches a single integration by provider name.

const integration = await trpc.integrations.getByProvider.query({ provider: 'notion' });

integrations.upsert

Creates or updates an integration. Typically called after completing an OAuth flow. Tokens are encrypted automatically before the database write.

await trpc.integrations.upsert.mutate({
  provider: 'github',
  providerAccountId: 'octocat',
  accessToken: 'gho_...',
  refreshToken: null,
  scopes: 'repo read:user',
  expiresAt: new Date('2026-06-01'),
});

integrations.updateStatus

Updates the status of an existing integration — useful when a sync job detects an auth error or token expiry.

await trpc.integrations.updateStatus.mutate({ provider: 'github', status: 'error' });

integrations.disconnect

Deletes the integration record and removes all stored tokens for the given provider.

await trpc.integrations.disconnect.mutate({ provider: 'github' });

integrations.getTokens

Server-only. Returns the decrypted access and refresh tokens for a given provider. Intended exclusively for background sync jobs that need to make authenticated API calls on the user's behalf.

// Server-side only — never expose this to the client
const { accessToken, refreshToken } = await trpc.integrations.getTokens.query({ provider: 'github' });

Configuration Reference

See .env.example for the full list of required environment variables. The variables relevant to this feature are:

VariableRequiredDescription
ENCRYPTION_KEY✅ Yes64-char hex string for AES-256-GCM token encryption
DATABASE_URL✅ YesNeon Postgres connection string