nuxt-users 1.10.0 → 1.12.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 (53) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/cli.mjs +2 -3
  3. package/dist/module.d.mts +1 -1
  4. package/dist/module.d.ts +1 -1
  5. package/dist/module.json +2 -2
  6. package/dist/module.mjs +55 -30
  7. package/dist/runtime/assets/nuxt-users.css +1 -0
  8. package/dist/runtime/components/NUsersList.vue +136 -0
  9. package/dist/runtime/components/NUsersList.vue.d.ts +74 -0
  10. package/dist/runtime/components/{LoginForm.vue → NUsersLoginForm.vue} +20 -22
  11. package/dist/runtime/components/{LoginForm.vue.d.ts → NUsersLoginForm.vue.d.ts} +5 -9
  12. package/dist/runtime/components/{LogoutLink.vue → NUsersLogoutLink.vue} +2 -6
  13. package/dist/runtime/components/{PasswordStrengthIndicator.vue → NUsersPasswordStrengthIndicator.vue} +18 -22
  14. package/dist/runtime/components/{ProfileInfo.vue → NUsersProfileInfo.vue} +5 -7
  15. package/dist/runtime/components/{ResetPasswordForm.vue → NUsersResetPasswordForm.vue} +25 -28
  16. package/dist/runtime/components/{ResetPasswordForm.vue.d.ts → NUsersResetPasswordForm.vue.d.ts} +3 -6
  17. package/dist/runtime/components/NUsersUserCard.vue +93 -0
  18. package/dist/runtime/components/NUsersUserCard.vue.d.ts +33 -0
  19. package/dist/runtime/components/NUsersUserForm.vue +141 -0
  20. package/dist/runtime/components/NUsersUserForm.vue.d.ts +12 -0
  21. package/dist/runtime/composables/useAuthentication.d.ts +2 -0
  22. package/dist/runtime/composables/useAuthentication.js +5 -7
  23. package/dist/runtime/composables/usePasswordValidation.d.ts +2 -2
  24. package/dist/runtime/constants.d.ts +1 -0
  25. package/dist/runtime/constants.js +2 -1
  26. package/dist/runtime/middleware/authorization.client.js +6 -1
  27. package/dist/runtime/server/api/nuxt-users/index.get.d.ts +20 -0
  28. package/dist/runtime/server/api/nuxt-users/index.get.js +58 -0
  29. package/dist/runtime/server/api/{auth/forgot-password.post.js → nuxt-users/password/forgot.post.js} +1 -1
  30. package/dist/runtime/server/api/{auth/update-password.post.js → nuxt-users/password/index.patch.js} +2 -2
  31. package/dist/runtime/server/api/{auth/reset-password.post.js → nuxt-users/password/reset.post.js} +2 -2
  32. package/dist/runtime/server/api/{auth/logout.get.js → nuxt-users/session/index.delete.js} +1 -1
  33. package/dist/runtime/server/api/{auth/login.post.d.ts → nuxt-users/session/index.post.d.ts} +1 -0
  34. package/dist/runtime/server/api/{auth/login.post.js → nuxt-users/session/index.post.js} +1 -1
  35. package/dist/runtime/server/middleware/authorization.server.js +29 -9
  36. package/dist/runtime/server/utils/user.js +8 -5
  37. package/dist/shared/{nuxt-users.CXezgQLO.d.mts → nuxt-users.B5ttEpIO.d.mts} +8 -1
  38. package/dist/shared/{nuxt-users.CXezgQLO.d.ts → nuxt-users.B5ttEpIO.d.ts} +8 -1
  39. package/dist/utils.d.mts +1 -2
  40. package/dist/utils.d.ts +1 -2
  41. package/dist/utils.mjs +162 -2
  42. package/package.json +2 -2
  43. package/dist/runtime/assets/colors.css +0 -1
  44. package/dist/shared/nuxt-users.BnOQs2LI.mjs +0 -162
  45. package/dist/runtime/components/{LogoutLink.vue.d.ts → NUsersLogoutLink.vue.d.ts} +4 -4
  46. /package/dist/runtime/components/{PasswordStrengthIndicator.vue.d.ts → NUsersPasswordStrengthIndicator.vue.d.ts} +0 -0
  47. /package/dist/runtime/components/{ProfileInfo.vue.d.ts → NUsersProfileInfo.vue.d.ts} +0 -0
  48. /package/dist/runtime/server/api/nuxt-users/{profile.get.d.ts → me.get.d.ts} +0 -0
  49. /package/dist/runtime/server/api/nuxt-users/{profile.get.js → me.get.js} +0 -0
  50. /package/dist/runtime/server/api/{auth/forgot-password.post.d.ts → nuxt-users/password/forgot.post.d.ts} +0 -0
  51. /package/dist/runtime/server/api/{auth/logout.get.d.ts → nuxt-users/password/index.patch.d.ts} +0 -0
  52. /package/dist/runtime/server/api/{auth/reset-password.post.d.ts → nuxt-users/password/reset.post.d.ts} +0 -0
  53. /package/dist/runtime/server/api/{auth/update-password.post.d.ts → nuxt-users/session/index.delete.d.ts} +0 -0
@@ -1 +1,2 @@
1
1
  export declare const NO_AUTH_PATHS: string[];
2
+ export declare const NO_AUTH_API_PATHS: string[];
@@ -1 +1,2 @@
1
- export const NO_AUTH_PATHS = ["/login", "/api/auth/login", "/api/auth/forgot-password"];
1
+ export const NO_AUTH_PATHS = ["/login"];
2
+ export const NO_AUTH_API_PATHS = ["/session", "/password/forgot", "/password/reset"];
@@ -1,14 +1,19 @@
1
1
  import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig } from "#app";
2
2
  import { useAuthentication } from "../composables/useAuthentication.js";
3
3
  import { hasPermission, isWhitelisted } from "../utils/permissions.js";
4
- import { NO_AUTH_PATHS } from "../constants.js";
4
+ import { NO_AUTH_PATHS, NO_AUTH_API_PATHS } from "../constants.js";
5
5
  export default defineNuxtRouteMiddleware((to) => {
6
6
  const { public: { nuxtUsers: publicNuxtUsers } } = useRuntimeConfig();
7
7
  const publicOptions = publicNuxtUsers;
8
+ const base = publicOptions.apiBasePath || "/api/nuxt-users";
8
9
  if (NO_AUTH_PATHS.includes(to.path)) {
9
10
  console.log(`[Nuxt Users] client.middleware.auth.global: ${to.path}`);
10
11
  return;
11
12
  }
13
+ const openApiPaths = NO_AUTH_API_PATHS.map((path) => `${base}${path}`);
14
+ if (openApiPaths.includes(to.path)) {
15
+ return;
16
+ }
12
17
  if (isWhitelisted(to.path, publicOptions.auth.whitelist)) {
13
18
  console.log(`[Nuxt Users] client.middleware.auth.global: Whitelisted: ${to.path}`);
14
19
  return;
@@ -0,0 +1,20 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ users: {
3
+ id: number;
4
+ email: string;
5
+ name: string;
6
+ role: string;
7
+ created_at: string;
8
+ updated_at: string;
9
+ last_login_at: string | null;
10
+ }[];
11
+ pagination: {
12
+ page: number;
13
+ limit: number;
14
+ total: number;
15
+ totalPages: number;
16
+ hasNext: boolean;
17
+ hasPrev: boolean;
18
+ };
19
+ }>>;
20
+ export default _default;
@@ -0,0 +1,58 @@
1
+ import { createError, defineEventHandler, getQuery } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { useDb } from "../../utils/db.js";
4
+ import { getLastLoginTime } from "../../utils/user.js";
5
+ export default defineEventHandler(async (event) => {
6
+ const { nuxtUsers } = useRuntimeConfig();
7
+ const options = nuxtUsers;
8
+ const db = await useDb(options);
9
+ const usersTable = options.tables.users;
10
+ const query = getQuery(event);
11
+ const page = Number(query.page) || 1;
12
+ const limit = Number(query.limit) || 10;
13
+ const offset = (page - 1) * limit;
14
+ if (page < 1 || limit < 1 || limit > 100) {
15
+ throw createError({
16
+ statusCode: 400,
17
+ statusMessage: "Invalid pagination parameters. Page must be >= 1, limit must be between 1 and 100."
18
+ });
19
+ }
20
+ try {
21
+ const countResult = await db.sql`SELECT COUNT(*) as total FROM {${usersTable}}`;
22
+ const total = countResult.rows[0].total;
23
+ const usersResult = await db.sql`
24
+ SELECT id, email, name, role, created_at, updated_at
25
+ FROM {${usersTable}}
26
+ ORDER BY name ASC
27
+ LIMIT ${limit} OFFSET ${offset}
28
+ `;
29
+ const users = await Promise.all(usersResult.rows.map(async (user) => {
30
+ const lastLoginTime = await getLastLoginTime(user.id, options);
31
+ return {
32
+ id: user.id,
33
+ email: user.email,
34
+ name: user.name,
35
+ role: user.role,
36
+ created_at: user.created_at instanceof Date ? user.created_at.toISOString() : user.created_at,
37
+ updated_at: user.updated_at instanceof Date ? user.updated_at.toISOString() : user.updated_at,
38
+ last_login_at: lastLoginTime
39
+ };
40
+ }));
41
+ return {
42
+ users,
43
+ pagination: {
44
+ page,
45
+ limit,
46
+ total,
47
+ totalPages: Math.ceil(total / limit),
48
+ hasNext: page * limit < total,
49
+ hasPrev: page > 1
50
+ }
51
+ };
52
+ } catch (error) {
53
+ throw createError({
54
+ statusCode: 500,
55
+ statusMessage: `Error fetching users: ${error instanceof Error ? error.message : String(error)}`
56
+ });
57
+ }
58
+ });
@@ -1,5 +1,5 @@
1
1
  import { defineEventHandler, readBody, createError } from "h3";
2
- import { sendPasswordResetLink } from "../../services/password.js";
2
+ import { sendPasswordResetLink } from "../../../services/password.js";
3
3
  export default defineEventHandler(async (event) => {
4
4
  const body = await readBody(event);
5
5
  const { email } = body;
@@ -1,8 +1,8 @@
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/index.js";
5
- import { validatePassword, getPasswordValidationOptions } from "../../../../utils.js";
4
+ import { getCurrentUserFromToken, updateUserPassword } from "../../../utils/index.js";
5
+ import { validatePassword, getPasswordValidationOptions } from "../../../../../utils.js";
6
6
  export default defineEventHandler(async (event) => {
7
7
  const { nuxtUsers } = useRuntimeConfig();
8
8
  const options = nuxtUsers;
@@ -1,7 +1,7 @@
1
1
  import { defineEventHandler, readBody, createError, H3Error } from "h3";
2
- import { resetPassword } from "../../services/password.js";
2
+ import { resetPassword } from "../../../services/password.js";
3
3
  import { useRuntimeConfig } from "#imports";
4
- import { validatePassword, getPasswordValidationOptions } from "../../../../utils.js";
4
+ import { validatePassword, getPasswordValidationOptions } from "../../../../../utils.js";
5
5
  export default defineEventHandler(async (event) => {
6
6
  const body = await readBody(event);
7
7
  const { token, email, password, password_confirmation } = body;
@@ -1,6 +1,6 @@
1
1
  import { defineEventHandler, getCookie, setCookie } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { useDb } from "../../utils/index.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;
@@ -6,6 +6,7 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
6
6
  role: string;
7
7
  created_at: string;
8
8
  updated_at: string;
9
+ last_login_at?: string;
9
10
  };
10
11
  }>>;
11
12
  export default _default;
@@ -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/index.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 } = body;
@@ -1,17 +1,22 @@
1
- import { defineEventHandler, getCookie, sendRedirect } from "h3";
1
+ import { defineEventHandler, getCookie, createError } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
3
  import { getCurrentUserFromToken } from "../utils/index.js";
4
4
  import { hasPermission, isWhitelisted } from "../../utils/permissions.js";
5
- import { NO_AUTH_PATHS } from "../../constants.js";
5
+ import { NO_AUTH_PATHS, NO_AUTH_API_PATHS } from "../../constants.js";
6
6
  export default defineEventHandler(async (event) => {
7
7
  const { nuxtUsers } = useRuntimeConfig();
8
8
  const options = nuxtUsers;
9
+ const base = options.apiBasePath || "/api/nuxt-users";
9
10
  const isPageOrApiRoute = !event.path.includes(".") && (event.path === "/" || event.path.startsWith("/api/") || !event.path.startsWith("/_"));
10
11
  if (!isPageOrApiRoute) {
11
12
  return;
12
13
  }
13
14
  if (NO_AUTH_PATHS.includes(event.path)) {
14
- console.log("[Nuxt Users] server.middleware.auth.global: /login");
15
+ console.log(`[Nuxt Users] server.middleware.auth.global: NO_AUTH_PATH: ${event.path}`);
16
+ return;
17
+ }
18
+ const openApiPaths = NO_AUTH_API_PATHS.map((path) => `${base}${path}`);
19
+ if (openApiPaths.includes(event.path)) {
15
20
  return;
16
21
  }
17
22
  if (isWhitelisted(event.path, options.auth.whitelist)) {
@@ -20,17 +25,32 @@ export default defineEventHandler(async (event) => {
20
25
  }
21
26
  const token = getCookie(event, "auth_token");
22
27
  if (!token) {
23
- console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No token found redirecting to login`);
24
- return sendRedirect(event, "/login");
28
+ if (event.path.startsWith("/api/")) {
29
+ console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No token found - API request rejected`);
30
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
31
+ } else {
32
+ console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No token found - letting client handle page redirect`);
33
+ return;
34
+ }
25
35
  }
26
36
  const user = await getCurrentUserFromToken(token, options);
27
37
  if (!user) {
28
- console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No user found redirecting to login`);
29
- return sendRedirect(event, "/login");
38
+ if (event.path.startsWith("/api/")) {
39
+ console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} Invalid token - API request rejected`);
40
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
41
+ } else {
42
+ console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} Invalid token - letting client handle page redirect`);
43
+ return;
44
+ }
30
45
  }
31
46
  if (!hasPermission(user.role, event.path, options.auth.permissions)) {
32
- console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} User ${user.id} with role ${user.role} denied access`);
33
- return sendRedirect(event, "/login");
47
+ if (event.path.startsWith("/api/")) {
48
+ console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} User ${user.id} with role ${user.role} denied access - API request rejected`);
49
+ throw createError({ statusCode: 403, statusMessage: "Forbidden" });
50
+ } else {
51
+ console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} User ${user.id} with role ${user.role} denied access - letting client handle page redirect`);
52
+ return;
53
+ }
34
54
  }
35
55
  console.log(`[Nuxt Users] server.middleware.auth.global: Authenticated request to ${event.path} for ${user.id} with role ${user.role}`);
36
56
  return;
@@ -69,21 +69,24 @@ export const findUserById = async (id, options, withPass = false) => {
69
69
  export const updateUser = async (id, userData, options) => {
70
70
  const db = await useDb(options);
71
71
  const usersTable = options.tables.users;
72
- const currentUser = await findUserById(id, options, true);
72
+ const currentUser = await findUserById(id, options);
73
73
  if (!currentUser) {
74
74
  throw new Error("User not found.");
75
75
  }
76
76
  const fieldsToUpdate = { ...userData };
77
77
  delete fieldsToUpdate.id;
78
+ delete fieldsToUpdate.created_at;
79
+ delete fieldsToUpdate.updated_at;
80
+ delete fieldsToUpdate.last_login_at;
78
81
  delete fieldsToUpdate.password;
79
82
  if (Object.keys(fieldsToUpdate).length === 0) {
80
- const { password, ...user } = currentUser;
81
- return user;
83
+ return currentUser;
82
84
  }
83
- for (const [key, value] of Object.entries(fieldsToUpdate)) {
85
+ const dirtyFields = Object.keys(fieldsToUpdate).filter((key) => currentUser[key] != fieldsToUpdate[key]);
86
+ for (const key of dirtyFields) {
84
87
  await db.sql`
85
88
  UPDATE {${usersTable}}
86
- SET ${key} = ${value}, updated_at = CURRENT_TIMESTAMP
89
+ SET {${key}} = ${fieldsToUpdate[key]}, updated_at = CURRENT_TIMESTAMP
87
90
  WHERE id = ${id}
88
91
  `;
89
92
  }
@@ -12,6 +12,11 @@ interface RuntimeModuleOptions {
12
12
  name: DatabaseType;
13
13
  options: DatabaseConfig;
14
14
  };
15
+ /**
16
+ * Base path for all module API endpoints
17
+ * @default '/api/nuxt-users'
18
+ */
19
+ apiBasePath?: string;
15
20
  tables?: {
16
21
  migrations?: string;
17
22
  users?: string;
@@ -49,7 +54,7 @@ interface RuntimeModuleOptions {
49
54
  * @default {}
50
55
  * @example {
51
56
  * admin: ['*'], // admin can access everything
52
- * user: ['/profile', '/api/user/profile'],
57
+ * user: ['/profile', '/api/nuxt-users/me'],
53
58
  * moderator: ['/admin/*', '/api/admin/*']
54
59
  * }
55
60
  */
@@ -96,6 +101,7 @@ interface ModuleOptions {
96
101
  name: DatabaseType;
97
102
  options: DatabaseConfig;
98
103
  };
104
+ apiBasePath: string;
99
105
  tables: {
100
106
  migrations: string;
101
107
  users: string;
@@ -138,6 +144,7 @@ interface User {
138
144
  role: string;
139
145
  created_at: string;
140
146
  updated_at: string;
147
+ last_login_at?: string;
141
148
  }
142
149
  type UserWithoutPassword = Omit<User, 'password'>;
143
150
  interface PersonalAccessToken {
@@ -12,6 +12,11 @@ interface RuntimeModuleOptions {
12
12
  name: DatabaseType;
13
13
  options: DatabaseConfig;
14
14
  };
15
+ /**
16
+ * Base path for all module API endpoints
17
+ * @default '/api/nuxt-users'
18
+ */
19
+ apiBasePath?: string;
15
20
  tables?: {
16
21
  migrations?: string;
17
22
  users?: string;
@@ -49,7 +54,7 @@ interface RuntimeModuleOptions {
49
54
  * @default {}
50
55
  * @example {
51
56
  * admin: ['*'], // admin can access everything
52
- * user: ['/profile', '/api/user/profile'],
57
+ * user: ['/profile', '/api/nuxt-users/me'],
53
58
  * moderator: ['/admin/*', '/api/admin/*']
54
59
  * }
55
60
  */
@@ -96,6 +101,7 @@ interface ModuleOptions {
96
101
  name: DatabaseType;
97
102
  options: DatabaseConfig;
98
103
  };
104
+ apiBasePath: string;
99
105
  tables: {
100
106
  migrations: string;
101
107
  users: string;
@@ -138,6 +144,7 @@ interface User {
138
144
  role: string;
139
145
  created_at: string;
140
146
  updated_at: string;
147
+ last_login_at?: string;
141
148
  }
142
149
  type UserWithoutPassword = Omit<User, 'password'>;
143
150
  interface PersonalAccessToken {
package/dist/utils.d.mts CHANGED
@@ -1,5 +1,4 @@
1
- export { L as LoginFormData, M as ModuleOptions, b as PasswordResetToken, P as PersonalAccessToken, U as User, a as UserWithoutPassword } from './shared/nuxt-users.CXezgQLO.mjs';
2
- export { findUserByEmail, getCurrentUserFromToken, getLastLoginTime } from '../dist/runtime/server/utils/user.js';
1
+ export { L as LoginFormData, M as ModuleOptions, b as PasswordResetToken, P as PersonalAccessToken, U as User, a as UserWithoutPassword } from './shared/nuxt-users.B5ttEpIO.mjs';
3
2
 
4
3
  interface PasswordValidationResult {
5
4
  isValid: boolean;
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- export { L as LoginFormData, M as ModuleOptions, b as PasswordResetToken, P as PersonalAccessToken, U as User, a as UserWithoutPassword } from './shared/nuxt-users.CXezgQLO.js';
2
- export { findUserByEmail, getCurrentUserFromToken, getLastLoginTime } from '../dist/runtime/server/utils/user.js';
1
+ export { L as LoginFormData, M as ModuleOptions, b as PasswordResetToken, P as PersonalAccessToken, U as User, a as UserWithoutPassword } from './shared/nuxt-users.B5ttEpIO.js';
3
2
 
4
3
  interface PasswordValidationResult {
5
4
  isValid: boolean;
package/dist/utils.mjs CHANGED
@@ -1,2 +1,162 @@
1
- export { a as getPasswordStrengthColor, b as getPasswordStrengthText, g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.BnOQs2LI.mjs';
2
- export { findUserByEmail, getCurrentUserFromToken, getLastLoginTime } from '../dist/runtime/server/utils/user.js';
1
+ const DEFAULT_OPTIONS = {
2
+ minLength: 8,
3
+ requireUppercase: true,
4
+ requireLowercase: true,
5
+ requireNumbers: true,
6
+ requireSpecialChars: true,
7
+ preventCommonPasswords: true
8
+ };
9
+ const getPasswordValidationOptions = (moduleOptions) => {
10
+ if (!moduleOptions?.passwordValidation) {
11
+ return DEFAULT_OPTIONS;
12
+ }
13
+ return {
14
+ ...DEFAULT_OPTIONS,
15
+ ...moduleOptions.passwordValidation
16
+ };
17
+ };
18
+ const COMMON_PASSWORDS = [
19
+ "password",
20
+ "123456",
21
+ "123456789",
22
+ "qwerty",
23
+ "abc123",
24
+ "password123",
25
+ "admin",
26
+ "letmein",
27
+ "welcome",
28
+ "monkey",
29
+ "dragon",
30
+ "master",
31
+ "hello",
32
+ "freedom",
33
+ "whatever",
34
+ "qazwsx",
35
+ "trustno1",
36
+ "jordan",
37
+ "harley",
38
+ "ranger",
39
+ "buster",
40
+ "thomas",
41
+ "tigger",
42
+ "robert",
43
+ "soccer",
44
+ "batman",
45
+ "test",
46
+ "pass",
47
+ "user",
48
+ "guest",
49
+ "login",
50
+ "secret",
51
+ "god",
52
+ "love",
53
+ "sex",
54
+ "money",
55
+ "password1",
56
+ "12345678",
57
+ "qwerty123",
58
+ "admin123",
59
+ "password!",
60
+ "password1!",
61
+ "password123",
62
+ "password123!"
63
+ ];
64
+ const validatePassword = (password, options = {}) => {
65
+ const config = { ...DEFAULT_OPTIONS, ...options };
66
+ const errors = [];
67
+ const hints = [];
68
+ let score = 0;
69
+ if (password.length < config.minLength) {
70
+ errors.push(`Password must be at least ${config.minLength} characters long`);
71
+ hints.push(`Use at least ${config.minLength} characters`);
72
+ } else {
73
+ score += 20;
74
+ if (password.length < 12) {
75
+ hints.push("Use 12 or more characters for extra security");
76
+ }
77
+ }
78
+ if (config.requireUppercase && !/[A-Z]/.test(password)) {
79
+ errors.push("Password must contain at least one uppercase letter");
80
+ hints.push("Add an uppercase letter (A-Z)");
81
+ } else if (/[A-Z]/.test(password)) {
82
+ score += 15;
83
+ }
84
+ if (config.requireLowercase && !/[a-z]/.test(password)) {
85
+ errors.push("Password must contain at least one lowercase letter");
86
+ hints.push("Add a lowercase letter (a-z)");
87
+ } else if (/[a-z]/.test(password)) {
88
+ score += 15;
89
+ }
90
+ if (config.requireNumbers && !/\d/.test(password)) {
91
+ errors.push("Password must contain at least one number");
92
+ hints.push("Add a number (0-9)");
93
+ } else if (/\d/.test(password)) {
94
+ score += 15;
95
+ }
96
+ if (config.requireSpecialChars && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
97
+ errors.push("Password must contain at least one special character");
98
+ hints.push("Add a special character (e.g. !@#$%)");
99
+ } else if (/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
100
+ score += 15;
101
+ }
102
+ if (config.preventCommonPasswords && COMMON_PASSWORDS.includes(password.toLowerCase())) {
103
+ errors.push("Password is too common. Please choose a more unique password");
104
+ hints.push("Avoid common passwords or names");
105
+ }
106
+ if (password.length >= 12) {
107
+ score += 10;
108
+ } else if (password.length >= 10) {
109
+ score += 5;
110
+ }
111
+ const hasUppercase = /[A-Z]/.test(password);
112
+ const hasLowercase = /[a-z]/.test(password);
113
+ const hasNumbers = /\d/.test(password);
114
+ const hasSpecial = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password);
115
+ const complexityTypes = [hasUppercase, hasLowercase, hasNumbers, hasSpecial].filter(Boolean).length;
116
+ if (complexityTypes >= 4) {
117
+ score += 10;
118
+ } else if (complexityTypes >= 3) {
119
+ score += 5;
120
+ hints.push("Use a mix of uppercase, lowercase, numbers, and special characters");
121
+ }
122
+ let strength = "weak";
123
+ if (score >= 80) {
124
+ strength = "strong";
125
+ } else if (score >= 60) {
126
+ strength = "medium";
127
+ }
128
+ return {
129
+ isValid: errors.length === 0,
130
+ errors,
131
+ strength,
132
+ score: Math.min(score, 100),
133
+ hints: Array.from(new Set(hints))
134
+ // deduplicate
135
+ };
136
+ };
137
+ const getPasswordStrengthColor = (strength) => {
138
+ switch (strength) {
139
+ case "weak":
140
+ return "#dc3545";
141
+ case "medium":
142
+ return "#ffc107";
143
+ case "strong":
144
+ return "#28a745";
145
+ default:
146
+ return "#6c757d";
147
+ }
148
+ };
149
+ const getPasswordStrengthText = (strength) => {
150
+ switch (strength) {
151
+ case "weak":
152
+ return "Weak";
153
+ case "medium":
154
+ return "Medium";
155
+ case "strong":
156
+ return "Strong";
157
+ default:
158
+ return "Unknown";
159
+ }
160
+ };
161
+
162
+ export { getPasswordStrengthColor, getPasswordStrengthText, getPasswordValidationOptions, validatePassword };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.10.0",
3
+ "version": "1.12.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",
@@ -111,7 +111,7 @@
111
111
  "@nuxt/devtools": "^2.6.2",
112
112
  "@nuxt/eslint-config": "^1.5.2",
113
113
  "@nuxt/kit": "^3.17.6",
114
- "@nuxt/module-builder": "^1.0.1",
114
+ "@nuxt/module-builder": "^1.0.2",
115
115
  "@nuxt/schema": "^3.17.6",
116
116
  "@nuxt/test-utils": "^3.19.2",
117
117
  "@types/bcrypt": "^6.0.0",
@@ -1 +0,0 @@
1
- :root{--color-primary:#60a5fa;--color-primary-dark:#3b82f6;--color-primary-light:#93c5fd;--color-secondary:#10b981;--color-secondary-dark:#059669;--color-secondary-light:#34d399;--color-white:#000;--color-black:#fff;--color-gray-50:#111827;--color-gray-100:#1f2937;--color-gray-200:#374151;--color-gray-300:#4b5563;--color-gray-400:#6b7280;--color-gray-500:#9ca3af;--color-gray-600:#d1d5db;--color-gray-700:#e5e7eb;--color-gray-800:#f3f4f6;--color-gray-900:#f9fafb;--color-success:#10b981;--color-success-light:#064e3b;--color-warning:#fbbf24;--color-warning-light:#451a03;--color-warning-text:#fef3c7;--color-error:#ef4444;--color-error-light:#450a0a;--color-error-border:#7f1d1d;--color-error-alt:#f87171;--color-border:#374151;--color-border-light:#4b5563;--color-border-dark:#6b7280;--color-bg-primary:#111827;--color-bg-secondary:#1f2937;--color-bg-tertiary:#374151}@media (prefers-color-scheme:light){.light,:root{--color-primary:#3b82f6;--color-primary-dark:#2563eb;--color-primary-light:#60a5fa;--color-secondary:#059669;--color-secondary-dark:#047857;--color-secondary-light:#10b981;--color-white:#fff;--color-black:#000;--color-gray-50:#f9fafb;--color-gray-100:#f3f4f6;--color-gray-200:#e5e7eb;--color-gray-300:#d1d5db;--color-gray-400:#9ca3af;--color-gray-500:#6b7280;--color-gray-600:#4b5563;--color-gray-700:#374151;--color-gray-800:#1f2937;--color-gray-900:#111827;--color-success:#059669;--color-success-light:#f0fdf4;--color-warning:#f59e0b;--color-warning-light:#fef3c7;--color-warning-text:#92400e;--color-error:#dc2626;--color-error-light:#fef2f2;--color-error-border:#fecaca;--color-error-alt:#db5050;--color-border:#e5e7eb;--color-border-light:#d1d5db;--color-border-dark:#ccc;--color-bg-primary:#fff;--color-bg-secondary:#f9fafb;--color-bg-tertiary:#f3f4f6}}