nuxt-users 1.34.6 → 1.35.0
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/CHANGELOG.md +67 -0
- package/dist/cli.mjs +74 -0
- package/dist/module.d.mts +1 -1
- package/dist/module.d.ts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +46 -3
- package/dist/runtime/assets/nuxt-users.css +1 -1
- package/dist/runtime/components/NUsersGoogleLoginButton.vue +75 -0
- package/dist/runtime/components/NUsersGoogleLoginButton.vue.d.ts +30 -0
- package/dist/runtime/composables/useAuthentication.d.ts +1 -1
- package/dist/runtime/composables/useAuthentication.js +26 -8
- package/dist/runtime/middleware/authorization.client.js +9 -7
- package/dist/runtime/plugin.js +12 -4
- package/dist/runtime/server/api/nuxt-users/auth/google/callback.get.d.ts +2 -0
- package/dist/runtime/server/api/nuxt-users/auth/google/callback.get.js +68 -0
- package/dist/runtime/server/api/nuxt-users/auth/google/redirect.get.d.ts +2 -0
- package/dist/runtime/server/api/nuxt-users/auth/google/redirect.get.js +39 -0
- package/dist/runtime/server/utils/add-google-oauth-fields.d.ts +2 -0
- package/dist/runtime/server/utils/add-google-oauth-fields.js +22 -0
- package/dist/runtime/server/utils/build-time.d.ts +5 -0
- package/dist/runtime/server/utils/build-time.js +12 -0
- package/dist/runtime/server/utils/create-users-table.js +7 -1
- package/dist/runtime/server/utils/db.js +4 -0
- package/dist/runtime/server/utils/google-oauth.d.ts +33 -0
- package/dist/runtime/server/utils/google-oauth.js +136 -0
- package/dist/runtime/server/utils/index.d.ts +2 -0
- package/dist/runtime/server/utils/index.js +2 -0
- package/dist/runtime/server/utils/migrate.js +5 -0
- package/dist/shared/{nuxt-users.BTVH9EyH.d.mts → nuxt-users.SXaMnaEL.d.mts} +45 -1
- package/dist/shared/{nuxt-users.BTVH9EyH.d.ts → nuxt-users.SXaMnaEL.d.ts} +45 -1
- package/dist/utils.d.mts +1 -1
- package/dist/utils.d.ts +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,73 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v1.35.0
|
|
5
|
+
|
|
6
|
+
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.34.7...v1.35.0)
|
|
7
|
+
|
|
8
|
+
### 🚀 Enhancements
|
|
9
|
+
|
|
10
|
+
- Implement google authentication ([e830872](https://github.com/rrd108/nuxt-users/commit/e830872))
|
|
11
|
+
- Add google auth to the playground ([2b43ca0](https://github.com/rrd108/nuxt-users/commit/2b43ca0))
|
|
12
|
+
- Add migration ([0280eca](https://github.com/rrd108/nuxt-users/commit/0280eca))
|
|
13
|
+
- Unregistered user gets error message ([eefbdf5](https://github.com/rrd108/nuxt-users/commit/eefbdf5))
|
|
14
|
+
|
|
15
|
+
### 🩹 Fixes
|
|
16
|
+
|
|
17
|
+
- Prevents database connection issues during build ([8a3aa38](https://github.com/rrd108/nuxt-users/commit/8a3aa38))
|
|
18
|
+
- Add api endpoints to public endpoints ([16aefa9](https://github.com/rrd108/nuxt-users/commit/16aefa9))
|
|
19
|
+
- Completes Google OAuth user fetch on client ([6c95b94](https://github.com/rrd108/nuxt-users/commit/6c95b94))
|
|
20
|
+
|
|
21
|
+
### 📖 Documentation
|
|
22
|
+
|
|
23
|
+
- Fix github link ([28662ca](https://github.com/rrd108/nuxt-users/commit/28662ca))
|
|
24
|
+
- Adds Google OAuth authentication ([463c19f](https://github.com/rrd108/nuxt-users/commit/463c19f))
|
|
25
|
+
- Adds Google OAuth registration ([2e0eebf](https://github.com/rrd108/nuxt-users/commit/2e0eebf))
|
|
26
|
+
|
|
27
|
+
### 🏡 Chore
|
|
28
|
+
|
|
29
|
+
- Lint fix ([8d75930](https://github.com/rrd108/nuxt-users/commit/8d75930))
|
|
30
|
+
- **release:** V1.34.7 ([b9d061c](https://github.com/rrd108/nuxt-users/commit/b9d061c))
|
|
31
|
+
- Remove unused files ([8bd3e58](https://github.com/rrd108/nuxt-users/commit/8bd3e58))
|
|
32
|
+
- Replace googleapis package with google-auth ([0e8e3d4](https://github.com/rrd108/nuxt-users/commit/0e8e3d4))
|
|
33
|
+
- Fix lint errors ([3ed5bcc](https://github.com/rrd108/nuxt-users/commit/3ed5bcc))
|
|
34
|
+
- Update lock ([8cd4a97](https://github.com/rrd108/nuxt-users/commit/8cd4a97))
|
|
35
|
+
|
|
36
|
+
### ✅ Tests
|
|
37
|
+
|
|
38
|
+
- Google OAuth authentication ([7d7bd68](https://github.com/rrd108/nuxt-users/commit/7d7bd68))
|
|
39
|
+
- Add oauth flow test ([b805718](https://github.com/rrd108/nuxt-users/commit/b805718))
|
|
40
|
+
- Fix db setup ([a097c4a](https://github.com/rrd108/nuxt-users/commit/a097c4a))
|
|
41
|
+
- Replace smoke test with middleware test ([7ee1893](https://github.com/rrd108/nuxt-users/commit/7ee1893))
|
|
42
|
+
- Add security checks ([a206962](https://github.com/rrd108/nuxt-users/commit/a206962))
|
|
43
|
+
- Fix ts errors ([d7e6c61](https://github.com/rrd108/nuxt-users/commit/d7e6c61))
|
|
44
|
+
|
|
45
|
+
### ❤️ Contributors
|
|
46
|
+
|
|
47
|
+
- Rrd108 ([@rrd108](https://github.com/rrd108))
|
|
48
|
+
|
|
49
|
+
## v1.34.7
|
|
50
|
+
|
|
51
|
+
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.34.6...v1.34.7)
|
|
52
|
+
|
|
53
|
+
### 🩹 Fixes
|
|
54
|
+
|
|
55
|
+
- Scopes CSS rules to nuxt-users components ([fa98529](https://github.com/rrd108/nuxt-users/commit/fa98529))
|
|
56
|
+
- Prevents database connection issues during build ([b80e6c7](https://github.com/rrd108/nuxt-users/commit/b80e6c7))
|
|
57
|
+
|
|
58
|
+
### 📖 Documentation
|
|
59
|
+
|
|
60
|
+
- Fix github link ([3bc8e96](https://github.com/rrd108/nuxt-users/commit/3bc8e96))
|
|
61
|
+
|
|
62
|
+
### 🏡 Chore
|
|
63
|
+
|
|
64
|
+
- **release:** V1.34.6 ([b84a188](https://github.com/rrd108/nuxt-users/commit/b84a188))
|
|
65
|
+
- Lint fix ([5cfb830](https://github.com/rrd108/nuxt-users/commit/5cfb830))
|
|
66
|
+
|
|
67
|
+
### ❤️ Contributors
|
|
68
|
+
|
|
69
|
+
- Rrd108 ([@rrd108](https://github.com/rrd108))
|
|
70
|
+
|
|
4
71
|
## v1.34.6
|
|
5
72
|
|
|
6
73
|
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.34.5...v1.34.6)
|
package/dist/cli.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { defu } from 'defu';
|
|
|
7
7
|
import { runMigrations, createUser as createUser$1, createUsersTable as createUsersTable$1, createPersonalAccessTokensTable as createPersonalAccessTokensTable$1, createPasswordResetTokensTable as createPasswordResetTokensTable$1, createMigrationsTable as createMigrationsTable$1 } from '../dist/runtime/server/utils/index.js';
|
|
8
8
|
import { g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.BnOQs2LI.mjs';
|
|
9
9
|
import { addActiveToUsers as addActiveToUsers$1 } from '../dist/runtime/server/utils/add-active-to-users.js';
|
|
10
|
+
import { useDb } from '../dist/runtime/server/utils/db.js';
|
|
10
11
|
import '../dist/runtime/server/internal/index.js';
|
|
11
12
|
|
|
12
13
|
const getOptionsFromEnv = () => {
|
|
@@ -245,6 +246,78 @@ const addActiveToUsers = defineCommand({
|
|
|
245
246
|
}
|
|
246
247
|
});
|
|
247
248
|
|
|
249
|
+
const addGoogleOauthFields = defineCommand({
|
|
250
|
+
meta: {
|
|
251
|
+
name: "add-google-oauth-fields",
|
|
252
|
+
description: "Add Google OAuth fields (google_id, profile_picture) to existing users table"
|
|
253
|
+
},
|
|
254
|
+
async run() {
|
|
255
|
+
console.log("[Nuxt Users] Adding Google OAuth fields to users table...");
|
|
256
|
+
const options = await loadOptions();
|
|
257
|
+
const connectorName = options.connector.name;
|
|
258
|
+
const db = await useDb(options);
|
|
259
|
+
const tableName = options.tables.users;
|
|
260
|
+
console.log(`[Nuxt Users] DB:Migrate ${connectorName} Users Table Adding Google OAuth fields...`);
|
|
261
|
+
try {
|
|
262
|
+
if (connectorName === "sqlite") {
|
|
263
|
+
const tableInfo = await db.sql`PRAGMA table_info(${tableName})`;
|
|
264
|
+
const columnNames = tableInfo.rows.map((row) => row.name);
|
|
265
|
+
if (!columnNames.includes("google_id")) {
|
|
266
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id TEXT`;
|
|
267
|
+
console.log("[Nuxt Users] Added google_id column to SQLite users table \u2705");
|
|
268
|
+
}
|
|
269
|
+
if (!columnNames.includes("profile_picture")) {
|
|
270
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN profile_picture TEXT`;
|
|
271
|
+
console.log("[Nuxt Users] Added profile_picture column to SQLite users table \u2705");
|
|
272
|
+
}
|
|
273
|
+
if (!columnNames.includes("last_login_at")) {
|
|
274
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN last_login_at DATETIME`;
|
|
275
|
+
console.log("[Nuxt Users] Added last_login_at column to SQLite users table \u2705");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (connectorName === "mysql") {
|
|
279
|
+
const checkColumns = await db.sql`
|
|
280
|
+
SELECT COLUMN_NAME
|
|
281
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
282
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
283
|
+
AND TABLE_NAME = ${tableName}
|
|
284
|
+
AND COLUMN_NAME IN ('google_id', 'profile_picture')
|
|
285
|
+
`;
|
|
286
|
+
const existingColumns = checkColumns.rows.map((row) => row.COLUMN_NAME);
|
|
287
|
+
if (!existingColumns.includes("google_id")) {
|
|
288
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id VARCHAR(255) UNIQUE`;
|
|
289
|
+
console.log("[Nuxt Users] Added google_id column to MySQL users table \u2705");
|
|
290
|
+
}
|
|
291
|
+
if (!existingColumns.includes("profile_picture")) {
|
|
292
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN profile_picture TEXT`;
|
|
293
|
+
console.log("[Nuxt Users] Added profile_picture column to MySQL users table \u2705");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (connectorName === "postgresql") {
|
|
297
|
+
const checkColumns = await db.sql`
|
|
298
|
+
SELECT column_name
|
|
299
|
+
FROM information_schema.columns
|
|
300
|
+
WHERE table_name = ${tableName}
|
|
301
|
+
AND column_name IN ('google_id', 'profile_picture')
|
|
302
|
+
`;
|
|
303
|
+
const existingColumns = checkColumns.rows.map((row) => row.column_name);
|
|
304
|
+
if (!existingColumns.includes("google_id")) {
|
|
305
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id VARCHAR(255) UNIQUE`;
|
|
306
|
+
console.log("[Nuxt Users] Added google_id column to PostgreSQL users table \u2705");
|
|
307
|
+
}
|
|
308
|
+
if (!existingColumns.includes("profile_picture")) {
|
|
309
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN profile_picture TEXT`;
|
|
310
|
+
console.log("[Nuxt Users] Added profile_picture column to PostgreSQL users table \u2705");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
console.log("[Nuxt Users] Google OAuth fields migration completed successfully! \u2705");
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error("[Nuxt Users] DB:Migration Error:", error);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
248
321
|
const projectInfo = defineCommand({
|
|
249
322
|
meta: {
|
|
250
323
|
name: "project-info",
|
|
@@ -304,6 +377,7 @@ const main = defineCommand({
|
|
|
304
377
|
"create-password-reset-tokens-table": createPasswordResetTokensTable,
|
|
305
378
|
"create-migrations-table": createMigrationsTable,
|
|
306
379
|
"add-active-to-users": addActiveToUsers,
|
|
380
|
+
"add-google-oauth-fields": addGoogleOauthFields,
|
|
307
381
|
"project-info": projectInfo
|
|
308
382
|
}
|
|
309
383
|
});
|
package/dist/module.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.
|
|
2
|
+
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.SXaMnaEL.mjs';
|
|
3
3
|
export * from '../dist/runtime/server/internal/index.js';
|
|
4
4
|
|
|
5
5
|
declare const defaultOptions: ModuleOptions;
|
package/dist/module.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.
|
|
2
|
+
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.SXaMnaEL.js';
|
|
3
3
|
export * from '../dist/runtime/server/internal/index.js';
|
|
4
4
|
|
|
5
5
|
declare const defaultOptions: ModuleOptions;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -65,11 +65,14 @@ const module = defineNuxtModule({
|
|
|
65
65
|
nuxt.options.runtimeConfig.nuxtUsers = {
|
|
66
66
|
...runtimeConfigOptions,
|
|
67
67
|
auth: {
|
|
68
|
-
|
|
68
|
+
tokenExpiration: runtimeConfigOptions.auth.tokenExpiration,
|
|
69
|
+
rememberMeExpiration: runtimeConfigOptions.auth.rememberMeExpiration,
|
|
70
|
+
permissions: runtimeConfigOptions.auth.permissions,
|
|
71
|
+
...runtimeConfigOptions.auth.google && { google: runtimeConfigOptions.auth.google },
|
|
69
72
|
whitelist: (() => {
|
|
70
73
|
const combinedWhitelist = [...defaultOptions.auth?.whitelist || [], ...runtimeConfigOptions.auth?.whitelist || []];
|
|
74
|
+
const apiBasePath = runtimeConfigOptions.apiBasePath || defaultOptions.apiBasePath;
|
|
71
75
|
if (combinedWhitelist.includes("/register")) {
|
|
72
|
-
const apiBasePath = runtimeConfigOptions.apiBasePath || defaultOptions.apiBasePath;
|
|
73
76
|
const registrationEndpoints = [
|
|
74
77
|
"/confirm-email",
|
|
75
78
|
// Page route for email confirmation
|
|
@@ -84,6 +87,17 @@ const module = defineNuxtModule({
|
|
|
84
87
|
}
|
|
85
88
|
});
|
|
86
89
|
}
|
|
90
|
+
if (runtimeConfigOptions.auth?.google) {
|
|
91
|
+
const googleOAuthEndpoints = [
|
|
92
|
+
`${apiBasePath}/auth/google/redirect`,
|
|
93
|
+
`${apiBasePath}/auth/google/callback`
|
|
94
|
+
];
|
|
95
|
+
googleOAuthEndpoints.forEach((endpoint) => {
|
|
96
|
+
if (!combinedWhitelist.includes(endpoint)) {
|
|
97
|
+
combinedWhitelist.push(endpoint);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
87
101
|
return combinedWhitelist;
|
|
88
102
|
})()
|
|
89
103
|
}
|
|
@@ -94,8 +108,8 @@ const module = defineNuxtModule({
|
|
|
94
108
|
auth: {
|
|
95
109
|
whitelist: (() => {
|
|
96
110
|
const combinedWhitelist = [...defaultOptions.auth?.whitelist || [], ...runtimeConfigOptions.auth?.whitelist || []];
|
|
111
|
+
const apiBasePath = runtimeConfigOptions.apiBasePath || defaultOptions.apiBasePath;
|
|
97
112
|
if (combinedWhitelist.includes("/register")) {
|
|
98
|
-
const apiBasePath = runtimeConfigOptions.apiBasePath || defaultOptions.apiBasePath;
|
|
99
113
|
const registrationEndpoints = [
|
|
100
114
|
"/confirm-email",
|
|
101
115
|
// Page route for email confirmation
|
|
@@ -110,6 +124,17 @@ const module = defineNuxtModule({
|
|
|
110
124
|
}
|
|
111
125
|
});
|
|
112
126
|
}
|
|
127
|
+
if (runtimeConfigOptions.auth?.google) {
|
|
128
|
+
const googleOAuthEndpoints = [
|
|
129
|
+
`${apiBasePath}/auth/google/redirect`,
|
|
130
|
+
`${apiBasePath}/auth/google/callback`
|
|
131
|
+
];
|
|
132
|
+
googleOAuthEndpoints.forEach((endpoint) => {
|
|
133
|
+
if (!combinedWhitelist.includes(endpoint)) {
|
|
134
|
+
combinedWhitelist.push(endpoint);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
113
138
|
return combinedWhitelist;
|
|
114
139
|
})(),
|
|
115
140
|
permissions: runtimeConfigOptions.auth?.permissions || defaultOptions.auth.permissions
|
|
@@ -177,6 +202,16 @@ const module = defineNuxtModule({
|
|
|
177
202
|
method: "get",
|
|
178
203
|
handler: resolver.resolve("./runtime/server/api/nuxt-users/confirm-email.get")
|
|
179
204
|
});
|
|
205
|
+
addServerHandler({
|
|
206
|
+
route: `${base}/auth/google/redirect`,
|
|
207
|
+
method: "get",
|
|
208
|
+
handler: resolver.resolve("./runtime/server/api/nuxt-users/auth/google/redirect.get")
|
|
209
|
+
});
|
|
210
|
+
addServerHandler({
|
|
211
|
+
route: `${base}/auth/google/callback`,
|
|
212
|
+
method: "get",
|
|
213
|
+
handler: resolver.resolve("./runtime/server/api/nuxt-users/auth/google/callback.get")
|
|
214
|
+
});
|
|
180
215
|
addServerHandler({
|
|
181
216
|
route: `${base}`,
|
|
182
217
|
method: "get",
|
|
@@ -208,6 +243,14 @@ const module = defineNuxtModule({
|
|
|
208
243
|
nitroConfig.experimental.tasks = true;
|
|
209
244
|
nitroConfig.scanDirs = nitroConfig.scanDirs || [];
|
|
210
245
|
nitroConfig.scanDirs.push(resolver.resolve("./runtime/server/tasks"));
|
|
246
|
+
nitroConfig.prerender = nitroConfig.prerender || {};
|
|
247
|
+
nitroConfig.prerender.ignore = nitroConfig.prerender.ignore || [];
|
|
248
|
+
const apiBasePath = nuxt.options.runtimeConfig.nuxtUsers.apiBasePath || defaultOptions.apiBasePath;
|
|
249
|
+
const ignorePattern = `${apiBasePath}/**`;
|
|
250
|
+
if (!nitroConfig.prerender.ignore.includes(ignorePattern)) {
|
|
251
|
+
nitroConfig.prerender.ignore.push(ignorePattern);
|
|
252
|
+
console.log(`[Nuxt Users] \u{1F527} Automatically excluding "${ignorePattern}" from prerendering to prevent build hangs`);
|
|
253
|
+
}
|
|
211
254
|
});
|
|
212
255
|
addComponentsDir({
|
|
213
256
|
path: resolver.resolve("./runtime/components"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--nu-color-primary:#3b82f6;--nu-color-primary-dark:#2563eb;--nu-color-primary-light:#60a5fa;--nu-color-white:#fff;--nu-color-gray-50:#f9fafb;--nu-color-gray-500:#6b7280;--nu-color-gray-600:#4b5563;--nu-color-gray-700:#374151;--nu-color-gray-900:#111827;--nu-color-success:#059669;--nu-color-success-light:#f0fdf4;--nu-color-success-border:#9ae6b4;--nu-color-error:#dc2626;--nu-color-error-light:#fef2f2;--nu-color-error-border:#fecaca;--nu-color-error-alt:#db5050;--nu-color-error-dark:#b91c1c;--nu-color-border:#e5e7eb;--nu-color-border-light:#d1d5db;--nu-color-border-dark:#ccc;--nu-color-bg-primary:#fff;--nu-color-bg-secondary:#f9fafb;--nu-color-bg-tertiary:#f3f4f6;--nu-color-text-primary:#000;--nu-color-text-secondary:#6b7280;--nu-color-strength-bg:#e9ecef;--nu-color-validation-passed:#28a745;--nu-color-validation-failed:#dc3545;--nu-color-validation-hint:#6c757d}.dark :root{--nu-color-primary:#60a5fa;--nu-color-primary-dark:#3b82f6;--nu-color-primary-light:#93c5fd;--nu-color-white:#000;--nu-color-gray-50:#111827;--nu-color-gray-500:#9ca3af;--nu-color-gray-600:#d1d5db;--nu-color-gray-700:#e5e7eb;--nu-color-gray-900:#f9fafb;--nu-color-success:#10b981;--nu-color-success-light:#064e3b;--nu-color-success-border:#065f46;--nu-color-error:#ef4444;--nu-color-error-light:#450a0a;--nu-color-error-border:#7f1d1d;--nu-color-error-alt:#f87171;--nu-color-error-dark:#dc2626;--nu-color-border:#374151;--nu-color-border-light:#4b5563;--nu-color-border-dark:#6b7280;--nu-color-bg-primary:#111827;--nu-color-bg-secondary:#1f2937;--nu-color-bg-tertiary:#374151;--nu-color-text-primary:#fff;--nu-color-text-secondary:#9ca3af;--nu-color-strength-bg:#374151;--nu-color-validation-passed:#22c55e;--nu-color-validation-failed:#ef4444;--nu-color-validation-hint:#9ca3af}.n-users-form-group{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;width:100%}.n-users-form-group.n-users-form-group-narrow{width:90%}.n-users-form-group label,.n-users-form-label{color:var(--nu-color-text-primary,#000);display:block;font-size:1rem;font-weight:500;margin-bottom:.5rem}.n-users-form-group input,.n-users-form-group select,.n-users-form-input{border:1px solid var(--nu-color-border,#ccc);border-radius:.25rem;font-size:1rem;padding:.5rem;transition:border-color .2s,box-shadow .2s;width:100%}.n-users-form-group input:focus,.n-users-form-group select:focus,.n-users-form-input:focus{border-color:var(--nu-color-primary);box-shadow:0 0 0 .1rem rgba(59,130,246,.1);outline:none}.n-users-form-group input:disabled,.n-users-form-input:disabled{background-color:var(--nu-color-bg-secondary,#f9fafb);cursor:not-allowed}.n-users-form-group input.error,.n-users-form-input.error{border-color:var(--nu-color-error,#dc3545)}.n-users-form-group input[type=email],.n-users-form-group input[type=password],.n-users-form-group input[type=text]{background-color:var(--nu-color-bg-secondary);border:1px solid var(--nu-color-border-light);border-radius:.5em;box-sizing:border-box;color:var(--nu-color-gray-700);font-size:.875rem;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}.n-users-form-group input[type=email]:focus,.n-users-form-group input[type=password]:focus,.n-users-form-group input[type=text]:focus{background-color:var(--nu-color-bg-primary);border-color:var(--nu-color-primary);box-shadow:0 0 0 3px rgba(59,130,246,.1);outline:none}.n-users-form-group input[type=checkbox]{margin:0;width:auto}.n-users-btn,.n-users-cancel-btn,.n-users-delete-btn,.n-users-edit-btn,.n-users-submit-btn{align-self:flex-start;border:none;border-radius:.25rem;cursor:pointer;display:inline-block;font-size:1rem;font-weight:500;padding:.5rem 1rem;text-decoration:none;transition:background-color .2s,transform .1s}.n-users-btn:hover,.n-users-edit-btn:hover,.n-users-submit-btn:hover{background-color:var(--nu-color-primary-dark,#2563eb);transform:translateY(-1px)}.n-users-btn:disabled,.n-users-submit-btn:disabled{cursor:not-allowed;opacity:.6;transform:none}.n-users-btn-primary,.n-users-edit-btn,.n-users-submit-btn{background-color:var(--nu-color-primary,#3b82f6);color:#fff}.n-users-btn-secondary,.n-users-cancel-btn{background-color:var(--nu-color-gray-500,#6b7280);color:#fff}.n-users-cancel-btn:hover{background-color:var(--nu-color-gray-600,#4b5563)}.n-users-delete-btn{background-color:var(--nu-color-error,#ef4444);color:#fff}.n-users-delete-btn:hover{background-color:var(--nu-color-error-alt,#dc2626)}.n-users-login-section button[type=submit],.n-users-register-section button[type=submit],.n-users-reset-password-container button[type=submit],.n-users-user-form button[type=submit],form.n-users-form button[type=submit]{align-items:center;background-color:var(--nu-color-primary);border:1px solid var(--nu-color-primary);border-radius:.5em;color:var(--nu-color-white);cursor:pointer;display:flex;font-size:.875rem;font-weight:500;gap:.5rem;justify-content:center;min-height:44px;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}.n-users-login-section button[type=submit]:hover:not(:disabled),.n-users-register-section button[type=submit]:hover:not(:disabled),.n-users-reset-password-container button[type=submit]:hover:not(:disabled),.n-users-user-form button[type=submit]:hover:not(:disabled),form.n-users-form button[type=submit]:hover:not(:disabled){background-color:var(--nu-color-primary-dark);border-color:var(--nu-color-primary-dark)}.n-users-login-section button[type=submit]:disabled,.n-users-register-section button[type=submit]:disabled,.n-users-reset-password-container button[type=submit]:disabled,.n-users-user-form button[type=submit]:disabled,form.n-users-form button[type=submit]:disabled{cursor:not-allowed;opacity:.6}.n-users-error-message,.n-users-error-text{background-color:var(--nu-color-error-light,#fef2f2);border:1px solid var(--nu-color-error-border,#fecaca);border-radius:.5rem;color:var(--nu-color-error,#dc3545);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-success-message{background-color:var(--nu-color-success-light,#f0fff4);border:1px solid var(--nu-color-success-border,#9ae6b4);border-radius:.5rem;color:var(--nu-color-success,#10b981);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-grid{display:grid;gap:1rem}.n-users-grid-2{grid-template-columns:1fr 5fr}.n-users-grid-auto{grid-template-columns:repeat(auto-fill,minmax(12em,1fr))}.n-users-form-actions{align-items:center;display:flex;gap:1rem;margin-top:1.5rem}.n-users-loading-spinner{animation:n-users-spin 1s linear infinite;border:2px solid var(--nu-color-white,#fff);border-radius:50%;border-top:2px solid transparent;height:16px;width:16px}@keyframes n-users-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.n-users-card{background-color:var(--nu-color-bg-primary,#fff);border:1px solid var(--nu-color-border,#e5e7eb);border-radius:.5rem;padding:1rem}.n-users-section{margin:0 auto;max-width:42em;padding:1em}.n-users-section-header{border-bottom:.1em solid var(--nu-color-border,#e5e7eb);margin-bottom:1rem;padding-bottom:.5rem}.n-users-form-help{color:var(--nu-color-text-secondary,#6b7280);font-size:.75rem;margin-top:.25rem}.n-users-strength-bar{background-color:var(--nu-color-strength-bg,#e9ecef);border-radius:2px;height:4px;margin-bottom:4px;overflow:hidden;width:100%}.n-users-strength-fill{height:100%;transition:width .3s ease,background-color .3s ease}.n-users-strength-text{font-size:.75rem;font-weight:500}.n-users-validation-container{background-color:#f8f9fa;border:1px solid #e9ecef;border-radius:4px;margin-top:12px;padding:12px}.n-users-container-title{color:#495057;font-size:.875rem;font-weight:600;margin-bottom:8px}.n-users-list-unstyled{list-style:none;margin:0;padding-left:0}.n-users-list-item{align-items:center;display:flex;font-size:.875rem;margin-bottom:6px;transition:color .2s ease}.n-users-list-item:last-child{margin-bottom:0}.n-users-icon{font-weight:700;margin-right:8px;min-width:16px;text-align:center}.n-users-text{flex:1}.n-users-list ul{list-style:none}.n-users-login-section form,.n-users-register-section form,.n-users-reset-password-container form,.n-users-user-form form,form.n-users-form{display:flex;flex-direction:column;gap:1.5rem}.n-users-login-section{background:linear-gradient(135deg,var(--nu-color-bg-primary) 0,var(--nu-color-bg-secondary) 100%);border:1px solid var(--nu-color-border);border-radius:.75em;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);max-width:25rem}.n-users-login-header{margin-bottom:1em;text-align:center}.n-users-login-title{color:var(--nu-color-gray-700);font-size:1.875rem;font-weight:700;line-height:1.2;margin:0 0 .5em}.n-users-login-subtitle{color:var(--nu-color-gray-700);font-size:.875rem;line-height:1.5;margin:0}.n-users-remember-me{align-items:center;display:flex;gap:.5rem}.n-users-remember-me label{color:var(--nu-color-gray-700);font-size:.875rem;margin:0}.n-users-login-footer{margin-top:1em;text-align:center}.n-users-forgot-password{margin:0}.n-users-forgot-link{align-items:center;color:var(--nu-color-primary);display:inline-flex;font-size:.875rem;gap:.5rem;text-decoration:none;transition:color .2s ease-in-out}.n-users-forgot-link:hover:not(.loading){color:var(--nu-color-primary-dark);text-decoration:underline}.n-users-forgot-link.loading{cursor:not-allowed;opacity:.6;pointer-events:none}.n-users-password-strength{margin-top:8px}.n-users-validation-rules{margin-top:12px}.n-users-rules-title{margin-bottom:8px}.n-users-rules-list{padding-left:0}.n-users-rule-item{margin-bottom:6px}.n-users-rule-item:last-child{margin-bottom:0}.n-users-rule-passed{color:var(--nu-color-validation-passed,#28a745)}.n-users-rule-failed{color:var(--nu-color-validation-failed,#dc3545)}.n-users-rule-icon{margin-right:8px}.n-users-rule-text{flex:1}.n-users-validation-errors{margin-top:8px}.n-users-error-text:last-child{margin-bottom:0}.n-users-password-hints{margin-top:8px}.n-users-hint-title{margin-bottom:8px}.n-users-hint-list{padding-left:16px}.n-users-hint-item{color:var(--nu-color-validation-hint,#6c757d);margin-bottom:4px}.n-users-hint-item:last-child{margin-bottom:0}.n-users-logout-link{background:var(--nu-color-error);border:2px solid transparent;border-color:var(--nu-color-error-dark);border-radius:.5em;color:#fff;padding:.5em 1em;text-decoration:none}.n-users-logout-link:hover{border-color:var(--nu-color-primary)}.n-users-user-card dd{margin:0}.n-users-user-card-actions{display:flex;gap:.5rem;margin-top:1rem}.n-users-user-form{margin:0 auto;max-width:500px}.n-users-form-group input.error{border-color:#dc3545}.n-users-profile-info{background-color:var(--nu-color-bg-primary);border-top-left-radius:.5em;border-top-right-radius:.5em}.n-users-profile-details{font-size:1rem;list-style:none;margin:0;padding:0}.n-users-reset-password-container{background-color:var(--nu-color-bg-primary);border-bottom-left-radius:.5em;border-bottom-right-radius:.5em}@media (max-width:480px){.n-users-login-title{font-size:1.5rem}}.n-users-list-enter-active,.n-users-list-leave-active,.n-users-list-move{transition:all .3s ease}.n-users-list-enter-from,.n-users-list-leave-to{opacity:0;transform:translateX(2rem)}.n-users-list-leave-active{position:absolute}.n-users-list-move{transition-timing-function:ease}
|
|
1
|
+
:root{--nu-color-primary:#3b82f6;--nu-color-primary-dark:#2563eb;--nu-color-primary-light:#60a5fa;--nu-color-white:#fff;--nu-color-gray-50:#f9fafb;--nu-color-gray-500:#6b7280;--nu-color-gray-600:#4b5563;--nu-color-gray-700:#374151;--nu-color-gray-900:#111827;--nu-color-success:#059669;--nu-color-success-light:#f0fdf4;--nu-color-success-border:#9ae6b4;--nu-color-error:#dc2626;--nu-color-error-light:#fef2f2;--nu-color-error-border:#fecaca;--nu-color-error-alt:#db5050;--nu-color-error-dark:#b91c1c;--nu-color-border:#e5e7eb;--nu-color-border-light:#d1d5db;--nu-color-border-dark:#ccc;--nu-color-bg-primary:#fff;--nu-color-bg-secondary:#f9fafb;--nu-color-bg-tertiary:#f3f4f6;--nu-color-text-primary:#000;--nu-color-text-secondary:#6b7280;--nu-color-strength-bg:#e9ecef;--nu-color-validation-passed:#28a745;--nu-color-validation-failed:#dc3545;--nu-color-validation-hint:#6c757d}.dark :root{--nu-color-primary:#60a5fa;--nu-color-primary-dark:#3b82f6;--nu-color-primary-light:#93c5fd;--nu-color-white:#000;--nu-color-gray-50:#111827;--nu-color-gray-500:#9ca3af;--nu-color-gray-600:#d1d5db;--nu-color-gray-700:#e5e7eb;--nu-color-gray-900:#f9fafb;--nu-color-success:#10b981;--nu-color-success-light:#064e3b;--nu-color-success-border:#065f46;--nu-color-error:#ef4444;--nu-color-error-light:#450a0a;--nu-color-error-border:#7f1d1d;--nu-color-error-alt:#f87171;--nu-color-error-dark:#dc2626;--nu-color-border:#374151;--nu-color-border-light:#4b5563;--nu-color-border-dark:#6b7280;--nu-color-bg-primary:#111827;--nu-color-bg-secondary:#1f2937;--nu-color-bg-tertiary:#374151;--nu-color-text-primary:#fff;--nu-color-text-secondary:#9ca3af;--nu-color-strength-bg:#374151;--nu-color-validation-passed:#22c55e;--nu-color-validation-failed:#ef4444;--nu-color-validation-hint:#9ca3af}.n-users-form-group{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;width:100%}.n-users-form-group.n-users-form-group-narrow{width:90%}.n-users-form-group label,.n-users-form-label{color:var(--nu-color-text-primary,#000);display:block;font-size:1rem;font-weight:500;margin-bottom:.5rem}.n-users-form-group input,.n-users-form-group select,.n-users-form-input{border:1px solid var(--nu-color-border,#ccc);border-radius:.25rem;font-size:1rem;padding:.5rem;transition:border-color .2s,box-shadow .2s;width:100%}.n-users-form-group input:focus,.n-users-form-group select:focus,.n-users-form-input:focus{border-color:var(--nu-color-primary);box-shadow:0 0 0 .1rem rgba(59,130,246,.1);outline:none}.n-users-form-group input:disabled,.n-users-form-input:disabled{background-color:var(--nu-color-bg-secondary,#f9fafb);cursor:not-allowed}.n-users-form-group input.error,.n-users-form-input.error{border-color:var(--nu-color-error,#dc3545)}.n-users-form-group input[type=email],.n-users-form-group input[type=password],.n-users-form-group input[type=text]{background-color:var(--nu-color-bg-secondary);border:1px solid var(--nu-color-border-light);border-radius:.5em;box-sizing:border-box;color:var(--nu-color-gray-700);font-size:.875rem;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}.n-users-form-group input[type=email]:focus,.n-users-form-group input[type=password]:focus,.n-users-form-group input[type=text]:focus{background-color:var(--nu-color-bg-primary);border-color:var(--nu-color-primary);box-shadow:0 0 0 3px rgba(59,130,246,.1);outline:none}.n-users-form-group input[type=checkbox]{margin:0;width:auto}.n-users-google-btn{align-items:center;display:inline-flex;gap:8px;justify-content:center}.n-users-google-logo{flex-shrink:0}.n-users-btn,.n-users-cancel-btn,.n-users-delete-btn,.n-users-edit-btn,.n-users-google-btn,.n-users-submit-btn{align-self:flex-start;border:none;border-radius:.25rem;cursor:pointer;display:inline-block;font-size:1rem;font-weight:500;padding:.5rem 1rem;text-decoration:none;transition:background-color .2s,transform .1s}.n-users-btn:hover,.n-users-edit-btn:hover,.n-users-submit-btn:hover{background-color:var(--nu-color-primary-dark,#2563eb);transform:translateY(-1px)}.n-users-btn:disabled,.n-users-submit-btn:disabled{cursor:not-allowed;opacity:.6;transform:none}.n-users-btn-primary,.n-users-edit-btn,.n-users-submit-btn{background-color:var(--nu-color-primary,#3b82f6);color:#fff}.n-users-btn-secondary,.n-users-cancel-btn{background-color:var(--nu-color-gray-500,#6b7280);color:#fff}.n-users-cancel-btn:hover{background-color:var(--nu-color-gray-600,#4b5563)}.n-users-delete-btn{background-color:var(--nu-color-error,#ef4444);color:#fff}.n-users-delete-btn:hover{background-color:var(--nu-color-error-alt,#dc2626)}.n-users-login-section button[type=submit],.n-users-register-section button[type=submit],.n-users-reset-password-container button[type=submit],.n-users-user-form button[type=submit],form.n-users-form button[type=submit]{align-items:center;background-color:var(--nu-color-primary);border:1px solid var(--nu-color-primary);border-radius:.5em;color:var(--nu-color-white);cursor:pointer;display:flex;font-size:.875rem;font-weight:500;gap:.5rem;justify-content:center;min-height:44px;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}.n-users-login-section button[type=submit]:hover:not(:disabled),.n-users-register-section button[type=submit]:hover:not(:disabled),.n-users-reset-password-container button[type=submit]:hover:not(:disabled),.n-users-user-form button[type=submit]:hover:not(:disabled),form.n-users-form button[type=submit]:hover:not(:disabled){background-color:var(--nu-color-primary-dark);border-color:var(--nu-color-primary-dark)}.n-users-login-section button[type=submit]:disabled,.n-users-register-section button[type=submit]:disabled,.n-users-reset-password-container button[type=submit]:disabled,.n-users-user-form button[type=submit]:disabled,form.n-users-form button[type=submit]:disabled{cursor:not-allowed;opacity:.6}.n-users-error-message,.n-users-error-text{background-color:var(--nu-color-error-light,#fef2f2);border:1px solid var(--nu-color-error-border,#fecaca);border-radius:.5rem;color:var(--nu-color-error,#dc3545);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-success-message{background-color:var(--nu-color-success-light,#f0fff4);border:1px solid var(--nu-color-success-border,#9ae6b4);border-radius:.5rem;color:var(--nu-color-success,#10b981);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-grid{display:grid;gap:1rem}.n-users-grid-2{grid-template-columns:1fr 5fr}.n-users-grid-auto{grid-template-columns:repeat(auto-fill,minmax(12em,1fr))}.n-users-form-actions{align-items:center;display:flex;gap:1rem;margin-top:1.5rem}.n-users-loading-spinner{animation:n-users-spin 1s linear infinite;border:2px solid var(--nu-color-white,#fff);border-radius:50%;border-top:2px solid transparent;height:16px;width:16px}@keyframes n-users-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.n-users-card{background-color:var(--nu-color-bg-primary,#fff);border:1px solid var(--nu-color-border,#e5e7eb);border-radius:.5rem;padding:1rem}.n-users-section{margin:0 auto;max-width:42em;padding:1em}.n-users-section-header{border-bottom:.1em solid var(--nu-color-border,#e5e7eb);margin-bottom:1rem;padding-bottom:.5rem}.n-users-form-help{color:var(--nu-color-text-secondary,#6b7280);font-size:.75rem;margin-top:.25rem}.n-users-strength-bar{background-color:var(--nu-color-strength-bg,#e9ecef);border-radius:2px;height:4px;margin-bottom:4px;overflow:hidden;width:100%}.n-users-strength-fill{height:100%;transition:width .3s ease,background-color .3s ease}.n-users-strength-text{font-size:.75rem;font-weight:500}.n-users-validation-container{background-color:#f8f9fa;border:1px solid #e9ecef;border-radius:4px;margin-top:12px;padding:12px}.n-users-container-title{color:#495057;font-size:.875rem;font-weight:600;margin-bottom:8px}.n-users-list-unstyled{list-style:none;margin:0;padding-left:0}.n-users-list-item{align-items:center;display:flex;font-size:.875rem;margin-bottom:6px;transition:color .2s ease}.n-users-list-item:last-child{margin-bottom:0}.n-users-icon{font-weight:700;margin-right:8px;min-width:16px;text-align:center}.n-users-text{flex:1}.n-users-list ul{list-style:none}.n-users-login-section form,.n-users-register-section form,.n-users-reset-password-container form,.n-users-user-form form,form.n-users-form{display:flex;flex-direction:column;gap:1.5rem}.n-users-login-section{background:linear-gradient(135deg,var(--nu-color-bg-primary) 0,var(--nu-color-bg-secondary) 100%);border:1px solid var(--nu-color-border);border-radius:.75em;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);max-width:25rem}.n-users-login-header{margin-bottom:1em;text-align:center}.n-users-login-title{color:var(--nu-color-gray-700);font-size:1.875rem;font-weight:700;line-height:1.2;margin:0 0 .5em}.n-users-login-subtitle{color:var(--nu-color-gray-700);font-size:.875rem;line-height:1.5;margin:0}.n-users-remember-me{align-items:center;display:flex;gap:.5rem}.n-users-remember-me label{color:var(--nu-color-gray-700);font-size:.875rem;margin:0}.n-users-login-footer{margin-top:1em;text-align:center}.n-users-forgot-password{margin:0}.n-users-forgot-link{align-items:center;color:var(--nu-color-primary);display:inline-flex;font-size:.875rem;gap:.5rem;text-decoration:none;transition:color .2s ease-in-out}.n-users-forgot-link:hover:not(.loading){color:var(--nu-color-primary-dark);text-decoration:underline}.n-users-forgot-link.loading{cursor:not-allowed;opacity:.6;pointer-events:none}.n-users-password-strength{margin-top:8px}.n-users-validation-rules{margin-top:12px}.n-users-rules-title{margin-bottom:8px}.n-users-rules-list{padding-left:0}.n-users-rule-item{margin-bottom:6px}.n-users-rule-item:last-child{margin-bottom:0}.n-users-rule-passed{color:var(--nu-color-validation-passed,#28a745)}.n-users-rule-failed{color:var(--nu-color-validation-failed,#dc3545)}.n-users-rule-icon{margin-right:8px}.n-users-rule-text{flex:1}.n-users-validation-errors{margin-top:8px}.n-users-error-text:last-child{margin-bottom:0}.n-users-password-hints{margin-top:8px}.n-users-hint-title{margin-bottom:8px}.n-users-hint-list{padding-left:16px}.n-users-hint-item{color:var(--nu-color-validation-hint,#6c757d);margin-bottom:4px}.n-users-hint-item:last-child{margin-bottom:0}.n-users-logout-link{background:var(--nu-color-error);border:2px solid transparent;border-color:var(--nu-color-error-dark);border-radius:.5em;color:#fff;padding:.5em 1em;text-decoration:none}.n-users-logout-link:hover{border-color:var(--nu-color-primary)}.n-users-user-card dd{margin:0}.n-users-user-card-actions{display:flex;gap:.5rem;margin-top:1rem}.n-users-user-form{margin:0 auto;max-width:500px}.n-users-form-group input.error{border-color:#dc3545}.n-users-profile-info{background-color:var(--nu-color-bg-primary);border-top-left-radius:.5em;border-top-right-radius:.5em}.n-users-profile-details{font-size:1rem;list-style:none;margin:0;padding:0}.n-users-reset-password-container{background-color:var(--nu-color-bg-primary);border-bottom-left-radius:.5em;border-bottom-right-radius:.5em}@media (max-width:480px){.n-users-login-title{font-size:1.5rem}}.n-users-list-enter-active,.n-users-list-leave-active,.n-users-list-move{transition:all .3s ease}.n-users-list-enter-from,.n-users-list-leave-to{opacity:0;transform:translateX(2rem)}.n-users-list-leave-active{position:absolute}.n-users-list-move{transition-timing-function:ease}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed } from "vue";
|
|
3
|
+
import { useRuntimeConfig, navigateTo } from "#imports";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
redirectEndpoint: { type: String, required: false },
|
|
6
|
+
buttonText: { type: String, required: false, default: "Continue with Google" },
|
|
7
|
+
showLogo: { type: Boolean, required: false, default: true },
|
|
8
|
+
class: { type: String, required: false }
|
|
9
|
+
});
|
|
10
|
+
const emit = defineEmits(["click"]);
|
|
11
|
+
const { public: { nuxtUsers } } = useRuntimeConfig();
|
|
12
|
+
const { apiBasePath } = nuxtUsers;
|
|
13
|
+
const isLoading = ref(false);
|
|
14
|
+
const redirectEndpoint = computed(
|
|
15
|
+
() => props.redirectEndpoint || `${apiBasePath}/auth/google/redirect`
|
|
16
|
+
);
|
|
17
|
+
const handleGoogleLogin = async () => {
|
|
18
|
+
isLoading.value = true;
|
|
19
|
+
emit("click");
|
|
20
|
+
try {
|
|
21
|
+
await navigateTo(redirectEndpoint.value, { external: true });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error("[Nuxt Users] Google OAuth redirect failed:", error);
|
|
24
|
+
} finally {
|
|
25
|
+
isLoading.value = false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
class="n-users-google-btn"
|
|
34
|
+
:class="[{ loading: isLoading }, props.class]"
|
|
35
|
+
:disabled="isLoading"
|
|
36
|
+
@click="handleGoogleLogin"
|
|
37
|
+
>
|
|
38
|
+
<!-- Loading spinner -->
|
|
39
|
+
<span
|
|
40
|
+
v-if="isLoading"
|
|
41
|
+
class="n-users-loading-spinner"
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
<!-- Google logo SVG -->
|
|
45
|
+
<svg
|
|
46
|
+
v-if="showLogo && !isLoading"
|
|
47
|
+
class="n-users-google-logo"
|
|
48
|
+
viewBox="0 0 24 24"
|
|
49
|
+
width="18"
|
|
50
|
+
height="18"
|
|
51
|
+
>
|
|
52
|
+
<path
|
|
53
|
+
fill="#4285F4"
|
|
54
|
+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
55
|
+
/>
|
|
56
|
+
<path
|
|
57
|
+
fill="#34A853"
|
|
58
|
+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
|
59
|
+
/>
|
|
60
|
+
<path
|
|
61
|
+
fill="#FBBC05"
|
|
62
|
+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
|
63
|
+
/>
|
|
64
|
+
<path
|
|
65
|
+
fill="#EA4335"
|
|
66
|
+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
|
67
|
+
/>
|
|
68
|
+
</svg>
|
|
69
|
+
|
|
70
|
+
<!-- Button text -->
|
|
71
|
+
<span class="n-users-google-text">
|
|
72
|
+
{{ isLoading ? "Redirecting..." : buttonText }}
|
|
73
|
+
</span>
|
|
74
|
+
</button>
|
|
75
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/**
|
|
3
|
+
* Custom redirect endpoint for Google OAuth
|
|
4
|
+
* @default '/api/nuxt-users/auth/google/redirect'
|
|
5
|
+
*/
|
|
6
|
+
redirectEndpoint?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Button text
|
|
9
|
+
* @default 'Continue with Google'
|
|
10
|
+
*/
|
|
11
|
+
buttonText?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Show Google logo in button
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
showLogo?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Custom CSS class for button
|
|
19
|
+
*/
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
23
|
+
click: () => any;
|
|
24
|
+
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
25
|
+
onClick?: (() => any) | undefined;
|
|
26
|
+
}>, {
|
|
27
|
+
buttonText: string;
|
|
28
|
+
showLogo: boolean;
|
|
29
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
30
|
+
export default _default;
|
|
@@ -4,6 +4,6 @@ export declare const useAuthentication: () => {
|
|
|
4
4
|
isAuthenticated: import("vue").ComputedRef<boolean>;
|
|
5
5
|
login: (userData: User, rememberMe?: boolean) => void;
|
|
6
6
|
logout: () => Promise<void>;
|
|
7
|
-
fetchUser: () => Promise<any>;
|
|
7
|
+
fetchUser: (useSSR?: boolean) => Promise<any>;
|
|
8
8
|
initializeUser: () => Promise<void>;
|
|
9
9
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useRuntimeConfig } from "#app";
|
|
1
|
+
import { useState, useRuntimeConfig, useFetch } from "#app";
|
|
2
2
|
import { computed, readonly } from "vue";
|
|
3
3
|
let initializationPromise = null;
|
|
4
4
|
export const useAuthentication = () => {
|
|
@@ -36,22 +36,40 @@ export const useAuthentication = () => {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
-
const fetchUser = async () => {
|
|
39
|
+
const fetchUser = async (useSSR = false) => {
|
|
40
40
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
let userData = null;
|
|
42
|
+
if (useSSR) {
|
|
43
|
+
const { data, error } = await useFetch(`${apiBasePath}/me`, {
|
|
44
|
+
server: true,
|
|
45
|
+
lazy: false
|
|
46
|
+
});
|
|
47
|
+
if (error.value) {
|
|
48
|
+
throw new Error(error.value.message || "Failed to fetch user");
|
|
49
|
+
}
|
|
50
|
+
userData = data.value?.user || null;
|
|
51
|
+
} else {
|
|
52
|
+
const response = await $fetch(`${apiBasePath}/me`, {
|
|
53
|
+
method: "GET"
|
|
54
|
+
});
|
|
55
|
+
userData = response.user;
|
|
56
|
+
}
|
|
57
|
+
if (!userData) {
|
|
58
|
+
throw new Error("No user data received");
|
|
59
|
+
}
|
|
60
|
+
user.value = userData;
|
|
43
61
|
if (import.meta.client) {
|
|
44
62
|
const wasInLocalStorage = localStorage.getItem("user") !== null;
|
|
45
63
|
const wasInSessionStorage = sessionStorage.getItem("user") !== null;
|
|
46
64
|
if (wasInLocalStorage) {
|
|
47
|
-
localStorage.setItem("user", JSON.stringify(
|
|
65
|
+
localStorage.setItem("user", JSON.stringify(userData));
|
|
48
66
|
} else if (wasInSessionStorage) {
|
|
49
|
-
sessionStorage.setItem("user", JSON.stringify(
|
|
67
|
+
sessionStorage.setItem("user", JSON.stringify(userData));
|
|
50
68
|
} else {
|
|
51
|
-
sessionStorage.setItem("user", JSON.stringify(
|
|
69
|
+
sessionStorage.setItem("user", JSON.stringify(userData));
|
|
52
70
|
}
|
|
53
71
|
}
|
|
54
|
-
return
|
|
72
|
+
return userData;
|
|
55
73
|
} catch (error) {
|
|
56
74
|
console.error("Failed to fetch user:", error);
|
|
57
75
|
user.value = null;
|
|
@@ -2,7 +2,7 @@ import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig } from "#app";
|
|
|
2
2
|
import { useAuthentication } from "../composables/useAuthentication.js";
|
|
3
3
|
import { hasPermission, isWhitelisted } from "../utils/permissions.js";
|
|
4
4
|
import { NO_AUTH_PATHS, NO_AUTH_API_PATHS } from "../constants.js";
|
|
5
|
-
export default defineNuxtRouteMiddleware((to) => {
|
|
5
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
6
6
|
const { public: { nuxtUsers: publicNuxtUsers } } = useRuntimeConfig();
|
|
7
7
|
const publicOptions = publicNuxtUsers;
|
|
8
8
|
const base = publicOptions.apiBasePath || "/api/nuxt-users";
|
|
@@ -18,7 +18,14 @@ export default defineNuxtRouteMiddleware((to) => {
|
|
|
18
18
|
console.log(`[Nuxt Users] client.middleware.auth.global: Whitelisted: ${to.path}`);
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
|
-
const { isAuthenticated, user } = useAuthentication();
|
|
21
|
+
const { isAuthenticated, user, fetchUser } = useAuthentication();
|
|
22
|
+
if (!isAuthenticated.value && to.query?.oauth_success === "true") {
|
|
23
|
+
try {
|
|
24
|
+
await fetchUser(true);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error("[Nuxt Users] Failed to fetch user after OAuth:", error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
22
29
|
if (!isAuthenticated.value) {
|
|
23
30
|
console.log(`[Nuxt Users] client.middleware.auth.global: Unauthenticated ${to.path}, redirecting to /login`);
|
|
24
31
|
return navigateTo("/login");
|
|
@@ -27,9 +34,4 @@ export default defineNuxtRouteMiddleware((to) => {
|
|
|
27
34
|
console.log(`[Nuxt Users] client.middleware.auth.global: User with role ${user.value?.role} denied access to ${to.path}`);
|
|
28
35
|
return navigateTo("/login");
|
|
29
36
|
}
|
|
30
|
-
console.log("[Nuxt Users] client.middleware.auth.global", {
|
|
31
|
-
isAuthenticated: isAuthenticated.value,
|
|
32
|
-
userRole: user.value?.role,
|
|
33
|
-
to: to.path
|
|
34
|
-
});
|
|
35
37
|
});
|
package/dist/runtime/plugin.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { defineNuxtPlugin, useRuntimeConfig } from "#app";
|
|
2
|
-
import { checkTableExists } from "./server/utils/index.js";
|
|
2
|
+
import { checkTableExists, isBuildTime } from "./server/utils/index.js";
|
|
3
3
|
export default defineNuxtPlugin(async (_nuxtApp) => {
|
|
4
4
|
const { nuxtUsers } = useRuntimeConfig();
|
|
5
5
|
const options = nuxtUsers;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
if (isBuildTime()) {
|
|
7
|
+
console.log("[Nuxt Users] \u{1F527} Build-time detected, skipping database connection checks");
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const hasMigrationsTable = await checkTableExists(options, options.tables.migrations);
|
|
12
|
+
if (!hasMigrationsTable) {
|
|
13
|
+
console.warn("[Nuxt Users] \u26A0\uFE0F Migrations table does not exist, you should run the migration script to create it by running: npx nuxt-users migrate");
|
|
14
|
+
}
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.warn("[Nuxt Users] \u26A0\uFE0F Database connection failed during initialization:", error instanceof Error ? error.message : "Unknown error");
|
|
9
17
|
}
|
|
10
18
|
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { defineEventHandler, sendRedirect, getQuery, setCookie } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import {
|
|
4
|
+
createGoogleOAuth2Client,
|
|
5
|
+
getGoogleUserFromCode,
|
|
6
|
+
findOrCreateGoogleUser,
|
|
7
|
+
createAuthTokenForUser
|
|
8
|
+
} from "../../../../utils/google-oauth.js";
|
|
9
|
+
export default defineEventHandler(async (event) => {
|
|
10
|
+
const { nuxtUsers } = useRuntimeConfig();
|
|
11
|
+
const options = nuxtUsers;
|
|
12
|
+
const query = getQuery(event);
|
|
13
|
+
if (!options.auth.google) {
|
|
14
|
+
return sendRedirect(event, "/login?error=oauth_not_configured");
|
|
15
|
+
}
|
|
16
|
+
if (query.error) {
|
|
17
|
+
console.error("[Nuxt Users] Google OAuth error:", query.error);
|
|
18
|
+
const errorRedirect = options.auth.google.errorRedirect || "/login?error=oauth_failed";
|
|
19
|
+
return sendRedirect(event, errorRedirect);
|
|
20
|
+
}
|
|
21
|
+
if (!query.code || typeof query.code !== "string") {
|
|
22
|
+
console.error("[Nuxt Users] Missing authorization code in OAuth callback");
|
|
23
|
+
const errorRedirect = options.auth.google.errorRedirect || "/login?error=oauth_failed";
|
|
24
|
+
return sendRedirect(event, errorRedirect);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const baseUrl = getRequestURL(event).origin;
|
|
28
|
+
const callbackPath = options.auth.google.callbackUrl || `${options.apiBasePath}/auth/google/callback`;
|
|
29
|
+
const callbackUrl = `${baseUrl}${callbackPath}`;
|
|
30
|
+
const oauth2Client = createGoogleOAuth2Client(options.auth.google, callbackUrl);
|
|
31
|
+
const googleUser = await getGoogleUserFromCode(oauth2Client, query.code);
|
|
32
|
+
const user = await findOrCreateGoogleUser(googleUser, options);
|
|
33
|
+
if (!user) {
|
|
34
|
+
console.warn(`[Nuxt Users] User not registered attempted Google OAuth login: ${googleUser.email}`);
|
|
35
|
+
const errorRedirect = options.auth.google.errorRedirect || "/login?error=user_not_registered";
|
|
36
|
+
return sendRedirect(event, errorRedirect);
|
|
37
|
+
}
|
|
38
|
+
if (!user.active) {
|
|
39
|
+
console.warn(`[Nuxt Users] Inactive user attempted Google OAuth login: ${user.email}`);
|
|
40
|
+
const errorRedirect = options.auth.google.errorRedirect || "/login?error=account_inactive";
|
|
41
|
+
return sendRedirect(event, errorRedirect);
|
|
42
|
+
}
|
|
43
|
+
const token = await createAuthTokenForUser(user, options, true);
|
|
44
|
+
const cookieOptions = {
|
|
45
|
+
httpOnly: true,
|
|
46
|
+
secure: process.env.NODE_ENV === "production",
|
|
47
|
+
sameSite: "lax",
|
|
48
|
+
path: "/",
|
|
49
|
+
maxAge: 60 * 60 * 24 * (options.auth.rememberMeExpiration || 30)
|
|
50
|
+
// 30 days default
|
|
51
|
+
};
|
|
52
|
+
setCookie(event, "auth_token", token, cookieOptions);
|
|
53
|
+
console.log(`[Nuxt Users] Google OAuth login successful for user: ${user.email}`);
|
|
54
|
+
const successRedirect = options.auth.google.successRedirect || "/";
|
|
55
|
+
const redirectUrl = successRedirect.includes("?") ? `${successRedirect}&oauth_success=true` : `${successRedirect}?oauth_success=true`;
|
|
56
|
+
return sendRedirect(event, redirectUrl);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error("[Nuxt Users] Google OAuth callback error:", error);
|
|
59
|
+
const errorRedirect = options.auth.google?.errorRedirect || "/login?error=oauth_failed";
|
|
60
|
+
return sendRedirect(event, errorRedirect);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const getRequestURL = (event) => {
|
|
64
|
+
const headers = event.node.req.headers;
|
|
65
|
+
const host = headers.host || headers[":authority"];
|
|
66
|
+
const protocol = headers["x-forwarded-proto"] || "https";
|
|
67
|
+
return new URL(`${protocol}://${host}`);
|
|
68
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineEventHandler, sendRedirect, createError } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { createGoogleOAuth2Client, getGoogleAuthUrl } from "../../../../utils/google-oauth.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const { nuxtUsers } = useRuntimeConfig();
|
|
6
|
+
const options = nuxtUsers;
|
|
7
|
+
if (!options.auth.google) {
|
|
8
|
+
throw createError({
|
|
9
|
+
statusCode: 500,
|
|
10
|
+
statusMessage: "Google OAuth is not configured"
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
if (!options.auth.google.clientId || !options.auth.google.clientSecret) {
|
|
14
|
+
throw createError({
|
|
15
|
+
statusCode: 500,
|
|
16
|
+
statusMessage: "Google OAuth client ID and secret are required"
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const baseUrl = getRequestURL(event).origin;
|
|
21
|
+
const callbackPath = options.auth.google.callbackUrl || `${options.apiBasePath}/auth/google/callback`;
|
|
22
|
+
const callbackUrl = `${baseUrl}${callbackPath}`;
|
|
23
|
+
const oauth2Client = createGoogleOAuth2Client(options.auth.google, callbackUrl);
|
|
24
|
+
const authUrl = getGoogleAuthUrl(oauth2Client, options.auth.google);
|
|
25
|
+
return sendRedirect(event, authUrl);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("[Nuxt Users] Google OAuth redirect error:", error);
|
|
28
|
+
throw createError({
|
|
29
|
+
statusCode: 500,
|
|
30
|
+
statusMessage: "Failed to initiate Google OAuth"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const getRequestURL = (event) => {
|
|
35
|
+
const headers = event.node.req.headers;
|
|
36
|
+
const host = headers.host || headers[":authority"];
|
|
37
|
+
const protocol = headers["x-forwarded-proto"];
|
|
38
|
+
return new URL(`${protocol}://${host}`);
|
|
39
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useDb } from "./db.js";
|
|
2
|
+
export const addGoogleOauthFields = async (options) => {
|
|
3
|
+
const connectorName = options.connector.name;
|
|
4
|
+
const db = await useDb(options);
|
|
5
|
+
const tableName = options.tables.users;
|
|
6
|
+
console.log(`[Nuxt Users] DB:Add Google OAuth fields to ${connectorName} Users Table in ${tableName}...`);
|
|
7
|
+
if (connectorName === "sqlite" || connectorName === "mysql" || connectorName === "postgresql") {
|
|
8
|
+
try {
|
|
9
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id TEXT`;
|
|
10
|
+
console.log("[Nuxt Users] Added google_id column \u2705");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error("[Nuxt Users] google_id column might already exist", error);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN profile_picture TEXT`;
|
|
16
|
+
console.log("[Nuxt Users] Added profile_picture column \u2705");
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error("[Nuxt Users] profile_picture column might already exist", error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
console.log(`[Nuxt Users] DB:Add Google OAuth fields to ${connectorName} Users Table \u2705`);
|
|
22
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const isBuildTime = () => {
|
|
2
|
+
if (process.env.NITRO_PRESET === "nitro-prerender" || process.env.NUXT_ENV === "prerender") {
|
|
3
|
+
return true;
|
|
4
|
+
}
|
|
5
|
+
if (process.env.NODE_ENV === "production" && process.argv.includes("--prerender")) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
const buildIndicators = ["prerender", "build", "generate"];
|
|
9
|
+
return buildIndicators.some(
|
|
10
|
+
(indicator) => process.argv.join(" ").toLowerCase().includes(indicator) || process.env.npm_lifecycle_event?.includes(indicator)
|
|
11
|
+
);
|
|
12
|
+
};
|
|
@@ -12,6 +12,8 @@ export const createUsersTable = async (options) => {
|
|
|
12
12
|
name TEXT NOT NULL,
|
|
13
13
|
password TEXT NOT NULL,
|
|
14
14
|
role TEXT NOT NULL DEFAULT 'user',
|
|
15
|
+
google_id TEXT UNIQUE,
|
|
16
|
+
profile_picture TEXT,
|
|
15
17
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
16
18
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
17
19
|
)
|
|
@@ -25,6 +27,8 @@ export const createUsersTable = async (options) => {
|
|
|
25
27
|
name VARCHAR(255) NOT NULL,
|
|
26
28
|
password VARCHAR(255) NOT NULL,
|
|
27
29
|
role VARCHAR(32) NOT NULL DEFAULT 'user',
|
|
30
|
+
google_id VARCHAR(255) UNIQUE,
|
|
31
|
+
profile_picture TEXT,
|
|
28
32
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
29
33
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
30
34
|
)
|
|
@@ -38,10 +42,12 @@ export const createUsersTable = async (options) => {
|
|
|
38
42
|
name VARCHAR(255) NOT NULL,
|
|
39
43
|
password VARCHAR(255) NOT NULL,
|
|
40
44
|
role VARCHAR(32) NOT NULL DEFAULT 'user',
|
|
45
|
+
google_id VARCHAR(255) UNIQUE,
|
|
46
|
+
profile_picture TEXT,
|
|
41
47
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
42
48
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
43
49
|
)
|
|
44
50
|
`;
|
|
45
51
|
}
|
|
46
|
-
console.log(`[Nuxt Users] DB:Create ${connectorName} Users Table Fields: id, email, name, password, role, created_at, updated_at \u2705`);
|
|
52
|
+
console.log(`[Nuxt Users] DB:Create ${connectorName} Users Table Fields: id, email, name, password, role, google_id, profile_picture, created_at, updated_at \u2705`);
|
|
47
53
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createDatabase } from "db0";
|
|
2
|
+
import { isBuildTime } from "./build-time.js";
|
|
2
3
|
const dbCache = /* @__PURE__ */ new Map();
|
|
3
4
|
export const closeAllDbConnections = async () => {
|
|
4
5
|
for (const db of dbCache.values()) {
|
|
@@ -31,6 +32,9 @@ export const getConnector = async (name) => {
|
|
|
31
32
|
}
|
|
32
33
|
};
|
|
33
34
|
export const useDb = async (options) => {
|
|
35
|
+
if (isBuildTime()) {
|
|
36
|
+
throw new Error("[Nuxt Users] Database connections are not available during build/prerendering phase. This should not happen - please check your prerender configuration.");
|
|
37
|
+
}
|
|
34
38
|
const cacheKey = JSON.stringify(options.connector);
|
|
35
39
|
if (dbCache.has(cacheKey)) {
|
|
36
40
|
return dbCache.get(cacheKey);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { OAuth2Client } from 'google-auth-library';
|
|
2
|
+
import type { ModuleOptions, User, GoogleOAuthOptions } from 'nuxt-users/utils';
|
|
3
|
+
export interface GoogleUserInfo {
|
|
4
|
+
id: string;
|
|
5
|
+
email: string;
|
|
6
|
+
name: string;
|
|
7
|
+
picture?: string;
|
|
8
|
+
verified_email: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create Google OAuth2 client
|
|
12
|
+
*/
|
|
13
|
+
export declare function createGoogleOAuth2Client(options: GoogleOAuthOptions, callbackUrl: string): OAuth2Client;
|
|
14
|
+
/**
|
|
15
|
+
* Generate authorization URL for Google OAuth
|
|
16
|
+
*/
|
|
17
|
+
export declare function getGoogleAuthUrl(oauth2Client: OAuth2Client, options: GoogleOAuthOptions): string;
|
|
18
|
+
/**
|
|
19
|
+
* Exchange authorization code for tokens and get user info
|
|
20
|
+
*/
|
|
21
|
+
export declare function getGoogleUserFromCode(oauth2Client: OAuth2Client, code: string): Promise<GoogleUserInfo>;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a secure random password for OAuth users
|
|
24
|
+
*/
|
|
25
|
+
export declare function generateSecurePassword(): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Find or create user from Google OAuth data
|
|
28
|
+
*/
|
|
29
|
+
export declare function findOrCreateGoogleUser(googleUser: GoogleUserInfo, moduleOptions: ModuleOptions): Promise<User | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Create authentication token for user (reusing existing logic)
|
|
32
|
+
*/
|
|
33
|
+
export declare function createAuthTokenForUser(user: User, moduleOptions: ModuleOptions, rememberMe?: boolean): Promise<string>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { OAuth2Client } from "google-auth-library";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import bcrypt from "bcrypt";
|
|
4
|
+
import { useDb } from "./db.js";
|
|
5
|
+
export function createGoogleOAuth2Client(options, callbackUrl) {
|
|
6
|
+
return new OAuth2Client(
|
|
7
|
+
options.clientId,
|
|
8
|
+
options.clientSecret,
|
|
9
|
+
callbackUrl
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
export function getGoogleAuthUrl(oauth2Client, options) {
|
|
13
|
+
const scopes = options.scopes || ["openid", "profile", "email"];
|
|
14
|
+
return oauth2Client.generateAuthUrl({
|
|
15
|
+
access_type: "offline",
|
|
16
|
+
scope: scopes,
|
|
17
|
+
include_granted_scopes: true
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export async function getGoogleUserFromCode(oauth2Client, code) {
|
|
21
|
+
const { tokens } = await oauth2Client.getToken(code);
|
|
22
|
+
oauth2Client.setCredentials(tokens);
|
|
23
|
+
const response = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${tokens.access_token}`
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Failed to fetch user info: ${response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
if (!data.email || !data.verified_email) {
|
|
33
|
+
throw new Error("Google account email not verified");
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id: data.id,
|
|
37
|
+
email: data.email,
|
|
38
|
+
name: data.name || data.email.split("@")[0],
|
|
39
|
+
picture: data.picture || void 0,
|
|
40
|
+
verified_email: data.verified_email
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export async function generateSecurePassword() {
|
|
44
|
+
const randomBytes = crypto.randomBytes(32).toString("hex");
|
|
45
|
+
return await bcrypt.hash(`OAUTH_${randomBytes}`, 10);
|
|
46
|
+
}
|
|
47
|
+
export async function findOrCreateGoogleUser(googleUser, moduleOptions) {
|
|
48
|
+
const db = await useDb(moduleOptions);
|
|
49
|
+
const usersTable = moduleOptions.tables.users;
|
|
50
|
+
const allowAutoRegistration = moduleOptions.auth.google?.allowAutoRegistration ?? false;
|
|
51
|
+
let userResult = await db.sql`
|
|
52
|
+
SELECT * FROM {${usersTable}}
|
|
53
|
+
WHERE google_id = ${googleUser.id}
|
|
54
|
+
`;
|
|
55
|
+
if (userResult.rows.length > 0) {
|
|
56
|
+
const user = userResult.rows[0];
|
|
57
|
+
if (user.profile_picture !== googleUser.picture) {
|
|
58
|
+
await db.sql`
|
|
59
|
+
UPDATE {${usersTable}}
|
|
60
|
+
SET profile_picture = ${googleUser.picture}, updated_at = CURRENT_TIMESTAMP
|
|
61
|
+
WHERE id = ${user.id}
|
|
62
|
+
`;
|
|
63
|
+
user.profile_picture = googleUser.picture;
|
|
64
|
+
}
|
|
65
|
+
return user;
|
|
66
|
+
}
|
|
67
|
+
userResult = await db.sql`
|
|
68
|
+
SELECT * FROM {${usersTable}}
|
|
69
|
+
WHERE email = ${googleUser.email}
|
|
70
|
+
`;
|
|
71
|
+
if (userResult.rows.length > 0) {
|
|
72
|
+
const user = userResult.rows[0];
|
|
73
|
+
await db.sql`
|
|
74
|
+
UPDATE {${usersTable}}
|
|
75
|
+
SET google_id = ${googleUser.id},
|
|
76
|
+
profile_picture = ${googleUser.picture},
|
|
77
|
+
updated_at = CURRENT_TIMESTAMP
|
|
78
|
+
WHERE id = ${user.id}
|
|
79
|
+
`;
|
|
80
|
+
user.google_id = googleUser.id;
|
|
81
|
+
user.profile_picture = googleUser.picture;
|
|
82
|
+
return user;
|
|
83
|
+
}
|
|
84
|
+
if (!allowAutoRegistration) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const securePassword = await generateSecurePassword();
|
|
88
|
+
await db.sql`
|
|
89
|
+
INSERT INTO {${usersTable}} (
|
|
90
|
+
email, name, password, role, google_id, profile_picture,
|
|
91
|
+
active, created_at, updated_at
|
|
92
|
+
) VALUES (
|
|
93
|
+
${googleUser.email},
|
|
94
|
+
${googleUser.name},
|
|
95
|
+
${securePassword},
|
|
96
|
+
'user',
|
|
97
|
+
${googleUser.id},
|
|
98
|
+
${googleUser.picture || null},
|
|
99
|
+
true,
|
|
100
|
+
CURRENT_TIMESTAMP,
|
|
101
|
+
CURRENT_TIMESTAMP
|
|
102
|
+
)
|
|
103
|
+
`;
|
|
104
|
+
const createdUserResult = await db.sql`
|
|
105
|
+
SELECT * FROM {${usersTable}}
|
|
106
|
+
WHERE google_id = ${googleUser.id}
|
|
107
|
+
`;
|
|
108
|
+
return createdUserResult.rows[0];
|
|
109
|
+
}
|
|
110
|
+
export async function createAuthTokenForUser(user, moduleOptions, rememberMe = false) {
|
|
111
|
+
const db = await useDb(moduleOptions);
|
|
112
|
+
const personalAccessTokensTable = moduleOptions.tables.personalAccessTokens;
|
|
113
|
+
const token = crypto.randomBytes(64).toString("hex");
|
|
114
|
+
const tokenName = "oauth_auth_token";
|
|
115
|
+
const expiresAt = /* @__PURE__ */ new Date();
|
|
116
|
+
if (rememberMe) {
|
|
117
|
+
const longTermDays = moduleOptions.auth.rememberMeExpiration || 30;
|
|
118
|
+
expiresAt.setDate(expiresAt.getDate() + longTermDays);
|
|
119
|
+
} else {
|
|
120
|
+
expiresAt.setMinutes(expiresAt.getMinutes() + moduleOptions.auth.tokenExpiration);
|
|
121
|
+
}
|
|
122
|
+
await db.sql`
|
|
123
|
+
INSERT INTO {${personalAccessTokensTable}} (
|
|
124
|
+
tokenable_type, tokenable_id, name, token, expires_at, created_at, updated_at
|
|
125
|
+
) VALUES (
|
|
126
|
+
'user',
|
|
127
|
+
${user.id},
|
|
128
|
+
${tokenName},
|
|
129
|
+
${token},
|
|
130
|
+
${expiresAt.toISOString().slice(0, 19).replace("T", " ")},
|
|
131
|
+
CURRENT_TIMESTAMP,
|
|
132
|
+
CURRENT_TIMESTAMP
|
|
133
|
+
)
|
|
134
|
+
`;
|
|
135
|
+
return token;
|
|
136
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { useDb, checkTableExists } from './db.js';
|
|
2
|
+
export { isBuildTime } from './build-time.js';
|
|
2
3
|
export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens, getLastLoginTime, findUserById, updateUser, deleteUser } from './user.js';
|
|
3
4
|
export { createUsersTable } from './create-users-table.js';
|
|
4
5
|
export { createPersonalAccessTokensTable } from './create-personal-access-tokens-table.js';
|
|
5
6
|
export { createPasswordResetTokensTable } from './create-password-reset-tokens-table.js';
|
|
6
7
|
export { createMigrationsTable } from './create-migrations-table.js';
|
|
7
8
|
export { runMigrations } from './migrate.js';
|
|
9
|
+
export { createGoogleOAuth2Client, getGoogleAuthUrl, getGoogleUserFromCode, findOrCreateGoogleUser, createAuthTokenForUser, generateSecurePassword } from './google-oauth.js';
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { useDb, checkTableExists } from "./db.js";
|
|
2
|
+
export { isBuildTime } from "./build-time.js";
|
|
2
3
|
export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens, getLastLoginTime, findUserById, updateUser, deleteUser } from "./user.js";
|
|
3
4
|
export { createUsersTable } from "./create-users-table.js";
|
|
4
5
|
export { createPersonalAccessTokensTable } from "./create-personal-access-tokens-table.js";
|
|
5
6
|
export { createPasswordResetTokensTable } from "./create-password-reset-tokens-table.js";
|
|
6
7
|
export { createMigrationsTable } from "./create-migrations-table.js";
|
|
7
8
|
export { runMigrations } from "./migrate.js";
|
|
9
|
+
export { createGoogleOAuth2Client, getGoogleAuthUrl, getGoogleUserFromCode, findOrCreateGoogleUser, createAuthTokenForUser, generateSecurePassword } from "./google-oauth.js";
|
|
@@ -4,6 +4,7 @@ import { createPersonalAccessTokensTable } from "./create-personal-access-tokens
|
|
|
4
4
|
import { createPasswordResetTokensTable } from "./create-password-reset-tokens-table.js";
|
|
5
5
|
import { createMigrationsTable } from "./create-migrations-table.js";
|
|
6
6
|
import { addActiveToUsers } from "./add-active-to-users.js";
|
|
7
|
+
import { addGoogleOauthFields } from "./add-google-oauth-fields.js";
|
|
7
8
|
const migrations = [
|
|
8
9
|
{
|
|
9
10
|
name: "create_migrations_table",
|
|
@@ -24,6 +25,10 @@ const migrations = [
|
|
|
24
25
|
{
|
|
25
26
|
name: "add_active_to_users",
|
|
26
27
|
run: addActiveToUsers
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "add_google_oauth_fields",
|
|
31
|
+
run: addGoogleOauthFields
|
|
27
32
|
}
|
|
28
33
|
];
|
|
29
34
|
export const getAppliedMigrations = async (options) => {
|
|
@@ -12,6 +12,42 @@ type DatabaseConfig = {
|
|
|
12
12
|
password?: string;
|
|
13
13
|
database?: string;
|
|
14
14
|
};
|
|
15
|
+
interface GoogleOAuthOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Google OAuth client ID from Google Cloud Console
|
|
18
|
+
*/
|
|
19
|
+
clientId: string;
|
|
20
|
+
/**
|
|
21
|
+
* Google OAuth client secret from Google Cloud Console
|
|
22
|
+
*/
|
|
23
|
+
clientSecret: string;
|
|
24
|
+
/**
|
|
25
|
+
* Callback URL for Google OAuth (must match what's configured in Google Cloud Console)
|
|
26
|
+
* @default '/api/nuxt-users/auth/google/callback'
|
|
27
|
+
*/
|
|
28
|
+
callbackUrl?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Redirect URL after successful authentication
|
|
31
|
+
* @default '/'
|
|
32
|
+
*/
|
|
33
|
+
successRedirect?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Redirect URL after failed authentication
|
|
36
|
+
* @default '/login?error=oauth_failed'
|
|
37
|
+
*/
|
|
38
|
+
errorRedirect?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Google OAuth scopes to request
|
|
41
|
+
* @default ['openid', 'profile', 'email']
|
|
42
|
+
*/
|
|
43
|
+
scopes?: string[];
|
|
44
|
+
/**
|
|
45
|
+
* Allow automatic user registration when logging in with Google for the first time
|
|
46
|
+
* If false, only existing users with matching email can log in with Google
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
allowAutoRegistration?: boolean;
|
|
50
|
+
}
|
|
15
51
|
interface RuntimeModuleOptions {
|
|
16
52
|
connector?: {
|
|
17
53
|
name: DatabaseType;
|
|
@@ -75,6 +111,11 @@ interface RuntimeModuleOptions {
|
|
|
75
111
|
* }
|
|
76
112
|
*/
|
|
77
113
|
permissions?: Record<string, (string | Permission)[]>;
|
|
114
|
+
/**
|
|
115
|
+
* Google OAuth configuration
|
|
116
|
+
* Enable Google OAuth login/registration
|
|
117
|
+
*/
|
|
118
|
+
google?: GoogleOAuthOptions;
|
|
78
119
|
};
|
|
79
120
|
/**
|
|
80
121
|
* Password validation configuration
|
|
@@ -139,6 +180,7 @@ interface ModuleOptions {
|
|
|
139
180
|
tokenExpiration: number;
|
|
140
181
|
rememberMeExpiration: number;
|
|
141
182
|
permissions: Record<string, (string | Permission)[]>;
|
|
183
|
+
google?: GoogleOAuthOptions;
|
|
142
184
|
};
|
|
143
185
|
passwordValidation: {
|
|
144
186
|
minLength: number;
|
|
@@ -169,6 +211,8 @@ interface User {
|
|
|
169
211
|
password: string;
|
|
170
212
|
role: string;
|
|
171
213
|
active: boolean;
|
|
214
|
+
google_id?: string;
|
|
215
|
+
profile_picture?: string;
|
|
172
216
|
created_at: string;
|
|
173
217
|
updated_at: string;
|
|
174
218
|
last_login_at?: string;
|
|
@@ -225,4 +269,4 @@ declare const defaultFieldLabels: {
|
|
|
225
269
|
};
|
|
226
270
|
|
|
227
271
|
export { defaultDisplayFields as h, defaultFieldLabels as i };
|
|
228
|
-
export type { DisplayFieldsProps as D, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g };
|
|
272
|
+
export type { DisplayFieldsProps as D, GoogleOAuthOptions as G, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g };
|
|
@@ -12,6 +12,42 @@ type DatabaseConfig = {
|
|
|
12
12
|
password?: string;
|
|
13
13
|
database?: string;
|
|
14
14
|
};
|
|
15
|
+
interface GoogleOAuthOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Google OAuth client ID from Google Cloud Console
|
|
18
|
+
*/
|
|
19
|
+
clientId: string;
|
|
20
|
+
/**
|
|
21
|
+
* Google OAuth client secret from Google Cloud Console
|
|
22
|
+
*/
|
|
23
|
+
clientSecret: string;
|
|
24
|
+
/**
|
|
25
|
+
* Callback URL for Google OAuth (must match what's configured in Google Cloud Console)
|
|
26
|
+
* @default '/api/nuxt-users/auth/google/callback'
|
|
27
|
+
*/
|
|
28
|
+
callbackUrl?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Redirect URL after successful authentication
|
|
31
|
+
* @default '/'
|
|
32
|
+
*/
|
|
33
|
+
successRedirect?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Redirect URL after failed authentication
|
|
36
|
+
* @default '/login?error=oauth_failed'
|
|
37
|
+
*/
|
|
38
|
+
errorRedirect?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Google OAuth scopes to request
|
|
41
|
+
* @default ['openid', 'profile', 'email']
|
|
42
|
+
*/
|
|
43
|
+
scopes?: string[];
|
|
44
|
+
/**
|
|
45
|
+
* Allow automatic user registration when logging in with Google for the first time
|
|
46
|
+
* If false, only existing users with matching email can log in with Google
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
allowAutoRegistration?: boolean;
|
|
50
|
+
}
|
|
15
51
|
interface RuntimeModuleOptions {
|
|
16
52
|
connector?: {
|
|
17
53
|
name: DatabaseType;
|
|
@@ -75,6 +111,11 @@ interface RuntimeModuleOptions {
|
|
|
75
111
|
* }
|
|
76
112
|
*/
|
|
77
113
|
permissions?: Record<string, (string | Permission)[]>;
|
|
114
|
+
/**
|
|
115
|
+
* Google OAuth configuration
|
|
116
|
+
* Enable Google OAuth login/registration
|
|
117
|
+
*/
|
|
118
|
+
google?: GoogleOAuthOptions;
|
|
78
119
|
};
|
|
79
120
|
/**
|
|
80
121
|
* Password validation configuration
|
|
@@ -139,6 +180,7 @@ interface ModuleOptions {
|
|
|
139
180
|
tokenExpiration: number;
|
|
140
181
|
rememberMeExpiration: number;
|
|
141
182
|
permissions: Record<string, (string | Permission)[]>;
|
|
183
|
+
google?: GoogleOAuthOptions;
|
|
142
184
|
};
|
|
143
185
|
passwordValidation: {
|
|
144
186
|
minLength: number;
|
|
@@ -169,6 +211,8 @@ interface User {
|
|
|
169
211
|
password: string;
|
|
170
212
|
role: string;
|
|
171
213
|
active: boolean;
|
|
214
|
+
google_id?: string;
|
|
215
|
+
profile_picture?: string;
|
|
172
216
|
created_at: string;
|
|
173
217
|
updated_at: string;
|
|
174
218
|
last_login_at?: string;
|
|
@@ -225,4 +269,4 @@ declare const defaultFieldLabels: {
|
|
|
225
269
|
};
|
|
226
270
|
|
|
227
271
|
export { defaultDisplayFields as h, defaultFieldLabels as i };
|
|
228
|
-
export type { DisplayFieldsProps as D, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g };
|
|
272
|
+
export type { DisplayFieldsProps as D, GoogleOAuthOptions as G, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g };
|
package/dist/utils.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, L as LoginFormData, c as LoginFormProps, M as ModuleOptions, b as PasswordResetToken, e as Permission, P as PersonalAccessToken, d as ResetPasswordFormProps, R as RuntimeModuleOptions, U as User, a as UserWithoutPassword, h as defaultDisplayFields, i as defaultFieldLabels } from './shared/nuxt-users.
|
|
1
|
+
export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, G as GoogleOAuthOptions, L as LoginFormData, c as LoginFormProps, M as ModuleOptions, b as PasswordResetToken, e as Permission, P as PersonalAccessToken, d as ResetPasswordFormProps, R as RuntimeModuleOptions, U as User, a as UserWithoutPassword, h as defaultDisplayFields, i as defaultFieldLabels } from './shared/nuxt-users.SXaMnaEL.mjs';
|
|
2
2
|
|
|
3
3
|
interface PasswordValidationResult {
|
|
4
4
|
isValid: boolean;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, L as LoginFormData, c as LoginFormProps, M as ModuleOptions, b as PasswordResetToken, e as Permission, P as PersonalAccessToken, d as ResetPasswordFormProps, R as RuntimeModuleOptions, U as User, a as UserWithoutPassword, h as defaultDisplayFields, i as defaultFieldLabels } from './shared/nuxt-users.
|
|
1
|
+
export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, G as GoogleOAuthOptions, L as LoginFormData, c as LoginFormProps, M as ModuleOptions, b as PasswordResetToken, e as Permission, P as PersonalAccessToken, d as ResetPasswordFormProps, R as RuntimeModuleOptions, U as User, a as UserWithoutPassword, h as defaultDisplayFields, i as defaultFieldLabels } from './shared/nuxt-users.SXaMnaEL.js';
|
|
2
2
|
|
|
3
3
|
interface PasswordValidationResult {
|
|
4
4
|
isValid: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-users",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "A comprehensive user management module for Nuxt 3 and Nuxt 4 applications with authentication, authorization, database support, and CLI tools",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nuxt",
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"bcrypt": "^6.0.0",
|
|
93
93
|
"citty": "^0.1.6",
|
|
94
94
|
"defu": "^6.1.4",
|
|
95
|
+
"google-auth-library": "^10.4.0",
|
|
95
96
|
"nodemailer": "^7.0.5"
|
|
96
97
|
},
|
|
97
98
|
"optionalDependencies": {
|