@voyant-travel/auth 0.133.0 → 0.133.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { apiKey } from "@better-auth/api-key";
2
2
  import { getDb } from "@voyant-travel/db";
3
- import { apikeyTable, authAccount, authSession, authUser, authVerification, } from "@voyant-travel/db/schema/iam";
3
+ import { apikeyTable, authAccount, authSession, authUser, authVerification, customerAuthAccount, customerAuthSession, customerAuthUser, customerAuthVerification, } from "@voyant-travel/db/schema/iam";
4
4
  import { betterAuth } from "better-auth";
5
5
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
6
6
  import { emailOTP } from "better-auth/plugins";
@@ -70,6 +70,19 @@ function stampCustomerSignupSurfaces(user, context, surfaces) {
70
70
  surfaces,
71
71
  };
72
72
  }
73
+ const ADMIN_AUTH_TABLES = {
74
+ user: authUser,
75
+ session: authSession,
76
+ account: authAccount,
77
+ verification: authVerification,
78
+ apikey: apikeyTable,
79
+ };
80
+ const CUSTOMER_AUTH_TABLES = {
81
+ user: customerAuthUser,
82
+ session: customerAuthSession,
83
+ account: customerAuthAccount,
84
+ verification: customerAuthVerification,
85
+ };
73
86
  /**
74
87
  * Framework-agnostic Better Auth factory.
75
88
  *
@@ -86,15 +99,13 @@ export function createBetterAuth(options = {}) {
86
99
  "http://localhost:3000";
87
100
  const trustedOrigins = expandTrustedOrigins(options.trustedOrigins ?? getTrustedOrigins(), baseURL);
88
101
  const extraPlugins = options.plugins ?? [];
102
+ const realm = options.realm ?? "admin";
103
+ const realmTables = options.realmTables ?? (realm === "customer" ? CUSTOMER_AUTH_TABLES : ADMIN_AUTH_TABLES);
89
104
  const signupBlockSurfaces = normalizeSignupBlockSurfaces(options.disableSignupWhenUsersExist);
90
105
  const signupBlockEnabled = isSignupBlockEnabled(options.disableSignupWhenUsersExist);
91
106
  const customerSignupSurfaces = normalizeSurfaceList(options.customerSignupSurfaces);
92
107
  const schema = {
93
- user: authUser,
94
- session: authSession,
95
- account: authAccount,
96
- verification: authVerification,
97
- apikey: apikeyTable,
108
+ ...realmTables,
98
109
  ...(options.extraSchema ?? {}),
99
110
  };
100
111
  const rateLimit = options.rateLimit ??
@@ -128,7 +139,7 @@ export function createBetterAuth(options = {}) {
128
139
  },
129
140
  }),
130
141
  emailAndPassword: {
131
- enabled: true,
142
+ enabled: options.emailPasswordEnabled !== false,
132
143
  minPasswordLength: 8,
133
144
  maxPasswordLength: 128,
134
145
  requireEmailVerification: true,
@@ -150,92 +161,127 @@ export function createBetterAuth(options = {}) {
150
161
  ...options.user?.changeEmail,
151
162
  },
152
163
  },
153
- socialProviders: {
154
- google: {
155
- clientId: process.env.GOOGLE_CLIENT_ID || "",
156
- clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
157
- enabled: Boolean(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET),
158
- },
159
- },
164
+ socialProviders: options.socialProviders ?? {},
160
165
  trustedOrigins,
161
166
  plugins: [
162
- apiKey({
163
- defaultPrefix: "voy_",
164
- apiKeyHeaders: ["authorization"],
165
- requireName: true,
166
- keyExpiration: {
167
- defaultExpiresIn: null,
168
- },
169
- }),
170
- emailOTP({
171
- // eslint-disable-next-line @typescript-eslint/require-await -- owner: auth; existing suppression is intentional pending typed cleanup.
172
- sendVerificationOTP: options.sendVerificationOTP ??
173
- (async ({ email, otp, type }) => {
174
- console.warn(`[Auth] OTP for ${email}: ${otp} (${type})`);
167
+ ...(realm === "admin"
168
+ ? [
169
+ apiKey({
170
+ defaultPrefix: "voy_",
171
+ apiKeyHeaders: ["authorization"],
172
+ requireName: true,
173
+ keyExpiration: {
174
+ defaultExpiresIn: null,
175
+ },
175
176
  }),
176
- otpLength: 6,
177
- expiresIn: 600,
178
- sendVerificationOnSignUp: true,
179
- changeEmail: {
180
- enabled: true,
181
- },
182
- }),
183
- createLocalMemberAccessPlugin({
184
- isEmailDeactivated: (email) => isLocalMemberEmailDeactivated(db, email),
185
- isSessionActive: (sessionId, userId) => isLocalMemberSessionActive(db, sessionId, userId),
186
- isUserDeactivated: (userId) => isLocalMemberDeactivated(db, userId),
187
- }),
177
+ ]
178
+ : []),
179
+ ...(options.emailCodeEnabled === false
180
+ ? []
181
+ : [
182
+ emailOTP({
183
+ // eslint-disable-next-line @typescript-eslint/require-await -- owner: auth; existing suppression is intentional pending typed cleanup.
184
+ sendVerificationOTP: options.sendVerificationOTP ??
185
+ (async ({ email, otp, type }) => {
186
+ console.warn(`[Auth] OTP for ${email}: ${otp} (${type})`);
187
+ }),
188
+ otpLength: 6,
189
+ expiresIn: 600,
190
+ sendVerificationOnSignUp: true,
191
+ changeEmail: {
192
+ enabled: true,
193
+ },
194
+ }),
195
+ ]),
196
+ ...(realm === "admin"
197
+ ? [
198
+ createLocalMemberAccessPlugin({
199
+ isEmailDeactivated: (email) => isLocalMemberEmailDeactivated(db, email),
200
+ isSessionActive: (sessionId, userId) => isLocalMemberSessionActive(db, sessionId, userId),
201
+ isUserDeactivated: (userId) => isLocalMemberDeactivated(db, userId),
202
+ }),
203
+ ]
204
+ : []),
188
205
  ...extraPlugins,
189
206
  ],
190
- databaseHooks: {
191
- user: {
192
- create: {
193
- // Single-tenant: once a user exists, reject any new-user creation.
194
- // Covers email sign-up AND social-provider sign-up (Google would
195
- // otherwise auto-create a user on first OAuth callback). Existing
196
- // social sign-ins still work because this hook only fires on CREATE.
197
- // Seed scripts do raw drizzle inserts, so they bypass this hook —
198
- // which is intentional.
199
- before: async (user, context) => {
200
- const normalizedUser = stampCustomerSignupSurfaces(user, context, customerSignupSurfaces);
201
- if (!signupBlockEnabled ||
202
- !signupBlockAppliesToUser(normalizedUser, signupBlockSurfaces)) {
203
- if (normalizedUser === user) {
204
- return;
205
- }
206
- return { data: normalizedUser };
207
- }
208
- const [row] = await db.select({ count: sql `count(*)::int` }).from(authUser);
209
- if ((row?.count ?? 0) > 0) {
210
- throw new Error("Sign-up is disabled. Ask an admin to invite you.");
211
- }
212
- if (normalizedUser !== user) {
213
- return { data: normalizedUser };
214
- }
215
- },
216
- after: async (user, context) => {
217
- if (customerSignupSurfaces.length > 0 &&
218
- isCustomerSignupCreate(context)) {
219
- return;
220
- }
221
- // Single-tenant bootstrap: the very first user to register becomes
222
- // the super-admin. Runs atomically after the `user` row is
223
- // inserted, so a simple COUNT(*) = 1 check identifies them.
224
- await provisionCurrentUserProfile(db, {
225
- userId: user.id,
226
- name: user.name,
227
- image: user.image,
228
- isSuperAdmin: await isFirstAuthUser(db),
229
- });
207
+ ...(realm === "admin"
208
+ ? {
209
+ databaseHooks: {
210
+ user: {
211
+ create: {
212
+ // Single-tenant: once a user exists, reject any new-user creation.
213
+ // Covers email sign-up AND social-provider sign-up (Google would
214
+ // otherwise auto-create a user on first OAuth callback). Existing
215
+ // social sign-ins still work because this hook only fires on CREATE.
216
+ // Seed scripts do raw drizzle inserts, so they bypass this hook —
217
+ // which is intentional.
218
+ before: async (user, context) => {
219
+ const normalizedUser = stampCustomerSignupSurfaces(user, context, customerSignupSurfaces);
220
+ if (!signupBlockEnabled ||
221
+ !signupBlockAppliesToUser(normalizedUser, signupBlockSurfaces)) {
222
+ if (normalizedUser === user) {
223
+ return;
224
+ }
225
+ return { data: normalizedUser };
226
+ }
227
+ const [row] = await db
228
+ .select({ count: sql `count(*)::int` })
229
+ .from(realmTables.user);
230
+ if ((row?.count ?? 0) > 0) {
231
+ throw new Error("Sign-up is disabled. Ask an admin to invite you.");
232
+ }
233
+ if (normalizedUser !== user) {
234
+ return { data: normalizedUser };
235
+ }
236
+ },
237
+ after: async (user, context) => {
238
+ if (customerSignupSurfaces.length > 0 &&
239
+ isCustomerSignupCreate(context)) {
240
+ return;
241
+ }
242
+ // Single-tenant bootstrap: the very first user to register becomes
243
+ // the super-admin. Runs atomically after the `user` row is
244
+ // inserted, so a simple COUNT(*) = 1 check identifies them.
245
+ await provisionCurrentUserProfile(db, {
246
+ userId: user.id,
247
+ name: user.name,
248
+ image: user.image,
249
+ isSuperAdmin: await isFirstAuthUser(db),
250
+ });
251
+ },
252
+ },
230
253
  },
231
254
  },
232
- },
233
- },
255
+ }
256
+ : {}),
234
257
  advanced: {
235
258
  ...options.advanced,
259
+ cookiePrefix: options.advanced?.cookiePrefix ??
260
+ (realm === "customer" ? "voyant-customer" : "voyant-admin"),
236
261
  useSecureCookies: options.advanced?.useSecureCookies ?? process.env.NODE_ENV !== "development",
237
262
  },
238
263
  };
239
264
  return betterAuth(authOptions);
240
265
  }
266
+ export function createAdminBetterAuth(options = {}) {
267
+ return createBetterAuth({
268
+ ...options,
269
+ realm: "admin",
270
+ realmTables: ADMIN_AUTH_TABLES,
271
+ basePath: options.basePath ?? "/auth/admin",
272
+ });
273
+ }
274
+ /** Storefront realm factory with isolated tables, cookies, secret, and routes. */
275
+ export function createCustomerBetterAuth(options = {}) {
276
+ return createBetterAuth({
277
+ ...options,
278
+ realm: "customer",
279
+ realmTables: CUSTOMER_AUTH_TABLES,
280
+ basePath: options.basePath ?? "/auth/customer",
281
+ disableSignupWhenUsersExist: { enabled: false },
282
+ emailCodeEnabled: options.methods?.emailCode ?? true,
283
+ emailPasswordEnabled: options.methods?.emailPassword ?? true,
284
+ socialProviders: options.methods?.socialProviders,
285
+ });
286
+ }
241
287
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { apiKey } from \"@better-auth/api-key\"\nimport { getDb } from \"@voyant-travel/db\"\nimport {\n apikeyTable,\n authAccount,\n authSession,\n authUser,\n authVerification,\n} from \"@voyant-travel/db/schema/iam\"\nimport { type BetterAuthOptions, betterAuth } from \"better-auth\"\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\"\nimport { emailOTP } from \"better-auth/plugins\"\nimport type { BetterAuthPlugin } from \"better-auth/types\"\nimport { sql } from \"drizzle-orm\"\nimport type { AnyPgTable } from \"drizzle-orm/pg-core\"\n\nimport {\n createLocalMemberAccessPlugin,\n isLocalMemberDeactivated,\n isLocalMemberEmailDeactivated,\n isLocalMemberSessionActive,\n} from \"./local-member-access.js\"\nimport { expandTrustedOrigins, getTrustedOrigins } from \"./trusted-origins.js\"\nimport { isFirstAuthUser, provisionCurrentUserProfile } from \"./workspace.js\"\n\nexport function getAuthSecret(): string {\n const secret = process.env.BETTER_AUTH_SECRET || \"\"\n\n if (!secret || secret.length < 32) {\n throw new Error(\"Missing BETTER_AUTH_SECRET with at least 32 characters\")\n }\n\n return secret\n}\n\nexport type {\n AccountProfileUpdateHandler,\n ApiTokenRotationStore,\n BetterAuthApiTokenManagement,\n HandleAccountProfileRequestOptions,\n HandleApiTokenManagementRequestOptions,\n HandleOrganizationMembersRequestOptions,\n OrganizationMemberRecord,\n OrganizationMembersListHandler,\n OrganizationMembersListInput,\n} from \"./auth-facades.js\"\nexport {\n handleAccountProfileRequest,\n handleApiTokenManagementRequest,\n handleOrganizationMembersRequest,\n} from \"./auth-facades.js\"\n\ntype DefinedBetterAuthUserOptions<UserOptions extends BetterAuthOptions[\"user\"]> =\n UserOptions extends undefined ? Record<PropertyKey, never> : NonNullable<UserOptions>\n\ntype ResolvedBetterAuthChangeEmail<UserOptions extends BetterAuthOptions[\"user\"]> =\n DefinedBetterAuthUserOptions<UserOptions> extends { changeEmail?: infer ChangeEmail }\n ? ChangeEmail extends { enabled: infer Enabled }\n ? Omit<ChangeEmail, \"enabled\"> & { enabled: Enabled }\n : NonNullable<ChangeEmail> & { enabled: true }\n : { enabled: true }\n\ntype ResolvedBetterAuthUserOptions<UserOptions extends BetterAuthOptions[\"user\"]> = Omit<\n DefinedBetterAuthUserOptions<UserOptions>,\n \"changeEmail\"\n> & {\n changeEmail: ResolvedBetterAuthChangeEmail<UserOptions>\n}\n\ntype VoyantBetterAuthPlugins = [\n ReturnType<typeof apiKey>,\n ReturnType<typeof emailOTP>,\n ReturnType<typeof createLocalMemberAccessPlugin>,\n]\n\ntype ResolvedBetterAuthPlugins<Plugins extends BetterAuthPlugin[] | undefined> =\n Plugins extends BetterAuthPlugin[]\n ? [...VoyantBetterAuthPlugins, ...Plugins]\n : VoyantBetterAuthPlugins\n\nconst DEFAULT_SIGNUP_BLOCK_SURFACES = [\"admin\"] as const\nconst CUSTOMER_SIGNUP_ENDPOINT_SUFFIXES = [\n \"/sign-up/email\",\n \"/phone-number/verify\",\n \"/sign-in/email-otp\",\n] as const\n\nexport interface DisableSignupWhenUsersExistOptions {\n /**\n * Set to false when the consuming app owns all signup admission checks.\n *\n * Defaults to true.\n */\n enabled?: boolean\n /**\n * User surfaces that should keep the single-tenant signup guard. New users\n * with only other explicit surfaces can still be created by customer-facing\n * auth plugins such as phone OTP.\n *\n * Defaults to [\"admin\"]. Users without an explicit surfaces array are\n * treated as admin users for backward compatibility.\n */\n surfaces?: readonly string[]\n}\n\ntype BetterAuthCreateBeforeResult =\n | boolean\n | undefined\n | {\n data: Record<string, unknown>\n }\n\ntype SignupBlockUserPayload = {\n surfaces?: unknown\n}\n\ntype BetterAuthHookContext = {\n path?: string\n}\n\nfunction normalizeSignupBlockSurfaces(\n options: DisableSignupWhenUsersExistOptions | undefined,\n): readonly string[] {\n return options?.surfaces ?? DEFAULT_SIGNUP_BLOCK_SURFACES\n}\n\nfunction isSignupBlockEnabled(options: DisableSignupWhenUsersExistOptions | undefined): boolean {\n return options?.enabled !== false\n}\n\nfunction normalizeSurfaceList(surfaces: readonly string[] | undefined): readonly string[] {\n if (!surfaces) return []\n\n const normalized = new Set<string>()\n for (const surface of surfaces) {\n const trimmed = surface.trim()\n if (trimmed.length > 0) {\n normalized.add(trimmed)\n }\n }\n return Array.from(normalized)\n}\n\nfunction signupBlockAppliesToUser(\n user: SignupBlockUserPayload | undefined,\n blockedSurfaces: readonly string[],\n): boolean {\n if (blockedSurfaces.length === 0) return false\n\n const surfaces = user?.surfaces\n if (Array.isArray(surfaces)) {\n const explicitSurfaces = surfaces.filter(\n (surface): surface is string => typeof surface === \"string\" && surface.trim().length > 0,\n )\n if (explicitSurfaces.length > 0) {\n return explicitSurfaces.some((surface) => blockedSurfaces.includes(surface.trim()))\n }\n }\n\n if (typeof surfaces === \"string\" && surfaces.trim().length > 0) {\n return blockedSurfaces.includes(surfaces.trim())\n }\n\n return blockedSurfaces.includes(\"admin\")\n}\n\nfunction isCustomerSignupCreate(context: BetterAuthHookContext | null | undefined): boolean {\n const path = context?.path\n if (!path) return false\n\n return CUSTOMER_SIGNUP_ENDPOINT_SUFFIXES.some((suffix) => path.endsWith(suffix))\n}\n\nfunction stampCustomerSignupSurfaces(\n user: SignupBlockUserPayload | undefined,\n context: BetterAuthHookContext | null | undefined,\n surfaces: readonly string[],\n): SignupBlockUserPayload | undefined {\n if (surfaces.length === 0 || !isCustomerSignupCreate(context)) {\n return user\n }\n\n return {\n ...(user ?? {}),\n surfaces,\n }\n}\n\ntype ResolvedCreateBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined,\n> = Omit<BetterAuthOptions, \"plugins\" | \"user\"> & {\n plugins: ResolvedBetterAuthPlugins<Plugins>\n user: ResolvedBetterAuthUserOptions<UserOptions>\n}\n\nexport type BetterAuthDrizzleSchema = Record<string, AnyPgTable>\n\nexport interface CreateBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"] = BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined = BetterAuthPlugin[] | undefined,\n> {\n db?: ReturnType<typeof getDb>\n secret?: string\n baseURL?: string\n basePath?: string\n trustedOrigins?: string[]\n /**\n * Additional Drizzle tables for Better Auth plugins. The consuming app owns\n * matching migrations for every table passed here.\n */\n extraSchema?: BetterAuthDrizzleSchema\n plugins?: Plugins\n user?: UserOptions\n /**\n * Surfaces stamped on Better Auth customer self-signups before the bundled\n * single-tenant signup guard evaluates the new user. Applies to Better\n * Auth OTP signup endpoints that create a user as part of verification.\n *\n * Leave undefined to preserve Better Auth's raw user payload.\n */\n customerSignupSurfaces?: readonly string[]\n disableSignupWhenUsersExist?: DisableSignupWhenUsersExistOptions\n /** Called when a user requests a password reset. If not provided, logs to console. */\n sendResetPassword?: (data: {\n user: { email: string; name: string }\n url: string\n token: string\n }) => Promise<void>\n /** Called to send a verification OTP. If not provided, logs to console. */\n sendVerificationOTP?: (data: { email: string; otp: string; type: string }) => Promise<void>\n /**\n * Better Auth session cookie cache: session data rides in a short-lived\n * signed cookie. Enabled by default with a 5-minute TTL. Voyant still\n * validates local membership and session existence against Postgres so a\n * deleted session cannot be revived from the cookie cache. Pass `false` to\n * disable payload caching, or tune `maxAge` (seconds).\n */\n sessionCookieCache?: false | { maxAge?: number }\n /** Better Auth secondary storage, typically Redis/KV, used for distributed rate limits. */\n secondaryStorage?: BetterAuthOptions[\"secondaryStorage\"]\n /** Better Auth rate-limit config. Defaults to secondary storage when present. */\n rateLimit?: BetterAuthOptions[\"rateLimit\"]\n /**\n * Additional Better Auth advanced options. Voyant supplies secure cookies\n * by default except in explicit local development. Set\n * `advanced.useSecureCookies` to override that default.\n */\n advanced?: BetterAuthOptions[\"advanced\"]\n}\n\n/**\n * Framework-agnostic Better Auth factory.\n *\n * Accepts optional overrides for db, secret, baseURL, trustedOrigins.\n * Does NOT depend on Next.js — safe to use in Hono workers, TanStack Start, etc.\n */\nexport function createBetterAuth<\n const UserOptions extends BetterAuthOptions[\"user\"] = undefined,\n const Plugins extends BetterAuthPlugin[] | undefined = undefined,\n>(options: CreateBetterAuthOptions<UserOptions, Plugins> = {}) {\n const db = options.db ?? getDb(\"edge\")\n const secret = options.secret ?? getAuthSecret()\n const baseURL =\n options.baseURL ??\n process.env.BETTER_AUTH_URL ??\n process.env.APP_URL ??\n process.env.NEXT_PUBLIC_APP_URL ??\n \"http://localhost:3000\"\n const trustedOrigins = expandTrustedOrigins(\n options.trustedOrigins ?? getTrustedOrigins(),\n baseURL,\n )\n const extraPlugins = options.plugins ?? []\n const signupBlockSurfaces = normalizeSignupBlockSurfaces(options.disableSignupWhenUsersExist)\n const signupBlockEnabled = isSignupBlockEnabled(options.disableSignupWhenUsersExist)\n const customerSignupSurfaces = normalizeSurfaceList(options.customerSignupSurfaces)\n const schema = {\n user: authUser,\n session: authSession,\n account: authAccount,\n verification: authVerification,\n apikey: apikeyTable,\n ...(options.extraSchema ?? {}),\n } satisfies BetterAuthDrizzleSchema\n const rateLimit =\n options.rateLimit ??\n (options.secondaryStorage\n ? ({\n enabled: true,\n window: 60,\n max: 100,\n storage: \"secondary-storage\",\n } as BetterAuthOptions[\"rateLimit\"])\n : undefined)\n\n const authOptions = {\n appName: \"Voyant\",\n baseURL,\n ...(options.basePath ? { basePath: options.basePath } : {}),\n secret,\n database: drizzleAdapter(db, {\n provider: \"pg\",\n schema,\n }),\n ...(options.secondaryStorage ? { secondaryStorage: options.secondaryStorage } : {}),\n ...(rateLimit ? { rateLimit } : {}),\n ...(options.sessionCookieCache === false\n ? {}\n : {\n session: {\n cookieCache: {\n enabled: true,\n maxAge: options.sessionCookieCache?.maxAge ?? 300,\n },\n },\n }),\n emailAndPassword: {\n enabled: true,\n minPasswordLength: 8,\n maxPasswordLength: 128,\n requireEmailVerification: true,\n revokeSessionsOnPasswordReset: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- owner: auth; existing suppression is intentional pending typed cleanup.\n sendResetPassword:\n options.sendResetPassword ??\n (async ({ user, url }) => {\n console.warn(\n `[Auth] No email provider configured — password reset for ${user.email}: ${url}`,\n )\n }),\n },\n emailVerification: {\n sendOnSignUp: false, // OTP plugin handles this\n autoSignInAfterVerification: true,\n },\n user: {\n ...options.user,\n changeEmail: {\n enabled: true,\n ...options.user?.changeEmail,\n },\n } as ResolvedBetterAuthUserOptions<UserOptions>,\n socialProviders: {\n google: {\n clientId: process.env.GOOGLE_CLIENT_ID || \"\",\n clientSecret: process.env.GOOGLE_CLIENT_SECRET || \"\",\n enabled: Boolean(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET),\n },\n },\n trustedOrigins,\n plugins: [\n apiKey({\n defaultPrefix: \"voy_\",\n apiKeyHeaders: [\"authorization\"],\n requireName: true,\n keyExpiration: {\n defaultExpiresIn: null,\n },\n }),\n emailOTP({\n // eslint-disable-next-line @typescript-eslint/require-await -- owner: auth; existing suppression is intentional pending typed cleanup.\n sendVerificationOTP:\n options.sendVerificationOTP ??\n (async ({ email, otp, type }) => {\n console.warn(`[Auth] OTP for ${email}: ${otp} (${type})`)\n }),\n otpLength: 6,\n expiresIn: 600,\n sendVerificationOnSignUp: true,\n changeEmail: {\n enabled: true,\n },\n }),\n createLocalMemberAccessPlugin({\n isEmailDeactivated: (email) => isLocalMemberEmailDeactivated(db, email),\n isSessionActive: (sessionId, userId) => isLocalMemberSessionActive(db, sessionId, userId),\n isUserDeactivated: (userId) => isLocalMemberDeactivated(db, userId),\n }),\n ...extraPlugins,\n ] as ResolvedBetterAuthPlugins<Plugins>,\n databaseHooks: {\n user: {\n create: {\n // Single-tenant: once a user exists, reject any new-user creation.\n // Covers email sign-up AND social-provider sign-up (Google would\n // otherwise auto-create a user on first OAuth callback). Existing\n // social sign-ins still work because this hook only fires on CREATE.\n // Seed scripts do raw drizzle inserts, so they bypass this hook —\n // which is intentional.\n before: async (user, context): Promise<BetterAuthCreateBeforeResult> => {\n const normalizedUser = stampCustomerSignupSurfaces(\n user as SignupBlockUserPayload,\n context as BetterAuthHookContext | null,\n customerSignupSurfaces,\n )\n\n if (\n !signupBlockEnabled ||\n !signupBlockAppliesToUser(normalizedUser, signupBlockSurfaces)\n ) {\n if (normalizedUser === user) {\n return\n }\n return { data: normalizedUser as Record<string, unknown> }\n }\n\n const [row] = await db.select({ count: sql<number>`count(*)::int` }).from(authUser)\n if ((row?.count ?? 0) > 0) {\n throw new Error(\"Sign-up is disabled. Ask an admin to invite you.\")\n }\n\n if (normalizedUser !== user) {\n return { data: normalizedUser as Record<string, unknown> }\n }\n },\n after: async (user, context) => {\n if (\n customerSignupSurfaces.length > 0 &&\n isCustomerSignupCreate(context as BetterAuthHookContext | null)\n ) {\n return\n }\n\n // Single-tenant bootstrap: the very first user to register becomes\n // the super-admin. Runs atomically after the `user` row is\n // inserted, so a simple COUNT(*) = 1 check identifies them.\n await provisionCurrentUserProfile(db, {\n userId: user.id,\n name: user.name,\n image: user.image,\n isSuperAdmin: await isFirstAuthUser(db),\n })\n },\n },\n },\n },\n advanced: {\n ...options.advanced,\n useSecureCookies:\n options.advanced?.useSecureCookies ?? process.env.NODE_ENV !== \"development\",\n },\n } as ResolvedCreateBetterAuthOptions<UserOptions, Plugins>\n\n return betterAuth<ResolvedCreateBetterAuthOptions<UserOptions, Plugins>>(authOptions)\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { apiKey } from \"@better-auth/api-key\"\nimport { getDb } from \"@voyant-travel/db\"\nimport {\n apikeyTable,\n authAccount,\n authSession,\n authUser,\n authVerification,\n customerAuthAccount,\n customerAuthSession,\n customerAuthUser,\n customerAuthVerification,\n} from \"@voyant-travel/db/schema/iam\"\nimport { type BetterAuthOptions, betterAuth } from \"better-auth\"\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\"\nimport { emailOTP } from \"better-auth/plugins\"\nimport type { BetterAuthPlugin } from \"better-auth/types\"\nimport { sql } from \"drizzle-orm\"\nimport type { AnyPgTable } from \"drizzle-orm/pg-core\"\n\nimport {\n createLocalMemberAccessPlugin,\n isLocalMemberDeactivated,\n isLocalMemberEmailDeactivated,\n isLocalMemberSessionActive,\n} from \"./local-member-access.js\"\nimport { expandTrustedOrigins, getTrustedOrigins } from \"./trusted-origins.js\"\nimport { isFirstAuthUser, provisionCurrentUserProfile } from \"./workspace.js\"\n\nexport function getAuthSecret(): string {\n const secret = process.env.BETTER_AUTH_SECRET || \"\"\n\n if (!secret || secret.length < 32) {\n throw new Error(\"Missing BETTER_AUTH_SECRET with at least 32 characters\")\n }\n\n return secret\n}\n\nexport type {\n AccountProfileUpdateHandler,\n ApiTokenRotationStore,\n BetterAuthApiTokenManagement,\n HandleAccountProfileRequestOptions,\n HandleApiTokenManagementRequestOptions,\n HandleOrganizationMembersRequestOptions,\n OrganizationMemberRecord,\n OrganizationMembersListHandler,\n OrganizationMembersListInput,\n} from \"./auth-facades.js\"\nexport {\n handleAccountProfileRequest,\n handleApiTokenManagementRequest,\n handleOrganizationMembersRequest,\n} from \"./auth-facades.js\"\n\ntype DefinedBetterAuthUserOptions<UserOptions extends BetterAuthOptions[\"user\"]> =\n UserOptions extends undefined ? Record<PropertyKey, never> : NonNullable<UserOptions>\n\ntype ResolvedBetterAuthChangeEmail<UserOptions extends BetterAuthOptions[\"user\"]> =\n DefinedBetterAuthUserOptions<UserOptions> extends { changeEmail?: infer ChangeEmail }\n ? ChangeEmail extends { enabled: infer Enabled }\n ? Omit<ChangeEmail, \"enabled\"> & { enabled: Enabled }\n : NonNullable<ChangeEmail> & { enabled: true }\n : { enabled: true }\n\ntype ResolvedBetterAuthUserOptions<UserOptions extends BetterAuthOptions[\"user\"]> = Omit<\n DefinedBetterAuthUserOptions<UserOptions>,\n \"changeEmail\"\n> & {\n changeEmail: ResolvedBetterAuthChangeEmail<UserOptions>\n}\n\ntype VoyantBetterAuthPlugins = [\n ReturnType<typeof apiKey>,\n ReturnType<typeof emailOTP>,\n ReturnType<typeof createLocalMemberAccessPlugin>,\n]\n\ntype ResolvedBetterAuthPlugins<_Plugins extends BetterAuthPlugin[] | undefined> =\n | VoyantBetterAuthPlugins\n | BetterAuthPlugin[]\n\nconst DEFAULT_SIGNUP_BLOCK_SURFACES = [\"admin\"] as const\nconst CUSTOMER_SIGNUP_ENDPOINT_SUFFIXES = [\n \"/sign-up/email\",\n \"/phone-number/verify\",\n \"/sign-in/email-otp\",\n] as const\n\nexport interface DisableSignupWhenUsersExistOptions {\n /**\n * Set to false when the consuming app owns all signup admission checks.\n *\n * Defaults to true.\n */\n enabled?: boolean\n /**\n * User surfaces that should keep the single-tenant signup guard. New users\n * with only other explicit surfaces can still be created by customer-facing\n * auth plugins such as phone OTP.\n *\n * Defaults to [\"admin\"]. Users without an explicit surfaces array are\n * treated as admin users for backward compatibility.\n */\n surfaces?: readonly string[]\n}\n\ntype BetterAuthCreateBeforeResult =\n | boolean\n | undefined\n | {\n data: Record<string, unknown>\n }\n\ntype SignupBlockUserPayload = {\n surfaces?: unknown\n}\n\ntype BetterAuthHookContext = {\n path?: string\n}\n\nfunction normalizeSignupBlockSurfaces(\n options: DisableSignupWhenUsersExistOptions | undefined,\n): readonly string[] {\n return options?.surfaces ?? DEFAULT_SIGNUP_BLOCK_SURFACES\n}\n\nfunction isSignupBlockEnabled(options: DisableSignupWhenUsersExistOptions | undefined): boolean {\n return options?.enabled !== false\n}\n\nfunction normalizeSurfaceList(surfaces: readonly string[] | undefined): readonly string[] {\n if (!surfaces) return []\n\n const normalized = new Set<string>()\n for (const surface of surfaces) {\n const trimmed = surface.trim()\n if (trimmed.length > 0) {\n normalized.add(trimmed)\n }\n }\n return Array.from(normalized)\n}\n\nfunction signupBlockAppliesToUser(\n user: SignupBlockUserPayload | undefined,\n blockedSurfaces: readonly string[],\n): boolean {\n if (blockedSurfaces.length === 0) return false\n\n const surfaces = user?.surfaces\n if (Array.isArray(surfaces)) {\n const explicitSurfaces = surfaces.filter(\n (surface): surface is string => typeof surface === \"string\" && surface.trim().length > 0,\n )\n if (explicitSurfaces.length > 0) {\n return explicitSurfaces.some((surface) => blockedSurfaces.includes(surface.trim()))\n }\n }\n\n if (typeof surfaces === \"string\" && surfaces.trim().length > 0) {\n return blockedSurfaces.includes(surfaces.trim())\n }\n\n return blockedSurfaces.includes(\"admin\")\n}\n\nfunction isCustomerSignupCreate(context: BetterAuthHookContext | null | undefined): boolean {\n const path = context?.path\n if (!path) return false\n\n return CUSTOMER_SIGNUP_ENDPOINT_SUFFIXES.some((suffix) => path.endsWith(suffix))\n}\n\nfunction stampCustomerSignupSurfaces(\n user: SignupBlockUserPayload | undefined,\n context: BetterAuthHookContext | null | undefined,\n surfaces: readonly string[],\n): SignupBlockUserPayload | undefined {\n if (surfaces.length === 0 || !isCustomerSignupCreate(context)) {\n return user\n }\n\n return {\n ...(user ?? {}),\n surfaces,\n }\n}\n\ntype ResolvedCreateBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined,\n> = Omit<BetterAuthOptions, \"plugins\" | \"user\"> & {\n plugins: ResolvedBetterAuthPlugins<Plugins>\n user: ResolvedBetterAuthUserOptions<UserOptions>\n}\n\nexport type BetterAuthDrizzleSchema = Record<string, AnyPgTable>\n\nexport type VoyantAuthRealm = \"admin\" | \"customer\"\n\nexport interface BetterAuthRealmTables {\n user: AnyPgTable\n session: AnyPgTable\n account: AnyPgTable\n verification: AnyPgTable\n apikey?: AnyPgTable\n}\n\nconst ADMIN_AUTH_TABLES: BetterAuthRealmTables = {\n user: authUser,\n session: authSession,\n account: authAccount,\n verification: authVerification,\n apikey: apikeyTable,\n}\n\nconst CUSTOMER_AUTH_TABLES: BetterAuthRealmTables = {\n user: customerAuthUser,\n session: customerAuthSession,\n account: customerAuthAccount,\n verification: customerAuthVerification,\n}\n\nexport interface CreateBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"] = BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined = BetterAuthPlugin[] | undefined,\n> {\n db?: ReturnType<typeof getDb>\n secret?: string\n baseURL?: string\n basePath?: string\n trustedOrigins?: string[]\n /** Security realm. Existing callers default to the isolated admin realm. */\n realm?: VoyantAuthRealm\n /** Override only when an adapter owns a compatible Better Auth schema. */\n realmTables?: BetterAuthRealmTables\n /**\n * Additional Drizzle tables for Better Auth plugins. The consuming app owns\n * matching migrations for every table passed here.\n */\n extraSchema?: BetterAuthDrizzleSchema\n plugins?: Plugins\n user?: UserOptions\n /** Resolved credentials; the auth package never persists provider secrets. */\n socialProviders?: BetterAuthOptions[\"socialProviders\"]\n emailCodeEnabled?: boolean\n emailPasswordEnabled?: boolean\n /**\n * Surfaces stamped on Better Auth customer self-signups before the bundled\n * single-tenant signup guard evaluates the new user. Applies to Better\n * Auth OTP signup endpoints that create a user as part of verification.\n *\n * Leave undefined to preserve Better Auth's raw user payload.\n */\n customerSignupSurfaces?: readonly string[]\n disableSignupWhenUsersExist?: DisableSignupWhenUsersExistOptions\n /** Called when a user requests a password reset. If not provided, logs to console. */\n sendResetPassword?: (data: {\n user: { email: string; name: string }\n url: string\n token: string\n }) => Promise<void>\n /** Called to send a verification OTP. If not provided, logs to console. */\n sendVerificationOTP?: (data: { email: string; otp: string; type: string }) => Promise<void>\n /**\n * Better Auth session cookie cache: session data rides in a short-lived\n * signed cookie. Enabled by default with a 5-minute TTL. Voyant still\n * validates local membership and session existence against Postgres so a\n * deleted session cannot be revived from the cookie cache. Pass `false` to\n * disable payload caching, or tune `maxAge` (seconds).\n */\n sessionCookieCache?: false | { maxAge?: number }\n /** Better Auth secondary storage, typically Redis/KV, used for distributed rate limits. */\n secondaryStorage?: BetterAuthOptions[\"secondaryStorage\"]\n /** Better Auth rate-limit config. Defaults to secondary storage when present. */\n rateLimit?: BetterAuthOptions[\"rateLimit\"]\n /**\n * Additional Better Auth advanced options. Voyant supplies secure cookies\n * by default except in explicit local development. Set\n * `advanced.useSecureCookies` to override that default.\n */\n advanced?: BetterAuthOptions[\"advanced\"]\n}\n\n/**\n * Framework-agnostic Better Auth factory.\n *\n * Accepts optional overrides for db, secret, baseURL, trustedOrigins.\n * Does NOT depend on Next.js — safe to use in Hono workers, TanStack Start, etc.\n */\nexport function createBetterAuth<\n const UserOptions extends BetterAuthOptions[\"user\"] = undefined,\n const Plugins extends BetterAuthPlugin[] | undefined = undefined,\n>(options: CreateBetterAuthOptions<UserOptions, Plugins> = {}) {\n const db = options.db ?? getDb(\"edge\")\n const secret = options.secret ?? getAuthSecret()\n const baseURL =\n options.baseURL ??\n process.env.BETTER_AUTH_URL ??\n process.env.APP_URL ??\n process.env.NEXT_PUBLIC_APP_URL ??\n \"http://localhost:3000\"\n const trustedOrigins = expandTrustedOrigins(\n options.trustedOrigins ?? getTrustedOrigins(),\n baseURL,\n )\n const extraPlugins = options.plugins ?? []\n const realm = options.realm ?? \"admin\"\n const realmTables =\n options.realmTables ?? (realm === \"customer\" ? CUSTOMER_AUTH_TABLES : ADMIN_AUTH_TABLES)\n const signupBlockSurfaces = normalizeSignupBlockSurfaces(options.disableSignupWhenUsersExist)\n const signupBlockEnabled = isSignupBlockEnabled(options.disableSignupWhenUsersExist)\n const customerSignupSurfaces = normalizeSurfaceList(options.customerSignupSurfaces)\n const schema = {\n ...realmTables,\n ...(options.extraSchema ?? {}),\n } satisfies BetterAuthDrizzleSchema\n const rateLimit =\n options.rateLimit ??\n (options.secondaryStorage\n ? ({\n enabled: true,\n window: 60,\n max: 100,\n storage: \"secondary-storage\",\n } as BetterAuthOptions[\"rateLimit\"])\n : undefined)\n\n const authOptions = {\n appName: \"Voyant\",\n baseURL,\n ...(options.basePath ? { basePath: options.basePath } : {}),\n secret,\n database: drizzleAdapter(db, {\n provider: \"pg\",\n schema,\n }),\n ...(options.secondaryStorage ? { secondaryStorage: options.secondaryStorage } : {}),\n ...(rateLimit ? { rateLimit } : {}),\n ...(options.sessionCookieCache === false\n ? {}\n : {\n session: {\n cookieCache: {\n enabled: true,\n maxAge: options.sessionCookieCache?.maxAge ?? 300,\n },\n },\n }),\n emailAndPassword: {\n enabled: options.emailPasswordEnabled !== false,\n minPasswordLength: 8,\n maxPasswordLength: 128,\n requireEmailVerification: true,\n revokeSessionsOnPasswordReset: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- owner: auth; existing suppression is intentional pending typed cleanup.\n sendResetPassword:\n options.sendResetPassword ??\n (async ({ user, url }) => {\n console.warn(\n `[Auth] No email provider configured — password reset for ${user.email}: ${url}`,\n )\n }),\n },\n emailVerification: {\n sendOnSignUp: false, // OTP plugin handles this\n autoSignInAfterVerification: true,\n },\n user: {\n ...options.user,\n changeEmail: {\n enabled: true,\n ...options.user?.changeEmail,\n },\n } as ResolvedBetterAuthUserOptions<UserOptions>,\n socialProviders: options.socialProviders ?? {},\n trustedOrigins,\n plugins: [\n ...(realm === \"admin\"\n ? [\n apiKey({\n defaultPrefix: \"voy_\",\n apiKeyHeaders: [\"authorization\"],\n requireName: true,\n keyExpiration: {\n defaultExpiresIn: null,\n },\n }),\n ]\n : []),\n ...(options.emailCodeEnabled === false\n ? []\n : [\n emailOTP({\n // eslint-disable-next-line @typescript-eslint/require-await -- owner: auth; existing suppression is intentional pending typed cleanup.\n sendVerificationOTP:\n options.sendVerificationOTP ??\n (async ({ email, otp, type }) => {\n console.warn(`[Auth] OTP for ${email}: ${otp} (${type})`)\n }),\n otpLength: 6,\n expiresIn: 600,\n sendVerificationOnSignUp: true,\n changeEmail: {\n enabled: true,\n },\n }),\n ]),\n ...(realm === \"admin\"\n ? [\n createLocalMemberAccessPlugin({\n isEmailDeactivated: (email) => isLocalMemberEmailDeactivated(db, email),\n isSessionActive: (sessionId, userId) =>\n isLocalMemberSessionActive(db, sessionId, userId),\n isUserDeactivated: (userId) => isLocalMemberDeactivated(db, userId),\n }),\n ]\n : []),\n ...extraPlugins,\n ] as ResolvedBetterAuthPlugins<Plugins>,\n ...(realm === \"admin\"\n ? {\n databaseHooks: {\n user: {\n create: {\n // Single-tenant: once a user exists, reject any new-user creation.\n // Covers email sign-up AND social-provider sign-up (Google would\n // otherwise auto-create a user on first OAuth callback). Existing\n // social sign-ins still work because this hook only fires on CREATE.\n // Seed scripts do raw drizzle inserts, so they bypass this hook —\n // which is intentional.\n before: async (user, context): Promise<BetterAuthCreateBeforeResult> => {\n const normalizedUser = stampCustomerSignupSurfaces(\n user as SignupBlockUserPayload,\n context as BetterAuthHookContext | null,\n customerSignupSurfaces,\n )\n\n if (\n !signupBlockEnabled ||\n !signupBlockAppliesToUser(normalizedUser, signupBlockSurfaces)\n ) {\n if (normalizedUser === user) {\n return\n }\n return { data: normalizedUser as Record<string, unknown> }\n }\n\n const [row] = await db\n .select({ count: sql<number>`count(*)::int` })\n .from(realmTables.user)\n if ((row?.count ?? 0) > 0) {\n throw new Error(\"Sign-up is disabled. Ask an admin to invite you.\")\n }\n\n if (normalizedUser !== user) {\n return { data: normalizedUser as Record<string, unknown> }\n }\n },\n after: async (user, context) => {\n if (\n customerSignupSurfaces.length > 0 &&\n isCustomerSignupCreate(context as BetterAuthHookContext | null)\n ) {\n return\n }\n\n // Single-tenant bootstrap: the very first user to register becomes\n // the super-admin. Runs atomically after the `user` row is\n // inserted, so a simple COUNT(*) = 1 check identifies them.\n await provisionCurrentUserProfile(db, {\n userId: user.id,\n name: user.name,\n image: user.image,\n isSuperAdmin: await isFirstAuthUser(db),\n })\n },\n },\n },\n },\n }\n : {}),\n advanced: {\n ...options.advanced,\n cookiePrefix:\n options.advanced?.cookiePrefix ??\n (realm === \"customer\" ? \"voyant-customer\" : \"voyant-admin\"),\n useSecureCookies:\n options.advanced?.useSecureCookies ?? process.env.NODE_ENV !== \"development\",\n },\n } as ResolvedCreateBetterAuthOptions<UserOptions, Plugins>\n\n return betterAuth<ResolvedCreateBetterAuthOptions<UserOptions, Plugins>>(authOptions)\n}\n\nexport type CreateAdminBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"] = BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined = BetterAuthPlugin[] | undefined,\n> = Omit<CreateBetterAuthOptions<UserOptions, Plugins>, \"realm\" | \"realmTables\">\n\nexport function createAdminBetterAuth<\n const UserOptions extends BetterAuthOptions[\"user\"] = undefined,\n const Plugins extends BetterAuthPlugin[] | undefined = undefined,\n>(options: CreateAdminBetterAuthOptions<UserOptions, Plugins> = {}) {\n return createBetterAuth({\n ...options,\n realm: \"admin\",\n realmTables: ADMIN_AUTH_TABLES,\n basePath: options.basePath ?? \"/auth/admin\",\n })\n}\n\nexport interface CustomerAuthMethods {\n emailCode?: boolean\n emailPassword?: boolean\n /** Better Auth provider configs such as google, facebook, and apple. */\n socialProviders?: BetterAuthOptions[\"socialProviders\"]\n}\n\nexport type CreateCustomerBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"] = BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined = BetterAuthPlugin[] | undefined,\n> = Omit<\n CreateBetterAuthOptions<UserOptions, Plugins>,\n | \"realm\"\n | \"realmTables\"\n | \"customerSignupSurfaces\"\n | \"disableSignupWhenUsersExist\"\n | \"socialProviders\"\n | \"emailCodeEnabled\"\n | \"emailPasswordEnabled\"\n> & { methods?: CustomerAuthMethods }\n\n/** Storefront realm factory with isolated tables, cookies, secret, and routes. */\nexport function createCustomerBetterAuth<\n const UserOptions extends BetterAuthOptions[\"user\"] = undefined,\n const Plugins extends BetterAuthPlugin[] | undefined = undefined,\n>(options: CreateCustomerBetterAuthOptions<UserOptions, Plugins> = {}) {\n return createBetterAuth({\n ...options,\n realm: \"customer\",\n realmTables: CUSTOMER_AUTH_TABLES,\n basePath: options.basePath ?? \"/auth/customer\",\n disableSignupWhenUsersExist: { enabled: false },\n emailCodeEnabled: options.methods?.emailCode ?? true,\n emailPasswordEnabled: options.methods?.emailPassword ?? true,\n socialProviders: options.methods?.socialProviders,\n })\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyant-travel/auth",
3
- "version": "0.133.0",
3
+ "version": "0.133.2",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -57,6 +57,11 @@
57
57
  "import": "./dist/permissions.js",
58
58
  "default": "./dist/permissions.js"
59
59
  },
60
+ "./auth-realms": {
61
+ "types": "./dist/auth-realms.d.ts",
62
+ "import": "./dist/auth-realms.js",
63
+ "default": "./dist/auth-realms.js"
64
+ },
60
65
  "./identity-access-runtime-port": {
61
66
  "types": "./dist/identity-access-runtime-port.d.ts",
62
67
  "import": "./dist/identity-access-runtime-port.js",
@@ -105,9 +110,9 @@
105
110
  "drizzle-orm": "^0.45.2",
106
111
  "hono": "^4.12.27",
107
112
  "zod": "^4.4.3",
108
- "@voyant-travel/core": "^0.125.2",
109
- "@voyant-travel/db": "^0.114.10",
110
- "@voyant-travel/hono": "^0.128.3",
113
+ "@voyant-travel/core": "^0.126.1",
114
+ "@voyant-travel/db": "^0.114.12",
115
+ "@voyant-travel/hono": "^0.128.5",
111
116
  "@voyant-travel/tools": "^0.3.0",
112
117
  "@voyant-travel/types": "^0.109.4",
113
118
  "@voyant-travel/utils": "^0.107.1"