nuxt-users 1.38.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 (94) hide show
  1. package/README.md +13 -1
  2. package/dist/cli.mjs +57 -27
  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 +29 -7
  7. package/dist/runtime/assets/nuxt-users.css +1 -1
  8. package/dist/runtime/components/NUsersEmailConfirmation.d.vue.ts +5 -3
  9. package/dist/runtime/components/NUsersEmailConfirmation.vue +22 -15
  10. package/dist/runtime/components/NUsersEmailConfirmation.vue.d.ts +5 -3
  11. package/dist/runtime/components/NUsersGoogleLoginButton.d.vue.ts +4 -2
  12. package/dist/runtime/components/NUsersGoogleLoginButton.vue +5 -2
  13. package/dist/runtime/components/NUsersGoogleLoginButton.vue.d.ts +4 -2
  14. package/dist/runtime/components/NUsersList.vue +25 -11
  15. package/dist/runtime/components/NUsersLoginForm.d.vue.ts +13 -2
  16. package/dist/runtime/components/NUsersLoginForm.vue +35 -13
  17. package/dist/runtime/components/NUsersLoginForm.vue.d.ts +13 -2
  18. package/dist/runtime/components/NUsersLogoutLink.d.vue.ts +3 -4
  19. package/dist/runtime/components/NUsersLogoutLink.vue +8 -5
  20. package/dist/runtime/components/NUsersLogoutLink.vue.d.ts +3 -4
  21. package/dist/runtime/components/NUsersPasswordStrengthIndicator.d.vue.ts +6 -0
  22. package/dist/runtime/components/NUsersPasswordStrengthIndicator.vue +17 -9
  23. package/dist/runtime/components/NUsersPasswordStrengthIndicator.vue.d.ts +6 -0
  24. package/dist/runtime/components/NUsersProfileInfo.d.vue.ts +13 -1
  25. package/dist/runtime/components/NUsersProfileInfo.vue +23 -9
  26. package/dist/runtime/components/NUsersProfileInfo.vue.d.ts +13 -1
  27. package/dist/runtime/components/NUsersRegisterForm.d.vue.ts +26 -11
  28. package/dist/runtime/components/NUsersRegisterForm.vue +34 -16
  29. package/dist/runtime/components/NUsersRegisterForm.vue.d.ts +26 -11
  30. package/dist/runtime/components/NUsersResetPasswordForm.d.vue.ts +11 -2
  31. package/dist/runtime/components/NUsersResetPasswordForm.vue +25 -12
  32. package/dist/runtime/components/NUsersResetPasswordForm.vue.d.ts +11 -2
  33. package/dist/runtime/components/NUsersUserCard.d.vue.ts +2 -0
  34. package/dist/runtime/components/NUsersUserCard.vue +8 -4
  35. package/dist/runtime/components/NUsersUserCard.vue.d.ts +2 -0
  36. package/dist/runtime/components/NUsersUserForm.d.vue.ts +2 -2
  37. package/dist/runtime/components/NUsersUserForm.vue +5 -4
  38. package/dist/runtime/components/NUsersUserForm.vue.d.ts +2 -2
  39. package/dist/runtime/composables/useAuthentication.js +14 -0
  40. package/dist/runtime/composables/useNuxtUsersLocale.d.ts +15 -0
  41. package/dist/runtime/composables/useNuxtUsersLocale.js +27 -0
  42. package/dist/runtime/composables/usePasswordValidation.d.ts +1 -1
  43. package/dist/runtime/composables/usePublicPaths.js +2 -1
  44. package/dist/runtime/locales/en.d.ts +2 -0
  45. package/dist/runtime/locales/en.js +130 -0
  46. package/dist/runtime/locales/hu.d.ts +3 -0
  47. package/dist/runtime/locales/hu.js +188 -0
  48. package/dist/runtime/locales/index.d.ts +5 -0
  49. package/dist/runtime/locales/index.js +8 -0
  50. package/dist/runtime/server/api/nuxt-users/[id].delete.js +1 -1
  51. package/dist/runtime/server/api/nuxt-users/[id].get.js +1 -1
  52. package/dist/runtime/server/api/nuxt-users/[id].patch.js +1 -1
  53. package/dist/runtime/server/api/nuxt-users/auth/google/callback.get.js +1 -1
  54. package/dist/runtime/server/api/nuxt-users/auth/google/redirect.get.js +1 -1
  55. package/dist/runtime/server/api/nuxt-users/confirm-email.get.js +3 -3
  56. package/dist/runtime/server/api/nuxt-users/inactive.get.js +1 -2
  57. package/dist/runtime/server/api/nuxt-users/index.get.js +1 -2
  58. package/dist/runtime/server/api/nuxt-users/index.post.js +1 -1
  59. package/dist/runtime/server/api/nuxt-users/me.get.js +20 -13
  60. package/dist/runtime/server/api/nuxt-users/me.patch.js +1 -1
  61. package/dist/runtime/server/api/nuxt-users/password/forgot.post.js +2 -1
  62. package/dist/runtime/server/api/nuxt-users/password/index.patch.js +1 -1
  63. package/dist/runtime/server/api/nuxt-users/password/reset.post.d.ts +1 -1
  64. package/dist/runtime/server/api/nuxt-users/password/reset.post.js +4 -2
  65. package/dist/runtime/server/api/nuxt-users/session/index.delete.js +1 -1
  66. package/dist/runtime/server/api/nuxt-users/session/index.post.js +5 -3
  67. package/dist/runtime/server/composables/useServerAuth.js +1 -1
  68. package/dist/runtime/server/internal/index.d.ts +1 -7
  69. package/dist/runtime/server/internal/index.js +23 -7
  70. package/dist/runtime/server/middleware/authorization.server.js +22 -5
  71. package/dist/runtime/server/tasks/cleanup-tokens.d.ts +1 -1
  72. package/dist/runtime/server/utils/add-indexes.d.ts +3 -0
  73. package/dist/runtime/server/utils/add-indexes.js +17 -0
  74. package/dist/runtime/server/utils/google-oauth.js +2 -1
  75. package/dist/runtime/server/utils/index.d.ts +2 -2
  76. package/dist/runtime/server/utils/index.js +2 -2
  77. package/dist/runtime/server/utils/migrate.d.ts +6 -0
  78. package/dist/runtime/server/utils/migrate.js +10 -1
  79. package/dist/runtime/utils/deep-merge.d.ts +8 -0
  80. package/dist/runtime/utils/deep-merge.js +17 -0
  81. package/dist/runtime/utils/locale.d.ts +14 -0
  82. package/dist/runtime/utils/locale.js +38 -0
  83. package/dist/shared/{nuxt-users.BnOQs2LI.mjs → nuxt-users.DfZ7bc_P.mjs} +16 -8
  84. package/dist/shared/{nuxt-users.SXaMnaEL.d.mts → nuxt-users.XW6DYM7t.d.mts} +46 -2
  85. package/dist/shared/{nuxt-users.SXaMnaEL.d.ts → nuxt-users.XW6DYM7t.d.ts} +46 -2
  86. package/dist/utils.d.mts +4 -1
  87. package/dist/utils.d.ts +4 -1
  88. package/dist/utils.mjs +4 -1
  89. package/package.json +11 -6
  90. package/scripts/check-locale-consistency.ts +156 -0
  91. package/scripts/check-peer-dependencies.js +5 -5
  92. package/scripts/check-translations.ts +255 -0
  93. package/scripts/post-build.js +1 -1
  94. package/scripts/test-nuxt3.sh +78 -0
@@ -28,11 +28,17 @@ 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
- } else {
40
+ }
41
+ if (!event.path.startsWith("/api/")) {
36
42
  console.debug(`[Nuxt Users] authorization: ${event.path} No token found - letting client handle page redirect`);
37
43
  return;
38
44
  }
@@ -40,13 +46,23 @@ export default defineEventHandler(async (event) => {
40
46
  const user = await getCurrentUserFromToken(token, options);
41
47
  if (!user) {
42
48
  if (event.path.startsWith("/api/")) {
43
- 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
+ }
44
54
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
45
- } else {
55
+ }
56
+ if (!event.path.startsWith("/api/")) {
46
57
  console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - letting client handle page redirect`);
47
58
  return;
48
59
  }
49
60
  }
61
+ if (!event.context) {
62
+ event.context = {};
63
+ }
64
+ event.context.nuxtUsers = event.context.nuxtUsers || {};
65
+ event.context.nuxtUsers.user = user;
50
66
  if (event.path === `${base}/me`) {
51
67
  console.debug(`[Nuxt Users] authorization: Auto-whitelisted /me endpoint for authenticated user ${user.id}`);
52
68
  return;
@@ -55,7 +71,8 @@ export default defineEventHandler(async (event) => {
55
71
  if (event.path.startsWith("/api/")) {
56
72
  console.warn(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - API request rejected`);
57
73
  throw createError({ statusCode: 403, statusMessage: "Forbidden" });
58
- } else {
74
+ }
75
+ if (!event.path.startsWith("/api/")) {
59
76
  console.debug(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - letting client handle page redirect`);
60
77
  return;
61
78
  }
@@ -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
+ };
@@ -122,7 +122,8 @@ export async function createAuthTokenForUser(user, moduleOptions, rememberMe = f
122
122
  if (rememberMe) {
123
123
  const longTermDays = moduleOptions.auth.rememberMeExpiration || 30;
124
124
  expiresAt.setDate(expiresAt.getDate() + longTermDays);
125
- } else {
125
+ }
126
+ if (!rememberMe) {
126
127
  expiresAt.setMinutes(expiresAt.getMinutes() + moduleOptions.auth.tokenExpiration);
127
128
  }
128
129
  await db.sql`
@@ -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) => {
@@ -0,0 +1,8 @@
1
+ import type { LocaleMessages } from '../../types.js';
2
+ /**
3
+ * Deep merge two objects
4
+ * @param target - Base object
5
+ * @param source - Object to merge into target
6
+ * @returns Merged object
7
+ */
8
+ export declare const deepMerge: (target: LocaleMessages, source: LocaleMessages) => LocaleMessages;
@@ -0,0 +1,17 @@
1
+ export const deepMerge = (target, source) => {
2
+ const result = { ...target };
3
+ for (const key in source) {
4
+ const sourceValue = source[key];
5
+ if (!sourceValue) {
6
+ result[key] = sourceValue;
7
+ } else if (typeof sourceValue === "object" && !Array.isArray(sourceValue)) {
8
+ result[key] = deepMerge(
9
+ result[key] || {},
10
+ sourceValue
11
+ );
12
+ } else {
13
+ result[key] = sourceValue;
14
+ }
15
+ }
16
+ return result;
17
+ };
@@ -0,0 +1,14 @@
1
+ import type { LocaleMessages } from '../../types.js';
2
+ /**
3
+ * Get a nested value from an object using dot notation
4
+ */
5
+ export declare const getNestedValue: (obj: LocaleMessages, path: string) => string | undefined;
6
+ /**
7
+ * Get translation for a key
8
+ * @param key - Translation key (e.g., 'login.title')
9
+ * @param locale - Current locale
10
+ * @param customTexts - Custom texts from config
11
+ * @param fallbackLocale - Fallback locale
12
+ * @param params - Parameters to replace in the translation (e.g., {0}, {1})
13
+ */
14
+ export declare const getTranslation: (key: string, locale?: string, customTexts?: Record<string, LocaleMessages>, fallbackLocale?: string, params?: (string | number)[]) => string;
@@ -0,0 +1,38 @@
1
+ import { defaultLocaleMessages } from "../locales/index.js";
2
+ import { deepMerge } from "./deep-merge.js";
3
+ export const getNestedValue = (obj, path) => {
4
+ const keys = path.split(".");
5
+ let value = obj;
6
+ for (const key of keys) {
7
+ if (value && typeof value === "object") {
8
+ value = value[key];
9
+ } else {
10
+ return void 0;
11
+ }
12
+ }
13
+ return typeof value === "string" ? value : void 0;
14
+ };
15
+ export const getTranslation = (key, locale = "en", customTexts, fallbackLocale = "en", params) => {
16
+ const localeMessages = customTexts?.[locale] ? deepMerge(defaultLocaleMessages[locale] || {}, customTexts[locale]) : defaultLocaleMessages[locale];
17
+ let translation = localeMessages ? getNestedValue(localeMessages, key) : void 0;
18
+ if (!translation && fallbackLocale !== locale) {
19
+ const fallbackMessages = customTexts?.[fallbackLocale] ? deepMerge(defaultLocaleMessages[fallbackLocale] || {}, customTexts[fallbackLocale]) : defaultLocaleMessages[fallbackLocale];
20
+ translation = fallbackMessages ? getNestedValue(fallbackMessages, key) : void 0;
21
+ }
22
+ if (!translation && locale !== "en" && fallbackLocale !== "en" && defaultLocaleMessages.en) {
23
+ translation = getNestedValue(defaultLocaleMessages.en, key);
24
+ }
25
+ if (translation && params && params.length > 0) {
26
+ const placeholderMatches = translation.match(/\{\d+\}/g);
27
+ const expectedParamCount = placeholderMatches ? placeholderMatches.length : 0;
28
+ if (expectedParamCount !== params.length && process.env.NODE_ENV !== "production") {
29
+ console.warn(
30
+ `[nuxt-users locale] Parameter count mismatch for key "${key}": expected ${expectedParamCount} parameter(s), but received ${params.length}`
31
+ );
32
+ }
33
+ params.forEach((param, index) => {
34
+ translation = translation.replace(`{${index}}`, String(param));
35
+ });
36
+ }
37
+ return translation || key;
38
+ };
@@ -69,7 +69,8 @@ const validatePassword = (password, options = {}) => {
69
69
  if (password.length < config.minLength) {
70
70
  errors.push(`Password must be at least ${config.minLength} characters long`);
71
71
  hints.push(`Use at least ${config.minLength} characters`);
72
- } else {
72
+ }
73
+ if (password.length >= config.minLength) {
73
74
  score += 20;
74
75
  if (password.length < 12) {
75
76
  hints.push("Use 12 or more characters for extra security");
@@ -78,25 +79,29 @@ const validatePassword = (password, options = {}) => {
78
79
  if (config.requireUppercase && !/[A-Z]/.test(password)) {
79
80
  errors.push("Password must contain at least one uppercase letter");
80
81
  hints.push("Add an uppercase letter (A-Z)");
81
- } else if (/[A-Z]/.test(password)) {
82
+ }
83
+ if (/[A-Z]/.test(password)) {
82
84
  score += 15;
83
85
  }
84
86
  if (config.requireLowercase && !/[a-z]/.test(password)) {
85
87
  errors.push("Password must contain at least one lowercase letter");
86
88
  hints.push("Add a lowercase letter (a-z)");
87
- } else if (/[a-z]/.test(password)) {
89
+ }
90
+ if (/[a-z]/.test(password)) {
88
91
  score += 15;
89
92
  }
90
93
  if (config.requireNumbers && !/\d/.test(password)) {
91
94
  errors.push("Password must contain at least one number");
92
95
  hints.push("Add a number (0-9)");
93
- } else if (/\d/.test(password)) {
96
+ }
97
+ if (/\d/.test(password)) {
94
98
  score += 15;
95
99
  }
96
100
  if (config.requireSpecialChars && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
97
101
  errors.push("Password must contain at least one special character");
98
102
  hints.push("Add a special character (e.g. !@#$%)");
99
- } else if (/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
103
+ }
104
+ if (/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
100
105
  score += 15;
101
106
  }
102
107
  if (config.preventCommonPasswords && COMMON_PASSWORDS.includes(password.toLowerCase())) {
@@ -105,7 +110,8 @@ const validatePassword = (password, options = {}) => {
105
110
  }
106
111
  if (password.length >= 12) {
107
112
  score += 10;
108
- } else if (password.length >= 10) {
113
+ }
114
+ if (password.length >= 10 && password.length < 12) {
109
115
  score += 5;
110
116
  }
111
117
  const hasUppercase = /[A-Z]/.test(password);
@@ -115,14 +121,16 @@ const validatePassword = (password, options = {}) => {
115
121
  const complexityTypes = [hasUppercase, hasLowercase, hasNumbers, hasSpecial].filter(Boolean).length;
116
122
  if (complexityTypes >= 4) {
117
123
  score += 10;
118
- } else if (complexityTypes >= 3) {
124
+ }
125
+ if (complexityTypes >= 3 && complexityTypes < 4) {
119
126
  score += 5;
120
127
  hints.push("Use a mix of uppercase, lowercase, numbers, and special characters");
121
128
  }
122
129
  let strength = "weak";
123
130
  if (score >= 80) {
124
131
  strength = "strong";
125
- } else if (score >= 60) {
132
+ }
133
+ if (score >= 60 && score < 80) {
126
134
  strength = "medium";
127
135
  }
128
136
  return {
@@ -12,6 +12,35 @@ type DatabaseConfig = {
12
12
  password?: string;
13
13
  database?: string;
14
14
  };
15
+ interface LocaleMessages {
16
+ [key: string]: string | LocaleMessages;
17
+ }
18
+ interface LocaleOptions {
19
+ /**
20
+ * Default locale code
21
+ * @default 'en'
22
+ */
23
+ default?: string;
24
+ /**
25
+ * Custom messages to override or extend default translations
26
+ * Use nested object structure for organizing translations
27
+ * @example
28
+ * {
29
+ * en: {
30
+ * login: { title: 'Welcome', submit: 'Sign In' }
31
+ * },
32
+ * hu: {
33
+ * login: { title: 'Üdvözöljük', submit: 'Bejelentkezés' }
34
+ * }
35
+ * }
36
+ */
37
+ texts?: Record<string, LocaleMessages>;
38
+ /**
39
+ * Fallback locale when translation is missing
40
+ * @default 'en'
41
+ */
42
+ fallbackLocale?: string;
43
+ }
15
44
  interface GoogleOAuthOptions {
16
45
  /**
17
46
  * Google OAuth client ID from Google Cloud Console
@@ -159,6 +188,10 @@ interface RuntimeModuleOptions {
159
188
  * @default false
160
189
  */
161
190
  hardDelete?: boolean;
191
+ /**
192
+ * Locale configuration for component labels and messages
193
+ */
194
+ locale?: LocaleOptions;
162
195
  }
163
196
  interface ModuleOptions {
164
197
  connector: {
@@ -191,6 +224,17 @@ interface ModuleOptions {
191
224
  preventCommonPasswords: boolean;
192
225
  };
193
226
  hardDelete: boolean;
227
+ locale?: {
228
+ default: string;
229
+ fallbackLocale?: string;
230
+ texts?: Record<string, LocaleMessages>;
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;
194
238
  }
195
239
  interface MailerOptions {
196
240
  host: string;
@@ -268,5 +312,5 @@ declare const defaultFieldLabels: {
268
312
  last_login_at: string;
269
313
  };
270
314
 
271
- export { defaultDisplayFields as h, defaultFieldLabels as i };
272
- export type { DisplayFieldsProps as D, GoogleOAuthOptions as G, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g };
315
+ export { defaultDisplayFields as j, defaultFieldLabels as k };
316
+ export type { DisplayFieldsProps as D, GoogleOAuthOptions as G, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g, LocaleOptions as h, LocaleMessages as i };
@@ -12,6 +12,35 @@ type DatabaseConfig = {
12
12
  password?: string;
13
13
  database?: string;
14
14
  };
15
+ interface LocaleMessages {
16
+ [key: string]: string | LocaleMessages;
17
+ }
18
+ interface LocaleOptions {
19
+ /**
20
+ * Default locale code
21
+ * @default 'en'
22
+ */
23
+ default?: string;
24
+ /**
25
+ * Custom messages to override or extend default translations
26
+ * Use nested object structure for organizing translations
27
+ * @example
28
+ * {
29
+ * en: {
30
+ * login: { title: 'Welcome', submit: 'Sign In' }
31
+ * },
32
+ * hu: {
33
+ * login: { title: 'Üdvözöljük', submit: 'Bejelentkezés' }
34
+ * }
35
+ * }
36
+ */
37
+ texts?: Record<string, LocaleMessages>;
38
+ /**
39
+ * Fallback locale when translation is missing
40
+ * @default 'en'
41
+ */
42
+ fallbackLocale?: string;
43
+ }
15
44
  interface GoogleOAuthOptions {
16
45
  /**
17
46
  * Google OAuth client ID from Google Cloud Console
@@ -159,6 +188,10 @@ interface RuntimeModuleOptions {
159
188
  * @default false
160
189
  */
161
190
  hardDelete?: boolean;
191
+ /**
192
+ * Locale configuration for component labels and messages
193
+ */
194
+ locale?: LocaleOptions;
162
195
  }
163
196
  interface ModuleOptions {
164
197
  connector: {
@@ -191,6 +224,17 @@ interface ModuleOptions {
191
224
  preventCommonPasswords: boolean;
192
225
  };
193
226
  hardDelete: boolean;
227
+ locale?: {
228
+ default: string;
229
+ fallbackLocale?: string;
230
+ texts?: Record<string, LocaleMessages>;
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;
194
238
  }
195
239
  interface MailerOptions {
196
240
  host: string;
@@ -268,5 +312,5 @@ declare const defaultFieldLabels: {
268
312
  last_login_at: string;
269
313
  };
270
314
 
271
- export { defaultDisplayFields as h, defaultFieldLabels as i };
272
- export type { DisplayFieldsProps as D, GoogleOAuthOptions as G, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g };
315
+ export { defaultDisplayFields as j, defaultFieldLabels as k };
316
+ export type { DisplayFieldsProps as D, GoogleOAuthOptions as G, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g, LocaleOptions as h, LocaleMessages as i };
package/dist/utils.d.mts CHANGED
@@ -1,4 +1,7 @@
1
- export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, G as GoogleOAuthOptions, L as LoginFormData, c as LoginFormProps, M as ModuleOptions, b as PasswordResetToken, e as Permission, P as PersonalAccessToken, d as ResetPasswordFormProps, R as RuntimeModuleOptions, U as User, a as UserWithoutPassword, h as defaultDisplayFields, i as defaultFieldLabels } from './shared/nuxt-users.SXaMnaEL.mjs';
1
+ export { defaultLocaleMessages, en, hu, huFormal } from '../dist/runtime/locales/index.js';
2
+ export { getNestedValue, getTranslation } from '../dist/runtime/utils/locale.js';
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.XW6DYM7t.mjs';
2
5
 
3
6
  interface PasswordValidationResult {
4
7
  isValid: boolean;
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, G as GoogleOAuthOptions, L as LoginFormData, c as LoginFormProps, M as ModuleOptions, b as PasswordResetToken, e as Permission, P as PersonalAccessToken, d as ResetPasswordFormProps, R as RuntimeModuleOptions, U as User, a as UserWithoutPassword, h as defaultDisplayFields, i as defaultFieldLabels } from './shared/nuxt-users.SXaMnaEL.js';
1
+ export { defaultLocaleMessages, en, hu, huFormal } from '../dist/runtime/locales/index.js';
2
+ export { getNestedValue, getTranslation } from '../dist/runtime/utils/locale.js';
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.XW6DYM7t.js';
2
5
 
3
6
  interface PasswordValidationResult {
4
7
  isValid: boolean;
package/dist/utils.mjs CHANGED
@@ -1,4 +1,7 @@
1
- export { a as getPasswordStrengthColor, b as getPasswordStrengthText, g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.BnOQs2LI.mjs';
1
+ export { a as getPasswordStrengthColor, b as getPasswordStrengthText, g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.DfZ7bc_P.mjs';
2
+ export { defaultLocaleMessages, en, hu, huFormal } from '../dist/runtime/locales/index.js';
3
+ export { getNestedValue, getTranslation } from '../dist/runtime/utils/locale.js';
4
+ export { deepMerge } from '../dist/runtime/utils/deep-merge.js';
2
5
 
3
6
  const defaultDisplayFields = ["id", "name", "email", "role", "active", "created_at", "updated_at", "last_login_at"];
4
7
  const defaultFieldLabels = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.38.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",
@@ -62,27 +62,32 @@
62
62
  "dev:prepare": "yarn nuxt-module-build build --stub && yarn nuxt-module-build prepare && nuxi prepare playground",
63
63
  "build": "nuxt-module-build build",
64
64
  "postbuild": "node scripts/post-build.js",
65
+ "check:translations": "tsx scripts/check-translations.ts",
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)",
65
68
  "prerelease": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for deployment' && exit 1)",
66
- "release": "yarn lint && 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",
67
70
  "lint": "eslint .",
68
71
  "lint:fix": "eslint . --fix",
69
72
  "pretest": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for testing' && exit 1)",
70
73
  "test:quick": "yarn test:unit && yarn test:sqlite",
71
74
  "test:release": "yarn test && yarn build && yarn lint:fix",
72
- "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",
73
76
  "test:unit": "./scripts/test-unit.sh",
74
77
  "test:integration": "./scripts/test-integration.sh",
75
78
  "test:sqlite": "./scripts/test-sqlite.sh",
76
79
  "test:mysql": "./scripts/test-mysql.sh",
77
80
  "test:postgresql": "./scripts/test-postgresql.sh",
78
- "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",
79
84
  "test:watch": "vitest watch",
80
85
  "db:create-users-table": "tsx src/cli/create-users-table.ts",
81
86
  "db:create-user": "tsx src/cli/create-user.ts",
82
87
  "db:create-personal-access-tokens-table": "tsx src/cli/create-personal-access-tokens-table.ts",
83
88
  "db:create-password-reset-tokens-table": "tsx src/cli/create-password-reset-tokens-table.ts",
84
89
  "db:create-migrations-table": "tsx src/cli/create-migrations-table.ts",
85
- "db:migrate": "tsx src/cli/migrate.ts",
90
+ "db:migrate": "tsx src/cli/main.ts migrate",
86
91
  "db:create-empty-sqlite": "node scripts/create-empty-sqlite.js",
87
92
  "docs:dev": "vitepress dev docs",
88
93
  "docs:build": "vitepress build docs",
@@ -121,7 +126,7 @@
121
126
  "@nuxt/test-utils": "^3.19.2",
122
127
  "@nuxt/vite-builder": "^4.2.0",
123
128
  "@types/bcrypt": "^6.0.0",
124
- "@types/node": "latest",
129
+ "@types/node": "^25.0.3",
125
130
  "@types/nodemailer": "^6.4.17",
126
131
  "@types/pg": "^8.15.4",
127
132
  "changelogen": "^0.6.2",