feat(db): add auth_provider_config table and AES-256-GCM encryption helpers

Implements GRO-387 (Schema: auth_provider_config table + encryption helpers):
- Add auth_provider_config Drizzle table with providerId, displayName,
  issuerUrl, internalBaseUrl, clientId, clientSecret (encrypted),
  scopes, enabled, timestamps
- Add encryptSecret/decryptSecret helpers using AES-256-GCM with
  BETTER_AUTH_SECRET as key-encryption-key (scrypt-derived)
- Store ciphertext as base64(iv:ciphertext:authTag) format
- Add unit tests for encryption helpers (9 tests, all passing)
- Generate Drizzle migration 0021_classy_hedge_knight

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
groombook-engineer[bot]
2026-04-02 11:07:22 +00:00
parent 804bb474d2
commit 0ca63f2c65
7 changed files with 2364 additions and 0 deletions
+16
View File
@@ -405,3 +405,19 @@ export const waitlistEntries = pgTable(
index("idx_waitlist_status").on(t.status),
]
);
// ─── Auth Provider Config ──────────────────────────────────────────────────
export const authProviderConfig = pgTable("auth_provider_config", {
id: uuid("id").primaryKey().defaultRandom(),
providerId: text("provider_id").notNull().unique(), // e.g. "authentik", "okta", "entra-id"
displayName: text("display_name").notNull(), // shown on login button
issuerUrl: text("issuer_url").notNull(), // OIDC issuer/discovery URL
internalBaseUrl: text("internal_base_url"), // for hairpin NAT / K8s internal routing
clientId: text("client_id").notNull(),
clientSecret: text("client_secret").notNull(), // AES-256-GCM encrypted using BETTER_AUTH_SECRET
scopes: text("scopes").notNull().default("openid profile email"),
enabled: boolean("enabled").notNull().default(true),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
});