nuxt-users 1.37.3 → 1.39.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 (74) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.mjs +10 -6
  3. package/dist/module.d.mts +1 -1
  4. package/dist/module.d.ts +1 -1
  5. package/dist/module.json +1 -1
  6. package/dist/module.mjs +16 -3
  7. package/dist/runtime/components/NUsersEmailConfirmation.d.vue.ts +5 -3
  8. package/dist/runtime/components/NUsersEmailConfirmation.vue +22 -15
  9. package/dist/runtime/components/NUsersEmailConfirmation.vue.d.ts +5 -3
  10. package/dist/runtime/components/NUsersGoogleLoginButton.d.vue.ts +4 -2
  11. package/dist/runtime/components/NUsersGoogleLoginButton.vue +5 -2
  12. package/dist/runtime/components/NUsersGoogleLoginButton.vue.d.ts +4 -2
  13. package/dist/runtime/components/NUsersList.vue +25 -11
  14. package/dist/runtime/components/NUsersLoginForm.d.vue.ts +13 -2
  15. package/dist/runtime/components/NUsersLoginForm.vue +35 -13
  16. package/dist/runtime/components/NUsersLoginForm.vue.d.ts +13 -2
  17. package/dist/runtime/components/NUsersLogoutLink.d.vue.ts +3 -4
  18. package/dist/runtime/components/NUsersLogoutLink.vue +8 -5
  19. package/dist/runtime/components/NUsersLogoutLink.vue.d.ts +3 -4
  20. package/dist/runtime/components/NUsersPasswordStrengthIndicator.d.vue.ts +6 -0
  21. package/dist/runtime/components/NUsersPasswordStrengthIndicator.vue +17 -9
  22. package/dist/runtime/components/NUsersPasswordStrengthIndicator.vue.d.ts +6 -0
  23. package/dist/runtime/components/NUsersProfileInfo.d.vue.ts +13 -1
  24. package/dist/runtime/components/NUsersProfileInfo.vue +23 -9
  25. package/dist/runtime/components/NUsersProfileInfo.vue.d.ts +13 -1
  26. package/dist/runtime/components/NUsersRegisterForm.d.vue.ts +26 -11
  27. package/dist/runtime/components/NUsersRegisterForm.vue +34 -16
  28. package/dist/runtime/components/NUsersRegisterForm.vue.d.ts +26 -11
  29. package/dist/runtime/components/NUsersResetPasswordForm.d.vue.ts +11 -2
  30. package/dist/runtime/components/NUsersResetPasswordForm.vue +25 -12
  31. package/dist/runtime/components/NUsersResetPasswordForm.vue.d.ts +11 -2
  32. package/dist/runtime/components/NUsersUserCard.d.vue.ts +2 -0
  33. package/dist/runtime/components/NUsersUserCard.vue +8 -4
  34. package/dist/runtime/components/NUsersUserCard.vue.d.ts +2 -0
  35. package/dist/runtime/components/NUsersUserForm.d.vue.ts +2 -2
  36. package/dist/runtime/components/NUsersUserForm.vue +5 -4
  37. package/dist/runtime/components/NUsersUserForm.vue.d.ts +2 -2
  38. package/dist/runtime/composables/useNuxtUsersLocale.d.ts +15 -0
  39. package/dist/runtime/composables/useNuxtUsersLocale.js +27 -0
  40. package/dist/runtime/composables/usePasswordValidation.d.ts +1 -1
  41. package/dist/runtime/composables/usePublicPaths.js +2 -1
  42. package/dist/runtime/locales/en.d.ts +2 -0
  43. package/dist/runtime/locales/en.js +130 -0
  44. package/dist/runtime/locales/hu.d.ts +3 -0
  45. package/dist/runtime/locales/hu.js +188 -0
  46. package/dist/runtime/locales/index.d.ts +5 -0
  47. package/dist/runtime/locales/index.js +8 -0
  48. package/dist/runtime/middleware/authorization.client.js +4 -1
  49. package/dist/runtime/plugin.auth-init.d.ts +9 -0
  50. package/dist/runtime/plugin.auth-init.js +16 -0
  51. package/dist/runtime/server/api/nuxt-users/confirm-email.get.js +3 -3
  52. package/dist/runtime/server/api/nuxt-users/password/forgot.post.js +2 -1
  53. package/dist/runtime/server/api/nuxt-users/password/reset.post.d.ts +1 -1
  54. package/dist/runtime/server/api/nuxt-users/password/reset.post.js +4 -2
  55. package/dist/runtime/server/api/nuxt-users/session/index.post.js +4 -2
  56. package/dist/runtime/server/middleware/authorization.server.js +6 -3
  57. package/dist/runtime/server/utils/google-oauth.js +2 -1
  58. package/dist/runtime/utils/deep-merge.d.ts +8 -0
  59. package/dist/runtime/utils/deep-merge.js +17 -0
  60. package/dist/runtime/utils/locale.d.ts +14 -0
  61. package/dist/runtime/utils/locale.js +38 -0
  62. package/dist/shared/{nuxt-users.BnOQs2LI.mjs → nuxt-users.DfZ7bc_P.mjs} +16 -8
  63. package/dist/shared/{nuxt-users.SXaMnaEL.d.ts → nuxt-users.FiQjvlZl.d.mts} +40 -2
  64. package/dist/shared/{nuxt-users.SXaMnaEL.d.mts → nuxt-users.FiQjvlZl.d.ts} +40 -2
  65. package/dist/utils.d.mts +4 -1
  66. package/dist/utils.d.ts +4 -1
  67. package/dist/utils.mjs +4 -1
  68. package/package.json +5 -3
  69. package/scripts/check-locale-consistency.ts +156 -0
  70. package/scripts/check-peer-dependencies.js +5 -5
  71. package/scripts/check-translations.ts +255 -0
  72. package/scripts/post-build.js +1 -1
  73. package/dist/runtime/plugin.client.d.ts +0 -2
  74. package/dist/runtime/plugin.client.js +0 -6
@@ -0,0 +1,188 @@
1
+ import { deepMerge } from "../utils/deep-merge.js";
2
+ const huBase = {
3
+ common: {
4
+ email: "E-mail",
5
+ password: "Jelsz\xF3",
6
+ name: "N\xE9v",
7
+ fullName: "Teljes n\xE9v",
8
+ role: "Szerepk\xF6r",
9
+ active: "Akt\xEDv",
10
+ yes: "Igen",
11
+ no: "Nem",
12
+ id: "ID",
13
+ created: "L\xE9trehozva",
14
+ updated: "Friss\xEDtve",
15
+ lastLogin: "Utols\xF3 bel\xE9p\xE9s",
16
+ registered: "Regisztr\xE1lva",
17
+ lastUpdated: "Utolj\xE1ra friss\xEDtve",
18
+ edit: "Szerkeszt\xE9s",
19
+ delete: "T\xF6rl\xE9s",
20
+ loading: "Bet\xF6lt\xE9s...",
21
+ error: "Hiba"
22
+ },
23
+ login: {
24
+ passwordLabel: "Jelsz\xF3",
25
+ submit: "Bejelentkez\xE9s",
26
+ submitting: "Bejelentkez\xE9s...",
27
+ forgotPasswordSending: "K\xFCld\xE9s...",
28
+ loginFailed: "Bejelentkez\xE9s sikertelen",
29
+ forgotPasswordFailed: "A jelsz\xF3 vissza\xE1ll\xEDt\xF3 e-mail k\xFCld\xE9se sikertelen"
30
+ },
31
+ register: {
32
+ title: "Regisztr\xE1ci\xF3",
33
+ nameLabel: "Teljes n\xE9v",
34
+ passwordLabel: "Jelsz\xF3",
35
+ confirmPasswordLabel: "Jelsz\xF3 meger\u0151s\xEDt\xE9s",
36
+ passwordMismatch: "A jelszavak nem egyeznek",
37
+ submit: "Regisztr\xE1ci\xF3",
38
+ submitting: "Fi\xF3k l\xE9trehoz\xE1sa..."
39
+ },
40
+ resetPassword: {
41
+ titleReset: "Jelsz\xF3 vissza\xE1ll\xEDt\xE1s",
42
+ titleChange: "Jelsz\xF3 megv\xE1ltoztat\xE1sa",
43
+ currentPasswordLabel: "Jelenlegi jelsz\xF3",
44
+ newPasswordLabel: "\xDAj jelsz\xF3",
45
+ confirmPasswordLabel: "\xDAj jelsz\xF3 meger\u0151s\xEDt\xE9se",
46
+ submitReset: "Jelsz\xF3 vissza\xE1ll\xEDt\xE1s",
47
+ submitUpdate: "Jelsz\xF3 friss\xEDt\xE9se",
48
+ submittingReset: "Vissza\xE1ll\xEDt\xE1s...",
49
+ submittingUpdate: "Friss\xEDt\xE9s...",
50
+ passwordHelpText: "A jelsz\xF3nak tartalmaznia kell legal\xE1bb",
51
+ secureResetInfo: "Biztons\xE1gos linket haszn\xE1lva \xE1ll\xEDthatod vissza a jelszavad."
52
+ },
53
+ passwordStrength: {
54
+ label: "Jelsz\xF3 k\xF6vetelm\xE9nyek",
55
+ strength: {
56
+ weak: "Gyenge",
57
+ medium: "K\xF6zepes",
58
+ strong: "Er\u0151s",
59
+ veryStrong: "Nagyon er\u0151s",
60
+ unknown: "Ismeretlen"
61
+ },
62
+ requirements: {
63
+ minLength: "Legal\xE1bb {0} karakter",
64
+ uppercase: "Tartalmaz nagybet\u0171t",
65
+ lowercase: "Tartalmaz kisbet\u0171t",
66
+ numbers: "Tartalmaz sz\xE1mot",
67
+ specialChars: "Tartalmaz speci\xE1lis karaktert"
68
+ }
69
+ },
70
+ profile: {
71
+ title: "Profil inform\xE1ci\xF3k",
72
+ name: "N\xE9v",
73
+ email: "E-mail",
74
+ role: "Jogosults\xE1g",
75
+ registered: "Regisztr\xE1lva",
76
+ lastUpdated: "Utolj\xE1ra friss\xEDtve",
77
+ active: "Akt\xEDv",
78
+ id: "ID"
79
+ },
80
+ emailConfirmation: {
81
+ successTitle: "E-mail meger\u0151s\xEDtve!",
82
+ errorTitle: "Meger\u0151s\xEDt\xE9s sikertelen",
83
+ loginButton: "Tov\xE1bb a bejelentkez\xE9shez",
84
+ backToLogin: "Vissza a bejelentkez\xE9shez",
85
+ processing: "Feldolgoz\xE1s..."
86
+ },
87
+ logout: {
88
+ linkText: "Kijelentkez\xE9s",
89
+ loggingOut: "Kijelentkez\xE9s...",
90
+ confirmMessage: "Biztosan ki szeretn\xE9l jelentkezni?"
91
+ },
92
+ userCard: {
93
+ edit: "Szerkeszt\xE9s",
94
+ delete: "T\xF6rl\xE9s"
95
+ },
96
+ googleLogin: {
97
+ buttonText: "Folytat\xE1s Google-lel",
98
+ redirecting: "\xC1tir\xE1ny\xEDt\xE1s..."
99
+ },
100
+ usersList: {
101
+ title: "Felhaszn\xE1l\xF3k list\xE1ja",
102
+ loading: "Felhaszn\xE1l\xF3k bet\xF6lt\xE9se...",
103
+ noUsers: "Nem tal\xE1lhat\xF3 felhaszn\xE1l\xF3",
104
+ error: "Hiba",
105
+ page: "Oldal",
106
+ of: "/",
107
+ total: "\xD6sszesen",
108
+ users: "felhaszn\xE1l\xF3",
109
+ previous: "El\u0151z\u0151",
110
+ next: "K\xF6vetkez\u0151"
111
+ }
112
+ };
113
+ const huInformalOverrides = {
114
+ login: {
115
+ title: "Hell\xF3!",
116
+ subtitle: "Jelentkezz be a fi\xF3kodba",
117
+ emailPlaceholder: "Add meg az e-mail c\xEDmed",
118
+ passwordPlaceholder: "Add meg a jelszavad",
119
+ forgotPassword: "Elfelejtetted a jelszavad?",
120
+ rememberMe: "Eml\xE9kezz r\xE1m",
121
+ emailRequiredForForgotPassword: "K\xE9rj\xFCk, el\u0151sz\xF6r add meg az e-mail c\xEDmed",
122
+ loginFailed: "Bejelentkez\xE9s sikertelen",
123
+ forgotPasswordFailed: "A jelsz\xF3 vissza\xE1ll\xEDt\xF3 e-mail k\xFCld\xE9se sikertelen"
124
+ },
125
+ register: {
126
+ subtitle: "Hozz l\xE9tre egy \xFAj fi\xF3kot",
127
+ namePlaceholder: "Add meg a teljes neved",
128
+ emailPlaceholder: "Add meg az e-mail c\xEDmed",
129
+ passwordPlaceholder: "Add meg a jelszavad",
130
+ confirmPasswordPlaceholder: "Er\u0151s\xEDtsd meg a jelszavad",
131
+ alreadyHaveAccount: "M\xE1r van fi\xF3kod?",
132
+ signInLink: "Jelentkezz be itt",
133
+ submit: "Regisztr\xE1lj"
134
+ },
135
+ passwordStrength: {
136
+ hintsTitle: "Hogyan tedd er\u0151sebb\xE9 a jelszavad"
137
+ },
138
+ logout: {
139
+ confirmMessage: "Biztosan ki szeretn\xE9l jelentkezni?"
140
+ },
141
+ userCard: {
142
+ deleteConfirm: "Biztosan t\xF6rl\xF6d {0} felhaszn\xE1l\xF3t?"
143
+ },
144
+ emailConfirmation: {
145
+ processingMessage: "K\xE9rj\xFCk, v\xE1rj, am\xEDg feldolgozzuk az e-mail meger\u0151s\xEDt\xE9s\xE9t.",
146
+ successMessage: "Az e-mail c\xEDmed meger\u0151s\xEDt\xE9sre ker\xFClt \xE9s a fi\xF3kod akt\xEDv.",
147
+ errorMessage: "A meger\u0151s\xEDt\u0151 link \xE9rv\xE9nytelen vagy lej\xE1rt. K\xE9rj\xFCk, pr\xF3b\xE1ld \xFAjra regisztr\xE1lni vagy l\xE9pj kapcsolatba az \xFCgyf\xE9lszolg\xE1lattal."
148
+ }
149
+ };
150
+ const huFormalOverrides = {
151
+ login: {
152
+ title: "\xDCdv\xF6z\xF6lj\xFCk",
153
+ subtitle: "Jelentkezzen be fi\xF3kj\xE1ba",
154
+ emailPlaceholder: "Adja meg az e-mail c\xEDm\xE9t",
155
+ passwordPlaceholder: "Adja meg a jelszav\xE1t",
156
+ forgotPassword: "Elfelejtette a jelszav\xE1t?",
157
+ rememberMe: "Bejelentkezve maradok",
158
+ emailRequiredForForgotPassword: "K\xE9rj\xFCk, el\u0151sz\xF6r adja meg az e-mail c\xEDm\xE9t",
159
+ loginFailed: "Bejelentkez\xE9s sikertelen",
160
+ forgotPasswordFailed: "A jelsz\xF3 vissza\xE1ll\xEDt\xF3 e-mail k\xFCld\xE9se sikertelen"
161
+ },
162
+ register: {
163
+ subtitle: "Hozzon l\xE9tre egy \xFAj fi\xF3kot",
164
+ namePlaceholder: "Adja meg a teljes nev\xE9t",
165
+ emailPlaceholder: "Adja meg az e-mail c\xEDm\xE9t",
166
+ passwordPlaceholder: "Adja meg a jelszav\xE1t",
167
+ confirmPasswordPlaceholder: "Er\u0151s\xEDtse meg a jelszav\xE1t",
168
+ alreadyHaveAccount: "M\xE1r van fi\xF3kja?",
169
+ signInLink: "Jelentkezzen be itt",
170
+ submit: "Regisztr\xE1ljon"
171
+ },
172
+ passwordStrength: {
173
+ hintsTitle: "Hogyan tegye er\u0151sebb\xE9 a jelszav\xE1t?"
174
+ },
175
+ logout: {
176
+ confirmMessage: "Biztosan ki szeretne jelentkezni?"
177
+ },
178
+ userCard: {
179
+ deleteConfirm: "Biztosan t\xF6rli {0} felhaszn\xE1l\xF3t?"
180
+ },
181
+ emailConfirmation: {
182
+ processingMessage: "K\xE9rj\xFCk, v\xE1rjon, am\xEDg feldolgozzuk az e-mail meger\u0151s\xEDt\xE9s\xE9t.",
183
+ successMessage: "Az e-mail c\xEDme meger\u0151s\xEDt\xE9sre ker\xFClt \xE9s a fi\xF3kja akt\xEDv.",
184
+ errorMessage: "A meger\u0151s\xEDt\u0151 link \xE9rv\xE9nytelen vagy lej\xE1rt. K\xE9rj\xFCk, pr\xF3b\xE1ljon meg \xFAjra regisztr\xE1lni vagy l\xE9pjen kapcsolatba az \xFCgyf\xE9lszolg\xE1lattal."
185
+ }
186
+ };
187
+ export const hu = deepMerge(huBase, huInformalOverrides);
188
+ export const huFormal = deepMerge(huBase, huFormalOverrides);
@@ -0,0 +1,5 @@
1
+ import type { LocaleMessages } from '../../types.js';
2
+ import { en } from './en.js';
3
+ import { hu, huFormal } from './hu.js';
4
+ export declare const defaultLocaleMessages: Record<string, LocaleMessages>;
5
+ export { en, hu, huFormal };
@@ -0,0 +1,8 @@
1
+ import { en } from "./en.js";
2
+ import { hu, huFormal } from "./hu.js";
3
+ export const defaultLocaleMessages = {
4
+ en,
5
+ hu,
6
+ "hu-formal": huFormal
7
+ };
8
+ export { en, hu, huFormal };
@@ -18,7 +18,10 @@ export default defineNuxtRouteMiddleware(async (to) => {
18
18
  console.log(`[Nuxt Users] client.middleware.auth.global: Whitelisted: ${to.path}`);
19
19
  return;
20
20
  }
21
- const { isAuthenticated, user, fetchUser } = useAuthentication();
21
+ const { isAuthenticated, user, fetchUser, initializeUser } = useAuthentication();
22
+ if (!isAuthenticated.value) {
23
+ await initializeUser();
24
+ }
22
25
  if (!isAuthenticated.value && to.query?.oauth_success === "true") {
23
26
  try {
24
27
  await fetchUser(true);
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Early authentication initialization plugin
3
+ *
4
+ * This plugin runs early (name starts with '0') to ensure authentication state
5
+ * is initialized from cookies before middleware runs. This prevents redirects
6
+ * to login page when opening new tabs with valid session cookies.
7
+ */
8
+ declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
9
+ export default _default;
@@ -0,0 +1,16 @@
1
+ import { defineNuxtPlugin } from "#app";
2
+ import { useAuthentication } from "./composables/useAuthentication.js";
3
+ export default defineNuxtPlugin(async () => {
4
+ if (import.meta.server) {
5
+ const { fetchUser } = useAuthentication();
6
+ try {
7
+ await fetchUser(true);
8
+ } catch {
9
+ }
10
+ return;
11
+ }
12
+ if (import.meta.client) {
13
+ const { initializeUser } = useAuthentication();
14
+ await initializeUser();
15
+ }
16
+ });
@@ -20,7 +20,8 @@ export default defineEventHandler(async (event) => {
20
20
  redirectUrl.searchParams.set("status", "success");
21
21
  redirectUrl.searchParams.set("message", "Email confirmed successfully! Your account is now active. You can now log in.");
22
22
  return sendRedirect(event, redirectUrl.pathname + redirectUrl.search, 302);
23
- } else {
23
+ }
24
+ if (!success) {
24
25
  const redirectUrl = new URL(options.emailConfirmationUrl, "http://localhost");
25
26
  redirectUrl.searchParams.set("status", "error");
26
27
  redirectUrl.searchParams.set("message", "Invalid or expired confirmation token");
@@ -32,10 +33,9 @@ export default defineEventHandler(async (event) => {
32
33
  const options = nuxtUsers;
33
34
  const redirectUrl = new URL(options.emailConfirmationUrl, "http://localhost");
34
35
  redirectUrl.searchParams.set("status", "error");
36
+ redirectUrl.searchParams.set("message", "Email confirmation failed");
35
37
  if (error instanceof Error) {
36
38
  redirectUrl.searchParams.set("message", error.message);
37
- } else {
38
- redirectUrl.searchParams.set("message", "Email confirmation failed");
39
39
  }
40
40
  return sendRedirect(event, redirectUrl.pathname + redirectUrl.search, 302);
41
41
  }
@@ -21,7 +21,8 @@ export default defineEventHandler(async (event) => {
21
21
  } catch (error) {
22
22
  if (error instanceof Error) {
23
23
  console.error("[Nuxt Users] Error in forgot-password endpoint:", error.message);
24
- } else {
24
+ }
25
+ if (!(error instanceof Error)) {
25
26
  console.error("[Nuxt Users] Error in forgot-password endpoint:", error);
26
27
  }
27
28
  throw createError({
@@ -1,4 +1,4 @@
1
1
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
2
  message: string;
3
- }>>;
3
+ } | undefined>>;
4
4
  export default _default;
@@ -27,7 +27,8 @@ export default defineEventHandler(async (event) => {
27
27
  const success = await resetPassword(token, email, password, options);
28
28
  if (success) {
29
29
  return { message: "Password has been reset successfully. You can now log in with your new password." };
30
- } else {
30
+ }
31
+ if (!success) {
31
32
  throw createError({
32
33
  statusCode: 400,
33
34
  // Or 422 if you prefer for semantic validation errors
@@ -37,7 +38,8 @@ export default defineEventHandler(async (event) => {
37
38
  } catch (error) {
38
39
  if (error instanceof Error) {
39
40
  console.error("[Nuxt Users] Error in reset-password endpoint:", error.message);
40
- } else {
41
+ }
42
+ if (!(error instanceof Error)) {
41
43
  console.error("[Nuxt Users] Error in reset-password endpoint:", error);
42
44
  }
43
45
  if (error instanceof H3Error) {
@@ -51,7 +51,8 @@ export default defineEventHandler(async (event) => {
51
51
  if (rememberMe) {
52
52
  const longTermDays = options.auth.rememberMeExpiration || 30;
53
53
  expiresAt.setDate(expiresAt.getDate() + longTermDays);
54
- } else {
54
+ }
55
+ if (!rememberMe) {
55
56
  expiresAt.setMinutes(expiresAt.getMinutes() + options.auth.tokenExpiration);
56
57
  }
57
58
  await db.sql`
@@ -69,7 +70,8 @@ export default defineEventHandler(async (event) => {
69
70
  if (rememberMe) {
70
71
  const longTermDays = options.auth.rememberMeExpiration || 30;
71
72
  cookieOptions.maxAge = 60 * 60 * 24 * longTermDays;
72
- } else {
73
+ }
74
+ if (!rememberMe) {
73
75
  }
74
76
  setCookie(event, "auth_token", token, cookieOptions);
75
77
  const { password: _, ...userWithoutPassword } = user;
@@ -32,7 +32,8 @@ export default defineEventHandler(async (event) => {
32
32
  if (event.path.startsWith("/api/")) {
33
33
  console.warn(`[Nuxt Users] authorization: ${event.path} No token found - API request rejected`);
34
34
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
35
- } else {
35
+ }
36
+ if (!event.path.startsWith("/api/")) {
36
37
  console.debug(`[Nuxt Users] authorization: ${event.path} No token found - letting client handle page redirect`);
37
38
  return;
38
39
  }
@@ -42,7 +43,8 @@ export default defineEventHandler(async (event) => {
42
43
  if (event.path.startsWith("/api/")) {
43
44
  console.warn(`[Nuxt Users] authorization: ${event.path} Invalid token - API request rejected`);
44
45
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
45
- } else {
46
+ }
47
+ if (!event.path.startsWith("/api/")) {
46
48
  console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - letting client handle page redirect`);
47
49
  return;
48
50
  }
@@ -55,7 +57,8 @@ export default defineEventHandler(async (event) => {
55
57
  if (event.path.startsWith("/api/")) {
56
58
  console.warn(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - API request rejected`);
57
59
  throw createError({ statusCode: 403, statusMessage: "Forbidden" });
58
- } else {
60
+ }
61
+ if (!event.path.startsWith("/api/")) {
59
62
  console.debug(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - letting client handle page redirect`);
60
63
  return;
61
64
  }
@@ -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`
@@ -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,11 @@ 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
+ };
194
232
  }
195
233
  interface MailerOptions {
196
234
  host: string;
@@ -268,5 +306,5 @@ declare const defaultFieldLabels: {
268
306
  last_login_at: string;
269
307
  };
270
308
 
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 };
309
+ export { defaultDisplayFields as j, defaultFieldLabels as k };
310
+ 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,11 @@ 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
+ };
194
232
  }
195
233
  interface MailerOptions {
196
234
  host: string;
@@ -268,5 +306,5 @@ declare const defaultFieldLabels: {
268
306
  last_login_at: string;
269
307
  };
270
308
 
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 };
309
+ export { defaultDisplayFields as j, defaultFieldLabels as k };
310
+ 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.FiQjvlZl.mjs';
2
5
 
3
6
  interface PasswordValidationResult {
4
7
  isValid: boolean;