nuxt-users 1.11.0 → 1.13.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 (44) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +27 -59
  3. package/dist/cli.mjs +2 -3
  4. package/dist/module.d.mts +1 -1
  5. package/dist/module.d.ts +1 -1
  6. package/dist/module.json +1 -1
  7. package/dist/module.mjs +35 -9
  8. package/dist/runtime/assets/nuxt-users.css +1 -0
  9. package/dist/runtime/components/NUsersList.vue +136 -0
  10. package/dist/runtime/components/NUsersList.vue.d.ts +74 -0
  11. package/dist/runtime/components/{LoginForm.vue → NUsersLoginForm.vue} +20 -23
  12. package/dist/runtime/components/{LoginForm.vue.d.ts → NUsersLoginForm.vue.d.ts} +5 -9
  13. package/dist/runtime/components/{LogoutLink.vue → NUsersLogoutLink.vue} +2 -6
  14. package/dist/runtime/components/{PasswordStrengthIndicator.vue → NUsersPasswordStrengthIndicator.vue} +18 -22
  15. package/dist/runtime/components/{ProfileInfo.vue → NUsersProfileInfo.vue} +5 -7
  16. package/dist/runtime/components/{ResetPasswordForm.vue → NUsersResetPasswordForm.vue} +22 -26
  17. package/dist/runtime/components/{ResetPasswordForm.vue.d.ts → NUsersResetPasswordForm.vue.d.ts} +3 -6
  18. package/dist/runtime/components/NUsersUserCard.vue +93 -0
  19. package/dist/runtime/components/NUsersUserCard.vue.d.ts +33 -0
  20. package/dist/runtime/components/NUsersUserForm.vue +141 -0
  21. package/dist/runtime/components/NUsersUserForm.vue.d.ts +12 -0
  22. package/dist/runtime/composables/useAuthentication.d.ts +2 -0
  23. package/dist/runtime/composables/usePasswordValidation.d.ts +2 -2
  24. package/dist/runtime/middleware/authorization.client.js +1 -1
  25. package/dist/runtime/server/api/nuxt-users/index.get.d.ts +20 -0
  26. package/dist/runtime/server/api/nuxt-users/index.get.js +58 -0
  27. package/dist/runtime/server/api/nuxt-users/me.patch.d.ts +4 -0
  28. package/dist/runtime/server/api/nuxt-users/me.patch.js +33 -0
  29. package/dist/runtime/server/api/nuxt-users/session/index.post.d.ts +1 -0
  30. package/dist/runtime/server/middleware/authorization.server.js +24 -9
  31. package/dist/runtime/server/utils/user.js +20 -18
  32. package/dist/runtime/utils/permissions.d.ts +5 -3
  33. package/dist/runtime/utils/permissions.js +20 -4
  34. package/dist/shared/{nuxt-users.C7T1DKBV.d.ts → nuxt-users.u7N4meyI.d.mts} +8 -2
  35. package/dist/shared/{nuxt-users.C7T1DKBV.d.mts → nuxt-users.u7N4meyI.d.ts} +8 -2
  36. package/dist/utils.d.mts +1 -2
  37. package/dist/utils.d.ts +1 -2
  38. package/dist/utils.mjs +162 -2
  39. package/package.json +1 -1
  40. package/dist/runtime/assets/colors.css +0 -1
  41. package/dist/shared/nuxt-users.BnOQs2LI.mjs +0 -162
  42. package/dist/runtime/components/{LogoutLink.vue.d.ts → NUsersLogoutLink.vue.d.ts} +4 -4
  43. /package/dist/runtime/components/{PasswordStrengthIndicator.vue.d.ts → NUsersPasswordStrengthIndicator.vue.d.ts} +0 -0
  44. /package/dist/runtime/components/{ProfileInfo.vue.d.ts → NUsersProfileInfo.vue.d.ts} +0 -0
@@ -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
+ });
@@ -0,0 +1,4 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ user: import("../../../../types.js").UserWithoutPassword;
3
+ }>>;
4
+ export default _default;
@@ -0,0 +1,33 @@
1
+ import { createError, defineEventHandler, readBody } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { updateUser } from "../../utils/user.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const { nuxtUsers } = useRuntimeConfig();
6
+ const options = nuxtUsers;
7
+ const user = event.context.user;
8
+ if (!user) {
9
+ throw createError({
10
+ statusCode: 401,
11
+ statusMessage: "Unauthorized"
12
+ });
13
+ }
14
+ const body = await readBody(event);
15
+ if (body.role) {
16
+ delete body.role;
17
+ }
18
+ try {
19
+ const updatedUser = await updateUser(user.id, body, options);
20
+ return { user: updatedUser };
21
+ } catch (error) {
22
+ if (error instanceof Error) {
23
+ throw createError({
24
+ statusCode: 400,
25
+ statusMessage: error.message
26
+ });
27
+ }
28
+ throw createError({
29
+ statusCode: 400,
30
+ statusMessage: "An unknown error occurred"
31
+ });
32
+ }
33
+ });
@@ -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;
@@ -1,4 +1,4 @@
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";
@@ -12,7 +12,7 @@ export default defineEventHandler(async (event) => {
12
12
  return;
13
13
  }
14
14
  if (NO_AUTH_PATHS.includes(event.path)) {
15
- console.log("[Nuxt Users] server.middleware.auth.global: /login");
15
+ console.log(`[Nuxt Users] server.middleware.auth.global: NO_AUTH_PATH: ${event.path}`);
16
16
  return;
17
17
  }
18
18
  const openApiPaths = NO_AUTH_API_PATHS.map((path) => `${base}${path}`);
@@ -25,17 +25,32 @@ export default defineEventHandler(async (event) => {
25
25
  }
26
26
  const token = getCookie(event, "auth_token");
27
27
  if (!token) {
28
- console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No token found redirecting to login`);
29
- 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
+ }
30
35
  }
31
36
  const user = await getCurrentUserFromToken(token, options);
32
37
  if (!user) {
33
- console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No user found redirecting to login`);
34
- 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
+ }
35
45
  }
36
- if (!hasPermission(user.role, event.path, options.auth.permissions)) {
37
- console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} User ${user.id} with role ${user.role} denied access`);
38
- return sendRedirect(event, "/login");
46
+ if (!hasPermission(user.role, event.path, event.method, options.auth.permissions)) {
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
+ }
39
54
  }
40
55
  console.log(`[Nuxt Users] server.middleware.auth.global: Authenticated request to ${event.path} for ${user.id} with role ${user.role}`);
41
56
  return;
@@ -69,24 +69,26 @@ 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);
73
- if (!currentUser) {
74
- throw new Error("User not found.");
75
- }
76
- const fieldsToUpdate = { ...userData };
77
- delete fieldsToUpdate.id;
78
- delete fieldsToUpdate.password;
79
- if (Object.keys(fieldsToUpdate).length === 0) {
80
- const { password, ...user } = currentUser;
81
- return user;
82
- }
83
- for (const [key, value] of Object.entries(fieldsToUpdate)) {
84
- await db.sql`
85
- UPDATE {${usersTable}}
86
- SET ${key} = ${value}, updated_at = CURRENT_TIMESTAMP
87
- WHERE id = ${id}
88
- `;
89
- }
72
+ const allowedFields = ["name", "email", "role"];
73
+ const updates = [];
74
+ const values = [];
75
+ for (const field of allowedFields) {
76
+ if (userData[field] !== void 0) {
77
+ updates.push(`${field} = ?`);
78
+ values.push(userData[field]);
79
+ }
80
+ }
81
+ if (updates.length === 0) {
82
+ const currentUser = await findUserById(id, options);
83
+ if (!currentUser) {
84
+ throw new Error("User not found.");
85
+ }
86
+ return currentUser;
87
+ }
88
+ updates.push("updated_at = CURRENT_TIMESTAMP");
89
+ values.push(id);
90
+ const setClause = updates.map((update) => update.replace(" = ?", "")).join(", ");
91
+ await db.sql`UPDATE {${usersTable}} SET {${setClause}} WHERE id = ${id}`;
90
92
  const updatedUser = await findUserById(id, options);
91
93
  if (!updatedUser) {
92
94
  throw new Error("Failed to retrieve updated user.");
@@ -1,3 +1,4 @@
1
+ import type { Permission } from '../../types.js';
1
2
  /**
2
3
  * Checks if a path matches a pattern (supports wildcards)
3
4
  * @param path - The path to check
@@ -6,13 +7,14 @@
6
7
  */
7
8
  export declare const pathMatchesPattern: (path: string, pattern: string) => boolean;
8
9
  /**
9
- * Checks if a user has permission to access a specific path
10
+ * Checks if a user has permission to access a specific path with a given method
10
11
  * @param userRole - The user's role
11
12
  * @param path - The path being accessed
13
+ * @param method - The HTTP method being used
12
14
  * @param permissions - The permissions configuration
13
- * @returns true if the user has permission to access the path
15
+ * @returns true if the user has permission
14
16
  */
15
- export declare const hasPermission: (userRole: string, path: string, permissions: Record<string, string[]>) => boolean;
17
+ export declare const hasPermission: (userRole: string, path: string, method: string, permissions: Record<string, (string | Permission)[]>) => boolean;
16
18
  /**
17
19
  * Checks if a path is accessible without authentication
18
20
  * @param path - The path being accessed
@@ -7,7 +7,7 @@ export const pathMatchesPattern = (path, pattern) => {
7
7
  }
8
8
  if ((pattern.match(/\*/g) || []).length > 1) {
9
9
  const regexPattern = pattern.replace(/\*/g, "[^/]*");
10
- const regex = new RegExp(`^${regexPattern}$`);
10
+ const regex = new RegExp(`^${regexPattern}`);
11
11
  return regex.test(path);
12
12
  }
13
13
  if (pattern.endsWith("/*")) {
@@ -16,12 +16,16 @@ export const pathMatchesPattern = (path, pattern) => {
16
16
  }
17
17
  if (pattern.includes("*")) {
18
18
  const regexPattern = pattern.replace(/\*/g, "[^/]*");
19
- const regex = new RegExp(`^${regexPattern}$`);
19
+ const regex = new RegExp(`^${regexPattern}`);
20
20
  return regex.test(path);
21
21
  }
22
22
  return false;
23
23
  };
24
- export const hasPermission = (userRole, path, permissions) => {
24
+ export const hasPermission = (userRole, path, method, permissions) => {
25
+ const safeMethods = ["OPTIONS", "HEAD", "TRACE"];
26
+ if (safeMethods.includes(method.toUpperCase())) {
27
+ return true;
28
+ }
25
29
  if (!permissions || Object.keys(permissions).length === 0) {
26
30
  return false;
27
31
  }
@@ -29,7 +33,19 @@ export const hasPermission = (userRole, path, permissions) => {
29
33
  if (!rolePermissions) {
30
34
  return false;
31
35
  }
32
- return rolePermissions.some((pattern) => pathMatchesPattern(path, pattern));
36
+ return rolePermissions.some((permission) => {
37
+ if (typeof permission === "string") {
38
+ return pathMatchesPattern(path, permission);
39
+ }
40
+ if (typeof permission === "object" && permission.path && permission.methods) {
41
+ const pathMatches = pathMatchesPattern(path, permission.path);
42
+ if (!pathMatches) {
43
+ return false;
44
+ }
45
+ return permission.methods.some((allowedMethod) => allowedMethod.toUpperCase() === method.toUpperCase());
46
+ }
47
+ return false;
48
+ });
33
49
  };
34
50
  export const isWhitelisted = (path, whitelist) => {
35
51
  return whitelist.some((whitelistedPath) => pathMatchesPattern(path, whitelistedPath));
@@ -1,4 +1,9 @@
1
1
  type DatabaseType = 'sqlite' | 'mysql' | 'postgresql';
2
+ type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH';
3
+ type Permission = string | {
4
+ path: string;
5
+ methods: HttpMethod[];
6
+ };
2
7
  type DatabaseConfig = {
3
8
  path?: string;
4
9
  host?: string;
@@ -58,7 +63,7 @@ interface RuntimeModuleOptions {
58
63
  * moderator: ['/admin/*', '/api/admin/*']
59
64
  * }
60
65
  */
61
- permissions?: Record<string, string[]>;
66
+ permissions?: Record<string, (string | Permission)[]>;
62
67
  };
63
68
  /**
64
69
  * Password validation configuration
@@ -113,7 +118,7 @@ interface ModuleOptions {
113
118
  auth: {
114
119
  whitelist: string[];
115
120
  tokenExpiration: number;
116
- permissions: Record<string, string[]>;
121
+ permissions: Record<string, (string | Permission)[]>;
117
122
  };
118
123
  passwordValidation: {
119
124
  minLength: number;
@@ -144,6 +149,7 @@ interface User {
144
149
  role: string;
145
150
  created_at: string;
146
151
  updated_at: string;
152
+ last_login_at?: string;
147
153
  }
148
154
  type UserWithoutPassword = Omit<User, 'password'>;
149
155
  interface PersonalAccessToken {
@@ -1,4 +1,9 @@
1
1
  type DatabaseType = 'sqlite' | 'mysql' | 'postgresql';
2
+ type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH';
3
+ type Permission = string | {
4
+ path: string;
5
+ methods: HttpMethod[];
6
+ };
2
7
  type DatabaseConfig = {
3
8
  path?: string;
4
9
  host?: string;
@@ -58,7 +63,7 @@ interface RuntimeModuleOptions {
58
63
  * moderator: ['/admin/*', '/api/admin/*']
59
64
  * }
60
65
  */
61
- permissions?: Record<string, string[]>;
66
+ permissions?: Record<string, (string | Permission)[]>;
62
67
  };
63
68
  /**
64
69
  * Password validation configuration
@@ -113,7 +118,7 @@ interface ModuleOptions {
113
118
  auth: {
114
119
  whitelist: string[];
115
120
  tokenExpiration: number;
116
- permissions: Record<string, string[]>;
121
+ permissions: Record<string, (string | Permission)[]>;
117
122
  };
118
123
  passwordValidation: {
119
124
  minLength: number;
@@ -144,6 +149,7 @@ interface User {
144
149
  role: string;
145
150
  created_at: string;
146
151
  updated_at: string;
152
+ last_login_at?: string;
147
153
  }
148
154
  type UserWithoutPassword = Omit<User, 'password'>;
149
155
  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.C7T1DKBV.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.u7N4meyI.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.C7T1DKBV.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.u7N4meyI.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.11.0",
3
+ "version": "1.13.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",
@@ -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}}