Skip to content

Commit ff306be

Browse files
authored
fix(types): support server auth config project refs (#312)
1 parent 5b0756c commit ff306be

25 files changed

Lines changed: 307 additions & 41 deletions

File tree

src/module.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,49 @@ export default defineNuxtModule<BetterAuthModuleOptions>({
109109
nuxt.options.alias['#auth/server'] = serverConfigPath
110110
nuxt.options.alias['#auth/client'] = clientConfigPath
111111

112+
if (!clientOnly) {
113+
nuxt.hook('prepare:types', ({ nodeTsConfig, nodeReferences, sharedReferences }) => {
114+
nodeTsConfig.compilerOptions ||= {}
115+
nodeTsConfig.compilerOptions.paths ||= {}
116+
117+
const serverDir = dirname(serverConfigPath)
118+
const projectReferenceTypePaths = [
119+
join(nuxt.options.buildDir, 'types/nitro-imports.d.ts'),
120+
join(nuxt.options.buildDir, 'types/auth-database.d.ts'),
121+
join(nuxt.options.buildDir, 'types/auth-schema.d.ts'),
122+
join(nuxt.options.buildDir, 'types/auth-secondary-storage.d.ts'),
123+
]
124+
125+
if (hasHubDb)
126+
projectReferenceTypePaths.push(join(nuxt.options.buildDir, 'hub/db.d.ts'))
127+
128+
const exactNodeAliases = {
129+
'#server': serverDir,
130+
'#auth/server': nuxt.options.alias['#auth/server'],
131+
'#auth/client': nuxt.options.alias['#auth/client'],
132+
'#auth/database': nuxt.options.alias['#auth/database'],
133+
'#auth/schema': nuxt.options.alias['#auth/schema'],
134+
'#auth/secondary-storage': nuxt.options.alias['#auth/secondary-storage'],
135+
'#auth/route-rules': nuxt.options.alias['#auth/route-rules'],
136+
'#nuxt-better-auth': nuxt.options.alias['#nuxt-better-auth'],
137+
} as const
138+
139+
for (const [key, value] of Object.entries(exactNodeAliases)) {
140+
if (typeof value === 'string')
141+
nodeTsConfig.compilerOptions.paths[key] = [value]
142+
}
143+
144+
nodeTsConfig.compilerOptions.paths['#server/*'] = [join(serverDir, '*')]
145+
146+
for (const path of projectReferenceTypePaths) {
147+
if (!nodeReferences.some(reference => 'path' in reference && reference.path === path))
148+
nodeReferences.push({ path })
149+
if (!sharedReferences.some(reference => 'path' in reference && reference.path === path))
150+
sharedReferences.push({ path })
151+
}
152+
})
153+
}
154+
112155
if (clientOnly) {
113156
setupRuntimeConfig({
114157
nuxt,

src/module/schema.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ export function resolveHubSchemaPath(
6868
async function loadAuthOptions(context: SchemaContext) {
6969
const isProduction = !context.nuxt.options.dev
7070
const configFile = `${context.serverConfigPath}.ts`
71-
const userConfig = await loadUserAuthConfig(configFile, isProduction)
71+
const alias = Object.fromEntries(
72+
Object.entries(context.nuxt.options.alias)
73+
.filter(([, value]) => typeof value === 'string')
74+
.map(([key, value]) => [key, value as string]),
75+
)
76+
const userConfig = await loadUserAuthConfig(configFile, isProduction, alias)
7277

7378
const extendedConfig: { plugins?: BetterAuthPlugin[] } = {}
7479
await context.nuxt.callHook('better-auth:config:extend', extendedConfig)

src/module/type-templates.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,18 @@ declare module '#auth/secondary-storage' {
2121
export function createSecondaryStorage(): SecondaryStorage | undefined
2222
}
2323
`,
24-
}, { nitro: true, node: true })
24+
}, { nitro: true })
2525

2626
addTypeTemplate({
2727
filename: 'types/auth-database.d.ts',
2828
getContents: () => `
2929
declare module '#auth/database' {
3030
import type { BetterAuthOptions } from 'better-auth'
31-
export function createDatabase(): BetterAuthOptions['database']
31+
export function createDatabase(event?: import('h3').H3Event): BetterAuthOptions['database']
3232
export const db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : 'undefined'}
3333
}
3434
`,
35-
}, { nitro: true, node: true })
35+
}, { nitro: true })
3636

3737
addTypeTemplate({
3838
filename: 'types/auth-schema.d.ts',
@@ -51,7 +51,7 @@ declare module '#auth/schema' {
5151
} | undefined
5252
}
5353
`,
54-
}, { nitro: true, node: true })
54+
}, { nitro: true })
5555

5656
addTypeTemplate({
5757
filename: 'types/nuxt-better-auth-infer.d.ts',
@@ -315,6 +315,15 @@ declare module 'nitropack/types' {
315315
}
316316
interface InternalApi extends _GeneratedAuthInternalApi {}
317317
}
318+
declare module 'nitro/types' {
319+
interface NitroRouteRules {
320+
auth?: import('${runtimeTypesPath}').AuthMeta
321+
}
322+
interface NitroRouteConfig {
323+
auth?: import('${runtimeTypesPath}').AuthMeta
324+
}
325+
interface InternalApi extends _GeneratedAuthInternalApi {}
326+
}
318327
export {}
319328
`,
320329
}, { nuxt: true, nitro: true, node: true })

src/runtime/server/utils/auth.ts

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import type { BetterAuthOptions } from 'better-auth'
22
import type { H3Event } from 'h3'
3-
// @ts-expect-error Nuxt generates this virtual module in app builds.
43
import { createDatabase, db } from '#auth/database'
5-
// @ts-expect-error Nuxt generates this virtual module in app builds.
64
import { createSecondaryStorage } from '#auth/secondary-storage'
75
import createServerAuth from '#auth/server'
86
import { betterAuth } from 'better-auth'
@@ -12,12 +10,42 @@ import { withoutProtocol } from 'ufo'
1210
import { resolveCustomSecondaryStorageRequirement } from './custom-secondary-storage'
1311

1412
type AuthOptions = ReturnType<typeof createServerAuth>
15-
type AuthInstance = ReturnType<typeof betterAuth<AuthOptions>>
13+
type UserAuthConfig = AuthOptions & {
14+
trustedOrigins?: BetterAuthOptions['trustedOrigins']
15+
secondaryStorage?: BetterAuthOptions['secondaryStorage']
16+
}
17+
type ResolvedAuthOptions = UserAuthConfig & {
18+
secret: string
19+
baseURL: string
20+
trustedOrigins?: BetterAuthOptions['trustedOrigins']
21+
database?: BetterAuthOptions['database']
22+
}
23+
type AuthInstance = ReturnType<typeof betterAuth<ResolvedAuthOptions>>
1624

1725
const _authCache = new Map<string, AuthInstance>()
26+
const requestAuthKey = Symbol.for('nuxt-better-auth.requestAuth')
1827
let _baseURLInferenceLogged = false
1928
let _customSecondaryStorageMisconfigWarned = false
2029

30+
interface RequestAuthContext {
31+
[requestAuthKey]?: AuthInstance
32+
}
33+
34+
const fallbackRequestAuthContext = new WeakMap<object, RequestAuthContext>()
35+
36+
function getRequestAuthContext(event: H3Event): RequestAuthContext {
37+
const eventWithContext = event as H3Event & { context?: unknown }
38+
if (eventWithContext.context && typeof eventWithContext.context === 'object')
39+
return eventWithContext.context as RequestAuthContext
40+
41+
let context = fallbackRequestAuthContext.get(event as object)
42+
if (!context) {
43+
context = {}
44+
fallbackRequestAuthContext.set(event as object, context)
45+
}
46+
return context
47+
}
48+
2149
function normalizeLoopbackOrigin(origin: string): string {
2250
if (!import.meta.dev)
2351
return origin
@@ -252,15 +280,13 @@ export function serverAuth(event?: H3Event): AuthInstance {
252280
const requestOrigin = resolveEventOrigin(event)
253281
const hasExplicitSiteUrl = runtimeConfig.public.siteUrl && typeof runtimeConfig.public.siteUrl === 'string'
254282
const cacheKey = hasExplicitSiteUrl ? '__explicit__' : siteUrl
283+
const requestContext = event ? getRequestAuthContext(event) : undefined
255284

256-
const cached = _authCache.get(cacheKey)
257-
if (cached)
258-
return cached
285+
if (requestContext?.[requestAuthKey])
286+
return requestContext[requestAuthKey]
259287

260-
const database = createDatabase()
261-
const userConfig = createServerAuth({ runtimeConfig, db, requestOrigin }) as BetterAuthOptions & {
262-
secondaryStorage?: BetterAuthOptions['secondaryStorage']
263-
}
288+
const database = createDatabase(event)
289+
const userConfig = createServerAuth({ runtimeConfig, db, requestOrigin }) as UserAuthConfig
264290
const trustedOrigins = withDevTrustedOrigins(userConfig.trustedOrigins, Boolean(hasExplicitSiteUrl))
265291

266292
const hubSecondaryStorage = (runtimeConfig.auth as { hubSecondaryStorage?: boolean | 'custom' })?.hubSecondaryStorage
@@ -272,15 +298,30 @@ export function serverAuth(event?: H3Event): AuthInstance {
272298
console.warn(customSecondaryStorage.message)
273299
}
274300

275-
const auth = betterAuth({
301+
if (!database) {
302+
const cached = _authCache.get(cacheKey)
303+
if (cached) {
304+
if (requestContext)
305+
requestContext[requestAuthKey] = cached
306+
return cached
307+
}
308+
}
309+
310+
const authOptions: ResolvedAuthOptions = {
276311
...userConfig,
277-
...(database && { database }),
278-
...(hubSecondaryStorage === true && { secondaryStorage: createSecondaryStorage() }),
312+
...(database ? { database } : {}),
313+
...(hubSecondaryStorage === true ? { secondaryStorage: createSecondaryStorage() } : {}),
279314
secret: runtimeConfig.betterAuthSecret,
280315
baseURL: siteUrl,
281316
trustedOrigins,
282-
})
317+
}
318+
const auth = betterAuth(authOptions)
319+
320+
if (requestContext)
321+
requestContext[requestAuthKey] = auth
322+
323+
if (!database)
324+
_authCache.set(cacheKey, auth)
283325

284-
_authCache.set(cacheKey, auth)
285326
return auth
286327
}

src/runtime/server/virtual-modules.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ declare module '#auth/database' {
33
export function createDatabase(...args: any[]): any
44
}
55

6+
declare module '#imports' {
7+
export function getRouteRules(event: any): any
8+
export function useRuntimeConfig(): any
9+
}
10+
611
declare module '#auth/secondary-storage' {
712
export function createSecondaryStorage(...args: any[]): any
813
}

src/schema-generator.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,14 @@ declare global {
6464
var __nuxtBetterAuthDefineServerAuth: RuntimeDefineServerAuthFn | undefined
6565
}
6666

67-
export async function loadUserAuthConfig(configPath: string, throwOnError = false): Promise<Partial<BetterAuthOptions>> {
67+
export async function loadUserAuthConfig(
68+
configPath: string,
69+
throwOnError = false,
70+
alias?: Record<string, string>,
71+
): Promise<Partial<BetterAuthOptions>> {
6872
const { createJiti } = await import('jiti')
6973
const { defineServerAuth: runtimeDefineServerAuth } = await import('./runtime/config')
70-
const jiti = createJiti(import.meta.url, { interopDefault: true, moduleCache: false })
74+
const jiti = createJiti(import.meta.url, { interopDefault: true, moduleCache: false, alias })
7175
const schemaGlobals = globalThis as typeof globalThis & SchemaGeneratorGlobals
7276

7377
if (!schemaGlobals.__nuxtBetterAuthDefineServerAuth) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<NuxtPage />
3+
</template>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { defineClientAuth } from '@onmax/nuxt-better-auth/config'
2+
3+
export default defineClientAuth({})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default defineNuxtConfig({
2+
modules: ['@nuxthub/core', '@onmax/nuxt-better-auth'],
3+
4+
hub: { db: 'sqlite' },
5+
6+
runtimeConfig: {
7+
betterAuthSecret: 'test-secret-for-testing-only-32chars!',
8+
},
9+
})
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
2+
3+
export default defineServerAuth(() => ({
4+
emailAndPassword: {
5+
enabled: true,
6+
},
7+
databaseHooks: {
8+
session: {
9+
create: {
10+
async after() {
11+
await sessionHookAfter()
12+
},
13+
},
14+
},
15+
},
16+
}))

0 commit comments

Comments
 (0)