diff --git a/packages/server/auth-schema2.ts b/packages/server/auth-schema2.ts index cb2202832..2d67ebf1b 100644 --- a/packages/server/auth-schema2.ts +++ b/packages/server/auth-schema2.ts @@ -1,299 +1,311 @@ import { relations } from "drizzle-orm"; import { - boolean, - index, - integer, - pgTable, - text, - timestamp, - uniqueIndex, + pgTable, + text, + timestamp, + boolean, + integer, + index, + uniqueIndex, } from "drizzle-orm/pg-core"; export const user = pgTable("user", { - id: text("id").primaryKey(), - firstName: text("first_name").notNull(), - email: text("email").notNull().unique(), - emailVerified: boolean("email_verified").default(false).notNull(), - image: text("image"), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at") - .defaultNow() - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - twoFactorEnabled: boolean("two_factor_enabled").default(false), - role: text("role"), - ownerId: text("owner_id"), - allowImpersonation: boolean("allow_impersonation").default(false), - lastName: text("last_name").default(""), - enableEnterpriseFeatures: boolean("enable_enterprise_features"), - isValidEnterpriseLicense: boolean("is_valid_enterprise_license"), + id: text("id").primaryKey(), + firstName: text("first_name").notNull(), + email: text("email").notNull().unique(), + emailVerified: boolean("email_verified").default(false).notNull(), + image: text("image"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + twoFactorEnabled: boolean("two_factor_enabled").default(false), + role: text("role"), + banned: boolean("banned").default(false), + banReason: text("ban_reason"), + banExpires: timestamp("ban_expires"), + ownerId: text("owner_id"), + allowImpersonation: boolean("allow_impersonation").default(false), + lastName: text("last_name").default(""), + enableEnterpriseFeatures: boolean("enable_enterprise_features"), + isValidEnterpriseLicense: boolean("is_valid_enterprise_license"), }); 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" }), - activeOrganizationId: text("active_organization_id"), - }, - (table) => [index("session_userId_idx").on(table.userId)], + "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" }), + activeOrganizationId: text("active_organization_id"), + impersonatedBy: text("impersonated_by"), + }, + (table) => [index("session_userId_idx").on(table.userId)], ); 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)], + "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)], ); 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)], + "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 apikey = pgTable( - "apikey", - { - id: text("id").primaryKey(), - configId: text("config_id").default("default").notNull(), - name: text("name"), - start: text("start"), - referenceId: text("reference_id").notNull(), - prefix: text("prefix"), - key: text("key").notNull(), - refillInterval: integer("refill_interval"), - refillAmount: integer("refill_amount"), - lastRefillAt: timestamp("last_refill_at"), - enabled: boolean("enabled").default(true), - rateLimitEnabled: boolean("rate_limit_enabled").default(true), - rateLimitTimeWindow: integer("rate_limit_time_window").default(86400000), - rateLimitMax: integer("rate_limit_max").default(10), - requestCount: integer("request_count").default(0), - remaining: integer("remaining"), - lastRequest: timestamp("last_request"), - expiresAt: timestamp("expires_at"), - createdAt: timestamp("created_at").notNull(), - updatedAt: timestamp("updated_at").notNull(), - permissions: text("permissions"), - metadata: text("metadata"), - }, - (table) => [ - index("apikey_configId_idx").on(table.configId), - index("apikey_referenceId_idx").on(table.referenceId), - index("apikey_key_idx").on(table.key), - ], + "apikey", + { + id: text("id").primaryKey(), + configId: text("config_id").default("default").notNull(), + name: text("name"), + start: text("start"), + referenceId: text("reference_id").notNull(), + prefix: text("prefix"), + key: text("key").notNull(), + refillInterval: integer("refill_interval"), + refillAmount: integer("refill_amount"), + lastRefillAt: timestamp("last_refill_at"), + enabled: boolean("enabled").default(true), + rateLimitEnabled: boolean("rate_limit_enabled").default(true), + rateLimitTimeWindow: integer("rate_limit_time_window").default(86400000), + rateLimitMax: integer("rate_limit_max").default(10), + requestCount: integer("request_count").default(0), + remaining: integer("remaining"), + lastRequest: timestamp("last_request"), + expiresAt: timestamp("expires_at"), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), + permissions: text("permissions"), + metadata: text("metadata"), + }, + (table) => [ + index("apikey_configId_idx").on(table.configId), + index("apikey_referenceId_idx").on(table.referenceId), + index("apikey_key_idx").on(table.key), + ], ); export const ssoProvider = pgTable("sso_provider", { - id: text("id").primaryKey(), - issuer: text("issuer").notNull(), - oidcConfig: text("oidc_config"), - samlConfig: text("saml_config"), - userId: text("user_id").references(() => user.id, { onDelete: "cascade" }), - providerId: text("provider_id").notNull().unique(), - organizationId: text("organization_id"), - domain: text("domain").notNull(), + id: text("id").primaryKey(), + issuer: text("issuer").notNull(), + oidcConfig: text("oidc_config"), + samlConfig: text("saml_config"), + userId: text("user_id").references(() => user.id, { onDelete: "cascade" }), + providerId: text("provider_id").notNull().unique(), + organizationId: text("organization_id"), + domain: text("domain").notNull(), }); export const twoFactor = pgTable( - "two_factor", - { - id: text("id").primaryKey(), - secret: text("secret").notNull(), - backupCodes: text("backup_codes").notNull(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - }, - (table) => [ - index("twoFactor_secret_idx").on(table.secret), - index("twoFactor_userId_idx").on(table.userId), - ], + "two_factor", + { + id: text("id").primaryKey(), + secret: text("secret").notNull(), + backupCodes: text("backup_codes").notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + verified: boolean("verified").default(true), + }, + (table) => [ + index("twoFactor_secret_idx").on(table.secret), + index("twoFactor_userId_idx").on(table.userId), + ], ); export const organization = pgTable( - "organization", - { - id: text("id").primaryKey(), - name: text("name").notNull(), - slug: text("slug").notNull().unique(), - logo: text("logo"), - createdAt: timestamp("created_at").notNull(), - metadata: text("metadata"), - }, - (table) => [uniqueIndex("organization_slug_uidx").on(table.slug)], + "organization", + { + id: text("id").primaryKey(), + name: text("name").notNull(), + slug: text("slug").notNull().unique(), + logo: text("logo"), + createdAt: timestamp("created_at").notNull(), + metadata: text("metadata"), + }, + (table) => [uniqueIndex("organization_slug_uidx").on(table.slug)], ); export const organizationRole = pgTable( - "organization_role", - { - id: text("id").primaryKey(), - organizationId: text("organization_id") - .notNull() - .references(() => organization.id, { onDelete: "cascade" }), - role: text("role").notNull(), - permission: text("permission").notNull(), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at").$onUpdate( - () => /* @__PURE__ */ new Date(), - ), - }, - (table) => [ - index("organizationRole_organizationId_idx").on(table.organizationId), - index("organizationRole_role_idx").on(table.role), - ], + "organization_role", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + role: text("role").notNull(), + permission: text("permission").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at").$onUpdate( + () => /* @__PURE__ */ new Date(), + ), + }, + (table) => [ + index("organizationRole_organizationId_idx").on(table.organizationId), + index("organizationRole_role_idx").on(table.role), + ], ); export const member = pgTable( - "member", - { - id: text("id").primaryKey(), - organizationId: text("organization_id") - .notNull() - .references(() => organization.id, { onDelete: "cascade" }), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - role: text("role").default("member").notNull(), - createdAt: timestamp("created_at").notNull(), - }, - (table) => [ - index("member_organizationId_idx").on(table.organizationId), - index("member_userId_idx").on(table.userId), - ], + "member", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + role: text("role").default("member").notNull(), + createdAt: timestamp("created_at").notNull(), + }, + (table) => [ + index("member_organizationId_idx").on(table.organizationId), + index("member_userId_idx").on(table.userId), + ], ); export const invitation = pgTable( - "invitation", - { - id: text("id").primaryKey(), - organizationId: text("organization_id") - .notNull() - .references(() => organization.id, { onDelete: "cascade" }), - email: text("email").notNull(), - role: text("role"), - status: text("status").default("pending").notNull(), - expiresAt: timestamp("expires_at").notNull(), - createdAt: timestamp("created_at").defaultNow().notNull(), - inviterId: text("inviter_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - }, - (table) => [ - index("invitation_organizationId_idx").on(table.organizationId), - index("invitation_email_idx").on(table.email), - ], + "invitation", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + email: text("email").notNull(), + role: text("role"), + status: text("status").default("pending").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + inviterId: text("inviter_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + }, + (table) => [ + index("invitation_organizationId_idx").on(table.organizationId), + index("invitation_email_idx").on(table.email), + ], ); +export const scimProvider = pgTable("scim_provider", { + id: text("id").primaryKey(), + providerId: text("provider_id").notNull().unique(), + scimToken: text("scim_token").notNull().unique(), + organizationId: text("organization_id"), +}); + export const userRelations = relations(user, ({ many }) => ({ - sessions: many(session), - accounts: many(account), - ssoProviders: many(ssoProvider), - twoFactors: many(twoFactor), - members: many(member), - invitations: many(invitation), + sessions: many(session), + accounts: many(account), + ssoProviders: many(ssoProvider), + twoFactors: many(twoFactor), + members: many(member), + invitations: many(invitation), })); export const sessionRelations = relations(session, ({ one }) => ({ - user: one(user, { - fields: [session.userId], - references: [user.id], - }), + user: one(user, { + fields: [session.userId], + references: [user.id], + }), })); export const accountRelations = relations(account, ({ one }) => ({ - user: one(user, { - fields: [account.userId], - references: [user.id], - }), + user: one(user, { + fields: [account.userId], + references: [user.id], + }), })); export const ssoProviderRelations = relations(ssoProvider, ({ one }) => ({ - user: one(user, { - fields: [ssoProvider.userId], - references: [user.id], - }), + user: one(user, { + fields: [ssoProvider.userId], + references: [user.id], + }), })); export const twoFactorRelations = relations(twoFactor, ({ one }) => ({ - user: one(user, { - fields: [twoFactor.userId], - references: [user.id], - }), + user: one(user, { + fields: [twoFactor.userId], + references: [user.id], + }), })); export const organizationRelations = relations(organization, ({ many }) => ({ - organizationRoles: many(organizationRole), - members: many(member), - invitations: many(invitation), + organizationRoles: many(organizationRole), + members: many(member), + invitations: many(invitation), })); export const organizationRoleRelations = relations( - organizationRole, - ({ one }) => ({ - organization: one(organization, { - fields: [organizationRole.organizationId], - references: [organization.id], - }), - }), + organizationRole, + ({ one }) => ({ + organization: one(organization, { + fields: [organizationRole.organizationId], + references: [organization.id], + }), + }), ); export const memberRelations = relations(member, ({ one }) => ({ - organization: one(organization, { - fields: [member.organizationId], - references: [organization.id], - }), - user: one(user, { - fields: [member.userId], - references: [user.id], - }), + organization: one(organization, { + fields: [member.organizationId], + references: [organization.id], + }), + user: one(user, { + fields: [member.userId], + references: [user.id], + }), })); export const invitationRelations = relations(invitation, ({ one }) => ({ - organization: one(organization, { - fields: [invitation.organizationId], - references: [organization.id], - }), - user: one(user, { - fields: [invitation.inviterId], - references: [user.id], - }), + organization: one(organization, { + fields: [invitation.organizationId], + references: [organization.id], + }), + user: one(user, { + fields: [invitation.inviterId], + references: [user.id], + }), })); diff --git a/packages/server/src/lib/auth-cli.ts b/packages/server/src/lib/auth-cli.ts new file mode 100644 index 000000000..7309a9773 --- /dev/null +++ b/packages/server/src/lib/auth-cli.ts @@ -0,0 +1,52 @@ +import { apiKey } from "@better-auth/api-key"; +import { scim } from "@better-auth/scim"; +import { sso } from "@better-auth/sso"; +import { betterAuth } from "better-auth"; +import { drizzleAdapter } from "better-auth/adapters/drizzle"; +import { admin, organization, twoFactor } from "better-auth/plugins"; +import { db } from "../db"; +import * as schema from "../db/schema"; +import { ac, adminRole, memberRole, ownerRole } from "./access-control"; + +/** + * Minimal better-auth config used only by `@better-auth/cli` to generate / + * inspect database schemas. Must mirror the plugin set in `auth.ts` so the CLI + * sees every table each plugin expects. + * + * Do NOT import this file from the runtime — use `auth.ts` for that. + */ +export const auth = betterAuth({ + database: drizzleAdapter(db, { + provider: "pg", + schema, + }), + user: { + modelName: "user", + fields: { + name: "firstName", + }, + additionalFields: { + role: { type: "string", input: false }, + ownerId: { type: "string", input: false }, + allowImpersonation: { type: "boolean", defaultValue: false }, + lastName: { type: "string", required: false, defaultValue: "" }, + enableEnterpriseFeatures: { type: "boolean", required: false }, + isValidEnterpriseLicense: { type: "boolean", required: false }, + }, + }, + plugins: [ + apiKey({ enableMetadata: true, references: "user" }), + sso(), + twoFactor(), + organization({ + ac, + roles: { owner: ownerRole, admin: adminRole, member: memberRole }, + dynamicAccessControl: { + enabled: true, + maximumRolesPerOrganization: 10, + }, + }), + scim(), + admin(), + ], +});