nuxt-users 1.3.0 → 1.5.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 (32) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/cli.mjs +12 -30
  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 +38 -1
  7. package/dist/runtime/components/LoginForm.vue +51 -4
  8. package/dist/runtime/components/LoginForm.vue.d.ts +13 -4
  9. package/dist/runtime/components/PasswordStrengthIndicator.vue +132 -0
  10. package/dist/runtime/components/PasswordStrengthIndicator.vue.d.ts +12 -0
  11. package/dist/runtime/components/ProfileForm.vue +240 -0
  12. package/dist/runtime/components/ProfileForm.vue.d.ts +20 -0
  13. package/dist/runtime/components/ResetPasswordForm.vue +30 -4
  14. package/dist/runtime/composables/useAuth.d.ts +20 -2
  15. package/dist/runtime/composables/useAuth.js +53 -2
  16. package/dist/runtime/composables/usePasswordValidation.d.ts +33 -0
  17. package/dist/runtime/composables/usePasswordValidation.js +37 -0
  18. package/dist/runtime/plugin.js +46 -1
  19. package/dist/runtime/server/api/auth/reset-password.post.js +9 -3
  20. package/dist/runtime/server/api/profile/update-password.post.d.ts +4 -0
  21. package/dist/runtime/server/api/profile/update-password.post.js +60 -0
  22. package/dist/runtime/server/api/profile.get.d.ts +11 -0
  23. package/dist/runtime/server/api/profile.get.js +23 -0
  24. package/dist/shared/{nuxt-users.DZkU2AbH.mjs → nuxt-users.37k1R3_g.mjs} +172 -1
  25. package/dist/shared/{nuxt-users.DGRKT2Tq.d.mts → nuxt-users.CMEtv_Mw.d.mts} +64 -6
  26. package/dist/shared/{nuxt-users.DGRKT2Tq.d.ts → nuxt-users.CMEtv_Mw.d.ts} +64 -6
  27. package/dist/utils.d.mts +28 -2
  28. package/dist/utils.d.ts +28 -2
  29. package/dist/utils.mjs +1 -1
  30. package/package.json +3 -2
  31. package/dist/runtime/middleware/auth.global.d.ts +0 -2
  32. package/dist/runtime/middleware/auth.global.js +0 -12
@@ -0,0 +1,23 @@
1
+ import { createError, defineEventHandler, getCookie } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { getCurrentUserFromToken } from "../../../utils/user";
4
+ export default defineEventHandler(async (event) => {
5
+ const { nuxtUsers } = useRuntimeConfig();
6
+ const options = nuxtUsers;
7
+ const token = getCookie(event, "auth_token");
8
+ if (!token) {
9
+ throw createError({
10
+ statusCode: 401,
11
+ statusMessage: "Unauthorized - No authentication token found"
12
+ });
13
+ }
14
+ const user = await getCurrentUserFromToken(token, options);
15
+ if (!user) {
16
+ throw createError({
17
+ statusCode: 401,
18
+ statusMessage: "Unauthorized - Invalid authentication token"
19
+ });
20
+ }
21
+ const { password: _, ...userWithoutPassword } = user;
22
+ return { user: userWithoutPassword };
23
+ });
@@ -47,9 +47,175 @@ const checkTableExists = async (options, tableName) => {
47
47
  }
48
48
  };
49
49
 
50
+ const DEFAULT_OPTIONS = {
51
+ minLength: 8,
52
+ requireUppercase: true,
53
+ requireLowercase: true,
54
+ requireNumbers: true,
55
+ requireSpecialChars: true,
56
+ preventCommonPasswords: true
57
+ };
58
+ const getPasswordValidationOptions = (moduleOptions) => {
59
+ if (!moduleOptions?.passwordValidation) {
60
+ return DEFAULT_OPTIONS;
61
+ }
62
+ return {
63
+ ...DEFAULT_OPTIONS,
64
+ ...moduleOptions.passwordValidation
65
+ };
66
+ };
67
+ const COMMON_PASSWORDS = [
68
+ "password",
69
+ "123456",
70
+ "123456789",
71
+ "qwerty",
72
+ "abc123",
73
+ "password123",
74
+ "admin",
75
+ "letmein",
76
+ "welcome",
77
+ "monkey",
78
+ "dragon",
79
+ "master",
80
+ "hello",
81
+ "freedom",
82
+ "whatever",
83
+ "qazwsx",
84
+ "trustno1",
85
+ "jordan",
86
+ "harley",
87
+ "ranger",
88
+ "buster",
89
+ "thomas",
90
+ "tigger",
91
+ "robert",
92
+ "soccer",
93
+ "batman",
94
+ "test",
95
+ "pass",
96
+ "user",
97
+ "guest",
98
+ "login",
99
+ "secret",
100
+ "god",
101
+ "love",
102
+ "sex",
103
+ "money",
104
+ "password1",
105
+ "12345678",
106
+ "qwerty123",
107
+ "admin123",
108
+ "password!",
109
+ "password1!",
110
+ "password123",
111
+ "password123!"
112
+ ];
113
+ const validatePassword = (password, options = {}) => {
114
+ const config = { ...DEFAULT_OPTIONS, ...options };
115
+ const errors = [];
116
+ const hints = [];
117
+ let score = 0;
118
+ if (password.length < config.minLength) {
119
+ errors.push(`Password must be at least ${config.minLength} characters long`);
120
+ hints.push(`Use at least ${config.minLength} characters`);
121
+ } else {
122
+ score += 20;
123
+ if (password.length < 12) {
124
+ hints.push("Use 12 or more characters for extra security");
125
+ }
126
+ }
127
+ if (config.requireUppercase && !/[A-Z]/.test(password)) {
128
+ errors.push("Password must contain at least one uppercase letter");
129
+ hints.push("Add an uppercase letter (A-Z)");
130
+ } else if (/[A-Z]/.test(password)) {
131
+ score += 15;
132
+ }
133
+ if (config.requireLowercase && !/[a-z]/.test(password)) {
134
+ errors.push("Password must contain at least one lowercase letter");
135
+ hints.push("Add a lowercase letter (a-z)");
136
+ } else if (/[a-z]/.test(password)) {
137
+ score += 15;
138
+ }
139
+ if (config.requireNumbers && !/\d/.test(password)) {
140
+ errors.push("Password must contain at least one number");
141
+ hints.push("Add a number (0-9)");
142
+ } else if (/\d/.test(password)) {
143
+ score += 15;
144
+ }
145
+ if (config.requireSpecialChars && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
146
+ errors.push("Password must contain at least one special character");
147
+ hints.push("Add a special character (e.g. !@#$%)");
148
+ } else if (/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
149
+ score += 15;
150
+ }
151
+ if (config.preventCommonPasswords && COMMON_PASSWORDS.includes(password.toLowerCase())) {
152
+ errors.push("Password is too common. Please choose a more unique password");
153
+ hints.push("Avoid common passwords or names");
154
+ }
155
+ if (password.length >= 12) {
156
+ score += 10;
157
+ } else if (password.length >= 10) {
158
+ score += 5;
159
+ }
160
+ const hasUppercase = /[A-Z]/.test(password);
161
+ const hasLowercase = /[a-z]/.test(password);
162
+ const hasNumbers = /\d/.test(password);
163
+ const hasSpecial = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password);
164
+ const complexityTypes = [hasUppercase, hasLowercase, hasNumbers, hasSpecial].filter(Boolean).length;
165
+ if (complexityTypes >= 4) {
166
+ score += 10;
167
+ } else if (complexityTypes >= 3) {
168
+ score += 5;
169
+ hints.push("Use a mix of uppercase, lowercase, numbers, and special characters");
170
+ }
171
+ let strength = "weak";
172
+ if (score >= 80) {
173
+ strength = "strong";
174
+ } else if (score >= 60) {
175
+ strength = "medium";
176
+ }
177
+ return {
178
+ isValid: errors.length === 0,
179
+ errors,
180
+ strength,
181
+ score: Math.min(score, 100),
182
+ hints: Array.from(new Set(hints))
183
+ // deduplicate
184
+ };
185
+ };
186
+ const getPasswordStrengthColor = (strength) => {
187
+ switch (strength) {
188
+ case "weak":
189
+ return "#dc3545";
190
+ case "medium":
191
+ return "#ffc107";
192
+ case "strong":
193
+ return "#28a745";
194
+ default:
195
+ return "#6c757d";
196
+ }
197
+ };
198
+ const getPasswordStrengthText = (strength) => {
199
+ switch (strength) {
200
+ case "weak":
201
+ return "Weak";
202
+ case "medium":
203
+ return "Medium";
204
+ case "strong":
205
+ return "Strong";
206
+ default:
207
+ return "Unknown";
208
+ }
209
+ };
210
+
50
211
  const createUser = async (userData, options) => {
51
212
  const db = await useDb(options);
52
213
  const usersTable = options.tables.users;
214
+ const passwordOptions = getPasswordValidationOptions(options);
215
+ const passwordValidation = validatePassword(userData.password, passwordOptions);
216
+ if (!passwordValidation.isValid) {
217
+ throw new Error(`Password validation failed: ${passwordValidation.errors.join(", ")}`);
218
+ }
53
219
  const hashedPassword = await bcrypt.hash(userData.password, 10);
54
220
  const role = userData.role || "user";
55
221
  await db.sql`
@@ -91,6 +257,11 @@ const findUserByEmail = async (email, options) => {
91
257
  const updateUserPassword = async (email, newPassword, options) => {
92
258
  const db = await useDb(options);
93
259
  const usersTable = options.tables.users;
260
+ const passwordOptions = getPasswordValidationOptions(options);
261
+ const passwordValidation = validatePassword(newPassword, passwordOptions);
262
+ if (!passwordValidation.isValid) {
263
+ throw new Error(`Password validation failed: ${passwordValidation.errors.join(", ")}`);
264
+ }
94
265
  const hashedPassword = await bcrypt.hash(newPassword, 10);
95
266
  await db.sql`
96
267
  UPDATE {${usersTable}}
@@ -336,4 +507,4 @@ const runMigrations = async (options) => {
336
507
  console.log("[Nuxt Users] All migrations successfull \u2705");
337
508
  };
338
509
 
339
- export { createUser as a, updateUserPassword as b, checkTableExists as c, createUsersTable as d, createPersonalAccessTokensTable as e, findUserByEmail as f, createPasswordResetTokensTable as g, createMigrationsTable as h, runMigrations as r, useDb as u };
510
+ export { createUser as a, updateUserPassword as b, checkTableExists as c, createUsersTable as d, createPersonalAccessTokensTable as e, findUserByEmail as f, createPasswordResetTokensTable as g, createMigrationsTable as h, getPasswordValidationOptions as i, getPasswordStrengthColor as j, getPasswordStrengthText as k, runMigrations as r, useDb as u, validatePassword as v };
@@ -7,16 +7,16 @@ type DatabaseConfig = {
7
7
  password?: string;
8
8
  database?: string;
9
9
  };
10
- interface ModuleOptions {
10
+ interface RuntimeModuleOptions {
11
11
  connector?: {
12
12
  name: DatabaseType;
13
13
  options: DatabaseConfig;
14
14
  };
15
- tables: {
16
- migrations: string;
17
- users: string;
18
- personalAccessTokens: string;
19
- passwordResetTokens: string;
15
+ tables?: {
16
+ migrations?: string;
17
+ users?: string;
18
+ personalAccessTokens?: string;
19
+ passwordResetTokens?: string;
20
20
  };
21
21
  /**
22
22
  * Mailer configuration options for sending emails (e.g., password resets)
@@ -38,8 +38,66 @@ interface ModuleOptions {
38
38
  * @default ['/login']
39
39
  * @example ['/login', '/register']
40
40
  */
41
+ whitelist?: string[];
42
+ };
43
+ /**
44
+ * Password validation configuration
45
+ */
46
+ passwordValidation?: {
47
+ /**
48
+ * Minimum password length
49
+ * @default 8
50
+ */
51
+ minLength?: number;
52
+ /**
53
+ * Require uppercase letters
54
+ * @default true
55
+ */
56
+ requireUppercase?: boolean;
57
+ /**
58
+ * Require lowercase letters
59
+ * @default true
60
+ */
61
+ requireLowercase?: boolean;
62
+ /**
63
+ * Require numbers
64
+ * @default true
65
+ */
66
+ requireNumbers?: boolean;
67
+ /**
68
+ * Require special characters
69
+ * @default true
70
+ */
71
+ requireSpecialChars?: boolean;
72
+ /**
73
+ * Prevent common passwords
74
+ * @default true
75
+ */
76
+ preventCommonPasswords?: boolean;
77
+ };
78
+ }
79
+ interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | 'passwordValidation'> {
80
+ connector: {
81
+ name: DatabaseType;
82
+ options: DatabaseConfig;
83
+ };
84
+ tables: {
85
+ migrations: string;
86
+ users: string;
87
+ personalAccessTokens: string;
88
+ passwordResetTokens: string;
89
+ };
90
+ auth: {
41
91
  whitelist: string[];
42
92
  };
93
+ passwordValidation: {
94
+ minLength: number;
95
+ requireUppercase: boolean;
96
+ requireLowercase: boolean;
97
+ requireNumbers: boolean;
98
+ requireSpecialChars: boolean;
99
+ preventCommonPasswords: boolean;
100
+ };
43
101
  }
44
102
  interface MailerOptions {
45
103
  host: string;
@@ -7,16 +7,16 @@ type DatabaseConfig = {
7
7
  password?: string;
8
8
  database?: string;
9
9
  };
10
- interface ModuleOptions {
10
+ interface RuntimeModuleOptions {
11
11
  connector?: {
12
12
  name: DatabaseType;
13
13
  options: DatabaseConfig;
14
14
  };
15
- tables: {
16
- migrations: string;
17
- users: string;
18
- personalAccessTokens: string;
19
- passwordResetTokens: string;
15
+ tables?: {
16
+ migrations?: string;
17
+ users?: string;
18
+ personalAccessTokens?: string;
19
+ passwordResetTokens?: string;
20
20
  };
21
21
  /**
22
22
  * Mailer configuration options for sending emails (e.g., password resets)
@@ -38,8 +38,66 @@ interface ModuleOptions {
38
38
  * @default ['/login']
39
39
  * @example ['/login', '/register']
40
40
  */
41
+ whitelist?: string[];
42
+ };
43
+ /**
44
+ * Password validation configuration
45
+ */
46
+ passwordValidation?: {
47
+ /**
48
+ * Minimum password length
49
+ * @default 8
50
+ */
51
+ minLength?: number;
52
+ /**
53
+ * Require uppercase letters
54
+ * @default true
55
+ */
56
+ requireUppercase?: boolean;
57
+ /**
58
+ * Require lowercase letters
59
+ * @default true
60
+ */
61
+ requireLowercase?: boolean;
62
+ /**
63
+ * Require numbers
64
+ * @default true
65
+ */
66
+ requireNumbers?: boolean;
67
+ /**
68
+ * Require special characters
69
+ * @default true
70
+ */
71
+ requireSpecialChars?: boolean;
72
+ /**
73
+ * Prevent common passwords
74
+ * @default true
75
+ */
76
+ preventCommonPasswords?: boolean;
77
+ };
78
+ }
79
+ interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | 'passwordValidation'> {
80
+ connector: {
81
+ name: DatabaseType;
82
+ options: DatabaseConfig;
83
+ };
84
+ tables: {
85
+ migrations: string;
86
+ users: string;
87
+ personalAccessTokens: string;
88
+ passwordResetTokens: string;
89
+ };
90
+ auth: {
41
91
  whitelist: string[];
42
92
  };
93
+ passwordValidation: {
94
+ minLength: number;
95
+ requireUppercase: boolean;
96
+ requireLowercase: boolean;
97
+ requireNumbers: boolean;
98
+ requireSpecialChars: boolean;
99
+ preventCommonPasswords: boolean;
100
+ };
43
101
  }
44
102
  interface MailerOptions {
45
103
  host: string;
package/dist/utils.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as db0 from 'db0';
2
- import { M as ModuleOptions, U as User } from './shared/nuxt-users.DGRKT2Tq.mjs';
2
+ import { M as ModuleOptions, U as User } from './shared/nuxt-users.CMEtv_Mw.mjs';
3
3
 
4
4
  declare const useDb: (options: ModuleOptions) => Promise<db0.Database<db0.Connector<Database.Database>>>;
5
5
  declare const checkTableExists: (options: ModuleOptions, tableName: string) => Promise<boolean>;
@@ -35,4 +35,30 @@ declare const createMigrationsTable: (options: ModuleOptions) => Promise<void>;
35
35
 
36
36
  declare const runMigrations: (options: ModuleOptions) => Promise<void>;
37
37
 
38
- export { checkTableExists, createMigrationsTable, createPasswordResetTokensTable, createPersonalAccessTokensTable, createUser, createUsersTable, findUserByEmail, runMigrations, updateUserPassword, useDb };
38
+ interface PasswordValidationResult {
39
+ isValid: boolean;
40
+ errors: string[];
41
+ strength: 'weak' | 'medium' | 'strong';
42
+ score: number;
43
+ hints: string[];
44
+ }
45
+ interface PasswordValidationOptions {
46
+ minLength?: number;
47
+ requireUppercase?: boolean;
48
+ requireLowercase?: boolean;
49
+ requireNumbers?: boolean;
50
+ requireSpecialChars?: boolean;
51
+ preventCommonPasswords?: boolean;
52
+ }
53
+ /**
54
+ * Get password validation options from module configuration
55
+ */
56
+ declare const getPasswordValidationOptions: (moduleOptions?: {
57
+ passwordValidation?: PasswordValidationOptions;
58
+ }) => PasswordValidationOptions;
59
+ declare const validatePassword: (password: string, options?: PasswordValidationOptions) => PasswordValidationResult;
60
+ declare const getPasswordStrengthColor: (strength: "weak" | "medium" | "strong") => string;
61
+ declare const getPasswordStrengthText: (strength: "weak" | "medium" | "strong") => string;
62
+
63
+ export { checkTableExists, createMigrationsTable, createPasswordResetTokensTable, createPersonalAccessTokensTable, createUser, createUsersTable, findUserByEmail, getPasswordStrengthColor, getPasswordStrengthText, getPasswordValidationOptions, runMigrations, updateUserPassword, useDb, validatePassword };
64
+ export type { PasswordValidationOptions, PasswordValidationResult };
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as db0 from 'db0';
2
- import { M as ModuleOptions, U as User } from './shared/nuxt-users.DGRKT2Tq.js';
2
+ import { M as ModuleOptions, U as User } from './shared/nuxt-users.CMEtv_Mw.js';
3
3
 
4
4
  declare const useDb: (options: ModuleOptions) => Promise<db0.Database<db0.Connector<Database.Database>>>;
5
5
  declare const checkTableExists: (options: ModuleOptions, tableName: string) => Promise<boolean>;
@@ -35,4 +35,30 @@ declare const createMigrationsTable: (options: ModuleOptions) => Promise<void>;
35
35
 
36
36
  declare const runMigrations: (options: ModuleOptions) => Promise<void>;
37
37
 
38
- export { checkTableExists, createMigrationsTable, createPasswordResetTokensTable, createPersonalAccessTokensTable, createUser, createUsersTable, findUserByEmail, runMigrations, updateUserPassword, useDb };
38
+ interface PasswordValidationResult {
39
+ isValid: boolean;
40
+ errors: string[];
41
+ strength: 'weak' | 'medium' | 'strong';
42
+ score: number;
43
+ hints: string[];
44
+ }
45
+ interface PasswordValidationOptions {
46
+ minLength?: number;
47
+ requireUppercase?: boolean;
48
+ requireLowercase?: boolean;
49
+ requireNumbers?: boolean;
50
+ requireSpecialChars?: boolean;
51
+ preventCommonPasswords?: boolean;
52
+ }
53
+ /**
54
+ * Get password validation options from module configuration
55
+ */
56
+ declare const getPasswordValidationOptions: (moduleOptions?: {
57
+ passwordValidation?: PasswordValidationOptions;
58
+ }) => PasswordValidationOptions;
59
+ declare const validatePassword: (password: string, options?: PasswordValidationOptions) => PasswordValidationResult;
60
+ declare const getPasswordStrengthColor: (strength: "weak" | "medium" | "strong") => string;
61
+ declare const getPasswordStrengthText: (strength: "weak" | "medium" | "strong") => string;
62
+
63
+ export { checkTableExists, createMigrationsTable, createPasswordResetTokensTable, createPersonalAccessTokensTable, createUser, createUsersTable, findUserByEmail, getPasswordStrengthColor, getPasswordStrengthText, getPasswordValidationOptions, runMigrations, updateUserPassword, useDb, validatePassword };
64
+ export type { PasswordValidationOptions, PasswordValidationResult };
package/dist/utils.mjs CHANGED
@@ -1,3 +1,3 @@
1
- export { c as checkTableExists, h as createMigrationsTable, g as createPasswordResetTokensTable, e as createPersonalAccessTokensTable, a as createUser, d as createUsersTable, f as findUserByEmail, r as runMigrations, b as updateUserPassword, u as useDb } from './shared/nuxt-users.DZkU2AbH.mjs';
1
+ export { c as checkTableExists, h as createMigrationsTable, g as createPasswordResetTokensTable, e as createPersonalAccessTokensTable, a as createUser, d as createUsersTable, f as findUserByEmail, j as getPasswordStrengthColor, k as getPasswordStrengthText, i as getPasswordValidationOptions, r as runMigrations, b as updateUserPassword, u as useDb, v as validatePassword } from './shared/nuxt-users.37k1R3_g.mjs';
2
2
  import 'bcrypt';
3
3
  import 'db0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "A comprehensive user management module for Nuxt 3 applications with authentication, database support, and CLI tools",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -64,8 +64,9 @@
64
64
  "lint": "eslint .",
65
65
  "lint:fix": "eslint . --fix",
66
66
  "pretest": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for testing' && exit 1)",
67
- "test": "yarn test:types && yarn test:sqlite && yarn test:mysql && yarn test:postgresql",
67
+ "test": "yarn test:types && yarn test:unit && yarn test:sqlite && yarn test:mysql && yarn test:postgresql",
68
68
  "test:watch": "vitest watch",
69
+ "test:unit": "./scripts/test-unit.sh",
69
70
  "test:sqlite": "./scripts/test-sqlite.sh",
70
71
  "test:mysql": "./scripts/test-mysql.sh",
71
72
  "test:postgresql": "./scripts/test-postgresql.sh",
@@ -1,2 +0,0 @@
1
- declare const _default: import("#app").RouteMiddleware;
2
- export default _default;
@@ -1,12 +0,0 @@
1
- import { defineNuxtRouteMiddleware, useRuntimeConfig, navigateTo } from "#app";
2
- import { useAuth } from "../composables/useAuth.js";
3
- export default defineNuxtRouteMiddleware((to, _from) => {
4
- const { nuxtUsers } = useRuntimeConfig();
5
- const { user } = useAuth();
6
- if (to.path === "/login") {
7
- return;
8
- }
9
- if (!user.value && !nuxtUsers.auth?.whitelist?.includes(to.path)) {
10
- return navigateTo("/login");
11
- }
12
- });