feat(federation): enrollment controller + single-use token flow (FED-M2-07)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful

Adds single-use enrollment token table, service, and controller enabling remote
peer gateways to enroll into a pending federation grant via CSR submission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jarvis
2026-04-21 23:07:46 -05:00
parent 01dd6b9fa1
commit fe4dffde15
10 changed files with 4141 additions and 3 deletions

View File

@@ -0,0 +1,11 @@
CREATE TABLE "federation_enrollment_tokens" (
"token" text PRIMARY KEY NOT NULL,
"grant_id" uuid NOT NULL,
"peer_id" uuid NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"used_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "federation_enrollment_tokens" ADD CONSTRAINT "federation_enrollment_tokens_grant_id_federation_grants_id_fk" FOREIGN KEY ("grant_id") REFERENCES "public"."federation_grants"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "federation_enrollment_tokens" ADD CONSTRAINT "federation_enrollment_tokens_peer_id_federation_peers_id_fk" FOREIGN KEY ("peer_id") REFERENCES "public"."federation_peers"("id") ON DELETE cascade ON UPDATE no action;

File diff suppressed because it is too large Load Diff

View File

@@ -71,6 +71,13 @@
"when": 1745280000000,
"tag": "0009_federation_grant_pending",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1745366400000,
"tag": "0010_federation_enrollment_tokens",
"breakpoints": true
}
]
}
}

View File

@@ -17,4 +17,5 @@ export {
federationPeers,
federationGrants,
federationAuditLog,
federationEnrollmentTokens,
} from './schema.js';

View File

@@ -778,3 +778,34 @@ export const federationAuditLog = pgTable(
index('federation_audit_log_created_at_idx').on(t.createdAt.desc()),
],
);
/**
* Single-use enrollment tokens — M2-07.
*
* An admin creates a token (with a TTL) and hands it out-of-band to the
* remote peer operator. The peer redeems it exactly once by posting its
* CSR to POST /api/federation/enrollment/:token. The token is atomically
* marked as used to prevent replay attacks.
*/
export const federationEnrollmentTokens = pgTable('federation_enrollment_tokens', {
/** 32-byte hex token — crypto.randomBytes(32).toString('hex') */
token: text('token').primaryKey(),
/** The federation grant this enrollment activates. */
grantId: uuid('grant_id')
.notNull()
.references(() => federationGrants.id, { onDelete: 'cascade' }),
/** The peer record that will be updated on successful enrollment. */
peerId: uuid('peer_id')
.notNull()
.references(() => federationPeers.id, { onDelete: 'cascade' }),
/** Hard expiry — token rejected after this time even if not used. */
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
/** NULL until the token is redeemed. Set atomically to prevent replay. */
usedAt: timestamp('used_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
});