Merged
Size
L
Change Breakdown
Feature75%
Maintenance15%
Config10%
#28097feat(core): Add DB infrastructure for trusted keys and key sources (no-changelog)

Trusted keys now persist in the database

Trusted keys now persist in the database

Dynamic key management is coming: the token exchange module can now store trusted keys and their sources in the database instead of holding everything in memory at startup.

Trusted keys used for token validation are now storable in the database. The token exchange module previously loaded keys only from environment variables at startup and kept them in memory — fine for static keys, but limiting for dynamic scenarios.

This change introduces two new tables. The trusted_key_source table tracks where keys come from (with fields for status tracking, configuration, and refresh metadata), while trusted_key stores individual keys with a composite primary key allowing the same key ID across different sources. A JSON column holds key metadata like algorithms, PEM material, issuer, audience, and expiry.

These tables are the first step toward dynamic key management: JWKS endpoints that rotate keys, and eventually keys managed through the UI rather than environment variables. The infrastructure handles status tracking (pending → healthy → error) for observability into refresh cycles, and a new configuration option sets the leader-only refresh interval.

In the n8n CLI package, TrustedKeySourceEntity and TrustedKeyEntity are now lazy-loaded in the TokenExchangeModule.

View Original GitHub Description

Summary

This PR adds the database layer for persisting trusted keys and their sources in the token exchange module. Currently, trusted keys are loaded from environment variables at startup and held only in memory. This change introduces two new tables (trusted_key_source and trusted_key) with TypeORM entities, repositories, and a reversible migration, laying the groundwork for dynamic key management (e.g., JWKS rotation and UI-managed keys).

What's included:

  • Migration (1776000000000-CreateTrustedKeyTables): Creates trusted_key_source (with status tracking, config, and refresh metadata) and trusted_key (composite PK on sourceId + kid, FK cascade to source) for both PostgreSQL and SQLite.
  • Entities: TrustedKeySourceEntity and TrustedKeyEntity with proper TypeORM decorators and relations.
  • Repositories: TrustedKeySourceRepository and TrustedKeyRepository (with findBySourceAndKid and findAllByKid query methods).
  • Schema additions: New ui source type in the Zod discriminated union, TrustedKeyData interface for the serialized JSON column, and exported type aliases (TrustedKeySourceType, TrustedKeySourceStatus).
  • Config: keyRefreshIntervalSeconds env var for future leader-only refresh scheduling.
  • Module registration: Entities lazy-loaded in TokenExchangeModule.entities().

Key design decisions:

  • Composite primary key (sourceId, kid) on trusted_key allows the same kid across different sources while enforcing uniqueness within a source.
  • trusted_key.data is a JSON text column storing TrustedKeyData (algorithms, PEM, issuer, audience, roles, expiry) — keeps the schema stable as key metadata evolves.
  • trusted_key_source.status tracks source health (pendinghealthy | error) for observability of JWKS refresh cycles.
  • ui source type is recognized in the schema but explicitly skipped during initialization (not yet supported).

How to test manually

  1. Start n8n with the token exchange feature flag enabled
  2. Verify the migration runs without errors on a fresh database (check startup logs)
  3. Confirm both tables exist: SELECT * FROM trusted_key_source; SELECT * FROM trusted_key;

Related tickets

closes https://linear.app/n8n/issue/IAM-523/2a31-trustedkeystore-db-tables-entities-and-repositories

Review checklist

  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
    • Integration tests cover: CRUD operations, FK cascade deletes, composite PK uniqueness, cross-source kid sharing, findAllByKid queries, optional field round-tripping, and orphan key rejection.
© 2026 · via Gitpulse