Files
airi/server/packages/auth-shared/src/schema.ts
T
RainbowBirdandautofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> 22b5249c64 refactor(server): split independent auth service (#2202)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-08-12 23:33:58 +08:00

292 lines
9.8 KiB
TypeScript

import { relations } from 'drizzle-orm'
import { boolean, index, jsonb, pgTable, text, timestamp } from 'drizzle-orm/pg-core'
export const user = pgTable('user', {
id: text('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
emailVerified: boolean('email_verified').default(false).notNull(),
image: text('image'),
// better-auth `admin` plugin fields. Field names must match the plugin's
// schema (role/banned/banReason/banExpires) so its drizzle adapter resolves
// them. `role` gates /admin/* (role === 'admin'); `banned` is enforced by the
// plugin at session.create.before AND re-checked on the OIDC JWT hot path
// (resolveRequestAuth / userinfo guard). Roles are granted out-of-band
// (manual DB update) — there is no env allowlist anymore.
role: text('role'),
banned: boolean('banned').default(false),
banReason: text('ban_reason'),
banExpires: timestamp('ban_expires'),
// NOTICE:
// Touched in `databaseHooks.session.create.after` (see auth.ts) which
// fires on sign-in AND on every OIDC access-token refresh (~hourly), so
// this is effectively "last activity" for any user with a live client.
// Better Auth has `session.updatedAt` but that's per-session-row; we
// want one stable per-user timestamp for DAU-style queries without
// joining/aggregating session rows. Nullable so existing rows backfill
// lazily on next login instead of needing a migration-time seed.
lastSeenAt: timestamp('last_seen_at'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at')
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
})
export const session = pgTable(
'session',
{
id: text('id').primaryKey(),
expiresAt: timestamp('expires_at').notNull(),
token: text('token').notNull().unique(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at')
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
ipAddress: text('ip_address'),
userAgent: text('user_agent'),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
// better-auth `admin` plugin: set when this session is an admin
// impersonating the user. Impersonation endpoints are disabled via
// disabledPaths, but the column stays so the schema matches the plugin.
impersonatedBy: text('impersonated_by'),
},
table => [
index('session_userId_idx').on(table.userId),
index('session_expires_at_idx').on(table.expiresAt),
],
)
export const account = pgTable(
'account',
{
id: text('id').primaryKey(),
accountId: text('account_id').notNull(),
providerId: text('provider_id').notNull(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
accessToken: text('access_token'),
refreshToken: text('refresh_token'),
idToken: text('id_token'),
accessTokenExpiresAt: timestamp('access_token_expires_at'),
refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
scope: text('scope'),
password: text('password'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at')
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
table => [
index('account_userId_idx').on(table.userId),
index('account_account_id_provider_id_idx').on(table.accountId, table.providerId),
],
)
export const verification = pgTable(
'verification',
{
id: text('id').primaryKey(),
identifier: text('identifier').notNull(),
value: text('value').notNull(),
expiresAt: timestamp('expires_at').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at')
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
table => [index('verification_identifier_idx').on(table.identifier)],
)
export const jwks = pgTable('jwks', {
id: text('id').primaryKey(),
publicKey: text('public_key').notNull(),
privateKey: text('private_key').notNull(),
createdAt: timestamp('created_at').notNull(),
expiresAt: timestamp('expires_at'),
})
export const oauthClient = pgTable('oauth_client', {
id: text('id').primaryKey(),
clientId: text('client_id').notNull().unique(),
clientSecret: text('client_secret'),
disabled: boolean('disabled').default(false),
skipConsent: boolean('skip_consent'),
enableEndSession: boolean('enable_end_session'),
subjectType: text('subject_type'),
scopes: text('scopes').array(),
userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at'),
updatedAt: timestamp('updated_at'),
name: text('name'),
uri: text('uri'),
icon: text('icon'),
contacts: text('contacts').array(),
tos: text('tos'),
policy: text('policy'),
softwareId: text('software_id'),
softwareVersion: text('software_version'),
softwareStatement: text('software_statement'),
redirectUris: text('redirect_uris').array().notNull(),
postLogoutRedirectUris: text('post_logout_redirect_uris').array(),
tokenEndpointAuthMethod: text('token_endpoint_auth_method'),
grantTypes: text('grant_types').array(),
responseTypes: text('response_types').array(),
public: boolean('public'),
type: text('type'),
requirePKCE: boolean('require_pkce'),
referenceId: text('reference_id'),
metadata: jsonb('metadata'),
})
export const oauthRefreshToken = pgTable(
'oauth_refresh_token',
{
id: text('id').primaryKey(),
token: text('token').notNull(),
clientId: text('client_id')
.notNull()
.references(() => oauthClient.clientId, { onDelete: 'cascade' }),
sessionId: text('session_id').references(() => session.id, {
onDelete: 'set null',
}),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
referenceId: text('reference_id'),
expiresAt: timestamp('expires_at'),
createdAt: timestamp('created_at'),
revoked: timestamp('revoked'),
authTime: timestamp('auth_time'),
scopes: text('scopes').array().notNull(),
},
table => [
index('oauth_refresh_token_token_idx').on(table.token),
index('oauth_refresh_token_user_id_idx').on(table.userId),
index('oauth_refresh_token_session_id_idx').on(table.sessionId),
index('oauth_refresh_token_client_id_idx').on(table.clientId),
],
)
export const oauthAccessToken = pgTable('oauth_access_token', {
id: text('id').primaryKey(),
token: text('token').unique(),
clientId: text('client_id')
.notNull()
.references(() => oauthClient.clientId, { onDelete: 'cascade' }),
sessionId: text('session_id').references(() => session.id, {
onDelete: 'set null',
}),
userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
referenceId: text('reference_id'),
refreshId: text('refresh_id').references(() => oauthRefreshToken.id, {
onDelete: 'cascade',
}),
expiresAt: timestamp('expires_at'),
createdAt: timestamp('created_at'),
scopes: text('scopes').array().notNull(),
})
export const oauthConsent = pgTable('oauth_consent', {
id: text('id').primaryKey(),
clientId: text('client_id')
.notNull()
.references(() => oauthClient.clientId, { onDelete: 'cascade' }),
userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
referenceId: text('reference_id'),
scopes: text('scopes').array().notNull(),
createdAt: timestamp('created_at'),
updatedAt: timestamp('updated_at'),
})
export const userRelations = relations(user, ({ many }) => ({
sessions: many(session),
accounts: many(account),
oauthClients: many(oauthClient),
oauthRefreshTokens: many(oauthRefreshToken),
oauthAccessTokens: many(oauthAccessToken),
oauthConsents: many(oauthConsent),
}))
export const sessionRelations = relations(session, ({ one, many }) => ({
user: one(user, {
fields: [session.userId],
references: [user.id],
}),
oauthRefreshTokens: many(oauthRefreshToken),
oauthAccessTokens: many(oauthAccessToken),
}))
export const accountRelations = relations(account, ({ one }) => ({
user: one(user, {
fields: [account.userId],
references: [user.id],
}),
}))
export const oauthClientRelations = relations(oauthClient, ({ one, many }) => ({
user: one(user, {
fields: [oauthClient.userId],
references: [user.id],
}),
oauthRefreshTokens: many(oauthRefreshToken),
oauthAccessTokens: many(oauthAccessToken),
oauthConsents: many(oauthConsent),
}))
export const oauthRefreshTokenRelations = relations(
oauthRefreshToken,
({ one, many }) => ({
oauthClient: one(oauthClient, {
fields: [oauthRefreshToken.clientId],
references: [oauthClient.clientId],
}),
session: one(session, {
fields: [oauthRefreshToken.sessionId],
references: [session.id],
}),
user: one(user, {
fields: [oauthRefreshToken.userId],
references: [user.id],
}),
oauthAccessTokens: many(oauthAccessToken),
}),
)
export const oauthAccessTokenRelations = relations(
oauthAccessToken,
({ one }) => ({
oauthClient: one(oauthClient, {
fields: [oauthAccessToken.clientId],
references: [oauthClient.clientId],
}),
session: one(session, {
fields: [oauthAccessToken.sessionId],
references: [session.id],
}),
user: one(user, {
fields: [oauthAccessToken.userId],
references: [user.id],
}),
oauthRefreshToken: one(oauthRefreshToken, {
fields: [oauthAccessToken.refreshId],
references: [oauthRefreshToken.id],
}),
}),
)
export const oauthConsentRelations = relations(oauthConsent, ({ one }) => ({
oauthClient: one(oauthClient, {
fields: [oauthConsent.clientId],
references: [oauthClient.clientId],
}),
user: one(user, {
fields: [oauthConsent.userId],
references: [user.id],
}),
}))