Files
auth-portal/packages/db/src/oidc.ts
T
DenozordecandCursor 0b6fa65b08
Build and Push Auth Portal Docker Image / build-and-push (push) Successful in 2m30s
Build and Push Auth Portal Docker Image / create-release (push) Skipped
feat(reui): update ReUI components and documentation
- Added new OIDC configuration options in `.env.example`.
- Expanded documentation in `AGENTS.md` to include OIDC endpoints and admin UI.
- Updated ReUI skill version and component count from 17 to 20 across various documentation files.
- Enhanced `README.md` and other related files to reflect the new component structure and usage guidelines.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 16:58:47 +07:00

220 lines
5.5 KiB
TypeScript

import { and, eq, isNull } from 'drizzle-orm'
import { randomBytes, randomUUID } from 'node:crypto'
import type { AppDb } from './index.js'
import { oidcAuthCodes, oidcClients, oidcSigningKeys } from './schema/index.js'
import { hashToken } from './users.js'
export type OidcClientRow = typeof oidcClients.$inferSelect
export type OidcAuthCodeRow = typeof oidcAuthCodes.$inferSelect
export type OidcSigningKeyRow = typeof oidcSigningKeys.$inferSelect
export function parseJsonStringArray(raw: string): string[] {
try {
const v = JSON.parse(raw) as unknown
if (!Array.isArray(v)) return []
return v.filter((x): x is string => typeof x === 'string')
} catch {
return []
}
}
export function listOidcClients(db: AppDb): OidcClientRow[] {
return db
.select()
.from(oidcClients)
.all()
.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))
}
export function getOidcClientById(
db: AppDb,
id: string,
): OidcClientRow | undefined {
return db.select().from(oidcClients).where(eq(oidcClients.id, id)).get()
}
export function getOidcClientByClientId(
db: AppDb,
clientId: string,
): OidcClientRow | undefined {
return db
.select()
.from(oidcClients)
.where(eq(oidcClients.clientId, clientId))
.get()
}
export function createOidcClient(
db: AppDb,
input: {
name: string
clientSecretHash: string
redirectUris: string[]
scopes: string[]
enabled: boolean
clientId?: string
},
): OidcClientRow {
const now = new Date().toISOString()
const id = randomUUID()
const clientId = input.clientId ?? randomUUID()
db.insert(oidcClients)
.values({
id,
clientId,
clientSecretHash: input.clientSecretHash,
name: input.name,
redirectUrisJson: JSON.stringify(input.redirectUris),
scopesJson: JSON.stringify(input.scopes),
enabled: input.enabled,
createdAt: now,
updatedAt: now,
})
.run()
return getOidcClientById(db, id)!
}
export function updateOidcClient(
db: AppDb,
id: string,
patch: {
name?: string
redirectUris?: string[]
scopes?: string[]
enabled?: boolean
clientSecretHash?: string
},
): OidcClientRow | undefined {
const existing = getOidcClientById(db, id)
if (!existing) return undefined
const now = new Date().toISOString()
db.update(oidcClients)
.set({
name: patch.name ?? existing.name,
redirectUrisJson: patch.redirectUris
? JSON.stringify(patch.redirectUris)
: existing.redirectUrisJson,
scopesJson: patch.scopes
? JSON.stringify(patch.scopes)
: existing.scopesJson,
enabled: patch.enabled ?? existing.enabled,
clientSecretHash: patch.clientSecretHash ?? existing.clientSecretHash,
updatedAt: now,
})
.where(eq(oidcClients.id, id))
.run()
return getOidcClientById(db, id)
}
export function deleteOidcClient(db: AppDb, id: string): boolean {
const result = db.delete(oidcClients).where(eq(oidcClients.id, id)).run()
return result.changes > 0
}
export function generateOidcClientSecret(): string {
return randomBytes(32).toString('base64url')
}
export function createOidcAuthCode(
db: AppDb,
input: {
rawCode: string
clientId: string
userId: string
redirectUri: string
scope: string
nonce?: string | null
codeChallenge?: string | null
codeChallengeMethod?: string | null
expiresAt: Date
},
): string {
const id = randomUUID()
db.insert(oidcAuthCodes)
.values({
id,
codeHash: hashToken(input.rawCode),
clientId: input.clientId,
userId: input.userId,
redirectUri: input.redirectUri,
scope: input.scope,
nonce: input.nonce ?? null,
codeChallenge: input.codeChallenge ?? null,
codeChallengeMethod: input.codeChallengeMethod ?? null,
expiresAt: input.expiresAt.toISOString(),
usedAt: null,
createdAt: new Date().toISOString(),
})
.run()
return id
}
export function consumeOidcAuthCode(
db: AppDb,
rawCode: string,
): OidcAuthCodeRow | undefined {
const codeHash = hashToken(rawCode)
const row = db
.select()
.from(oidcAuthCodes)
.where(
and(eq(oidcAuthCodes.codeHash, codeHash), isNull(oidcAuthCodes.usedAt)),
)
.get()
if (!row) return undefined
const now = new Date().toISOString()
if (row.expiresAt < now) return undefined
db.update(oidcAuthCodes)
.set({ usedAt: now })
.where(eq(oidcAuthCodes.id, row.id))
.run()
return row
}
export function getActiveOidcSigningKey(
db: AppDb,
): OidcSigningKeyRow | undefined {
return db
.select()
.from(oidcSigningKeys)
.where(eq(oidcSigningKeys.active, true))
.all()
.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))[0]
}
export function listOidcSigningKeys(db: AppDb): OidcSigningKeyRow[] {
return db.select().from(oidcSigningKeys).all()
}
export function insertOidcSigningKey(
db: AppDb,
input: {
kid: string
privatePem: string
publicJwkJson: string
},
): OidcSigningKeyRow {
for (const key of listOidcSigningKeys(db)) {
if (key.active) {
db.update(oidcSigningKeys)
.set({ active: false })
.where(eq(oidcSigningKeys.kid, key.kid))
.run()
}
}
db.insert(oidcSigningKeys)
.values({
kid: input.kid,
privatePem: input.privatePem,
publicJwkJson: input.publicJwkJson,
active: true,
createdAt: new Date().toISOString(),
})
.run()
return db
.select()
.from(oidcSigningKeys)
.where(eq(oidcSigningKeys.kid, input.kid))
.get()!
}