@nuxtjs/better-auth 0.0.0 → 0.1.3

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.
Files changed (95) hide show
  1. package/README.md +67 -0
  2. package/dist/module.d.mts +47 -0
  3. package/dist/module.json +12 -0
  4. package/dist/module.mjs +1468 -0
  5. package/dist/runtime/app/components/BetterAuthState.d.vue.ts +20 -0
  6. package/dist/runtime/app/components/BetterAuthState.vue +9 -0
  7. package/dist/runtime/app/components/BetterAuthState.vue.d.ts +20 -0
  8. package/dist/runtime/app/composables/runWithSessionRefresh.d.ts +1 -0
  9. package/dist/runtime/app/composables/runWithSessionRefresh.js +12 -0
  10. package/dist/runtime/app/composables/useAction.d.ts +2 -0
  11. package/dist/runtime/app/composables/useAction.js +6 -0
  12. package/dist/runtime/app/composables/useAuthAsyncData.d.ts +6 -0
  13. package/dist/runtime/app/composables/useAuthAsyncData.js +16 -0
  14. package/dist/runtime/app/composables/useAuthClient.d.ts +9 -0
  15. package/dist/runtime/app/composables/useAuthClient.js +35 -0
  16. package/dist/runtime/app/composables/useAuthClientAction.d.ts +3 -0
  17. package/dist/runtime/app/composables/useAuthClientAction.js +15 -0
  18. package/dist/runtime/app/composables/useAuthRequestFetch.d.ts +11 -0
  19. package/dist/runtime/app/composables/useAuthRequestFetch.js +4 -0
  20. package/dist/runtime/app/composables/useSignIn.d.ts +16 -0
  21. package/dist/runtime/app/composables/useSignIn.js +8 -0
  22. package/dist/runtime/app/composables/useSignUp.d.ts +5 -0
  23. package/dist/runtime/app/composables/useSignUp.js +8 -0
  24. package/dist/runtime/app/composables/useUserSession.d.ts +23 -0
  25. package/dist/runtime/app/composables/useUserSession.js +247 -0
  26. package/dist/runtime/app/composables/useUserSessionState.d.ts +3 -0
  27. package/dist/runtime/app/composables/useUserSessionState.js +4 -0
  28. package/dist/runtime/app/internal/auth-action-error.d.ts +3 -0
  29. package/dist/runtime/app/internal/auth-action-error.js +33 -0
  30. package/dist/runtime/app/internal/auth-action-handles.d.ts +21 -0
  31. package/dist/runtime/app/internal/auth-action-handles.js +105 -0
  32. package/dist/runtime/app/internal/redirect-helpers.d.ts +4 -0
  33. package/dist/runtime/app/internal/redirect-helpers.js +37 -0
  34. package/dist/runtime/app/internal/session-fetch.d.ts +12 -0
  35. package/dist/runtime/app/internal/session-fetch.js +56 -0
  36. package/dist/runtime/app/internal/utils.d.ts +1 -0
  37. package/dist/runtime/app/internal/utils.js +3 -0
  38. package/dist/runtime/app/internal/vue-safe-auth-proxy.d.ts +3 -0
  39. package/dist/runtime/app/internal/vue-safe-auth-proxy.js +68 -0
  40. package/dist/runtime/app/internal/wrap-auth-method.d.ts +18 -0
  41. package/dist/runtime/app/internal/wrap-auth-method.js +69 -0
  42. package/dist/runtime/app/middleware/auth.global.d.ts +13 -0
  43. package/dist/runtime/app/middleware/auth.global.js +104 -0
  44. package/dist/runtime/app/pages/__better-auth-devtools.d.vue.ts +3 -0
  45. package/dist/runtime/app/pages/__better-auth-devtools.vue +380 -0
  46. package/dist/runtime/app/pages/__better-auth-devtools.vue.d.ts +3 -0
  47. package/dist/runtime/app/plugins/session.client.d.ts +2 -0
  48. package/dist/runtime/app/plugins/session.client.js +16 -0
  49. package/dist/runtime/app/plugins/session.server.d.ts +2 -0
  50. package/dist/runtime/app/plugins/session.server.js +24 -0
  51. package/dist/runtime/composables.d.ts +12 -0
  52. package/dist/runtime/composables.js +10 -0
  53. package/dist/runtime/config.d.ts +100 -0
  54. package/dist/runtime/config.js +13 -0
  55. package/dist/runtime/internal/auth-route-rules.d.ts +1 -0
  56. package/dist/runtime/internal/auth-route-rules.js +24 -0
  57. package/dist/runtime/server/api/_better-auth/_schema.d.ts +4 -0
  58. package/dist/runtime/server/api/_better-auth/_schema.js +12 -0
  59. package/dist/runtime/server/api/_better-auth/accounts.get.d.ts +2 -0
  60. package/dist/runtime/server/api/_better-auth/accounts.get.js +29 -0
  61. package/dist/runtime/server/api/_better-auth/config.get.d.ts +2 -0
  62. package/dist/runtime/server/api/_better-auth/config.get.js +58 -0
  63. package/dist/runtime/server/api/_better-auth/sessions.delete.d.ts +2 -0
  64. package/dist/runtime/server/api/_better-auth/sessions.delete.js +23 -0
  65. package/dist/runtime/server/api/_better-auth/sessions.get.d.ts +2 -0
  66. package/dist/runtime/server/api/_better-auth/sessions.get.js +44 -0
  67. package/dist/runtime/server/api/_better-auth/users.get.d.ts +2 -0
  68. package/dist/runtime/server/api/_better-auth/users.get.js +35 -0
  69. package/dist/runtime/server/api/auth/[...all].d.ts +2 -0
  70. package/dist/runtime/server/api/auth/[...all].js +6 -0
  71. package/dist/runtime/server/internal/nitro-compat.d.ts +1 -0
  72. package/dist/runtime/server/internal/nitro-compat.js +1 -0
  73. package/dist/runtime/server/internal/nitro2.d.ts +8 -0
  74. package/dist/runtime/server/internal/nitro2.js +29 -0
  75. package/dist/runtime/server/internal/nitro3.d.ts +10 -0
  76. package/dist/runtime/server/internal/nitro3.js +60 -0
  77. package/dist/runtime/server/middleware/route-access.d.ts +2 -0
  78. package/dist/runtime/server/middleware/route-access.js +29 -0
  79. package/dist/runtime/server/tsconfig.json +12 -0
  80. package/dist/runtime/server/utils/auth.d.ts +20 -0
  81. package/dist/runtime/server/utils/auth.js +246 -0
  82. package/dist/runtime/server/utils/custom-secondary-storage.d.ts +6 -0
  83. package/dist/runtime/server/utils/custom-secondary-storage.js +8 -0
  84. package/dist/runtime/server/utils/session.d.ts +8 -0
  85. package/dist/runtime/server/utils/session.js +280 -0
  86. package/dist/runtime/server/virtual-modules.d.ts +27 -0
  87. package/dist/runtime/types/augment.d.ts +56 -0
  88. package/dist/runtime/types/augment.js +0 -0
  89. package/dist/runtime/types.d.ts +21 -0
  90. package/dist/runtime/types.js +0 -0
  91. package/dist/runtime/utils/match-user.d.ts +2 -0
  92. package/dist/runtime/utils/match-user.js +13 -0
  93. package/dist/types.d.mts +11 -0
  94. package/package.json +111 -10
  95. package/index.js +0 -1
@@ -0,0 +1,380 @@
1
+ <script setup>
2
+ import { useFetch, useRuntimeConfig } from "#imports";
3
+ import { useDevtoolsClient } from "@nuxt/devtools-kit/iframe-client";
4
+ import { computed, ref, watch, watchEffect } from "vue";
5
+ const pageSize = 20;
6
+ const tabLabels = {
7
+ sessions: "Sessions",
8
+ users: "Users",
9
+ accounts: "Accounts",
10
+ config: "Config"
11
+ };
12
+ const devtoolsClient = useDevtoolsClient();
13
+ const runtimeConfig = useRuntimeConfig();
14
+ const hasDb = computed(() => runtimeConfig.public.auth?.useDatabase ?? false);
15
+ const isDark = computed(() => devtoolsClient.value?.host?.app?.colorMode?.value === "dark");
16
+ watchEffect(() => {
17
+ if (import.meta.client)
18
+ document.documentElement.classList.toggle("dark", isDark.value);
19
+ });
20
+ const activeTab = ref(hasDb.value ? "sessions" : "config");
21
+ const tabs = computed(() => hasDb.value ? ["sessions", "users", "accounts", "config"] : ["config"]);
22
+ watch(hasDb, (enabled) => {
23
+ if (!enabled)
24
+ activeTab.value = "config";
25
+ });
26
+ const sessionsPage = ref(1);
27
+ const usersPage = ref(1);
28
+ const accountsPage = ref(1);
29
+ const deleteConfirm = ref(null);
30
+ const notice = ref("");
31
+ const sessionsSearch = ref("");
32
+ const usersSearch = ref("");
33
+ const accountsSearch = ref("");
34
+ watch(sessionsSearch, () => sessionsPage.value = 1);
35
+ watch(usersSearch, () => usersPage.value = 1);
36
+ watch(accountsSearch, () => accountsPage.value = 1);
37
+ const sessionsQuery = computed(() => ({ page: sessionsPage.value, limit: pageSize, search: sessionsSearch.value }));
38
+ const usersQuery = computed(() => ({ page: usersPage.value, limit: pageSize, search: usersSearch.value }));
39
+ const accountsQuery = computed(() => ({ page: accountsPage.value, limit: pageSize, search: accountsSearch.value }));
40
+ const { data: sessionsData, refresh: refreshSessions } = await useFetch("/api/_better-auth/sessions", { query: sessionsQuery, immediate: hasDb.value });
41
+ const { data: usersData, refresh: refreshUsers } = await useFetch("/api/_better-auth/users", { query: usersQuery, immediate: hasDb.value });
42
+ const { data: accountsData, refresh: refreshAccounts } = await useFetch("/api/_better-auth/accounts", { query: accountsQuery, immediate: hasDb.value });
43
+ const { data: configData } = await useFetch("/api/_better-auth/config");
44
+ const sessions = computed(() => sessionsData.value?.sessions ?? []);
45
+ const users = computed(() => usersData.value?.users ?? []);
46
+ const accounts = computed(() => accountsData.value?.accounts ?? []);
47
+ let noticeTimer;
48
+ function showNotice(message) {
49
+ notice.value = message;
50
+ if (noticeTimer)
51
+ clearTimeout(noticeTimer);
52
+ noticeTimer = setTimeout(() => notice.value = "", 2200);
53
+ }
54
+ function isExpired(date) {
55
+ return date ? new Date(date) < /* @__PURE__ */ new Date() : false;
56
+ }
57
+ function formatDate(date) {
58
+ return date ? new Date(date).toLocaleString() : "-";
59
+ }
60
+ function truncate(str, len = 14) {
61
+ if (!str)
62
+ return "-";
63
+ if (str.length <= len)
64
+ return str;
65
+ const half = Math.floor((len - 3) / 2);
66
+ return `${str.slice(0, half)}...${str.slice(-half)}`;
67
+ }
68
+ function totalPages(total) {
69
+ return Math.max(1, Math.ceil((total ?? 0) / pageSize));
70
+ }
71
+ function pageBack(page) {
72
+ page.value = Math.max(1, page.value - 1);
73
+ }
74
+ function pageNext(page, total) {
75
+ page.value = Math.min(totalPages(total), page.value + 1);
76
+ }
77
+ async function copyToClipboard(text, label = "Value") {
78
+ try {
79
+ await navigator.clipboard.writeText(text);
80
+ showNotice(`${label} copied`);
81
+ } catch {
82
+ showNotice("Copy failed");
83
+ }
84
+ }
85
+ function generateConfigMarkdown() {
86
+ const config = configData.value?.config;
87
+ if (!config)
88
+ return "";
89
+ return `## Module Config (\`nuxt.config.ts\`)
90
+
91
+ \`\`\`json
92
+ ${JSON.stringify(config.module ?? {}, null, 2)}
93
+ \`\`\`
94
+
95
+ ## Server Config (\`server/auth.config.ts\`)
96
+
97
+ \`\`\`json
98
+ ${JSON.stringify(config.server ?? {}, null, 2)}
99
+ \`\`\`
100
+ `;
101
+ }
102
+ function json(value) {
103
+ return JSON.stringify(value ?? {}, null, 2);
104
+ }
105
+ async function deleteSession(id) {
106
+ try {
107
+ const response = await fetch("/api/_better-auth/sessions", {
108
+ method: "DELETE",
109
+ headers: { "content-type": "application/json" },
110
+ body: JSON.stringify({ id })
111
+ });
112
+ if (!response.ok)
113
+ throw new Error("Delete failed");
114
+ deleteConfirm.value = null;
115
+ showNotice("Session deleted");
116
+ await refreshSessions();
117
+ } catch {
118
+ showNotice("Failed to delete session");
119
+ }
120
+ }
121
+ async function deleteConfirmedSession() {
122
+ if (deleteConfirm.value)
123
+ await deleteSession(deleteConfirm.value);
124
+ }
125
+ </script>
126
+
127
+ <template>
128
+ <div class="devtools-shell">
129
+ <header class="devtools-header">
130
+ <div class="brand">
131
+ <svg width="60" height="45" viewBox="0 0 60 45" fill="none" class="brand-mark" xmlns="http://www.w3.org/2000/svg">
132
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H15V15H30V30H15V45H0V30V15V0ZM45 30V15H30V0H45H60V15V30V45H45H30V30H45Z" fill="currentColor" />
133
+ </svg>
134
+ <span>Better Auth DevTools</span>
135
+ </div>
136
+ <nav class="links">
137
+ <a href="https://www.better-auth.com/docs" target="_blank" rel="noreferrer">Docs</a>
138
+ <a href="https://github.com/nuxt-modules/better-auth" target="_blank" rel="noreferrer">GitHub</a>
139
+ </nav>
140
+ </header>
141
+
142
+ <nav class="tabs" aria-label="Devtools sections">
143
+ <button
144
+ v-for="tab in tabs"
145
+ :key="tab"
146
+ type="button"
147
+ :class="{ active: activeTab === tab }"
148
+ @click="activeTab = tab"
149
+ >
150
+ {{ tabLabels[tab] }}
151
+ </button>
152
+ </nav>
153
+
154
+ <p v-if="notice" class="notice">
155
+ {{ notice }}
156
+ </p>
157
+
158
+ <main class="panel">
159
+ <section v-if="activeTab === 'sessions'" class="section">
160
+ <div class="toolbar">
161
+ <input v-model="sessionsSearch" type="search" placeholder="Search by user ID or IP...">
162
+ <div class="toolbar-meta">
163
+ <span>{{ sessionsData?.total ?? 0 }} sessions</span>
164
+ <button type="button" @click="() => refreshSessions()">
165
+ Refresh
166
+ </button>
167
+ </div>
168
+ </div>
169
+
170
+ <div v-if="deleteConfirm" class="confirm">
171
+ <span>Delete this session?</span>
172
+ <button type="button" @click="deleteConfirm = null">
173
+ Cancel
174
+ </button>
175
+ <button type="button" class="danger" @click="deleteConfirmedSession">
176
+ Delete
177
+ </button>
178
+ </div>
179
+
180
+ <p v-if="sessionsData?.error" class="error">
181
+ {{ sessionsData.error }}
182
+ </p>
183
+ <table v-else-if="sessions.length">
184
+ <thead>
185
+ <tr>
186
+ <th>ID</th>
187
+ <th>User</th>
188
+ <th>User Agent</th>
189
+ <th>Status</th>
190
+ <th>Created</th>
191
+ <th />
192
+ </tr>
193
+ </thead>
194
+ <tbody>
195
+ <tr v-for="session in sessions" :key="session.id">
196
+ <td><code>{{ truncate(session.id) }}</code></td>
197
+ <td>
198
+ <code>{{ truncate(session.userId) }}</code>
199
+ <small>{{ session.ipAddress || "No IP" }}</small>
200
+ </td>
201
+ <td>{{ truncate(session.userAgent, 32) }}</td>
202
+ <td>
203
+ <span class="badge" :class="isExpired(session.expiresAt) ? 'bad' : 'good'">
204
+ {{ isExpired(session.expiresAt) ? "Expired" : "Active" }}
205
+ </span>
206
+ </td>
207
+ <td>{{ formatDate(session.createdAt) }}</td>
208
+ <td class="actions">
209
+ <button type="button" @click="copyToClipboard(session.id, 'Session ID')">
210
+ Copy
211
+ </button>
212
+ <button type="button" class="danger" @click="deleteConfirm = session.id">
213
+ Delete
214
+ </button>
215
+ </td>
216
+ </tr>
217
+ </tbody>
218
+ </table>
219
+ <p v-else class="empty">
220
+ No sessions found
221
+ </p>
222
+
223
+ <div v-if="(sessionsData?.total ?? 0) > pageSize" class="pagination">
224
+ <button type="button" :disabled="sessionsPage === 1" @click="pageBack(sessionsPage)">
225
+ Previous
226
+ </button>
227
+ <span>Page {{ sessionsPage }} of {{ totalPages(sessionsData?.total) }}</span>
228
+ <button type="button" :disabled="sessionsPage >= totalPages(sessionsData?.total)" @click="pageNext(sessionsPage, sessionsData?.total)">
229
+ Next
230
+ </button>
231
+ </div>
232
+ </section>
233
+
234
+ <section v-else-if="activeTab === 'users'" class="section">
235
+ <div class="toolbar">
236
+ <input v-model="usersSearch" type="search" placeholder="Search by name or email...">
237
+ <div class="toolbar-meta">
238
+ <span>{{ usersData?.total ?? 0 }} users</span>
239
+ <button type="button" @click="() => refreshUsers()">
240
+ Refresh
241
+ </button>
242
+ </div>
243
+ </div>
244
+
245
+ <p v-if="usersData?.error" class="error">
246
+ {{ usersData.error }}
247
+ </p>
248
+ <table v-else-if="users.length">
249
+ <thead>
250
+ <tr>
251
+ <th>ID</th>
252
+ <th>User</th>
253
+ <th>Verified</th>
254
+ <th>Created</th>
255
+ <th />
256
+ </tr>
257
+ </thead>
258
+ <tbody>
259
+ <tr v-for="user in users" :key="user.id">
260
+ <td><code>{{ truncate(user.id) }}</code></td>
261
+ <td>
262
+ <strong>{{ user.name || "Unnamed" }}</strong>
263
+ <small>{{ user.email }}</small>
264
+ </td>
265
+ <td>
266
+ <span class="badge" :class="user.emailVerified ? 'good' : ''">
267
+ {{ user.emailVerified ? "Yes" : "No" }}
268
+ </span>
269
+ </td>
270
+ <td>{{ formatDate(user.createdAt) }}</td>
271
+ <td class="actions">
272
+ <button type="button" @click="copyToClipboard(user.id, 'User ID')">
273
+ Copy ID
274
+ </button>
275
+ <button type="button" @click="copyToClipboard(user.email, 'Email')">
276
+ Copy Email
277
+ </button>
278
+ </td>
279
+ </tr>
280
+ </tbody>
281
+ </table>
282
+ <p v-else class="empty">
283
+ No users found
284
+ </p>
285
+
286
+ <div v-if="(usersData?.total ?? 0) > pageSize" class="pagination">
287
+ <button type="button" :disabled="usersPage === 1" @click="pageBack(usersPage)">
288
+ Previous
289
+ </button>
290
+ <span>Page {{ usersPage }} of {{ totalPages(usersData?.total) }}</span>
291
+ <button type="button" :disabled="usersPage >= totalPages(usersData?.total)" @click="pageNext(usersPage, usersData?.total)">
292
+ Next
293
+ </button>
294
+ </div>
295
+ </section>
296
+
297
+ <section v-else-if="activeTab === 'accounts'" class="section">
298
+ <div class="toolbar">
299
+ <input v-model="accountsSearch" type="search" placeholder="Search by provider...">
300
+ <div class="toolbar-meta">
301
+ <span>{{ accountsData?.total ?? 0 }} accounts</span>
302
+ <button type="button" @click="() => refreshAccounts()">
303
+ Refresh
304
+ </button>
305
+ </div>
306
+ </div>
307
+
308
+ <p v-if="accountsData?.error" class="error">
309
+ {{ accountsData.error }}
310
+ </p>
311
+ <table v-else-if="accounts.length">
312
+ <thead>
313
+ <tr>
314
+ <th>Provider</th>
315
+ <th>Account</th>
316
+ <th>User ID</th>
317
+ <th>Created</th>
318
+ <th />
319
+ </tr>
320
+ </thead>
321
+ <tbody>
322
+ <tr v-for="account in accounts" :key="account.id">
323
+ <td class="capitalize">
324
+ {{ account.providerId }}
325
+ </td>
326
+ <td><code>{{ truncate(account.accountId, 18) }}</code></td>
327
+ <td><code>{{ truncate(account.userId) }}</code></td>
328
+ <td>{{ formatDate(account.createdAt) }}</td>
329
+ <td class="actions">
330
+ <button type="button" @click="copyToClipboard(account.id, 'Account ID')">
331
+ Copy
332
+ </button>
333
+ </td>
334
+ </tr>
335
+ </tbody>
336
+ </table>
337
+ <p v-else class="empty">
338
+ No accounts found
339
+ </p>
340
+
341
+ <div v-if="(accountsData?.total ?? 0) > pageSize" class="pagination">
342
+ <button type="button" :disabled="accountsPage === 1" @click="pageBack(accountsPage)">
343
+ Previous
344
+ </button>
345
+ <span>Page {{ accountsPage }} of {{ totalPages(accountsData?.total) }}</span>
346
+ <button type="button" :disabled="accountsPage >= totalPages(accountsData?.total)" @click="pageNext(accountsPage, accountsData?.total)">
347
+ Next
348
+ </button>
349
+ </div>
350
+ </section>
351
+
352
+ <section v-else class="section config-grid">
353
+ <div class="toolbar">
354
+ <span />
355
+ <button type="button" @click="copyToClipboard(generateConfigMarkdown(), 'Config')">
356
+ Copy config
357
+ </button>
358
+ </div>
359
+
360
+ <p v-if="configData?.error" class="error">
361
+ {{ configData.error }}
362
+ </p>
363
+ <template v-else>
364
+ <article>
365
+ <h2>Module Config</h2>
366
+ <pre>{{ json(configData?.config?.module) }}</pre>
367
+ </article>
368
+ <article>
369
+ <h2>Server Config</h2>
370
+ <pre>{{ json(configData?.config?.server) }}</pre>
371
+ </article>
372
+ </template>
373
+ </section>
374
+ </main>
375
+ </div>
376
+ </template>
377
+
378
+ <style>
379
+ :root{--ba-bg:#fff;--ba-fg:#1f1f1f;--ba-muted:#707070;--ba-border:#e5e5e5;--ba-soft:#f7f7f7;--ba-hover:#efefef;--ba-good:#047857;--ba-bad:#b91c1c}.dark{--ba-bg:#111;--ba-fg:#f4f4f5;--ba-muted:#a1a1aa;--ba-border:#2d2d2d;--ba-soft:#191919;--ba-hover:#242424;--ba-good:#34d399;--ba-bad:#f87171}*{box-sizing:border-box}body{margin:0}button,input{font:inherit}.devtools-shell{background:var(--ba-bg);color:var(--ba-fg);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:13px;min-height:100vh}.devtools-header{border-bottom:1px solid var(--ba-border);gap:16px;justify-content:space-between;min-height:48px;padding:0 16px}.actions,.brand,.confirm,.devtools-header,.links,.pagination,.toolbar,.toolbar-meta{align-items:center;display:flex}.brand{font-weight:600;gap:10px}.brand-mark{height:auto;width:24px}.links a{color:var(--ba-muted);padding:8px 10px;text-decoration:none}.links a:hover{color:var(--ba-fg)}.tabs{border-bottom:1px solid var(--ba-border);display:flex;gap:4px;padding:8px 12px 0}.actions button,.confirm button,.pagination button,.tabs button,.toolbar button{background:transparent;border:1px solid var(--ba-border);color:var(--ba-fg);cursor:pointer;min-height:30px;padding:5px 10px}.tabs button{border-bottom:0}.tabs button.active,button:hover:not(:disabled){background:var(--ba-hover)}button:disabled{color:var(--ba-muted);cursor:default}button.danger{color:var(--ba-bad)}.panel{padding:16px}.section{display:grid;gap:14px}.toolbar{gap:12px;justify-content:space-between}.actions,.confirm,.pagination,.toolbar-meta{gap:8px}input{background:var(--ba-bg);border:1px solid var(--ba-border);color:var(--ba-fg);padding:7px 10px;width:min(340px,100%)}table{border:1px solid var(--ba-border);border-collapse:collapse;width:100%}td,th{border-bottom:1px solid var(--ba-border);padding:9px 10px;text-align:left;vertical-align:top}th{background:var(--ba-soft);font-weight:500}td small,th{color:var(--ba-muted)}td small{display:block;margin-top:3px}code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace}.badge{background:var(--ba-soft);border:1px solid var(--ba-border);color:var(--ba-muted);display:inline-flex;padding:2px 8px}.badge.good{color:var(--ba-good)}.badge.bad,.error{color:var(--ba-bad)}.confirm,.empty,.notice{background:var(--ba-soft);border:1px solid var(--ba-border);padding:10px 12px}.empty,.notice{color:var(--ba-muted)}.config-grid{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}.config-grid .error,.config-grid .toolbar{grid-column:1/-1}article{background:var(--ba-soft);border:1px solid var(--ba-border)}h2{border-bottom:1px solid var(--ba-border);font-size:13px;padding:10px 12px}h2,pre{margin:0}pre{font-size:12px;line-height:1.5;overflow:auto;padding:12px}.capitalize{text-transform:capitalize}@media (max-width:720px){.devtools-header,.toolbar,.toolbar-meta{align-items:stretch;flex-direction:column}.actions,.tabs{flex-wrap:wrap}table{display:block;overflow-x:auto}}
380
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,16 @@
1
+ import { defineNuxtPlugin } from "#imports";
2
+ import { useUserSession } from "../composables/useUserSession.js";
3
+ export default defineNuxtPlugin(async (nuxtApp) => {
4
+ const { fetchSession } = useUserSession();
5
+ const safeFetch = async () => {
6
+ try {
7
+ await fetchSession();
8
+ } catch {
9
+ }
10
+ };
11
+ if (!nuxtApp.payload.serverRendered) {
12
+ await safeFetch();
13
+ } else if (nuxtApp.payload.prerenderedAt || nuxtApp.payload.isCached) {
14
+ nuxtApp.hook("app:mounted", safeFetch);
15
+ }
16
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,24 @@
1
+ import { defineNuxtPlugin, useRequestEvent, useRequestHeaders, useState } from "#imports";
2
+ export default defineNuxtPlugin({
3
+ name: "auth:session-init",
4
+ enforce: "pre",
5
+ async setup() {
6
+ const session = useState("auth:session", () => null);
7
+ const user = useState("auth:user", () => null);
8
+ const authReady = useState("auth:ready", () => false);
9
+ const event = useRequestEvent();
10
+ if (event) {
11
+ try {
12
+ const headers = useRequestHeaders(["cookie"]);
13
+ const data = await $fetch("/api/auth/get-session", { headers });
14
+ if (data?.session && data?.user) {
15
+ const { token: _, ...safeSession } = data.session;
16
+ session.value = safeSession;
17
+ user.value = data.user;
18
+ }
19
+ } catch {
20
+ }
21
+ }
22
+ authReady.value = true;
23
+ }
24
+ });
@@ -0,0 +1,12 @@
1
+ export { runWithSessionRefresh } from './app/composables/runWithSessionRefresh.js';
2
+ export { useAction } from './app/composables/useAction.js';
3
+ export { useAuthAsyncData } from './app/composables/useAuthAsyncData.js';
4
+ export { useAuthClient } from './app/composables/useAuthClient.js';
5
+ export { useAuthClientAction } from './app/composables/useAuthClientAction.js';
6
+ export { useAuthRequestFetch } from './app/composables/useAuthRequestFetch.js';
7
+ export { useSignIn } from './app/composables/useSignIn.js';
8
+ export { useSignUp } from './app/composables/useSignUp.js';
9
+ export { useUserSession } from './app/composables/useUserSession.js';
10
+ export type { SignOutOptions, UseUserSessionReturn } from './app/composables/useUserSession.js';
11
+ export { useUserSessionState } from './app/composables/useUserSessionState.js';
12
+ export type { UseUserSessionStateReturn } from './app/composables/useUserSessionState.js';
@@ -0,0 +1,10 @@
1
+ export { runWithSessionRefresh } from "./app/composables/runWithSessionRefresh.js";
2
+ export { useAction } from "./app/composables/useAction.js";
3
+ export { useAuthAsyncData } from "./app/composables/useAuthAsyncData.js";
4
+ export { useAuthClient } from "./app/composables/useAuthClient.js";
5
+ export { useAuthClientAction } from "./app/composables/useAuthClientAction.js";
6
+ export { useAuthRequestFetch } from "./app/composables/useAuthRequestFetch.js";
7
+ export { useSignIn } from "./app/composables/useSignIn.js";
8
+ export { useSignUp } from "./app/composables/useSignUp.js";
9
+ export { useUserSession } from "./app/composables/useUserSession.js";
10
+ export { useUserSessionState } from "./app/composables/useUserSessionState.js";
@@ -0,0 +1,100 @@
1
+ import type { BetterAuthOptions, BetterAuthPlugin } from 'better-auth';
2
+ import type { BetterAuthClientOptions } from 'better-auth/client';
3
+ import type { Casing } from 'drizzle-orm/utils';
4
+ import type { ServerAuthContext as BaseServerAuthContext } from './types/augment.js';
5
+ import { createAuthClient } from 'better-auth/vue';
6
+ export interface ServerAuthContextExtension {
7
+ }
8
+ export type ServerAuthContext = BaseServerAuthContext & ServerAuthContextExtension;
9
+ export interface ClientAuthContext {
10
+ siteUrl: string;
11
+ }
12
+ export type ServerAuthConfig = Omit<BetterAuthOptions, 'secret' | 'baseURL'> & {
13
+ plugins?: readonly BetterAuthPlugin[];
14
+ };
15
+ export type ClientAuthConfig = Omit<BetterAuthClientOptions, 'baseURL'> & {
16
+ baseURL?: string;
17
+ };
18
+ export type ServerAuthConfigFn = (ctx: ServerAuthContext) => ServerAuthConfig;
19
+ export type ClientAuthConfigFn = (ctx: ClientAuthContext) => ClientAuthConfig;
20
+ export type ModuleDatabaseProviderId = 'none' | 'nuxthub' | (string & {});
21
+ export type EffectiveDatabaseProviderId = 'user' | ModuleDatabaseProviderId;
22
+ export interface BetterAuthModuleOptions {
23
+ /** Client-only mode - skip server setup for external auth backends */
24
+ clientOnly?: boolean;
25
+ /** Server config path. Relative paths resolve from the layer that declares them. Default: 'server/auth.config' */
26
+ serverConfig?: string;
27
+ /** Client config path. Relative paths resolve from the layer that declares them. Default: 'app/auth.config' */
28
+ clientConfig?: string;
29
+ redirects?: {
30
+ /** Where to redirect unauthenticated users. Default: '/login' */
31
+ login?: string;
32
+ /** Where to redirect authenticated users on guest-only routes. Default: '/' */
33
+ guest?: string;
34
+ /** Where to navigate after successful signIn/signUp when no onSuccess is provided. Default: no automatic navigation */
35
+ authenticated?: string;
36
+ /** Where to navigate after logout. Default: no automatic navigation */
37
+ logout?: string;
38
+ };
39
+ /**
40
+ * When redirecting unauthenticated users to the login route, append a query param
41
+ * containing the originally requested path (for safe "return-to" redirects).
42
+ *
43
+ * Default: true
44
+ */
45
+ preserveRedirect?: boolean;
46
+ /**
47
+ * Query param key used by preserveRedirect.
48
+ *
49
+ * Default: 'redirect'
50
+ */
51
+ redirectQueryKey?: string;
52
+ session?: {
53
+ /**
54
+ * When enabled, and session/user are already hydrated from SSR, skip the initial
55
+ * client `/api/auth/get-session` bootstrap request. This also skips Better Auth's
56
+ * session refresh manager on those pages.
57
+ *
58
+ * Default: false
59
+ */
60
+ skipHydratedSsrGetSession?: boolean;
61
+ };
62
+ /**
63
+ * Enable secondary storage for sessions.
64
+ * - `true`: Use NuxtHub KV (requires hub.kv: true)
65
+ * - `'custom'`: User provides own secondaryStorage in defineServerAuth()
66
+ * - `false` (default): No secondary storage from module
67
+ */
68
+ hubSecondaryStorage?: boolean | 'custom';
69
+ /** Schema generation options. Must match drizzleAdapter config. */
70
+ schema?: {
71
+ /** Plural table names: user → users. Default: false */
72
+ usePlural?: boolean;
73
+ /** Column/table name casing. Explicit value takes precedence over hub.db.casing. */
74
+ casing?: Casing;
75
+ /** PostgreSQL schema namespace. */
76
+ schemaName?: string;
77
+ };
78
+ }
79
+ export interface AuthRuntimeConfig {
80
+ redirects: {
81
+ login: string;
82
+ guest: string;
83
+ authenticated?: string;
84
+ logout?: string;
85
+ };
86
+ preserveRedirect: boolean;
87
+ redirectQueryKey: string;
88
+ useDatabase: boolean;
89
+ databaseProvider: EffectiveDatabaseProviderId;
90
+ clientOnly: boolean;
91
+ session: {
92
+ skipHydratedSsrGetSession: boolean;
93
+ };
94
+ }
95
+ export interface AuthPrivateRuntimeConfig {
96
+ hubSecondaryStorage: boolean | 'custom';
97
+ }
98
+ export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
99
+ export declare function defineServerAuth<const R>(config: R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
100
+ export declare function defineClientAuth<T extends ClientAuthConfig>(config: T | ((ctx: ClientAuthContext) => T)): (baseURL: string) => ReturnType<typeof createAuthClient<T>>;
@@ -0,0 +1,13 @@
1
+ import { createAuthClient } from "better-auth/vue";
2
+ export function defineServerAuth(config) {
3
+ return typeof config === "function" ? config : () => config;
4
+ }
5
+ export function defineClientAuth(config) {
6
+ return (baseURL) => {
7
+ const ctx = { siteUrl: baseURL };
8
+ const resolved = typeof config === "function" ? config(ctx) : config;
9
+ const { baseURL: configuredBaseURL, ...resolvedOptions } = resolved;
10
+ const clientOptions = { ...resolvedOptions, baseURL: configuredBaseURL ?? baseURL };
11
+ return createAuthClient(clientOptions);
12
+ };
13
+ }
@@ -0,0 +1 @@
1
+ export declare function shouldSkipAuthRouteRules(path: string): boolean;
@@ -0,0 +1,24 @@
1
+ const internalRouteRuleAuthPaths = /* @__PURE__ */ new Set([
2
+ "/_ipx",
3
+ "/_nuxt",
4
+ "/__better-auth-devtools",
5
+ "/__nuxt_devtools__",
6
+ "/__nuxt_error",
7
+ "/__nuxt_vite_node__",
8
+ "/api/_better-auth",
9
+ "/api/_nuxt_icon",
10
+ "/api/auth"
11
+ ]);
12
+ const internalRouteRuleAuthPrefixes = [
13
+ "/_ipx/",
14
+ "/_nuxt/",
15
+ "/__nuxt_devtools__/",
16
+ "/__nuxt_vite_node__/",
17
+ "/api/_better-auth/",
18
+ "/api/_nuxt_icon/",
19
+ "/api/auth/"
20
+ ];
21
+ export function shouldSkipAuthRouteRules(path) {
22
+ const pathname = path.split(/[?#]/, 1)[0] || "/";
23
+ return internalRouteRuleAuthPaths.has(pathname) || internalRouteRuleAuthPrefixes.some((prefix) => pathname.startsWith(prefix));
24
+ }
@@ -0,0 +1,4 @@
1
+ import { z } from 'zod';
2
+ export declare const paginationQuerySchema: any;
3
+ export type PaginationQuery = z.infer<typeof paginationQuerySchema>;
4
+ export declare function sanitizeSearchPattern(search: string): string;
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ const SQL_LIKE_ESCAPE_RE = /[%_\\]/g;
3
+ export const paginationQuerySchema = z.object({
4
+ page: z.coerce.number().int().min(1).default(1),
5
+ limit: z.coerce.number().int().min(1).max(100).default(20),
6
+ search: z.string().max(100).default("")
7
+ });
8
+ export function sanitizeSearchPattern(search) {
9
+ if (!search)
10
+ return "";
11
+ return `%${search.replace(SQL_LIKE_ESCAPE_RE, "\\$&")}%`;
12
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,29 @@
1
+ import { paginationQuerySchema, sanitizeSearchPattern } from "./_schema.js";
2
+ import { defineEventHandler, getQuery } from "../../internal/nitro-compat.js";
3
+ export default defineEventHandler(async (event) => {
4
+ try {
5
+ const { db } = await import("@nuxthub/db");
6
+ const { schema } = await import("#auth/schema");
7
+ if (!schema?.account)
8
+ return { accounts: [], total: 0, error: "Account table not found" };
9
+ const query = paginationQuerySchema.parse(getQuery(event));
10
+ const { page, limit, search } = query;
11
+ const offset = (page - 1) * limit;
12
+ const { count, like, desc } = await import("drizzle-orm");
13
+ let dbQuery = db.select().from(schema.account);
14
+ let countQuery = db.select({ count: count() }).from(schema.account);
15
+ if (search) {
16
+ const pattern = sanitizeSearchPattern(search);
17
+ dbQuery = dbQuery.where(like(schema.account.providerId, pattern));
18
+ countQuery = countQuery.where(like(schema.account.providerId, pattern));
19
+ }
20
+ const [accounts, totalResult] = await Promise.all([
21
+ dbQuery.orderBy(desc(schema.account.createdAt)).limit(limit).offset(offset),
22
+ countQuery
23
+ ]);
24
+ return { accounts, total: totalResult[0]?.count ?? 0, page, limit };
25
+ } catch (error) {
26
+ console.error("[DevTools] Fetch accounts failed:", error);
27
+ return { accounts: [], total: 0, error: "Failed to fetch accounts" };
28
+ }
29
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;