nuxt-users 1.34.7 → 1.36.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/dist/cli.mjs +111 -0
  3. package/dist/module.d.mts +1 -1
  4. package/dist/module.d.ts +1 -1
  5. package/dist/module.json +1 -1
  6. package/dist/module.mjs +38 -3
  7. package/dist/runtime/assets/nuxt-users.css +1 -1
  8. package/dist/runtime/components/NUsersEmailConfirmation.d.vue.ts +38 -0
  9. package/dist/runtime/components/NUsersEmailConfirmation.vue.d.ts +3 -2
  10. package/dist/runtime/components/NUsersGoogleLoginButton.d.vue.ts +31 -0
  11. package/dist/runtime/components/NUsersGoogleLoginButton.vue +75 -0
  12. package/dist/runtime/components/NUsersGoogleLoginButton.vue.d.ts +31 -0
  13. package/dist/runtime/components/NUsersList.d.vue.ts +0 -0
  14. package/dist/runtime/components/NUsersLoginForm.d.vue.ts +48 -0
  15. package/dist/runtime/components/NUsersLoginForm.vue.d.ts +5 -4
  16. package/dist/runtime/components/NUsersLogoutLink.d.vue.ts +38 -0
  17. package/dist/runtime/components/NUsersLogoutLink.vue.d.ts +5 -4
  18. package/dist/runtime/components/NUsersPasswordStrengthIndicator.d.vue.ts +13 -0
  19. package/dist/runtime/components/NUsersPasswordStrengthIndicator.vue.d.ts +2 -1
  20. package/dist/runtime/components/NUsersProfileInfo.d.vue.ts +3 -0
  21. package/dist/runtime/components/NUsersProfileInfo.vue.d.ts +2 -1
  22. package/dist/runtime/components/NUsersRegisterForm.d.vue.ts +61 -0
  23. package/dist/runtime/components/NUsersRegisterForm.vue.d.ts +5 -4
  24. package/dist/runtime/components/NUsersResetPasswordForm.d.vue.ts +18 -0
  25. package/dist/runtime/components/NUsersResetPasswordForm.vue.d.ts +4 -3
  26. package/dist/runtime/components/NUsersUserCard.d.vue.ts +44 -0
  27. package/dist/runtime/components/NUsersUserCard.vue.d.ts +3 -2
  28. package/dist/runtime/components/NUsersUserForm.d.vue.ts +15 -0
  29. package/dist/runtime/components/NUsersUserForm.vue.d.ts +2 -1
  30. package/dist/runtime/composables/useAuthentication.d.ts +1 -1
  31. package/dist/runtime/composables/useAuthentication.js +26 -8
  32. package/dist/runtime/middleware/authorization.client.js +9 -7
  33. package/dist/runtime/server/api/nuxt-users/auth/google/callback.get.d.ts +2 -0
  34. package/dist/runtime/server/api/nuxt-users/auth/google/callback.get.js +68 -0
  35. package/dist/runtime/server/api/nuxt-users/auth/google/redirect.get.d.ts +2 -0
  36. package/dist/runtime/server/api/nuxt-users/auth/google/redirect.get.js +39 -0
  37. package/dist/runtime/server/api/nuxt-users/inactive.get.js +1 -1
  38. package/dist/runtime/server/api/nuxt-users/index.get.js +1 -1
  39. package/dist/runtime/server/api/nuxt-users/session/index.post.d.ts +1 -1
  40. package/dist/runtime/server/api/nuxt-users/session/index.post.js +6 -0
  41. package/dist/runtime/server/services/password.js +8 -0
  42. package/dist/runtime/server/services/registration.js +14 -1
  43. package/dist/runtime/server/utils/add-google-oauth-fields.d.ts +2 -0
  44. package/dist/runtime/server/utils/add-google-oauth-fields.js +22 -0
  45. package/dist/runtime/server/utils/create-users-table.js +7 -1
  46. package/dist/runtime/server/utils/google-oauth.d.ts +33 -0
  47. package/dist/runtime/server/utils/google-oauth.js +142 -0
  48. package/dist/runtime/server/utils/index.d.ts +1 -0
  49. package/dist/runtime/server/utils/index.js +1 -0
  50. package/dist/runtime/server/utils/migrate.js +5 -0
  51. package/dist/runtime/server/utils/user.js +24 -3
  52. package/dist/shared/{nuxt-users.BTVH9EyH.d.mts → nuxt-users.SXaMnaEL.d.mts} +45 -1
  53. package/dist/shared/{nuxt-users.BTVH9EyH.d.ts → nuxt-users.SXaMnaEL.d.ts} +45 -1
  54. package/dist/utils.d.mts +1 -1
  55. package/dist/utils.d.ts +1 -1
  56. package/package.json +8 -6
  57. package/scripts/check-peer-dependencies.js +0 -0
  58. package/scripts/post-build.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,104 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## v1.36.0
5
+
6
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.35.0...v1.36.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
+ - Enhances project info command output ([2d91bd9](https://github.com/rrd108/nuxt-users/commit/2d91bd9))
15
+
16
+ ### 🩹 Fixes
17
+
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
+ - **deps:** Update nuxtjs monorepo to v4 ([0c4b199](https://github.com/rrd108/nuxt-users/commit/0c4b199))
21
+ - Ts errors ([44f5d9e](https://github.com/rrd108/nuxt-users/commit/44f5d9e))
22
+ - Add nuxt builder ([aaa8288](https://github.com/rrd108/nuxt-users/commit/aaa8288))
23
+ - Ts errors ([d488513](https://github.com/rrd108/nuxt-users/commit/d488513))
24
+ - **docs:** Updates last release timestamp ([3d3e0d8](https://github.com/rrd108/nuxt-users/commit/3d3e0d8))
25
+
26
+ ### 📖 Documentation
27
+
28
+ - Adds Google OAuth authentication ([463c19f](https://github.com/rrd108/nuxt-users/commit/463c19f))
29
+ - Adds Google OAuth registration ([2e0eebf](https://github.com/rrd108/nuxt-users/commit/2e0eebf))
30
+ - Adds project info command to troubleshooting ([8768090](https://github.com/rrd108/nuxt-users/commit/8768090))
31
+
32
+ ### 🏡 Chore
33
+
34
+ - Remove unused files ([8bd3e58](https://github.com/rrd108/nuxt-users/commit/8bd3e58))
35
+ - Replace googleapis package with google-auth ([0e8e3d4](https://github.com/rrd108/nuxt-users/commit/0e8e3d4))
36
+ - Fix lint errors ([3ed5bcc](https://github.com/rrd108/nuxt-users/commit/3ed5bcc))
37
+ - Update lock ([8cd4a97](https://github.com/rrd108/nuxt-users/commit/8cd4a97))
38
+ - **release:** V1.35.0 ([41cb690](https://github.com/rrd108/nuxt-users/commit/41cb690))
39
+ - Add snyk ([9c932c8](https://github.com/rrd108/nuxt-users/commit/9c932c8))
40
+ - Remove unsued import ([04fd61d](https://github.com/rrd108/nuxt-users/commit/04fd61d))
41
+ - Package updates ([3173073](https://github.com/rrd108/nuxt-users/commit/3173073))
42
+
43
+ ### ✅ Tests
44
+
45
+ - Google OAuth authentication ([7d7bd68](https://github.com/rrd108/nuxt-users/commit/7d7bd68))
46
+ - Add oauth flow test ([b805718](https://github.com/rrd108/nuxt-users/commit/b805718))
47
+ - Fix db setup ([a097c4a](https://github.com/rrd108/nuxt-users/commit/a097c4a))
48
+ - Replace smoke test with middleware test ([7ee1893](https://github.com/rrd108/nuxt-users/commit/7ee1893))
49
+ - Add security checks ([a206962](https://github.com/rrd108/nuxt-users/commit/a206962))
50
+ - Fix ts errors ([d7e6c61](https://github.com/rrd108/nuxt-users/commit/d7e6c61))
51
+ - Fix gauth ([b925cf2](https://github.com/rrd108/nuxt-users/commit/b925cf2))
52
+
53
+ ### ❤️ Contributors
54
+
55
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
56
+
57
+ ## v1.35.0
58
+
59
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.34.7...v1.35.0)
60
+
61
+ ### 🚀 Enhancements
62
+
63
+ - Implement google authentication ([e830872](https://github.com/rrd108/nuxt-users/commit/e830872))
64
+ - Add google auth to the playground ([2b43ca0](https://github.com/rrd108/nuxt-users/commit/2b43ca0))
65
+ - Add migration ([0280eca](https://github.com/rrd108/nuxt-users/commit/0280eca))
66
+ - Unregistered user gets error message ([eefbdf5](https://github.com/rrd108/nuxt-users/commit/eefbdf5))
67
+
68
+ ### 🩹 Fixes
69
+
70
+ - Prevents database connection issues during build ([8a3aa38](https://github.com/rrd108/nuxt-users/commit/8a3aa38))
71
+ - Add api endpoints to public endpoints ([16aefa9](https://github.com/rrd108/nuxt-users/commit/16aefa9))
72
+ - Completes Google OAuth user fetch on client ([6c95b94](https://github.com/rrd108/nuxt-users/commit/6c95b94))
73
+
74
+ ### 📖 Documentation
75
+
76
+ - Fix github link ([28662ca](https://github.com/rrd108/nuxt-users/commit/28662ca))
77
+ - Adds Google OAuth authentication ([463c19f](https://github.com/rrd108/nuxt-users/commit/463c19f))
78
+ - Adds Google OAuth registration ([2e0eebf](https://github.com/rrd108/nuxt-users/commit/2e0eebf))
79
+
80
+ ### 🏡 Chore
81
+
82
+ - Lint fix ([8d75930](https://github.com/rrd108/nuxt-users/commit/8d75930))
83
+ - **release:** V1.34.7 ([b9d061c](https://github.com/rrd108/nuxt-users/commit/b9d061c))
84
+ - Remove unused files ([8bd3e58](https://github.com/rrd108/nuxt-users/commit/8bd3e58))
85
+ - Replace googleapis package with google-auth ([0e8e3d4](https://github.com/rrd108/nuxt-users/commit/0e8e3d4))
86
+ - Fix lint errors ([3ed5bcc](https://github.com/rrd108/nuxt-users/commit/3ed5bcc))
87
+ - Update lock ([8cd4a97](https://github.com/rrd108/nuxt-users/commit/8cd4a97))
88
+
89
+ ### ✅ Tests
90
+
91
+ - Google OAuth authentication ([7d7bd68](https://github.com/rrd108/nuxt-users/commit/7d7bd68))
92
+ - Add oauth flow test ([b805718](https://github.com/rrd108/nuxt-users/commit/b805718))
93
+ - Fix db setup ([a097c4a](https://github.com/rrd108/nuxt-users/commit/a097c4a))
94
+ - Replace smoke test with middleware test ([7ee1893](https://github.com/rrd108/nuxt-users/commit/7ee1893))
95
+ - Add security checks ([a206962](https://github.com/rrd108/nuxt-users/commit/a206962))
96
+ - Fix ts errors ([d7e6c61](https://github.com/rrd108/nuxt-users/commit/d7e6c61))
97
+
98
+ ### ❤️ Contributors
99
+
100
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
101
+
4
102
  ## v1.34.7
5
103
 
6
104
  [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.34.6...v1.34.7)
package/dist/cli.mjs CHANGED
@@ -7,6 +7,8 @@ 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, checkTableExists } from '../dist/runtime/server/utils/db.js';
11
+ import { getAppliedMigrations } from '../dist/runtime/server/utils/migrate.js';
10
12
  import '../dist/runtime/server/internal/index.js';
11
13
 
12
14
  const getOptionsFromEnv = () => {
@@ -245,6 +247,78 @@ const addActiveToUsers = defineCommand({
245
247
  }
246
248
  });
247
249
 
250
+ const addGoogleOauthFields = defineCommand({
251
+ meta: {
252
+ name: "add-google-oauth-fields",
253
+ description: "Add Google OAuth fields (google_id, profile_picture) to existing users table"
254
+ },
255
+ async run() {
256
+ console.log("[Nuxt Users] Adding Google OAuth fields to users table...");
257
+ const options = await loadOptions();
258
+ const connectorName = options.connector.name;
259
+ const db = await useDb(options);
260
+ const tableName = options.tables.users;
261
+ console.log(`[Nuxt Users] DB:Migrate ${connectorName} Users Table Adding Google OAuth fields...`);
262
+ try {
263
+ if (connectorName === "sqlite") {
264
+ const tableInfo = await db.sql`PRAGMA table_info(${tableName})`;
265
+ const columnNames = tableInfo.rows.map((row) => row.name);
266
+ if (!columnNames.includes("google_id")) {
267
+ await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id TEXT`;
268
+ console.log("[Nuxt Users] Added google_id column to SQLite users table \u2705");
269
+ }
270
+ if (!columnNames.includes("profile_picture")) {
271
+ await db.sql`ALTER TABLE {${tableName}} ADD COLUMN profile_picture TEXT`;
272
+ console.log("[Nuxt Users] Added profile_picture column to SQLite users table \u2705");
273
+ }
274
+ if (!columnNames.includes("last_login_at")) {
275
+ await db.sql`ALTER TABLE {${tableName}} ADD COLUMN last_login_at DATETIME`;
276
+ console.log("[Nuxt Users] Added last_login_at column to SQLite users table \u2705");
277
+ }
278
+ }
279
+ if (connectorName === "mysql") {
280
+ const checkColumns = await db.sql`
281
+ SELECT COLUMN_NAME
282
+ FROM INFORMATION_SCHEMA.COLUMNS
283
+ WHERE TABLE_SCHEMA = DATABASE()
284
+ AND TABLE_NAME = ${tableName}
285
+ AND COLUMN_NAME IN ('google_id', 'profile_picture')
286
+ `;
287
+ const existingColumns = checkColumns.rows.map((row) => row.COLUMN_NAME);
288
+ if (!existingColumns.includes("google_id")) {
289
+ await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id VARCHAR(255) UNIQUE`;
290
+ console.log("[Nuxt Users] Added google_id column to MySQL users table \u2705");
291
+ }
292
+ if (!existingColumns.includes("profile_picture")) {
293
+ await db.sql`ALTER TABLE {${tableName}} ADD COLUMN profile_picture TEXT`;
294
+ console.log("[Nuxt Users] Added profile_picture column to MySQL users table \u2705");
295
+ }
296
+ }
297
+ if (connectorName === "postgresql") {
298
+ const checkColumns = await db.sql`
299
+ SELECT column_name
300
+ FROM information_schema.columns
301
+ WHERE table_name = ${tableName}
302
+ AND column_name IN ('google_id', 'profile_picture')
303
+ `;
304
+ const existingColumns = checkColumns.rows.map((row) => row.column_name);
305
+ if (!existingColumns.includes("google_id")) {
306
+ await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id VARCHAR(255) UNIQUE`;
307
+ console.log("[Nuxt Users] Added google_id column to PostgreSQL users table \u2705");
308
+ }
309
+ if (!existingColumns.includes("profile_picture")) {
310
+ await db.sql`ALTER TABLE {${tableName}} ADD COLUMN profile_picture TEXT`;
311
+ console.log("[Nuxt Users] Added profile_picture column to PostgreSQL users table \u2705");
312
+ }
313
+ }
314
+ console.log("[Nuxt Users] Google OAuth fields migration completed successfully! \u2705");
315
+ } catch (error) {
316
+ console.error("[Nuxt Users] DB:Migration Error:", error);
317
+ process.exit(1);
318
+ }
319
+ }
320
+ });
321
+
248
322
  const projectInfo = defineCommand({
249
323
  meta: {
250
324
  name: "project-info",
@@ -261,9 +335,45 @@ const projectInfo = defineCommand({
261
335
  if (nuxtUsersConfig) {
262
336
  console.log("[Nuxt Users] \u{1F527} Nuxt Users module configuration:");
263
337
  console.log("[Nuxt Users] Database connector:", nuxtUsersConfig.connector?.name);
338
+ if (nuxtUsersConfig.connector?.options) {
339
+ const options = nuxtUsersConfig.connector.options;
340
+ if (options.host) {
341
+ console.log("[Nuxt Users] Database host:", options.host);
342
+ }
343
+ if (options.port) {
344
+ console.log("[Nuxt Users] Database port:", options.port);
345
+ }
346
+ if (options.user) {
347
+ console.log("[Nuxt Users] Database user:", options.user);
348
+ }
349
+ if (options.database) {
350
+ console.log("[Nuxt Users] Database name:", options.database);
351
+ }
352
+ if (options.path) {
353
+ console.log("[Nuxt Users] Database path:", options.path);
354
+ }
355
+ }
264
356
  console.log("[Nuxt Users] Users table:", nuxtUsersConfig.tables?.users);
265
357
  console.log("[Nuxt Users] Personal access tokens table:", nuxtUsersConfig.tables?.personalAccessTokens);
266
358
  console.log("[Nuxt Users] Password reset tokens table:", nuxtUsersConfig.tables?.passwordResetTokens);
359
+ try {
360
+ const migrationsTableExists = await checkTableExists(nuxtUsersConfig, "migrations");
361
+ if (migrationsTableExists) {
362
+ const appliedMigrations = await getAppliedMigrations(nuxtUsersConfig);
363
+ console.log("[Nuxt Users] \u{1F4CA} Migrations:");
364
+ console.log("[Nuxt Users] Migrations table exists: \u2705");
365
+ console.log("[Nuxt Users] Applied migrations count:", appliedMigrations.length);
366
+ if (appliedMigrations.length > 0) {
367
+ console.log("[Nuxt Users] Applied migrations:", appliedMigrations.join(", "));
368
+ }
369
+ } else {
370
+ console.log("[Nuxt Users] \u{1F4CA} Migrations:");
371
+ console.log("[Nuxt Users] Migrations table exists: \u274C");
372
+ console.log('[Nuxt Users] Run "npx nuxt-users migrate" to set up the database');
373
+ }
374
+ } catch (error) {
375
+ console.log("[Nuxt Users] \u26A0\uFE0F Could not check migrations:", error instanceof Error ? error.message : "Unknown error");
376
+ }
267
377
  } else {
268
378
  console.log("[Nuxt Users] \u26A0\uFE0F Nuxt Users module not configured in runtime config");
269
379
  }
@@ -304,6 +414,7 @@ const main = defineCommand({
304
414
  "create-password-reset-tokens-table": createPasswordResetTokensTable,
305
415
  "create-migrations-table": createMigrationsTable,
306
416
  "add-active-to-users": addActiveToUsers,
417
+ "add-google-oauth-fields": addGoogleOauthFields,
307
418
  "project-info": projectInfo
308
419
  }
309
420
  });
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.BTVH9EyH.mjs';
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.BTVH9EyH.js';
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.34.7",
4
+ "version": "1.36.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
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
- ...runtimeConfigOptions.auth,
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",
@@ -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,38 @@
1
+ interface Props {
2
+ successTitle?: string;
3
+ errorTitle?: string;
4
+ loginButtonText?: string;
5
+ loginUrl?: string;
6
+ showLoginButton?: boolean;
7
+ }
8
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {}, __VLS_9: {}, __VLS_11: {}, __VLS_13: {};
9
+ type __VLS_Slots = {} & {
10
+ 'success-icon'?: (props: typeof __VLS_1) => any;
11
+ } & {
12
+ 'success-content'?: (props: typeof __VLS_3) => any;
13
+ } & {
14
+ 'success-actions'?: (props: typeof __VLS_5) => any;
15
+ } & {
16
+ 'error-icon'?: (props: typeof __VLS_7) => any;
17
+ } & {
18
+ 'error-content'?: (props: typeof __VLS_9) => any;
19
+ } & {
20
+ 'error-actions'?: (props: typeof __VLS_11) => any;
21
+ } & {
22
+ 'loading-content'?: (props: typeof __VLS_13) => any;
23
+ };
24
+ declare const __VLS_base: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
25
+ successTitle: string;
26
+ errorTitle: string;
27
+ loginButtonText: string;
28
+ loginUrl: string;
29
+ showLoginButton: boolean;
30
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
31
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
32
+ declare const _default: typeof __VLS_export;
33
+ export default _default;
34
+ type __VLS_WithSlots<T, S> = T & {
35
+ new (): {
36
+ $slots: S;
37
+ };
38
+ };
@@ -21,14 +21,15 @@ type __VLS_Slots = {} & {
21
21
  } & {
22
22
  'loading-content'?: (props: typeof __VLS_13) => any;
23
23
  };
24
- declare const __VLS_component: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
24
+ declare const __VLS_base: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
25
25
  successTitle: string;
26
26
  errorTitle: string;
27
27
  loginButtonText: string;
28
28
  loginUrl: string;
29
29
  showLoginButton: boolean;
30
30
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
31
- declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
31
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
32
+ declare const _default: typeof __VLS_export;
32
33
  export default _default;
33
34
  type __VLS_WithSlots<T, S> = T & {
34
35
  new (): {
@@ -0,0 +1,31 @@
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 __VLS_export: 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
+ declare const _default: typeof __VLS_export;
31
+ export default _default;
@@ -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,31 @@
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 __VLS_export: 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
+ declare const _default: typeof __VLS_export;
31
+ export default _default;
File without changes
@@ -0,0 +1,48 @@
1
+ interface Props {
2
+ apiEndpoint?: string;
3
+ forgotPasswordEndpoint?: string;
4
+ redirectTo?: string;
5
+ }
6
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {}, __VLS_9: {}, __VLS_11: {}, __VLS_13: {
7
+ message: string;
8
+ }, __VLS_15: {
9
+ error: string;
10
+ };
11
+ type __VLS_Slots = {} & {
12
+ header?: (props: typeof __VLS_1) => any;
13
+ } & {
14
+ 'email-field'?: (props: typeof __VLS_3) => any;
15
+ } & {
16
+ 'password-field'?: (props: typeof __VLS_5) => any;
17
+ } & {
18
+ 'remember-me'?: (props: typeof __VLS_7) => any;
19
+ } & {
20
+ 'submit-button'?: (props: typeof __VLS_9) => any;
21
+ } & {
22
+ footer?: (props: typeof __VLS_11) => any;
23
+ } & {
24
+ 'success-message'?: (props: typeof __VLS_13) => any;
25
+ } & {
26
+ 'error-message'?: (props: typeof __VLS_15) => any;
27
+ };
28
+ declare const __VLS_base: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
29
+ error: (error: string) => any;
30
+ success: (user: UserWithoutPassword, rememberMe: boolean) => any;
31
+ submit: (data: LoginFormData) => any;
32
+ "forgot-password-error": (error: string) => any;
33
+ "forgot-password-success": () => any;
34
+ }, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
35
+ onError?: ((error: string) => any) | undefined;
36
+ onSuccess?: ((user: UserWithoutPassword, rememberMe: boolean) => any) | undefined;
37
+ onSubmit?: ((data: LoginFormData) => any) | undefined;
38
+ "onForgot-password-error"?: ((error: string) => any) | undefined;
39
+ "onForgot-password-success"?: (() => any) | undefined;
40
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
41
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
42
+ declare const _default: typeof __VLS_export;
43
+ export default _default;
44
+ type __VLS_WithSlots<T, S> = T & {
45
+ new (): {
46
+ $slots: S;
47
+ };
48
+ };