nuxt-users 1.39.0 → 1.40.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 (38) hide show
  1. package/README.md +8 -0
  2. package/dist/cli.mjs +47 -21
  3. package/dist/module.d.mts +2 -2
  4. package/dist/module.d.ts +2 -2
  5. package/dist/module.json +4 -1
  6. package/dist/module.mjs +18 -5
  7. package/dist/runtime/assets/nuxt-users.css +1 -1
  8. package/dist/runtime/composables/useAuthentication.js +14 -0
  9. package/dist/runtime/server/api/nuxt-users/[id].delete.js +1 -1
  10. package/dist/runtime/server/api/nuxt-users/[id].get.js +1 -1
  11. package/dist/runtime/server/api/nuxt-users/[id].patch.js +1 -1
  12. package/dist/runtime/server/api/nuxt-users/auth/google/callback.get.js +1 -1
  13. package/dist/runtime/server/api/nuxt-users/auth/google/redirect.get.js +1 -1
  14. package/dist/runtime/server/api/nuxt-users/inactive.get.js +1 -2
  15. package/dist/runtime/server/api/nuxt-users/index.get.js +1 -2
  16. package/dist/runtime/server/api/nuxt-users/index.post.js +1 -1
  17. package/dist/runtime/server/api/nuxt-users/me.get.js +20 -13
  18. package/dist/runtime/server/api/nuxt-users/me.patch.js +1 -1
  19. package/dist/runtime/server/api/nuxt-users/password/index.patch.js +1 -1
  20. package/dist/runtime/server/api/nuxt-users/session/index.delete.js +1 -1
  21. package/dist/runtime/server/api/nuxt-users/session/index.post.js +1 -1
  22. package/dist/runtime/server/composables/useServerAuth.js +1 -1
  23. package/dist/runtime/server/internal/index.d.ts +1 -7
  24. package/dist/runtime/server/internal/index.js +23 -7
  25. package/dist/runtime/server/middleware/authorization.server.js +16 -2
  26. package/dist/runtime/server/tasks/cleanup-tokens.d.ts +1 -1
  27. package/dist/runtime/server/utils/add-indexes.d.ts +3 -0
  28. package/dist/runtime/server/utils/add-indexes.js +17 -0
  29. package/dist/runtime/server/utils/index.d.ts +2 -2
  30. package/dist/runtime/server/utils/index.js +2 -2
  31. package/dist/runtime/server/utils/migrate.d.ts +6 -0
  32. package/dist/runtime/server/utils/migrate.js +10 -1
  33. package/dist/shared/{nuxt-users.FiQjvlZl.d.mts → nuxt-users.XW6DYM7t.d.mts} +6 -0
  34. package/dist/shared/{nuxt-users.FiQjvlZl.d.ts → nuxt-users.XW6DYM7t.d.ts} +6 -0
  35. package/dist/utils.d.mts +1 -1
  36. package/dist/utils.d.ts +1 -1
  37. package/package.json +8 -5
  38. package/scripts/test-nuxt3.sh +78 -0
package/README.md CHANGED
@@ -1,4 +1,12 @@
1
1
  # Nuxt Users
2
+ [![Context7](https://img.shields.io/badge/Context7-Indexed-blue)](https://context7.com/rrd108/nuxt-users)
3
+ [![npm version](https://img.shields.io/npm/v/nuxt-users/latest.svg)](https://www.npmjs.com/package/nuxt-users)
4
+ [![npm downloads](https://img.shields.io/npm/dm/nuxt-users.svg)](https://www.npmjs.com/package/nuxt-users)
5
+ [![License](https://img.shields.io/npm/l/nuxt-users.svg)](https://github.com/rrd108/nuxt-users/blob/main/LICENSE)
6
+ [![ci](https://github.com/rrd108/nuxt-users/actions/workflows/ci.yml/badge.svg)](https://github.com/rrd108/nuxt-users/actions/workflows/ci.yml)
7
+ [![Nuxt 3](https://img.shields.io/badge/Nuxt-3-00DC82.svg?logo=nuxt.js&logoColor=white)](https://nuxt.com)
8
+ [![Nuxt 4](https://img.shields.io/badge/Nuxt-4-00DC82.svg?logo=nuxt.js&logoColor=white)](https://nuxt.com)
9
+
2
10
 
3
11
  A user authentication module for Nuxt 3 and Nuxt 4 with database support for SQLite, MySQL, and PostgreSQL.
4
12
 
package/dist/cli.mjs CHANGED
@@ -4,14 +4,12 @@ import { readFileSync } from 'node:fs';
4
4
  import { defaultOptions } from './module.mjs';
5
5
  import { loadNuxt } from '@nuxt/kit';
6
6
  import { defu } from 'defu';
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';
7
+ import { runMigrations, createUser as createUser$1, createUsersTable as createUsersTable$1, createPersonalAccessTokensTable as createPersonalAccessTokensTable$1, createPasswordResetTokensTable as createPasswordResetTokensTable$1, createMigrationsTable as createMigrationsTable$1, useDb, checkTableExists, getAppliedMigrations } from '../dist/runtime/server/utils/index.js';
8
8
  import { g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.DfZ7bc_P.mjs';
9
9
  import '../dist/runtime/locales/index.js';
10
10
  import '../dist/runtime/utils/locale.js';
11
11
  import '../dist/runtime/utils/deep-merge.js';
12
12
  import { addActiveToUsers as addActiveToUsers$1 } from '../dist/runtime/server/utils/add-active-to-users.js';
13
- import { useDb, checkTableExists } from '../dist/runtime/server/utils/db.js';
14
- import { getAppliedMigrations } from '../dist/runtime/server/utils/migrate.js';
15
13
  import '../dist/runtime/server/internal/index.js';
16
14
 
17
15
  const getOptionsFromEnv = () => {
@@ -55,27 +53,53 @@ const getOptionsFromEnv = () => {
55
53
  const loadOptions = async () => {
56
54
  try {
57
55
  console.log("[Nuxt Users] Loading Nuxt project...");
56
+ console.log("[Nuxt Users] \u2139\uFE0F Note: CLI commands read .env files. If you use .env.local in development, export variables first:");
57
+ console.log("[Nuxt Users] set -a; source .env.local; set +a; npx nuxt-users <command>");
58
+ console.log("[Nuxt Users] In production, environment variables are set directly in the deployment environment.");
58
59
  const nuxt = await loadNuxt({ cwd: process.cwd(), ready: false });
59
60
  const topLevelConfig = nuxt.options.nuxtUsers;
60
- const originalRuntimeConfig = await (async () => {
61
- try {
62
- const configPath = await import('node:path').then((p) => p.resolve(process.cwd(), "nuxt.config.ts"));
63
- const config = await import(configPath).then((m) => m.default);
64
- return config?.runtimeConfig?.nuxtUsers;
65
- } catch {
66
- return nuxt.options.runtimeConfig?.nuxtUsers;
67
- }
68
- })();
61
+ const processedRuntimeConfig = nuxt.options.runtimeConfig?.nuxtUsers;
69
62
  await nuxt.close();
70
- if (topLevelConfig || originalRuntimeConfig) {
63
+ if (topLevelConfig || processedRuntimeConfig) {
71
64
  console.log("[Nuxt Users] Using configuration from nuxt.config");
72
- let mergedConfig = defu(topLevelConfig, originalRuntimeConfig, defaultOptions);
73
- const configuredConnector = topLevelConfig?.connector || originalRuntimeConfig?.connector;
74
- if (configuredConnector) {
75
- mergedConfig = {
76
- ...mergedConfig,
77
- connector: configuredConnector
78
- };
65
+ const { connector: _defaultConnector, ...defaultsWithoutConnector } = defaultOptions;
66
+ const mergedConfig = defu(topLevelConfig, processedRuntimeConfig, defaultsWithoutConnector);
67
+ const configuredConnector = topLevelConfig?.connector || processedRuntimeConfig?.connector;
68
+ mergedConfig.connector = configuredConnector || defaultOptions.connector;
69
+ if (mergedConfig.connector.name === "mysql" || mergedConfig.connector.name === "postgresql") {
70
+ const options = mergedConfig.connector.options;
71
+ const envOptions = {};
72
+ if (!options.user && process.env.DB_USER) {
73
+ envOptions.user = process.env.DB_USER;
74
+ }
75
+ if (!options.password && process.env.DB_PASSWORD) {
76
+ envOptions.password = process.env.DB_PASSWORD;
77
+ }
78
+ if (!options.host && process.env.DB_HOST) {
79
+ envOptions.host = process.env.DB_HOST;
80
+ }
81
+ if (!options.database && process.env.DB_NAME) {
82
+ envOptions.database = process.env.DB_NAME;
83
+ }
84
+ if (!options.port && process.env.DB_PORT) {
85
+ envOptions.port = Number.parseInt(process.env.DB_PORT);
86
+ }
87
+ if (Object.keys(envOptions).length > 0) {
88
+ mergedConfig.connector.options = { ...options, ...envOptions };
89
+ console.log("[Nuxt Users] Filled in missing connector options from environment variables");
90
+ }
91
+ const finalOptions = mergedConfig.connector.options;
92
+ if (!finalOptions.user || !finalOptions.password || !finalOptions.host || !finalOptions.database) {
93
+ console.warn("[Nuxt Users] Warning: MySQL/PostgreSQL connector options are incomplete.");
94
+ console.warn("[Nuxt Users] Required: user, password, host, database");
95
+ console.warn("[Nuxt Users] Found:", {
96
+ user: finalOptions.user || "(missing)",
97
+ password: finalOptions.password ? "***" : "(missing)",
98
+ host: finalOptions.host || "(missing)",
99
+ database: finalOptions.database || "(missing)"
100
+ });
101
+ console.warn("[Nuxt Users] Please set these in nuxt.config.ts or as environment variables (DB_USER, DB_PASSWORD, DB_HOST, DB_NAME)");
102
+ }
79
103
  }
80
104
  return mergedConfig;
81
105
  }
@@ -329,7 +353,9 @@ const projectInfo = defineCommand({
329
353
  async run() {
330
354
  try {
331
355
  console.log("[Nuxt Users] Loading Nuxt project...");
332
- console.log("[Nuxt Users] \u2139\uFE0F Note: This command only reads .env files. If you use .env.local or other env files, export those variables first.");
356
+ console.log("[Nuxt Users] \u2139\uFE0F Note: CLI commands read .env files. If you use .env.local in development, export variables first:");
357
+ console.log("[Nuxt Users] set -a; source .env.local; set +a; npx nuxt-users <command>");
358
+ console.log("[Nuxt Users] In production, environment variables are set directly in the deployment environment.");
333
359
  const nuxt = await loadNuxt({ cwd: process.cwd() });
334
360
  console.log("[Nuxt Users] \u2705 Nuxt project loaded successfully!");
335
361
  console.log("[Nuxt Users] \u{1F4C1} Source directory:", nuxt.options.srcDir);
package/dist/module.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.FiQjvlZl.mjs';
2
+ import { M as ModuleOptions } from './shared/nuxt-users.XW6DYM7t.mjs';
3
3
  export * from '../dist/runtime/server/internal/index.js';
4
4
 
5
5
  declare const defaultOptions: ModuleOptions;
6
- declare const _default: _nuxt_schema.NuxtModule<RuntimeModuleOptions, RuntimeModuleOptions, false>;
6
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
7
7
 
8
8
  export { _default as default, defaultOptions };
package/dist/module.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.FiQjvlZl.js';
2
+ import { M as ModuleOptions } from './shared/nuxt-users.XW6DYM7t.js';
3
3
  export * from '../dist/runtime/server/internal/index.js';
4
4
 
5
5
  declare const defaultOptions: ModuleOptions;
6
- declare const _default: _nuxt_schema.NuxtModule<RuntimeModuleOptions, RuntimeModuleOptions, false>;
6
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
7
7
 
8
8
  export { _default as default, defaultOptions };
package/dist/module.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.39.0",
4
+ "compatibility": {
5
+ "nuxt": ">=3.0.0"
6
+ },
7
+ "version": "1.40.0",
5
8
  "builder": {
6
9
  "@nuxt/module-builder": "1.0.2",
7
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addPlugin, addImportsDir, addServerImportsDir, addServerHandler, addRouteMiddleware, addComponentsDir } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addPlugin, addImportsDir, addServerImportsDir, addServerScanDir, addServerHandler, addRouteMiddleware, addComponentsDir } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
3
  export * from '../dist/runtime/server/internal/index.js';
4
4
 
@@ -55,12 +55,17 @@ const defaultOptions = {
55
55
  default: "en",
56
56
  fallbackLocale: "en",
57
57
  texts: {}
58
- }
58
+ },
59
+ tokenCleanupSchedule: "0 2 * * *"
60
+ // Daily at 2 AM
59
61
  };
60
62
  const module$1 = defineNuxtModule({
61
63
  meta: {
62
64
  name: "nuxt-users",
63
- configKey: "nuxtUsers"
65
+ configKey: "nuxtUsers",
66
+ compatibility: {
67
+ nuxt: ">=3.0.0"
68
+ }
64
69
  },
65
70
  // Default configuration options of the Nuxt module
66
71
  defaults: defaultOptions,
@@ -163,6 +168,7 @@ const module$1 = defineNuxtModule({
163
168
  });
164
169
  addImportsDir(resolver.resolve("./runtime/composables"));
165
170
  addServerImportsDir(resolver.resolve("./runtime/server/composables"));
171
+ addServerScanDir(resolver.resolve("./runtime/server"));
166
172
  addServerHandler({
167
173
  middleware: true,
168
174
  handler: resolver.resolve("./runtime/server/middleware/authorization.server")
@@ -257,8 +263,15 @@ const module$1 = defineNuxtModule({
257
263
  nitroConfig.experimental = nitroConfig.experimental || {};
258
264
  nitroConfig.experimental.database = true;
259
265
  nitroConfig.experimental.tasks = true;
260
- nitroConfig.scanDirs = nitroConfig.scanDirs || [];
261
- nitroConfig.scanDirs.push(resolver.resolve("./runtime/server/tasks"));
266
+ const cleanupSchedule = runtimeConfigOptions.tokenCleanupSchedule ?? defaultOptions.tokenCleanupSchedule;
267
+ if (cleanupSchedule) {
268
+ nitroConfig.scheduledTasks = nitroConfig.scheduledTasks || {};
269
+ nitroConfig.scheduledTasks[cleanupSchedule] = [
270
+ ...nitroConfig.scheduledTasks[cleanupSchedule] || [],
271
+ "nuxt-users:cleanup-tokens"
272
+ ];
273
+ console.log(`[Nuxt Users] \u{1F504} Token cleanup scheduled: ${cleanupSchedule}`);
274
+ }
262
275
  nitroConfig.prerender = nitroConfig.prerender || {};
263
276
  nitroConfig.prerender.ignore = nitroConfig.prerender.ignore || [];
264
277
  const apiBasePath = runtimeConfigOptions.apiBasePath || defaultOptions.apiBasePath;
@@ -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-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}
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-success-text:#047857;--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:#15803d;--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-success-text:#6ee7b7;--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;border:none;border-radius:.25rem;cursor:pointer;display:inline-flex;font-size:1rem;font-weight:500;gap:8px;justify-content:center;padding:.5rem 1rem;text-decoration:none;transition:background-color .2s,transform .1s}.n-users-google-logo{flex-shrink:0}.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-600,#4b5563);color:#fff}.n-users-cancel-btn:hover{background-color:var(--nu-color-gray-700,#374151)}.n-users-delete-btn{background-color:var(--nu-color-error-dark,#b91c1c);color:#fff}.n-users-delete-btn:hover{background-color:#991b1b}.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-dark,#b91c1c);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-success-message{background-color:var(--nu-color-success-light,#f0fdf4);border:1px solid var(--nu-color-success-border,#9ae6b4);border-radius:.5rem;color:var(--nu-color-success-text,#047857);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,#15803d)}.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-dark,#b91c1c);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}
@@ -45,6 +45,11 @@ export const useAuthentication = () => {
45
45
  lazy: false
46
46
  });
47
47
  if (error.value) {
48
+ const statusCode = error.value.statusCode || error.value.status;
49
+ if (statusCode === 401) {
50
+ user.value = null;
51
+ return null;
52
+ }
48
53
  throw new Error(error.value.message || "Failed to fetch user");
49
54
  }
50
55
  userData = data.value?.user || null;
@@ -74,6 +79,15 @@ export const useAuthentication = () => {
74
79
  }
75
80
  return userData;
76
81
  } catch (error) {
82
+ const statusCode = error?.statusCode || error?.status || error?.response?.status;
83
+ if (statusCode === 401) {
84
+ user.value = null;
85
+ if (import.meta.client) {
86
+ localStorage.removeItem("user");
87
+ sessionStorage.removeItem("user");
88
+ }
89
+ return null;
90
+ }
77
91
  console.error("Failed to fetch user:", error);
78
92
  user.value = null;
79
93
  if (import.meta.client) {
@@ -1,6 +1,6 @@
1
1
  import { createError, defineEventHandler } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { deleteUser } from "../../utils/user.js";
3
+ import { deleteUser } from "../../utils/index.js";
4
4
  export default defineEventHandler(async (event) => {
5
5
  const { nuxtUsers } = useRuntimeConfig();
6
6
  const options = nuxtUsers;
@@ -1,6 +1,6 @@
1
1
  import { createError, defineEventHandler } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { findUserById } from "../../utils/user.js";
3
+ import { findUserById } from "../../utils/index.js";
4
4
  export default defineEventHandler(async (event) => {
5
5
  const { nuxtUsers } = useRuntimeConfig();
6
6
  const options = nuxtUsers;
@@ -1,6 +1,6 @@
1
1
  import { createError, defineEventHandler, readBody } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { updateUser } from "../../utils/user.js";
3
+ import { updateUser } from "../../utils/index.js";
4
4
  export default defineEventHandler(async (event) => {
5
5
  const { nuxtUsers } = useRuntimeConfig();
6
6
  const options = nuxtUsers;
@@ -5,7 +5,7 @@ import {
5
5
  getGoogleUserFromCode,
6
6
  findOrCreateGoogleUser,
7
7
  createAuthTokenForUser
8
- } from "../../../../utils/google-oauth.js";
8
+ } from "../../../../utils/index.js";
9
9
  export default defineEventHandler(async (event) => {
10
10
  const { nuxtUsers } = useRuntimeConfig();
11
11
  const options = nuxtUsers;
@@ -1,6 +1,6 @@
1
1
  import { defineEventHandler, sendRedirect, createError } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { createGoogleOAuth2Client, getGoogleAuthUrl } from "../../../../utils/google-oauth.js";
3
+ import { createGoogleOAuth2Client, getGoogleAuthUrl } from "../../../../utils/index.js";
4
4
  export default defineEventHandler(async (event) => {
5
5
  const { nuxtUsers } = useRuntimeConfig();
6
6
  const options = nuxtUsers;
@@ -1,7 +1,6 @@
1
1
  import { createError, defineEventHandler, getQuery } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { useDb } from "../../utils/db.js";
4
- import { getLastLoginTime } from "../../utils/user.js";
3
+ import { useDb, getLastLoginTime } from "../../utils/index.js";
5
4
  export default defineEventHandler(async (event) => {
6
5
  const { nuxtUsers } = useRuntimeConfig();
7
6
  const options = nuxtUsers;
@@ -1,7 +1,6 @@
1
1
  import { createError, defineEventHandler, getQuery } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { useDb } from "../../utils/db.js";
4
- import { getLastLoginTime } from "../../utils/user.js";
3
+ import { useDb, getLastLoginTime } from "../../utils/index.js";
5
4
  export default defineEventHandler(async (event) => {
6
5
  const { nuxtUsers } = useRuntimeConfig();
7
6
  const options = nuxtUsers;
@@ -1,6 +1,6 @@
1
1
  import { createError, defineEventHandler, readBody } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { createUser } from "../../utils/user.js";
3
+ import { createUser } from "../../utils/index.js";
4
4
  export default defineEventHandler(async (event) => {
5
5
  const { nuxtUsers } = useRuntimeConfig();
6
6
  const options = nuxtUsers;
@@ -2,21 +2,28 @@ import { createError, defineEventHandler, getCookie } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
3
  import { getCurrentUserFromToken } from "../../utils/index.js";
4
4
  export default defineEventHandler(async (event) => {
5
- const { nuxtUsers } = useRuntimeConfig();
6
- const options = nuxtUsers;
7
- const token = getCookie(event, "auth_token");
8
- if (!token) {
9
- throw createError({
10
- statusCode: 401,
11
- statusMessage: "Unauthorized - No authentication token found"
12
- });
5
+ if (!event.context) {
6
+ event.context = {};
13
7
  }
14
- const user = await getCurrentUserFromToken(token, options);
8
+ let user = event.context.nuxtUsers?.user;
15
9
  if (!user) {
16
- throw createError({
17
- statusCode: 401,
18
- statusMessage: "Unauthorized - Invalid authentication token"
19
- });
10
+ const token = getCookie(event, "auth_token");
11
+ if (!token) {
12
+ throw createError({
13
+ statusCode: 401,
14
+ statusMessage: "Unauthorized - No authentication token found"
15
+ });
16
+ }
17
+ const { nuxtUsers } = useRuntimeConfig();
18
+ const options = nuxtUsers;
19
+ const validatedUser = await getCurrentUserFromToken(token, options);
20
+ if (!validatedUser) {
21
+ throw createError({
22
+ statusCode: 401,
23
+ statusMessage: "Unauthorized - Invalid authentication token"
24
+ });
25
+ }
26
+ user = validatedUser;
20
27
  }
21
28
  return { user };
22
29
  });
@@ -1,6 +1,6 @@
1
1
  import { createError, defineEventHandler, readBody } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { updateUser } from "../../utils/user.js";
3
+ import { updateUser } from "../../utils/index.js";
4
4
  export default defineEventHandler(async (event) => {
5
5
  const { nuxtUsers } = useRuntimeConfig();
6
6
  const options = nuxtUsers;
@@ -1,7 +1,7 @@
1
1
  import { createError, defineEventHandler, getCookie, readBody } from "h3";
2
2
  import bcrypt from "bcrypt";
3
3
  import { useRuntimeConfig } from "#imports";
4
- import { getCurrentUserFromToken, updateUserPassword } from "../../../utils/user.js";
4
+ import { getCurrentUserFromToken, updateUserPassword } from "../../../utils/index.js";
5
5
  import { validatePassword, getPasswordValidationOptions } from "../../../../../utils";
6
6
  export default defineEventHandler(async (event) => {
7
7
  const { nuxtUsers } = useRuntimeConfig();
@@ -1,6 +1,6 @@
1
1
  import { defineEventHandler, getCookie, setCookie } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { useDb } from "../../../utils/db.js";
3
+ import { useDb } from "../../../utils/index.js";
4
4
  export default defineEventHandler(async (event) => {
5
5
  const { nuxtUsers } = useRuntimeConfig();
6
6
  const options = nuxtUsers;
@@ -2,7 +2,7 @@ import { createError, defineEventHandler, readBody, setCookie } from "h3";
2
2
  import bcrypt from "bcrypt";
3
3
  import crypto from "node:crypto";
4
4
  import { useRuntimeConfig } from "#imports";
5
- import { useDb } from "../../../utils/db.js";
5
+ import { useDb } from "../../../utils/index.js";
6
6
  export default defineEventHandler(async (event) => {
7
7
  const body = await readBody(event);
8
8
  const { email, password, rememberMe } = body;
@@ -1,4 +1,4 @@
1
- import { getCurrentUserFromToken } from "../utils/user.js";
1
+ import { getCurrentUserFromToken } from "../utils/index.js";
2
2
  export const useServerAuth = () => {
3
3
  const getCurrentUser = async (event) => {
4
4
  const { useRuntimeConfig } = await import("#imports");
@@ -1,9 +1,3 @@
1
- export { useDb, checkTableExists } from '../utils/db.js';
2
- export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens, getLastLoginTime } from '../utils/user.js';
3
- export { createUsersTable } from '../utils/create-users-table.js';
4
- export { createPersonalAccessTokensTable } from '../utils/create-personal-access-tokens-table.js';
5
- export { createPasswordResetTokensTable } from '../utils/create-password-reset-tokens-table.js';
6
- export { createMigrationsTable } from '../utils/create-migrations-table.js';
7
- export { runMigrations } from '../utils/migrate.js';
1
+ export { useDb, checkTableExists, createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens, getLastLoginTime, findUserById, updateUser, deleteUser, createUsersTable, createPersonalAccessTokensTable, createPasswordResetTokensTable, createMigrationsTable, runMigrations, getAppliedMigrations } from '../utils/index.js';
8
2
  export { useNuxtUsersDatabase } from '../composables/useNuxtUsersDatabase.js';
9
3
  export { useServerAuth } from '../composables/useServerAuth.js';
@@ -1,9 +1,25 @@
1
- export { useDb, checkTableExists } from "../utils/db.js";
2
- export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens, getLastLoginTime } from "../utils/user.js";
3
- export { createUsersTable } from "../utils/create-users-table.js";
4
- export { createPersonalAccessTokensTable } from "../utils/create-personal-access-tokens-table.js";
5
- export { createPasswordResetTokensTable } from "../utils/create-password-reset-tokens-table.js";
6
- export { createMigrationsTable } from "../utils/create-migrations-table.js";
7
- export { runMigrations } from "../utils/migrate.js";
1
+ export {
2
+ useDb,
3
+ checkTableExists,
4
+ createUser,
5
+ findUserByEmail,
6
+ updateUserPassword,
7
+ getCurrentUserFromToken,
8
+ hasAnyUsers,
9
+ deleteExpiredPersonalAccessTokens,
10
+ deleteTokensWithoutExpiration,
11
+ cleanupPersonalAccessTokens,
12
+ revokeUserTokens,
13
+ getLastLoginTime,
14
+ findUserById,
15
+ updateUser,
16
+ deleteUser,
17
+ createUsersTable,
18
+ createPersonalAccessTokensTable,
19
+ createPasswordResetTokensTable,
20
+ createMigrationsTable,
21
+ runMigrations,
22
+ getAppliedMigrations
23
+ } from "../utils/index.js";
8
24
  export { useNuxtUsersDatabase } from "../composables/useNuxtUsersDatabase.js";
9
25
  export { useServerAuth } from "../composables/useServerAuth.js";
@@ -28,9 +28,14 @@ export default defineEventHandler(async (event) => {
28
28
  return;
29
29
  }
30
30
  const token = getCookie(event, "auth_token");
31
+ const isMeEndpoint = event.path === `${base}/me`;
31
32
  if (!token) {
32
33
  if (event.path.startsWith("/api/")) {
33
- console.warn(`[Nuxt Users] authorization: ${event.path} No token found - API request rejected`);
34
+ if (isMeEndpoint) {
35
+ console.debug(`[Nuxt Users] authorization: ${event.path} No token found - expected for auth check`);
36
+ } else {
37
+ console.warn(`[Nuxt Users] authorization: ${event.path} No token found - API request rejected`);
38
+ }
34
39
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
35
40
  }
36
41
  if (!event.path.startsWith("/api/")) {
@@ -41,7 +46,11 @@ export default defineEventHandler(async (event) => {
41
46
  const user = await getCurrentUserFromToken(token, options);
42
47
  if (!user) {
43
48
  if (event.path.startsWith("/api/")) {
44
- console.warn(`[Nuxt Users] authorization: ${event.path} Invalid token - API request rejected`);
49
+ if (isMeEndpoint) {
50
+ console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - expected for auth check`);
51
+ } else {
52
+ console.warn(`[Nuxt Users] authorization: ${event.path} Invalid token - API request rejected`);
53
+ }
45
54
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
46
55
  }
47
56
  if (!event.path.startsWith("/api/")) {
@@ -49,6 +58,11 @@ export default defineEventHandler(async (event) => {
49
58
  return;
50
59
  }
51
60
  }
61
+ if (!event.context) {
62
+ event.context = {};
63
+ }
64
+ event.context.nuxtUsers = event.context.nuxtUsers || {};
65
+ event.context.nuxtUsers.user = user;
52
66
  if (event.path === `${base}/me`) {
53
67
  console.debug(`[Nuxt Users] authorization: Auto-whitelisted /me endpoint for authenticated user ${user.id}`);
54
68
  return;
@@ -1,2 +1,2 @@
1
- declare const _default: import("nitropack").Task<"success">;
1
+ declare const _default: import("nitropack").Task<string>;
2
2
  export default _default;
@@ -0,0 +1,3 @@
1
+ import type { ModuleOptions } from 'nuxt-users/utils';
2
+ export declare const addActiveIndexToUsers: (options: ModuleOptions) => Promise<void>;
3
+ export declare const addTokenableIndexToPersonalAccessTokens: (options: ModuleOptions) => Promise<void>;
@@ -0,0 +1,17 @@
1
+ import { useDb } from "./db.js";
2
+ export const addActiveIndexToUsers = 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 active index to ${connectorName} Users Table in ${tableName}...`);
7
+ await db.sql`CREATE INDEX IF NOT EXISTS idx_users_active ON {${tableName}} (active)`;
8
+ console.log(`[Nuxt Users] DB:Add active index to ${connectorName} Users Table \u2705`);
9
+ };
10
+ export const addTokenableIndexToPersonalAccessTokens = async (options) => {
11
+ const connectorName = options.connector.name;
12
+ const db = await useDb(options);
13
+ const tableName = options.tables.personalAccessTokens;
14
+ console.log(`[Nuxt Users] DB:Add tokenable index to ${connectorName} Personal Access Tokens Table in ${tableName}...`);
15
+ await db.sql`CREATE INDEX IF NOT EXISTS idx_personal_access_tokens_tokenable ON {${tableName}} (tokenable_type, tokenable_id)`;
16
+ console.log(`[Nuxt Users] DB:Add tokenable index to ${connectorName} Personal Access Tokens Table \u2705`);
17
+ };
@@ -1,9 +1,9 @@
1
- export { useDb, checkTableExists } from './db.js';
1
+ export { useDb, checkTableExists, getConnector, closeAllDbConnections } from './db.js';
2
2
  export { isBuildTime } from './build-time.js';
3
3
  export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens, getLastLoginTime, findUserById, updateUser, deleteUser } from './user.js';
4
4
  export { createUsersTable } from './create-users-table.js';
5
5
  export { createPersonalAccessTokensTable } from './create-personal-access-tokens-table.js';
6
6
  export { createPasswordResetTokensTable } from './create-password-reset-tokens-table.js';
7
7
  export { createMigrationsTable } from './create-migrations-table.js';
8
- export { runMigrations } from './migrate.js';
8
+ export { runMigrations, getAppliedMigrations } from './migrate.js';
9
9
  export { createGoogleOAuth2Client, getGoogleAuthUrl, getGoogleUserFromCode, findOrCreateGoogleUser, createAuthTokenForUser, generateSecurePassword } from './google-oauth.js';
@@ -1,9 +1,9 @@
1
- export { useDb, checkTableExists } from "./db.js";
1
+ export { useDb, checkTableExists, getConnector, closeAllDbConnections } from "./db.js";
2
2
  export { isBuildTime } from "./build-time.js";
3
3
  export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens, getLastLoginTime, findUserById, updateUser, deleteUser } from "./user.js";
4
4
  export { createUsersTable } from "./create-users-table.js";
5
5
  export { createPersonalAccessTokensTable } from "./create-personal-access-tokens-table.js";
6
6
  export { createPasswordResetTokensTable } from "./create-password-reset-tokens-table.js";
7
7
  export { createMigrationsTable } from "./create-migrations-table.js";
8
- export { runMigrations } from "./migrate.js";
8
+ export { runMigrations, getAppliedMigrations } from "./migrate.js";
9
9
  export { createGoogleOAuth2Client, getGoogleAuthUrl, getGoogleUserFromCode, findOrCreateGoogleUser, createAuthTokenForUser, generateSecurePassword } from "./google-oauth.js";
@@ -1,4 +1,10 @@
1
1
  import type { ModuleOptions } from 'nuxt-users/utils';
2
+ interface Migration {
3
+ name: string;
4
+ run: (options: ModuleOptions) => Promise<void>;
5
+ }
6
+ export declare const migrations: Migration[];
2
7
  export declare const getAppliedMigrations: (options: ModuleOptions) => Promise<string[]>;
3
8
  export declare const markMigrationAsApplied: (options: ModuleOptions, migrationName: string) => Promise<void>;
4
9
  export declare const runMigrations: (options: ModuleOptions) => Promise<void>;
10
+ export {};
@@ -5,7 +5,8 @@ import { createPasswordResetTokensTable } from "./create-password-reset-tokens-t
5
5
  import { createMigrationsTable } from "./create-migrations-table.js";
6
6
  import { addActiveToUsers } from "./add-active-to-users.js";
7
7
  import { addGoogleOauthFields } from "./add-google-oauth-fields.js";
8
- const migrations = [
8
+ import { addActiveIndexToUsers, addTokenableIndexToPersonalAccessTokens } from "./add-indexes.js";
9
+ export const migrations = [
9
10
  {
10
11
  name: "create_migrations_table",
11
12
  run: createMigrationsTable
@@ -29,6 +30,14 @@ const migrations = [
29
30
  {
30
31
  name: "add_google_oauth_fields",
31
32
  run: addGoogleOauthFields
33
+ },
34
+ {
35
+ name: "add_active_index_to_users",
36
+ run: addActiveIndexToUsers
37
+ },
38
+ {
39
+ name: "add_tokenable_index_to_personal_access_tokens",
40
+ run: addTokenableIndexToPersonalAccessTokens
32
41
  }
33
42
  ];
34
43
  export const getAppliedMigrations = async (options) => {
@@ -229,6 +229,12 @@ interface ModuleOptions {
229
229
  fallbackLocale?: string;
230
230
  texts?: Record<string, LocaleMessages>;
231
231
  };
232
+ /**
233
+ * Token cleanup schedule (cron expression)
234
+ * Set to null or false to disable automatic cleanup
235
+ * Default: '0 2 * * *' (daily at 2 AM)
236
+ */
237
+ tokenCleanupSchedule?: string | false | null;
232
238
  }
233
239
  interface MailerOptions {
234
240
  host: string;
@@ -229,6 +229,12 @@ interface ModuleOptions {
229
229
  fallbackLocale?: string;
230
230
  texts?: Record<string, LocaleMessages>;
231
231
  };
232
+ /**
233
+ * Token cleanup schedule (cron expression)
234
+ * Set to null or false to disable automatic cleanup
235
+ * Default: '0 2 * * *' (daily at 2 AM)
236
+ */
237
+ tokenCleanupSchedule?: string | false | null;
232
238
  }
233
239
  interface MailerOptions {
234
240
  host: string;
package/dist/utils.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { defaultLocaleMessages, en, hu, huFormal } from '../dist/runtime/locales/index.js';
2
2
  export { getNestedValue, getTranslation } from '../dist/runtime/utils/locale.js';
3
3
  export { deepMerge } from '../dist/runtime/utils/deep-merge.js';
4
- export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, G as GoogleOAuthOptions, i as LocaleMessages, h as LocaleOptions, 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, j as defaultDisplayFields, k as defaultFieldLabels } from './shared/nuxt-users.FiQjvlZl.mjs';
4
+ export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, G as GoogleOAuthOptions, i as LocaleMessages, h as LocaleOptions, 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, j as defaultDisplayFields, k as defaultFieldLabels } from './shared/nuxt-users.XW6DYM7t.mjs';
5
5
 
6
6
  interface PasswordValidationResult {
7
7
  isValid: boolean;
package/dist/utils.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { defaultLocaleMessages, en, hu, huFormal } from '../dist/runtime/locales/index.js';
2
2
  export { getNestedValue, getTranslation } from '../dist/runtime/utils/locale.js';
3
3
  export { deepMerge } from '../dist/runtime/utils/deep-merge.js';
4
- export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, G as GoogleOAuthOptions, i as LocaleMessages, h as LocaleOptions, 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, j as defaultDisplayFields, k as defaultFieldLabels } from './shared/nuxt-users.FiQjvlZl.js';
4
+ export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, G as GoogleOAuthOptions, i as LocaleMessages, h as LocaleOptions, 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, j as defaultDisplayFields, k as defaultFieldLabels } from './shared/nuxt-users.XW6DYM7t.js';
5
5
 
6
6
  interface PasswordValidationResult {
7
7
  isValid: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.39.0",
3
+ "version": "1.40.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",
@@ -64,27 +64,30 @@
64
64
  "postbuild": "node scripts/post-build.js",
65
65
  "check:translations": "tsx scripts/check-translations.ts",
66
66
  "check:locale-consistency": "tsx scripts/check-locale-consistency.ts",
67
+ "check:auth": "npm whoami --registry=https://registry.npmjs.org/ || (echo '❌ Not authenticated with npm. Please run: npm login' && exit 1)",
67
68
  "prerelease": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for deployment' && exit 1)",
68
- "release": "yarn lint && yarn check:translations && yarn check:locale-consistency && yarn test && yarn prepack && changelogen --release && npm publish && git push --follow-tags",
69
+ "release": "yarn check:auth && yarn lint && yarn check:translations && yarn check:locale-consistency && yarn test && yarn prepack && changelogen --release && npm publish && git push --follow-tags",
69
70
  "lint": "eslint .",
70
71
  "lint:fix": "eslint . --fix",
71
72
  "pretest": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for testing' && exit 1)",
72
73
  "test:quick": "yarn test:unit && yarn test:sqlite",
73
74
  "test:release": "yarn test && yarn build && yarn lint:fix",
74
- "test:types": "yarn dev:prepare && vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
75
+ "test:types": "yarn dev:prepare && vue-tsc --noEmit --project tsconfig.vitest.json && cd playground && vue-tsc --noEmit",
75
76
  "test:unit": "./scripts/test-unit.sh",
76
77
  "test:integration": "./scripts/test-integration.sh",
77
78
  "test:sqlite": "./scripts/test-sqlite.sh",
78
79
  "test:mysql": "./scripts/test-mysql.sh",
79
80
  "test:postgresql": "./scripts/test-postgresql.sh",
80
- "test": "yarn test:types && yarn test:unit && yarn test:integration && yarn test:sqlite && yarn test:mysql && yarn test:postgresql",
81
+ "test:nuxt3": "./scripts/test-nuxt3.sh unit && ./scripts/test-nuxt3.sh integration && ./scripts/test-nuxt3.sh sqlite && ./scripts/test-nuxt3.sh mysql && ./scripts/test-nuxt3.sh postgresql",
82
+ "test:nuxt4": "yarn test:types && yarn test:unit && yarn test:integration && yarn test:sqlite && yarn test:mysql && yarn test:postgresql",
83
+ "test": "yarn lint && yarn test:nuxt3 && yarn test:nuxt4",
81
84
  "test:watch": "vitest watch",
82
85
  "db:create-users-table": "tsx src/cli/create-users-table.ts",
83
86
  "db:create-user": "tsx src/cli/create-user.ts",
84
87
  "db:create-personal-access-tokens-table": "tsx src/cli/create-personal-access-tokens-table.ts",
85
88
  "db:create-password-reset-tokens-table": "tsx src/cli/create-password-reset-tokens-table.ts",
86
89
  "db:create-migrations-table": "tsx src/cli/create-migrations-table.ts",
87
- "db:migrate": "tsx src/cli/migrate.ts",
90
+ "db:migrate": "tsx src/cli/main.ts migrate",
88
91
  "db:create-empty-sqlite": "node scripts/create-empty-sqlite.js",
89
92
  "docs:dev": "vitepress dev docs",
90
93
  "docs:build": "vitepress build docs",
@@ -0,0 +1,78 @@
1
+ #!/bin/bash
2
+
3
+ # Nuxt 3 Local Test Runner
4
+ # This script temporarily installs Nuxt 3, runs the specified test,
5
+ # and then restores the original Nuxt 4 dependencies.
6
+
7
+ set -e
8
+
9
+ # --- Configuration ---
10
+ NUXT3_VERSION="^3.17.6"
11
+ TEST_TYPE=$1
12
+
13
+ # --- Colors for output ---
14
+ RED='\033[0;31m'
15
+ GREEN='\033[0;32m'
16
+ BLUE='\033[0;34m'
17
+ NC='\033[0m'
18
+
19
+ # --- Helper functions ---
20
+ print_status() {
21
+ echo -e "${BLUE}$1${NC}"
22
+ }
23
+
24
+ print_error() {
25
+ echo -e "${RED}❌ $1${NC}"
26
+ }
27
+
28
+ print_success() {
29
+ echo -e "${GREEN}✅ $1${NC}"
30
+ }
31
+
32
+ # --- Cleanup function ---
33
+ cleanup() {
34
+ print_status "🧹 Restoring original dependencies..."
35
+ if [ -f "package.json.bak" ]; then
36
+ mv package.json.bak package.json
37
+ fi
38
+ if [ -f "yarn.lock.bak" ]; then
39
+ mv yarn.lock.bak yarn.lock
40
+ fi
41
+ npx nypm@latest i
42
+ print_success "Dependencies restored to Nuxt 4."
43
+ }
44
+
45
+ # --- Main script ---
46
+
47
+ # 1. Validate input
48
+ if [ -z "$TEST_TYPE" ]; then
49
+ print_error "Usage: ./scripts/test-nuxt3.sh [sqlite|mysql|postgresql|unit|integration]"
50
+ exit 1
51
+ fi
52
+
53
+ # 2. Set up cleanup trap
54
+ trap cleanup EXIT
55
+
56
+ # 3. Backup original package files
57
+ print_status "📦 Backing up package files..."
58
+ cp package.json package.json.bak
59
+ cp yarn.lock yarn.lock.bak
60
+
61
+ # 4. Install Nuxt 3
62
+ print_status "🚀 Installing Nuxt v3 (version: ${NUXT3_VERSION})..."
63
+ npx nypm@latest i nuxt@${NUXT3_VERSION}
64
+ print_success "Nuxt 3 installed."
65
+
66
+ # 5. Run the specified test
67
+ print_status "🧪 Running tests for '$TEST_TYPE' against Nuxt 3..."
68
+ if [ -f "scripts/test-${TEST_TYPE}.sh" ]; then
69
+ "./scripts/test-${TEST_TYPE}.sh"
70
+ else
71
+ print_error "Invalid test type '$TEST_TYPE'. No script found at scripts/test-${TEST_TYPE}.sh"
72
+ exit 1
73
+ fi
74
+
75
+ print_success "Nuxt 3 tests completed successfully!"
76
+
77
+ # 6. Cleanup will be handled by the trap
78
+ exit 0