Welcome to the Jose Madrid Salsa developer docs — explore features, APIs, and deployment guides.
Jose Madrid SalsaJMS Docs

Key Management

API key management for partners and service integrations

Key Management

Jose Madrid Salsa manages three types of keys: partner API keys for external integrations, service keys for third-party credentials, and internal service keys for service-to-service communication.

Partner API Keys

Partners authenticate with the API using keys sent in the X-API-Key header. Keys are SHA-256 hashed before storage.

Database Model

prisma/schema.prisma
model PartnerApiKey {
  id         String    @id @default(cuid())
  name       String
  keyHash    String    @unique  // SHA-256 hash
  scopes     String[]  @default([])
  isActive   Boolean   @default(true)
  userId     String?
  lastUsedAt DateTime?
}

Key Hashing

lib/api/api-key-utils.ts
import crypto from 'crypto'

export const hashApiKey = (token: string) =>
  crypto.createHash('sha256').update(token).digest('hex')

Authentication Flow

lib/api/partner-keys.ts
export async function requirePartner(request: NextRequest, scope: string) {
  const apiKey = request.headers.get('x-api-key')
  if (!apiKey) return { error: 'Missing X-API-Key header' }

  const keyHash = hashApiKey(apiKey)
  const partner = await prisma.partnerApiKey.findUnique({ where: { keyHash } })

  if (!partner || !partner.isActive) return { error: 'Invalid API key' }

  // Check scope-based access
  if (scope && partner.scopes.length > 0 && !partner.scopes.includes(scope)) {
    return { error: 'Insufficient scope' }
  }

  // Update last used timestamp
  await prisma.partnerApiKey.update({
    where: { id: partner.id },
    data: { lastUsedAt: new Date() },
  })

  return { partner }
}

Scopes

Partner keys support scope-based access control. Common scopes:

ScopeDescription
forms.readRead form submissions
forms.writeSubmit forms
products.readRead product catalog
orders.readRead order data

An empty scopes array grants access to all endpoints.

Audit Logging

Partner API calls are logged to the AuditLog table:

lib/api/partner-keys.ts
export async function logPartnerApiCall(
  partner: PartnerApiKey,
  request: NextRequest,
  status: number,
  metadata?: Record<string, unknown>,
) {
  await prisma.auditLog.create({
    data: {
      userId: partner.userId,
      action: 'forms.api',
      entityType: 'PartnerApiKey',
      entityId: partner.id,
      changes: { path: request.nextUrl.pathname, method: request.method, status },
    },
  })
}

Service Keys

Third-party API credentials (Stripe tokens, OAuth secrets, etc.) are stored encrypted in the ServiceKey table.

Database Model

prisma/schema.prisma
model ServiceKey {
  id             String  @id @default(cuid())
  serviceName    String  // e.g., "stripe", "meta", "google_calendar"
  keyName        String  // e.g., "api_key", "access_token"
  encryptedValue String  @db.Text
  iv             String  // AES-GCM initialization vector
  isActive       Boolean @default(true)
  @@unique([serviceName, keyName])
}

Reading Service Keys

lib/service-keys.ts
export async function getDecryptedServiceKeyValue(
  serviceName: string,
  keyName: string,
): Promise<string | null> {
  const record = await prisma.serviceKey.findUnique({
    where: { serviceName_keyName: { serviceName, keyName } },
  })

  if (!record || !record.isActive) return null
  if (!record.encryptedValue || !record.iv) return null

  return decryptSecret(record.encryptedValue, record.iv)
}

Checking Key Existence

export async function hasActiveServiceKey(
  serviceName: string,
  keyName?: string,
): Promise<boolean>

Internal Service API Key

For service-to-service communication (e.g., cron jobs calling internal endpoints), a shared secret is used:

lib/email/rate-limit.ts
export function validateServiceApiKey(request: Request): boolean {
  const apiKey = request.headers.get('x-api-key')
  const expectedKey = process.env.SERVICE_API_KEY
  if (!expectedKey) {
    console.warn('SERVICE_API_KEY not configured - denying request')
    return false
  }
  return apiKey === expectedKey
}

Set in environment variables:

.env.local
SERVICE_API_KEY="your-internal-service-key"

The SERVICE_API_KEY is a simple shared secret. It should only be used for internal service-to-service calls, not for external partner access. Use PartnerApiKey for external integrations.

Key Lifecycle

ActionPartner KeysService Keys
CreateAdmin panel generates key, stores hashAdmin panel encrypts with MASTER_KEY
AuthenticateHash incoming key, lookup by hashDecrypt with MASTER_KEY on read
RotateGenerate new key, deactivate old oneUpdate encrypted value and IV
RevokeSet isActive = falseSet isActive = false
AuditLogged in AuditLog tableAccessed via getDecryptedServiceKeyValue

How is this guide?

Edit on GitHub

Last updated on

On this page