@onmax/nuxt-better-auth 0.0.1 → 0.0.2-alpha.10
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/README.md +17 -170
- package/dist/module.d.mts +20 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +432 -14
- package/dist/runtime/app/components/BetterAuthState.d.vue.ts +20 -0
- package/dist/runtime/app/components/BetterAuthState.vue +8 -0
- package/dist/runtime/app/components/BetterAuthState.vue.d.ts +20 -0
- package/dist/runtime/app/composables/useUserSession.d.ts +22 -0
- package/dist/runtime/app/composables/useUserSession.js +159 -0
- package/dist/runtime/app/middleware/auth.global.d.ts +13 -0
- package/dist/runtime/app/middleware/auth.global.js +37 -0
- package/dist/runtime/app/pages/__better-auth-devtools.d.vue.ts +3 -0
- package/dist/runtime/app/pages/__better-auth-devtools.vue +426 -0
- package/dist/runtime/app/pages/__better-auth-devtools.vue.d.ts +3 -0
- package/dist/runtime/app/plugins/session.client.d.ts +2 -0
- package/dist/runtime/app/plugins/session.client.js +16 -0
- package/dist/runtime/app/plugins/session.server.d.ts +2 -0
- package/dist/runtime/app/plugins/session.server.js +24 -0
- package/dist/runtime/config.d.ts +44 -0
- package/dist/runtime/config.js +6 -0
- package/dist/runtime/server/api/_better-auth/_schema.d.ts +8 -0
- package/dist/runtime/server/api/_better-auth/_schema.js +11 -0
- package/dist/runtime/server/api/_better-auth/accounts.get.d.ts +14 -0
- package/dist/runtime/server/api/_better-auth/accounts.get.js +28 -0
- package/dist/runtime/server/api/_better-auth/config.get.d.ts +35 -0
- package/dist/runtime/server/api/_better-auth/config.get.js +47 -0
- package/dist/runtime/server/api/_better-auth/sessions.delete.d.ts +4 -0
- package/dist/runtime/server/api/_better-auth/sessions.delete.js +22 -0
- package/dist/runtime/server/api/_better-auth/sessions.get.d.ts +14 -0
- package/dist/runtime/server/api/_better-auth/sessions.get.js +43 -0
- package/dist/runtime/server/api/_better-auth/users.get.d.ts +14 -0
- package/dist/runtime/server/api/_better-auth/users.get.js +34 -0
- package/dist/runtime/server/api/auth/[...all].d.ts +2 -0
- package/dist/runtime/server/api/auth/[...all].js +6 -0
- package/dist/runtime/server/middleware/route-access.d.ts +2 -0
- package/dist/runtime/server/middleware/route-access.js +28 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/utils/auth.d.ts +11 -0
- package/dist/runtime/server/utils/auth.js +32 -0
- package/dist/runtime/server/utils/session.d.ts +9 -0
- package/dist/runtime/server/utils/session.js +23 -0
- package/dist/runtime/types/augment.d.ts +42 -0
- package/dist/runtime/types/augment.js +0 -0
- package/dist/runtime/types.d.ts +23 -0
- package/dist/runtime/types.js +0 -0
- package/dist/runtime/utils/match-user.d.ts +2 -0
- package/dist/runtime/utils/match-user.js +13 -0
- package/dist/types.d.mts +8 -10
- package/package.json +40 -11
- package/dist/module.d.cts +0 -2
package/README.md
CHANGED
|
@@ -1,180 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/onmax/nuxt-better-auth/main/.github/og.png" alt="Nuxt Better Auth" width="100%">
|
|
3
|
+
<br>
|
|
4
|
+
<sub>Designed by <a href="https://github.com/HugoRCD">HugoRCD</a></sub>
|
|
5
|
+
</p>
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
-
[![License][license-src]][license-href]
|
|
6
|
-
[![Nuxt][nuxt-src]][nuxt-href]
|
|
7
|
+
<h1 align="center">@onmax/nuxt-better-auth</h1>
|
|
7
8
|
|
|
8
|
-
Nuxt module for
|
|
9
|
+
<p align="center">Nuxt module for <a href="https://better-auth.com">Better Auth</a></p>
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://npmjs.com/package/@onmax/nuxt-better-auth"><img src="https://img.shields.io/npm/v/@onmax/nuxt-better-auth/latest.svg?style=flat&colorA=020420&colorB=00DC82" alt="npm version"></a>
|
|
13
|
+
<a href="https://npm.chart.dev/@onmax/nuxt-better-auth"><img src="https://img.shields.io/npm/dm/@onmax/nuxt-better-auth.svg?style=flat&colorA=020420&colorB=00DC82" alt="npm downloads"></a>
|
|
14
|
+
<a href="https://npmjs.com/package/@onmax/nuxt-better-auth"><img src="https://img.shields.io/npm/l/@onmax/nuxt-better-auth.svg?style=flat&colorA=020420&colorB=00DC82" alt="License"></a>
|
|
15
|
+
<a href="https://nuxt.com"><img src="https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js" alt="Nuxt"></a>
|
|
16
|
+
</p>
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- **Session Management** - SSR-safe session handling with client hydration
|
|
15
|
-
- **Role-Based Access** - Support for `admin`, `user`, and custom roles
|
|
16
|
-
- **Auto-Imports** - `useUserSession`, `requireUserSession`, `getUserSession`
|
|
17
|
-
- **BetterAuthState Component** - Ready-to-use Vue component with slots
|
|
18
|
+
> [!WARNING]
|
|
19
|
+
> This library is a work in progress and not ready for production use.
|
|
18
20
|
|
|
19
|
-
##
|
|
21
|
+
## Documentation
|
|
20
22
|
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
## Quick Start
|
|
24
|
-
|
|
25
|
-
### 1. Install
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
pnpm add @onmax/nuxt-better-auth better-auth drizzle-orm @nuxthub/core
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### 2. Configure Nuxt
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
export default defineNuxtConfig({
|
|
35
|
-
modules: ['@nuxthub/core', '@onmax/nuxt-better-auth'],
|
|
36
|
-
|
|
37
|
-
hub: { database: true },
|
|
38
|
-
|
|
39
|
-
runtimeConfig: {
|
|
40
|
-
betterAuthSecret: '', // BETTER_AUTH_SECRET env var
|
|
41
|
-
public: { siteUrl: 'http://localhost:3000' },
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
routeRules: {
|
|
45
|
-
'/app/**': { auth: 'user' },
|
|
46
|
-
'/admin/**': { auth: { role: 'admin' } },
|
|
47
|
-
'/login': { auth: 'guest' },
|
|
48
|
-
},
|
|
49
|
-
})
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### 3. Create Server Config
|
|
53
|
-
|
|
54
|
-
Create `server/auth.config.ts`:
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
import { admin } from 'better-auth/plugins'
|
|
58
|
-
import { defineServerAuth } from '@onmax/nuxt-better-auth'
|
|
59
|
-
|
|
60
|
-
export default defineServerAuth(({ db }) => ({
|
|
61
|
-
appName: 'My App',
|
|
62
|
-
plugins: [admin()],
|
|
63
|
-
emailAndPassword: { enabled: true },
|
|
64
|
-
}))
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
> Schema is auto-generated from your plugins! No manual Drizzle schema needed.
|
|
68
|
-
|
|
69
|
-
### 4. Create Client Config
|
|
70
|
-
|
|
71
|
-
Create `app/auth.client.ts`:
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
import { adminClient } from 'better-auth/client/plugins'
|
|
75
|
-
import { createAuthClient } from 'better-auth/vue'
|
|
76
|
-
|
|
77
|
-
export function createAppAuthClient(baseURL: string) {
|
|
78
|
-
return createAuthClient({
|
|
79
|
-
baseURL,
|
|
80
|
-
plugins: [adminClient()],
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export type AppAuthClient = ReturnType<typeof createAppAuthClient>
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### 5. Add Type Extensions
|
|
88
|
-
|
|
89
|
-
Create `shared/types/auth.d.ts`:
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
import '#nuxt-better-auth'
|
|
93
|
-
|
|
94
|
-
declare module '#nuxt-better-auth' {
|
|
95
|
-
interface AuthUser {
|
|
96
|
-
role?: string | null
|
|
97
|
-
banned?: boolean | null
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## Route Rules
|
|
103
|
-
|
|
104
|
-
| Option | Type | Description |
|
|
105
|
-
|--------|------|-------------|
|
|
106
|
-
| `auth` | `false \| 'guest' \| 'user' \| { role: string }` | Auth requirement |
|
|
107
|
-
|
|
108
|
-
Examples:
|
|
109
|
-
- `auth: false` - Public (default)
|
|
110
|
-
- `auth: 'guest'` - Only unauthenticated users
|
|
111
|
-
- `auth: 'user'` - Any authenticated user
|
|
112
|
-
- `auth: { role: 'admin' }` - Requires specific role
|
|
113
|
-
|
|
114
|
-
## Composables
|
|
115
|
-
|
|
116
|
-
### `useUserSession()`
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
const { user, session, loggedIn, ready, client } = useUserSession()
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### `<BetterAuthState>`
|
|
123
|
-
|
|
124
|
-
```vue
|
|
125
|
-
<BetterAuthState>
|
|
126
|
-
<template #default="{ loggedIn, user, signOut }">
|
|
127
|
-
<p v-if="loggedIn">Welcome, {{ user?.name }}</p>
|
|
128
|
-
<p v-else>Not logged in</p>
|
|
129
|
-
</template>
|
|
130
|
-
<template #placeholder>
|
|
131
|
-
<p>Loading...</p>
|
|
132
|
-
</template>
|
|
133
|
-
</BetterAuthState>
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Server Utils
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
// Require auth
|
|
140
|
-
const { user, session } = await requireUserSession(event)
|
|
141
|
-
|
|
142
|
-
// Require admin
|
|
143
|
-
const { user } = await requireUserSession(event, { role: 'admin' })
|
|
144
|
-
|
|
145
|
-
// Optional session
|
|
146
|
-
const session = await getUserSession(event)
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Module Aliases
|
|
150
|
-
|
|
151
|
-
| Alias | Points To |
|
|
152
|
-
|-------|-----------|
|
|
153
|
-
| `#auth/server` | `server/auth.config.ts` |
|
|
154
|
-
| `#auth/client` | `app/auth.client.ts` |
|
|
155
|
-
| `#nuxt-better-auth` | Module type augmentation |
|
|
156
|
-
|
|
157
|
-
## Development
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
pnpm install
|
|
161
|
-
pnpm dev:prepare
|
|
162
|
-
pnpm dev
|
|
163
|
-
```
|
|
23
|
+
**[nuxt-better-auth.onmax.me](https://nuxt-better-auth.onmax.me/)**
|
|
164
24
|
|
|
165
25
|
## License
|
|
166
26
|
|
|
167
27
|
MIT
|
|
168
|
-
|
|
169
|
-
<!-- Badges -->
|
|
170
|
-
[npm-version-src]: https://img.shields.io/npm/v/@onmax/nuxt-better-auth/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
171
|
-
[npm-version-href]: https://npmjs.com/package/@onmax/nuxt-better-auth
|
|
172
|
-
|
|
173
|
-
[npm-downloads-src]: https://img.shields.io/npm/dm/@onmax/nuxt-better-auth.svg?style=flat&colorA=020420&colorB=00DC82
|
|
174
|
-
[npm-downloads-href]: https://npm.chart.dev/@onmax/nuxt-better-auth
|
|
175
|
-
|
|
176
|
-
[license-src]: https://img.shields.io/npm/l/@onmax/nuxt-better-auth.svg?style=flat&colorA=020420&colorB=00DC82
|
|
177
|
-
[license-href]: https://npmjs.com/package/@onmax/nuxt-better-auth
|
|
178
|
-
|
|
179
|
-
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
|
|
180
|
-
[nuxt-href]: https://nuxt.com
|
package/dist/module.d.mts
CHANGED
|
@@ -1,2 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import { BetterAuthModuleOptions } from '../dist/runtime/config.js';
|
|
3
|
+
export { BetterAuthModuleOptions, defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
|
|
4
|
+
import { BetterAuthOptions } from 'better-auth';
|
|
5
|
+
export { Auth, AuthMeta, AuthMode, AuthRouteRules, AuthSession, AuthUser, InferSession, InferUser, RequireSessionOptions, ServerAuthContext, UserMatch } from '../dist/runtime/types.js';
|
|
6
|
+
|
|
7
|
+
declare module '@nuxt/schema' {
|
|
8
|
+
interface NuxtHooks {
|
|
9
|
+
/**
|
|
10
|
+
* Extend better-auth config with additional plugins or options.
|
|
11
|
+
* Called after user's auth.config.ts is loaded.
|
|
12
|
+
* @param config - Partial config to merge into the auth options
|
|
13
|
+
*/
|
|
14
|
+
'better-auth:config:extend': (config: Partial<BetterAuthOptions>) => void | Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare const _default: _nuxt_schema.NuxtModule<BetterAuthModuleOptions, BetterAuthModuleOptions, false>;
|
|
19
|
+
|
|
20
|
+
export { _default as default };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,20 +1,438 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { defineNuxtModule, createResolver, hasNuxtModule, addTemplate, addTypeTemplate, updateTemplates, addServerImportsDir, addServerScanDir, addServerHandler, addImportsDir, addPlugin, addComponentsDir, extendPages } from '@nuxt/kit';
|
|
4
|
+
import { consola as consola$1 } from 'consola';
|
|
5
|
+
import { defu } from 'defu';
|
|
6
|
+
import { join } from 'pathe';
|
|
7
|
+
import { toRouteMatcher, createRouter } from 'radix3';
|
|
8
|
+
import pluralize from 'pluralize';
|
|
9
|
+
export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
|
|
2
10
|
|
|
3
|
-
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
function setupDevTools(nuxt) {
|
|
12
|
+
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
13
|
+
tabs.push({
|
|
14
|
+
category: "server",
|
|
15
|
+
name: "better-auth",
|
|
16
|
+
title: "Auth",
|
|
17
|
+
icon: "simple-icons:betterauth",
|
|
18
|
+
view: {
|
|
19
|
+
type: "iframe",
|
|
20
|
+
src: "/__better-auth-devtools"
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function toSnakeCase(str) {
|
|
27
|
+
return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
function generateDrizzleSchema(tables, dialect, options) {
|
|
30
|
+
const typedTables = tables;
|
|
31
|
+
const imports = getImports(dialect, options);
|
|
32
|
+
const tableDefinitions = Object.entries(typedTables).map(([tableName, table]) => generateTable(tableName, table, dialect, typedTables, options)).join("\n\n");
|
|
33
|
+
return `${imports}
|
|
34
|
+
|
|
35
|
+
${tableDefinitions}
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
function getImports(dialect, options) {
|
|
39
|
+
switch (dialect) {
|
|
40
|
+
case "sqlite":
|
|
41
|
+
return `import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'`;
|
|
42
|
+
case "postgresql":
|
|
43
|
+
return options?.useUuid ? `import { boolean, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'` : `import { boolean, integer, pgTable, text, timestamp } from 'drizzle-orm/pg-core'`;
|
|
44
|
+
case "mysql":
|
|
45
|
+
return `import { boolean, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core'`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function generateTable(tableName, table, dialect, allTables, options) {
|
|
49
|
+
const tableFunc = dialect === "sqlite" ? "sqliteTable" : dialect === "postgresql" ? "pgTable" : "mysqlTable";
|
|
50
|
+
const hasCustomModelName = table.modelName && table.modelName !== tableName;
|
|
51
|
+
let dbTableName = hasCustomModelName ? table.modelName : tableName;
|
|
52
|
+
if (options?.casing === "snake_case" && !hasCustomModelName)
|
|
53
|
+
dbTableName = toSnakeCase(dbTableName);
|
|
54
|
+
if (options?.usePlural && !hasCustomModelName)
|
|
55
|
+
dbTableName = pluralize(dbTableName);
|
|
56
|
+
const fields = Object.entries(table.fields).map(([fieldName, field]) => generateField(fieldName, field, dialect, allTables, options)).join(",\n ");
|
|
57
|
+
const idField = generateIdField(dialect, options);
|
|
58
|
+
return `export const ${tableName} = ${tableFunc}('${dbTableName}', {
|
|
59
|
+
${idField},
|
|
60
|
+
${fields}
|
|
61
|
+
})`;
|
|
62
|
+
}
|
|
63
|
+
function generateIdField(dialect, options) {
|
|
64
|
+
switch (dialect) {
|
|
65
|
+
case "sqlite":
|
|
66
|
+
if (options?.useUuid)
|
|
67
|
+
consola$1.warn("[@onmax/nuxt-better-auth] useUuid ignored for SQLite (no native uuid type). Using text.");
|
|
68
|
+
return `id: text('id').primaryKey()`;
|
|
69
|
+
case "postgresql":
|
|
70
|
+
return options?.useUuid ? `id: uuid('id').defaultRandom().primaryKey()` : `id: text('id').primaryKey()`;
|
|
71
|
+
case "mysql":
|
|
72
|
+
return `id: varchar('id', { length: 36 }).primaryKey()`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function generateField(fieldName, field, dialect, allTables, options) {
|
|
76
|
+
const dbFieldName = options?.casing === "snake_case" ? toSnakeCase(fieldName) : fieldName;
|
|
77
|
+
const isFkToId = options?.useUuid && field.references?.field === "id";
|
|
78
|
+
let fieldDef;
|
|
79
|
+
if (isFkToId && dialect === "postgresql")
|
|
80
|
+
fieldDef = `uuid('${dbFieldName}')`;
|
|
81
|
+
else if (isFkToId && dialect === "mysql")
|
|
82
|
+
fieldDef = `varchar('${dbFieldName}', { length: 36 })`;
|
|
83
|
+
else
|
|
84
|
+
fieldDef = getFieldType(field.type, dialect, dbFieldName);
|
|
85
|
+
if (field.required && field.defaultValue === void 0)
|
|
86
|
+
fieldDef += ".notNull()";
|
|
87
|
+
if (field.unique)
|
|
88
|
+
fieldDef += ".unique()";
|
|
89
|
+
if (field.defaultValue !== void 0) {
|
|
90
|
+
if (typeof field.defaultValue === "boolean")
|
|
91
|
+
fieldDef += `.default(${field.defaultValue})`;
|
|
92
|
+
else if (typeof field.defaultValue === "string")
|
|
93
|
+
fieldDef += `.default('${field.defaultValue}')`;
|
|
94
|
+
else if (typeof field.defaultValue === "function")
|
|
95
|
+
fieldDef += `.$defaultFn(${field.defaultValue})`;
|
|
96
|
+
else
|
|
97
|
+
fieldDef += `.default(${field.defaultValue})`;
|
|
98
|
+
if (field.required)
|
|
99
|
+
fieldDef += ".notNull()";
|
|
100
|
+
}
|
|
101
|
+
if (typeof field.onUpdate === "function" && field.type === "date")
|
|
102
|
+
fieldDef += `.$onUpdate(${field.onUpdate})`;
|
|
103
|
+
if (field.references) {
|
|
104
|
+
const refTable = field.references.model;
|
|
105
|
+
if (allTables[refTable])
|
|
106
|
+
fieldDef += `.references(() => ${refTable}.${field.references.field})`;
|
|
107
|
+
}
|
|
108
|
+
return `${fieldName}: ${fieldDef}`;
|
|
109
|
+
}
|
|
110
|
+
function getFieldType(type, dialect, fieldName) {
|
|
111
|
+
const normalizedType = Array.isArray(type) ? "string" : type;
|
|
112
|
+
switch (dialect) {
|
|
113
|
+
case "sqlite":
|
|
114
|
+
return getSqliteType(normalizedType, fieldName);
|
|
115
|
+
case "postgresql":
|
|
116
|
+
return getPostgresType(normalizedType, fieldName);
|
|
117
|
+
case "mysql":
|
|
118
|
+
return getMysqlType(normalizedType, fieldName);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function getSqliteType(type, fieldName) {
|
|
122
|
+
switch (type) {
|
|
123
|
+
case "string":
|
|
124
|
+
return `text('${fieldName}')`;
|
|
125
|
+
case "boolean":
|
|
126
|
+
return `integer('${fieldName}', { mode: 'boolean' })`;
|
|
127
|
+
case "date":
|
|
128
|
+
return `integer('${fieldName}', { mode: 'timestamp' })`;
|
|
129
|
+
case "number":
|
|
130
|
+
return `integer('${fieldName}')`;
|
|
131
|
+
default:
|
|
132
|
+
return `text('${fieldName}')`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function getPostgresType(type, fieldName) {
|
|
136
|
+
switch (type) {
|
|
137
|
+
case "string":
|
|
138
|
+
return `text('${fieldName}')`;
|
|
139
|
+
case "boolean":
|
|
140
|
+
return `boolean('${fieldName}')`;
|
|
141
|
+
case "date":
|
|
142
|
+
return `timestamp('${fieldName}')`;
|
|
143
|
+
case "number":
|
|
144
|
+
return `integer('${fieldName}')`;
|
|
145
|
+
default:
|
|
146
|
+
return `text('${fieldName}')`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function getMysqlType(type, fieldName) {
|
|
150
|
+
switch (type) {
|
|
151
|
+
case "string":
|
|
152
|
+
return `text('${fieldName}')`;
|
|
153
|
+
case "boolean":
|
|
154
|
+
return `boolean('${fieldName}')`;
|
|
155
|
+
case "date":
|
|
156
|
+
return `timestamp('${fieldName}')`;
|
|
157
|
+
case "number":
|
|
158
|
+
return `int('${fieldName}')`;
|
|
159
|
+
default:
|
|
160
|
+
return `text('${fieldName}')`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function loadUserAuthConfig(configPath, throwOnError = false) {
|
|
164
|
+
const { createJiti } = await import('jiti');
|
|
165
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true });
|
|
166
|
+
try {
|
|
167
|
+
const mod = await jiti.import(configPath);
|
|
168
|
+
const configFn = typeof mod === "object" && mod !== null && "default" in mod ? mod.default : mod;
|
|
169
|
+
if (typeof configFn === "function") {
|
|
170
|
+
return configFn({ runtimeConfig: {}, db: null });
|
|
171
|
+
}
|
|
172
|
+
if (throwOnError) {
|
|
173
|
+
throw new Error("auth.config.ts must export default defineServerAuth(...)");
|
|
174
|
+
}
|
|
175
|
+
return {};
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (throwOnError) {
|
|
178
|
+
throw new Error(`Failed to load auth config: ${error instanceof Error ? error.message : error}`);
|
|
179
|
+
}
|
|
180
|
+
consola$1.error("[@onmax/nuxt-better-auth] Failed to load auth config for schema generation. Schema may be incomplete:", error);
|
|
181
|
+
return {};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getHubDialect(hub) {
|
|
186
|
+
if (!hub?.db)
|
|
187
|
+
return void 0;
|
|
188
|
+
if (typeof hub.db === "string")
|
|
189
|
+
return hub.db;
|
|
190
|
+
if (typeof hub.db === "object" && hub.db !== null)
|
|
191
|
+
return hub.db.dialect;
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
function getHubCasing(hub) {
|
|
195
|
+
if (!hub?.db || typeof hub.db !== "object" || hub.db === null)
|
|
196
|
+
return void 0;
|
|
197
|
+
return hub.db.casing;
|
|
198
|
+
}
|
|
199
|
+
const consola = consola$1.withTag("nuxt-better-auth");
|
|
200
|
+
const module$1 = defineNuxtModule({
|
|
201
|
+
meta: { name: "@onmax/nuxt-better-auth", configKey: "auth", compatibility: { nuxt: ">=3.0.0" } },
|
|
202
|
+
defaults: {
|
|
203
|
+
serverConfig: "server/auth.config",
|
|
204
|
+
clientConfig: "app/auth.config",
|
|
205
|
+
redirects: { login: "/login", guest: "/" },
|
|
206
|
+
secondaryStorage: false
|
|
7
207
|
},
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
208
|
+
async setup(options, nuxt) {
|
|
209
|
+
const resolver = createResolver(import.meta.url);
|
|
210
|
+
const serverConfigFile = options.serverConfig;
|
|
211
|
+
const clientConfigFile = options.clientConfig;
|
|
212
|
+
const serverConfigPath = resolver.resolve(nuxt.options.rootDir, serverConfigFile);
|
|
213
|
+
const clientConfigPath = resolver.resolve(nuxt.options.rootDir, clientConfigFile);
|
|
214
|
+
const serverConfigExists = existsSync(`${serverConfigPath}.ts`) || existsSync(`${serverConfigPath}.js`);
|
|
215
|
+
const clientConfigExists = existsSync(`${clientConfigPath}.ts`) || existsSync(`${clientConfigPath}.js`);
|
|
216
|
+
if (!serverConfigExists)
|
|
217
|
+
throw new Error(`[nuxt-better-auth] Missing ${serverConfigFile}.ts - create with defineServerAuth()`);
|
|
218
|
+
if (!clientConfigExists)
|
|
219
|
+
throw new Error(`[nuxt-better-auth] Missing ${clientConfigFile}.ts - export createAppAuthClient()`);
|
|
220
|
+
const hasNuxtHub = hasNuxtModule("@nuxthub/core", nuxt);
|
|
221
|
+
const hub = hasNuxtHub ? nuxt.options.hub : void 0;
|
|
222
|
+
const hasHubDb = hasNuxtHub && !!hub?.db;
|
|
223
|
+
let secondaryStorageEnabled = options.secondaryStorage ?? false;
|
|
224
|
+
if (secondaryStorageEnabled && (!hasNuxtHub || !hub?.kv)) {
|
|
225
|
+
consola.warn("secondaryStorage requires @nuxthub/core with hub.kv: true. Disabling.");
|
|
226
|
+
secondaryStorageEnabled = false;
|
|
227
|
+
}
|
|
228
|
+
nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
|
|
229
|
+
nuxt.options.runtimeConfig.public.auth = defu(nuxt.options.runtimeConfig.public.auth, {
|
|
230
|
+
redirects: { login: options.redirects?.login ?? "/login", guest: options.redirects?.guest ?? "/" },
|
|
231
|
+
useDatabase: hasHubDb
|
|
232
|
+
});
|
|
233
|
+
const betterAuthSecret = process.env.BETTER_AUTH_SECRET || process.env.NUXT_BETTER_AUTH_SECRET || nuxt.options.runtimeConfig.betterAuthSecret || "";
|
|
234
|
+
if (!nuxt.options.dev && !betterAuthSecret) {
|
|
235
|
+
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET is required in production. Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET environment variable.");
|
|
11
236
|
}
|
|
237
|
+
if (betterAuthSecret && betterAuthSecret.length < 32) {
|
|
238
|
+
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET must be at least 32 characters for security");
|
|
239
|
+
}
|
|
240
|
+
nuxt.options.runtimeConfig.betterAuthSecret = betterAuthSecret;
|
|
241
|
+
nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
|
|
242
|
+
secondaryStorage: secondaryStorageEnabled
|
|
243
|
+
});
|
|
244
|
+
nuxt.options.alias["#nuxt-better-auth"] = resolver.resolve("./runtime/types/augment");
|
|
245
|
+
nuxt.options.alias["#auth/server"] = serverConfigPath;
|
|
246
|
+
nuxt.options.alias["#auth/client"] = clientConfigPath;
|
|
247
|
+
const secondaryStorageCode = secondaryStorageEnabled ? `import { kv } from '../hub/kv.mjs'
|
|
248
|
+
export function createSecondaryStorage() {
|
|
249
|
+
return {
|
|
250
|
+
get: async (key) => kv.get(\`_auth:\${key}\`),
|
|
251
|
+
set: async (key, value, ttl) => kv.set(\`_auth:\${key}\`, value, { ttl }),
|
|
252
|
+
delete: async (key) => kv.del(\`_auth:\${key}\`),
|
|
253
|
+
}
|
|
254
|
+
}` : `export function createSecondaryStorage() { return undefined }`;
|
|
255
|
+
const secondaryStorageTemplate = addTemplate({ filename: "better-auth/secondary-storage.mjs", getContents: () => secondaryStorageCode, write: true });
|
|
256
|
+
nuxt.options.alias["#auth/secondary-storage"] = secondaryStorageTemplate.dst;
|
|
257
|
+
const hubDialect = getHubDialect(hub) ?? "sqlite";
|
|
258
|
+
const databaseCode = hasHubDb ? `import { db, schema } from '../hub/db.mjs'
|
|
259
|
+
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
260
|
+
const rawDialect = '${hubDialect}'
|
|
261
|
+
const dialect = rawDialect === 'postgresql' ? 'pg' : rawDialect
|
|
262
|
+
export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema }) }
|
|
263
|
+
export { db }` : `export function createDatabase() { return undefined }
|
|
264
|
+
export const db = undefined`;
|
|
265
|
+
const databaseTemplate = addTemplate({ filename: "better-auth/database.mjs", getContents: () => databaseCode, write: true });
|
|
266
|
+
nuxt.options.alias["#auth/database"] = databaseTemplate.dst;
|
|
267
|
+
addTypeTemplate({
|
|
268
|
+
filename: "types/auth-secondary-storage.d.ts",
|
|
269
|
+
getContents: () => `
|
|
270
|
+
declare module '#auth/secondary-storage' {
|
|
271
|
+
interface SecondaryStorage {
|
|
272
|
+
get: (key: string) => Promise<string | null>
|
|
273
|
+
set: (key: string, value: unknown, ttl?: number) => Promise<void>
|
|
274
|
+
delete: (key: string) => Promise<void>
|
|
12
275
|
}
|
|
13
|
-
|
|
276
|
+
export function createSecondaryStorage(): SecondaryStorage | undefined
|
|
277
|
+
}
|
|
278
|
+
`
|
|
279
|
+
});
|
|
280
|
+
addTypeTemplate({
|
|
281
|
+
filename: "types/auth-database.d.ts",
|
|
282
|
+
getContents: () => `
|
|
283
|
+
declare module '#auth/database' {
|
|
284
|
+
import type { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
285
|
+
export function createDatabase(): ReturnType<typeof drizzleAdapter> | undefined
|
|
286
|
+
export const db: unknown
|
|
287
|
+
}
|
|
288
|
+
`
|
|
289
|
+
});
|
|
290
|
+
addTypeTemplate({
|
|
291
|
+
filename: "types/nuxt-better-auth.d.ts",
|
|
292
|
+
getContents: () => `
|
|
293
|
+
export * from '${resolver.resolve("./runtime/types/augment")}'
|
|
294
|
+
export type { AuthMeta, AuthMode, AuthRouteRules, UserMatch, RequireSessionOptions, Auth, InferUser, InferSession } from '${resolver.resolve("./runtime/types")}'
|
|
295
|
+
`
|
|
296
|
+
});
|
|
297
|
+
addTypeTemplate({
|
|
298
|
+
filename: "types/nuxt-better-auth-infer.d.ts",
|
|
299
|
+
getContents: () => `
|
|
300
|
+
import type { InferUser, InferSession } from 'better-auth'
|
|
301
|
+
import type { RuntimeConfig } from 'nuxt/schema'
|
|
302
|
+
import type configFn from '${serverConfigPath}'
|
|
14
303
|
|
|
15
|
-
|
|
16
|
-
|
|
304
|
+
type _Config = ReturnType<typeof configFn>
|
|
305
|
+
|
|
306
|
+
declare module '#nuxt-better-auth' {
|
|
307
|
+
interface AuthUser extends InferUser<_Config> {}
|
|
308
|
+
interface AuthSession { session: InferSession<_Config>['session'], user: InferUser<_Config> }
|
|
309
|
+
interface ServerAuthContext {
|
|
310
|
+
runtimeConfig: RuntimeConfig
|
|
311
|
+
${hasHubDb ? `db: typeof import('hub:db')['db']` : ""}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
`
|
|
315
|
+
});
|
|
316
|
+
addTypeTemplate({
|
|
317
|
+
filename: "types/nuxt-better-auth-client.d.ts",
|
|
318
|
+
getContents: () => `
|
|
319
|
+
import type { createAppAuthClient } from '${clientConfigPath}'
|
|
320
|
+
declare module '#nuxt-better-auth' {
|
|
321
|
+
export type AppAuthClient = ReturnType<typeof createAppAuthClient>
|
|
322
|
+
}
|
|
323
|
+
`
|
|
324
|
+
});
|
|
325
|
+
addTypeTemplate({
|
|
326
|
+
filename: "types/nuxt-better-auth-nitro.d.ts",
|
|
327
|
+
getContents: () => `
|
|
328
|
+
declare module 'nitropack/types' {
|
|
329
|
+
interface NitroRouteRules {
|
|
330
|
+
auth?: import('${resolver.resolve("./runtime/types")}').AuthMeta
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
`
|
|
334
|
+
});
|
|
335
|
+
nuxt.hook("builder:watch", async (_event, relativePath) => {
|
|
336
|
+
if (relativePath.includes("auth.config")) {
|
|
337
|
+
await updateTemplates({ filter: (t) => t.filename.includes("nuxt-better-auth") });
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
addServerImportsDir(resolver.resolve("./runtime/server/utils"));
|
|
341
|
+
addServerScanDir(resolver.resolve("./runtime/server/middleware"));
|
|
342
|
+
addServerHandler({ route: "/api/auth/**", handler: resolver.resolve("./runtime/server/api/auth/[...all]") });
|
|
343
|
+
addImportsDir(resolver.resolve("./runtime/app/composables"));
|
|
344
|
+
addImportsDir(resolver.resolve("./runtime/utils"));
|
|
345
|
+
addPlugin({ src: resolver.resolve("./runtime/app/plugins/session.server"), mode: "server" });
|
|
346
|
+
addPlugin({ src: resolver.resolve("./runtime/app/plugins/session.client"), mode: "client" });
|
|
347
|
+
addComponentsDir({ path: resolver.resolve("./runtime/app/components") });
|
|
348
|
+
nuxt.hook("app:resolve", (app) => {
|
|
349
|
+
app.middleware.push({ name: "auth", path: resolver.resolve("./runtime/app/middleware/auth.global"), global: true });
|
|
350
|
+
});
|
|
351
|
+
if (hasHubDb) {
|
|
352
|
+
await setupBetterAuthSchema(nuxt, serverConfigPath, options);
|
|
353
|
+
}
|
|
354
|
+
const isProduction = process.env.NODE_ENV === "production" || !nuxt.options.dev;
|
|
355
|
+
if (!isProduction) {
|
|
356
|
+
setupDevTools(nuxt);
|
|
357
|
+
addServerHandler({ route: "/api/_better-auth/config", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/config.get") });
|
|
358
|
+
if (hasHubDb) {
|
|
359
|
+
addServerHandler({ route: "/api/_better-auth/sessions", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/sessions.get") });
|
|
360
|
+
addServerHandler({ route: "/api/_better-auth/sessions", method: "delete", handler: resolver.resolve("./runtime/server/api/_better-auth/sessions.delete") });
|
|
361
|
+
addServerHandler({ route: "/api/_better-auth/users", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/users.get") });
|
|
362
|
+
addServerHandler({ route: "/api/_better-auth/accounts", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/accounts.get") });
|
|
363
|
+
}
|
|
364
|
+
extendPages((pages) => {
|
|
365
|
+
pages.push({ name: "better-auth-devtools", path: "/__better-auth-devtools", file: resolver.resolve("./runtime/app/pages/__better-auth-devtools.vue") });
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
nuxt.hook("pages:extend", (pages) => {
|
|
369
|
+
const routeRules = nuxt.options.routeRules || {};
|
|
370
|
+
if (!Object.keys(routeRules).length)
|
|
371
|
+
return;
|
|
372
|
+
const matcher = toRouteMatcher(createRouter({ routes: routeRules }));
|
|
373
|
+
const applyMetaFromRules = (page) => {
|
|
374
|
+
const matches = matcher.matchAll(page.path);
|
|
375
|
+
if (!matches.length)
|
|
376
|
+
return;
|
|
377
|
+
const matchedRules = defu({}, ...matches.reverse());
|
|
378
|
+
if (matchedRules.auth !== void 0) {
|
|
379
|
+
page.meta = page.meta || {};
|
|
380
|
+
page.meta.auth = matchedRules.auth;
|
|
381
|
+
}
|
|
382
|
+
page.children?.forEach((child) => applyMetaFromRules(child));
|
|
383
|
+
};
|
|
384
|
+
pages.forEach((page) => applyMetaFromRules(page));
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
async function setupBetterAuthSchema(nuxt, serverConfigPath, options) {
|
|
389
|
+
const hub = nuxt.options.hub;
|
|
390
|
+
const dialect = getHubDialect(hub);
|
|
391
|
+
if (!dialect || !["sqlite", "postgresql", "mysql"].includes(dialect)) {
|
|
392
|
+
consola.warn(`Unsupported database dialect: ${dialect}`);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const isProduction = !nuxt.options.dev;
|
|
396
|
+
try {
|
|
397
|
+
const configFile = `${serverConfigPath}.ts`;
|
|
398
|
+
const userConfig = await loadUserAuthConfig(configFile, isProduction);
|
|
399
|
+
const extendedConfig = {};
|
|
400
|
+
await nuxt.callHook("better-auth:config:extend", extendedConfig);
|
|
401
|
+
const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
|
|
402
|
+
const { getAuthTables } = await import('better-auth/db');
|
|
403
|
+
const tables = getAuthTables({
|
|
404
|
+
plugins,
|
|
405
|
+
secondaryStorage: options.secondaryStorage ? {
|
|
406
|
+
get: async (_key) => null,
|
|
407
|
+
set: async (_key, _value, _ttl) => {
|
|
408
|
+
},
|
|
409
|
+
delete: async (_key) => {
|
|
410
|
+
}
|
|
411
|
+
} : void 0
|
|
412
|
+
});
|
|
413
|
+
const useUuid = userConfig.advanced?.database?.generateId === "uuid";
|
|
414
|
+
const hubCasing = getHubCasing(hub);
|
|
415
|
+
const schemaOptions = { ...options.schema, useUuid, casing: options.schema?.casing ?? hubCasing };
|
|
416
|
+
const schemaCode = generateDrizzleSchema(tables, dialect, schemaOptions);
|
|
417
|
+
const schemaDir = join(nuxt.options.buildDir, "better-auth");
|
|
418
|
+
const schemaPath = join(schemaDir, `schema.${dialect}.ts`);
|
|
419
|
+
await mkdir(schemaDir, { recursive: true });
|
|
420
|
+
await writeFile(schemaPath, schemaCode);
|
|
421
|
+
addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
|
|
422
|
+
consola.info(`Generated ${dialect} schema with ${Object.keys(tables).length} tables`);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (isProduction) {
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
consola.error("Failed to generate schema:", error);
|
|
428
|
+
}
|
|
429
|
+
const nuxtWithHubHooks = nuxt;
|
|
430
|
+
nuxtWithHubHooks.hook("hub:db:schema:extend", ({ paths, dialect: hookDialect }) => {
|
|
431
|
+
const schemaPath = join(nuxt.options.buildDir, "better-auth", `schema.${hookDialect}.ts`);
|
|
432
|
+
if (existsSync(schemaPath)) {
|
|
433
|
+
paths.unshift(schemaPath);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
17
437
|
|
|
18
|
-
export
|
|
19
|
-
export const defineClientAuth = _module.defineClientAuth;
|
|
20
|
-
export const defineServerAuth = _module.defineServerAuth;
|
|
438
|
+
export { module$1 as default };
|