@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.
- package/README.md +67 -0
- package/dist/module.d.mts +47 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +1468 -0
- package/dist/runtime/app/components/BetterAuthState.d.vue.ts +20 -0
- package/dist/runtime/app/components/BetterAuthState.vue +9 -0
- package/dist/runtime/app/components/BetterAuthState.vue.d.ts +20 -0
- package/dist/runtime/app/composables/runWithSessionRefresh.d.ts +1 -0
- package/dist/runtime/app/composables/runWithSessionRefresh.js +12 -0
- package/dist/runtime/app/composables/useAction.d.ts +2 -0
- package/dist/runtime/app/composables/useAction.js +6 -0
- package/dist/runtime/app/composables/useAuthAsyncData.d.ts +6 -0
- package/dist/runtime/app/composables/useAuthAsyncData.js +16 -0
- package/dist/runtime/app/composables/useAuthClient.d.ts +9 -0
- package/dist/runtime/app/composables/useAuthClient.js +35 -0
- package/dist/runtime/app/composables/useAuthClientAction.d.ts +3 -0
- package/dist/runtime/app/composables/useAuthClientAction.js +15 -0
- package/dist/runtime/app/composables/useAuthRequestFetch.d.ts +11 -0
- package/dist/runtime/app/composables/useAuthRequestFetch.js +4 -0
- package/dist/runtime/app/composables/useSignIn.d.ts +16 -0
- package/dist/runtime/app/composables/useSignIn.js +8 -0
- package/dist/runtime/app/composables/useSignUp.d.ts +5 -0
- package/dist/runtime/app/composables/useSignUp.js +8 -0
- package/dist/runtime/app/composables/useUserSession.d.ts +23 -0
- package/dist/runtime/app/composables/useUserSession.js +247 -0
- package/dist/runtime/app/composables/useUserSessionState.d.ts +3 -0
- package/dist/runtime/app/composables/useUserSessionState.js +4 -0
- package/dist/runtime/app/internal/auth-action-error.d.ts +3 -0
- package/dist/runtime/app/internal/auth-action-error.js +33 -0
- package/dist/runtime/app/internal/auth-action-handles.d.ts +21 -0
- package/dist/runtime/app/internal/auth-action-handles.js +105 -0
- package/dist/runtime/app/internal/redirect-helpers.d.ts +4 -0
- package/dist/runtime/app/internal/redirect-helpers.js +37 -0
- package/dist/runtime/app/internal/session-fetch.d.ts +12 -0
- package/dist/runtime/app/internal/session-fetch.js +56 -0
- package/dist/runtime/app/internal/utils.d.ts +1 -0
- package/dist/runtime/app/internal/utils.js +3 -0
- package/dist/runtime/app/internal/vue-safe-auth-proxy.d.ts +3 -0
- package/dist/runtime/app/internal/vue-safe-auth-proxy.js +68 -0
- package/dist/runtime/app/internal/wrap-auth-method.d.ts +18 -0
- package/dist/runtime/app/internal/wrap-auth-method.js +69 -0
- package/dist/runtime/app/middleware/auth.global.d.ts +13 -0
- package/dist/runtime/app/middleware/auth.global.js +104 -0
- package/dist/runtime/app/pages/__better-auth-devtools.d.vue.ts +3 -0
- package/dist/runtime/app/pages/__better-auth-devtools.vue +380 -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/composables.d.ts +12 -0
- package/dist/runtime/composables.js +10 -0
- package/dist/runtime/config.d.ts +100 -0
- package/dist/runtime/config.js +13 -0
- package/dist/runtime/internal/auth-route-rules.d.ts +1 -0
- package/dist/runtime/internal/auth-route-rules.js +24 -0
- package/dist/runtime/server/api/_better-auth/_schema.d.ts +4 -0
- package/dist/runtime/server/api/_better-auth/_schema.js +12 -0
- package/dist/runtime/server/api/_better-auth/accounts.get.d.ts +2 -0
- package/dist/runtime/server/api/_better-auth/accounts.get.js +29 -0
- package/dist/runtime/server/api/_better-auth/config.get.d.ts +2 -0
- package/dist/runtime/server/api/_better-auth/config.get.js +58 -0
- package/dist/runtime/server/api/_better-auth/sessions.delete.d.ts +2 -0
- package/dist/runtime/server/api/_better-auth/sessions.delete.js +23 -0
- package/dist/runtime/server/api/_better-auth/sessions.get.d.ts +2 -0
- package/dist/runtime/server/api/_better-auth/sessions.get.js +44 -0
- package/dist/runtime/server/api/_better-auth/users.get.d.ts +2 -0
- package/dist/runtime/server/api/_better-auth/users.get.js +35 -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/internal/nitro-compat.d.ts +1 -0
- package/dist/runtime/server/internal/nitro-compat.js +1 -0
- package/dist/runtime/server/internal/nitro2.d.ts +8 -0
- package/dist/runtime/server/internal/nitro2.js +29 -0
- package/dist/runtime/server/internal/nitro3.d.ts +10 -0
- package/dist/runtime/server/internal/nitro3.js +60 -0
- package/dist/runtime/server/middleware/route-access.d.ts +2 -0
- package/dist/runtime/server/middleware/route-access.js +29 -0
- package/dist/runtime/server/tsconfig.json +12 -0
- package/dist/runtime/server/utils/auth.d.ts +20 -0
- package/dist/runtime/server/utils/auth.js +246 -0
- package/dist/runtime/server/utils/custom-secondary-storage.d.ts +6 -0
- package/dist/runtime/server/utils/custom-secondary-storage.js +8 -0
- package/dist/runtime/server/utils/session.d.ts +8 -0
- package/dist/runtime/server/utils/session.js +280 -0
- package/dist/runtime/server/virtual-modules.d.ts +27 -0
- package/dist/runtime/types/augment.d.ts +56 -0
- package/dist/runtime/types/augment.js +0 -0
- package/dist/runtime/types.d.ts +21 -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 +11 -0
- package/package.json +111 -10
- 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,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,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,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,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
|
+
});
|