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.
- package/CHANGELOG.md +68 -0
- package/README.md +27 -59
- package/dist/cli.mjs +2 -3
- package/dist/module.d.mts +1 -1
- package/dist/module.d.ts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +35 -9
- package/dist/runtime/assets/nuxt-users.css +1 -0
- package/dist/runtime/components/NUsersList.vue +136 -0
- package/dist/runtime/components/NUsersList.vue.d.ts +74 -0
- package/dist/runtime/components/{LoginForm.vue → NUsersLoginForm.vue} +20 -23
- package/dist/runtime/components/{LoginForm.vue.d.ts → NUsersLoginForm.vue.d.ts} +5 -9
- package/dist/runtime/components/{LogoutLink.vue → NUsersLogoutLink.vue} +2 -6
- package/dist/runtime/components/{PasswordStrengthIndicator.vue → NUsersPasswordStrengthIndicator.vue} +18 -22
- package/dist/runtime/components/{ProfileInfo.vue → NUsersProfileInfo.vue} +5 -7
- package/dist/runtime/components/{ResetPasswordForm.vue → NUsersResetPasswordForm.vue} +22 -26
- package/dist/runtime/components/{ResetPasswordForm.vue.d.ts → NUsersResetPasswordForm.vue.d.ts} +3 -6
- package/dist/runtime/components/NUsersUserCard.vue +93 -0
- package/dist/runtime/components/NUsersUserCard.vue.d.ts +33 -0
- package/dist/runtime/components/NUsersUserForm.vue +141 -0
- package/dist/runtime/components/NUsersUserForm.vue.d.ts +12 -0
- package/dist/runtime/composables/useAuthentication.d.ts +2 -0
- package/dist/runtime/composables/usePasswordValidation.d.ts +2 -2
- package/dist/runtime/middleware/authorization.client.js +1 -1
- package/dist/runtime/server/api/nuxt-users/index.get.d.ts +20 -0
- package/dist/runtime/server/api/nuxt-users/index.get.js +58 -0
- package/dist/runtime/server/api/nuxt-users/me.patch.d.ts +4 -0
- package/dist/runtime/server/api/nuxt-users/me.patch.js +33 -0
- package/dist/runtime/server/api/nuxt-users/session/index.post.d.ts +1 -0
- package/dist/runtime/server/middleware/authorization.server.js +24 -9
- package/dist/runtime/server/utils/user.js +20 -18
- package/dist/runtime/utils/permissions.d.ts +5 -3
- package/dist/runtime/utils/permissions.js +20 -4
- package/dist/shared/{nuxt-users.C7T1DKBV.d.ts → nuxt-users.u7N4meyI.d.mts} +8 -2
- package/dist/shared/{nuxt-users.C7T1DKBV.d.mts → nuxt-users.u7N4meyI.d.ts} +8 -2
- package/dist/utils.d.mts +1 -2
- package/dist/utils.d.ts +1 -2
- package/dist/utils.mjs +162 -2
- package/package.json +1 -1
- package/dist/runtime/assets/colors.css +0 -1
- package/dist/shared/nuxt-users.BnOQs2LI.mjs +0 -162
- package/dist/runtime/components/{LogoutLink.vue.d.ts → NUsersLogoutLink.vue.d.ts} +4 -4
- /package/dist/runtime/components/{PasswordStrengthIndicator.vue.d.ts → NUsersPasswordStrengthIndicator.vue.d.ts} +0 -0
- /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,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
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineEventHandler, getCookie,
|
|
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(
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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((
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2
|
-
|
|
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 +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}}
|