nuxt-users 1.12.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 +35 -0
- package/README.md +27 -59
- package/dist/cli.mjs +1 -1
- package/dist/module.d.mts +1 -1
- package/dist/module.d.ts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/middleware/authorization.client.js +1 -1
- 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/middleware/authorization.server.js +1 -1
- package/dist/runtime/server/utils/user.js +18 -19
- package/dist/runtime/utils/permissions.d.ts +5 -3
- package/dist/runtime/utils/permissions.js +20 -4
- package/dist/shared/{nuxt-users.B5ttEpIO.d.mts → nuxt-users.u7N4meyI.d.mts} +7 -2
- package/dist/shared/{nuxt-users.B5ttEpIO.d.ts → nuxt-users.u7N4meyI.d.ts} +7 -2
- package/dist/utils.d.mts +1 -1
- package/dist/utils.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v1.13.0
|
|
5
|
+
|
|
6
|
+
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.12.0...v1.13.0)
|
|
7
|
+
|
|
8
|
+
### 🚀 Enhancements
|
|
9
|
+
|
|
10
|
+
- Enhances user management and login flow ([894563a](https://github.com/rrd108/nuxt-users/commit/894563a))
|
|
11
|
+
- Implement new components ([87689c9](https://github.com/rrd108/nuxt-users/commit/87689c9))
|
|
12
|
+
- Define allowed HTTP methods for api endpoints permissions ([7feca0a](https://github.com/rrd108/nuxt-users/commit/7feca0a))
|
|
13
|
+
- New api endpoint for user edit own data is added ([3bd13f9](https://github.com/rrd108/nuxt-users/commit/3bd13f9))
|
|
14
|
+
|
|
15
|
+
### 🩹 Fixes
|
|
16
|
+
|
|
17
|
+
- Authorization middleware for API routes closes #76 ([#76](https://github.com/rrd108/nuxt-users/issues/76))
|
|
18
|
+
- (temporary) strange test failing ([ff8c376](https://github.com/rrd108/nuxt-users/commit/ff8c376))
|
|
19
|
+
|
|
20
|
+
### 💅 Refactors
|
|
21
|
+
|
|
22
|
+
- Component names ([159f334](https://github.com/rrd108/nuxt-users/commit/159f334))
|
|
23
|
+
- Css classes renamed, removed or added ([c5c3334](https://github.com/rrd108/nuxt-users/commit/c5c3334))
|
|
24
|
+
- Remove unused css ([7abc8a5](https://github.com/rrd108/nuxt-users/commit/7abc8a5))
|
|
25
|
+
|
|
26
|
+
### 🏡 Chore
|
|
27
|
+
|
|
28
|
+
- **release:** V1.12.0 ([0727c0f](https://github.com/rrd108/nuxt-users/commit/0727c0f))
|
|
29
|
+
|
|
30
|
+
### ✅ Tests
|
|
31
|
+
|
|
32
|
+
- Fix ([cbf300c](https://github.com/rrd108/nuxt-users/commit/cbf300c))
|
|
33
|
+
- Fix failing test ([3973eec](https://github.com/rrd108/nuxt-users/commit/3973eec))
|
|
34
|
+
|
|
35
|
+
### ❤️ Contributors
|
|
36
|
+
|
|
37
|
+
- Rrd108 ([@rrd108](https://github.com/rrd108))
|
|
38
|
+
|
|
4
39
|
## v1.12.0
|
|
5
40
|
|
|
6
41
|
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.11.0...v1.12.0)
|
package/README.md
CHANGED
|
@@ -4,68 +4,36 @@ A user authentication module for Nuxt 3 and Nuxt 4 with database support for SQL
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- 🔐
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# Run migrations
|
|
35
|
-
npx nuxt-users migrate
|
|
36
|
-
|
|
37
|
-
# Create your first user
|
|
38
|
-
npx nuxt-users create-user user@example.com "John Doe" password123
|
|
39
|
-
# Create an admin user
|
|
40
|
-
npx nuxt-users create-user admin@example.com "Admin User" adminpass123 admin
|
|
41
|
-
```
|
|
7
|
+
- 🔐 **Authentication & Security**
|
|
8
|
+
- User registration, login, and logout
|
|
9
|
+
- Secure password hashing with bcrypt
|
|
10
|
+
- Password reset functionality
|
|
11
|
+
- Session management
|
|
12
|
+
|
|
13
|
+
- 🗄️ **Database Support**
|
|
14
|
+
- SQLite, MySQL, and PostgreSQL
|
|
15
|
+
- Automatic migrations and schema management
|
|
16
|
+
- CLI tools for database operations
|
|
17
|
+
|
|
18
|
+
- 🛡️ **Access Control**
|
|
19
|
+
- Role-Based Access Control (RBAC)
|
|
20
|
+
- Middleware for route protection
|
|
21
|
+
- Permission-based authorization
|
|
22
|
+
|
|
23
|
+
- 🎨 **UI Components**
|
|
24
|
+
- Pre-built authentication forms
|
|
25
|
+
- User management interfaces
|
|
26
|
+
- Responsive and customizable components
|
|
27
|
+
|
|
28
|
+
- ⚡ **Developer Experience**
|
|
29
|
+
- Zero-config setup with sensible defaults
|
|
30
|
+
- TypeScript support throughout
|
|
31
|
+
- Nuxt 3 & 4 compatibility
|
|
32
|
+
- Easy customization and extension
|
|
42
33
|
|
|
43
34
|
## Documentation
|
|
44
35
|
|
|
45
|
-
📚 **Full documentation is available at: [
|
|
46
|
-
|
|
47
|
-
- [Installation Guide](/docs/guide/installation)
|
|
48
|
-
- [Configuration Options](/docs/guide/configuration)
|
|
49
|
-
- [API Reference](/docs/api/)
|
|
50
|
-
- [Component Documentation](/docs/components/)
|
|
51
|
-
- [Database Schema](/docs/database/schema)
|
|
52
|
-
- [Contributing Guidelines](/docs/contributing/)
|
|
53
|
-
|
|
54
|
-
## Development
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
# Install dependencies
|
|
58
|
-
yarn install
|
|
59
|
-
|
|
60
|
-
# Start development server
|
|
61
|
-
yarn dev
|
|
62
|
-
|
|
63
|
-
# Run tests
|
|
64
|
-
yarn test
|
|
65
|
-
|
|
66
|
-
# Build documentation
|
|
67
|
-
cd docs && yarn dev
|
|
68
|
-
```
|
|
36
|
+
📚 **Full documentation is available at: [https://nuxt-users.webmania.cc/](https://nuxt-users.webmania.cc/)**
|
|
69
37
|
|
|
70
38
|
## License
|
|
71
39
|
|
package/dist/cli.mjs
CHANGED
package/dist/module.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.
|
|
2
|
+
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.u7N4meyI.mjs';
|
|
3
3
|
export * from '../dist/runtime/server/utils/index.js';
|
|
4
4
|
|
|
5
5
|
declare const defaultOptions: ModuleOptions;
|
package/dist/module.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.
|
|
2
|
+
import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.u7N4meyI.js';
|
|
3
3
|
export * from '../dist/runtime/server/utils/index.js';
|
|
4
4
|
|
|
5
5
|
declare const defaultOptions: ModuleOptions;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -115,6 +115,11 @@ const module = defineNuxtModule({
|
|
|
115
115
|
method: "get",
|
|
116
116
|
handler: resolver.resolve("./runtime/server/api/nuxt-users/me.get")
|
|
117
117
|
});
|
|
118
|
+
addServerHandler({
|
|
119
|
+
route: `${base}/me`,
|
|
120
|
+
method: "patch",
|
|
121
|
+
handler: resolver.resolve("./runtime/server/api/nuxt-users/me.patch")
|
|
122
|
+
});
|
|
118
123
|
addServerHandler({
|
|
119
124
|
route: `${base}/password`,
|
|
120
125
|
method: "patch",
|
|
@@ -23,7 +23,7 @@ export default defineNuxtRouteMiddleware((to) => {
|
|
|
23
23
|
console.log(`[Nuxt Users] client.middleware.auth.global: Unauthenticated ${to.path}, redirecting to /login`);
|
|
24
24
|
return navigateTo("/login");
|
|
25
25
|
}
|
|
26
|
-
if (!user.value || !hasPermission(user.value.role, to.path, publicOptions.auth.permissions)) {
|
|
26
|
+
if (!user.value || !hasPermission(user.value.role, to.path, "GET", publicOptions.auth.permissions)) {
|
|
27
27
|
console.log(`[Nuxt Users] client.middleware.auth.global: User with role ${user.value?.role} denied access to ${to.path}`);
|
|
28
28
|
return navigateTo("/login");
|
|
29
29
|
}
|
|
@@ -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
|
+
});
|
|
@@ -43,7 +43,7 @@ export default defineEventHandler(async (event) => {
|
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
if (!hasPermission(user.role, event.path, options.auth.permissions)) {
|
|
46
|
+
if (!hasPermission(user.role, event.path, event.method, options.auth.permissions)) {
|
|
47
47
|
if (event.path.startsWith("/api/")) {
|
|
48
48
|
console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} User ${user.id} with role ${user.role} denied access - API request rejected`);
|
|
49
49
|
throw createError({ statusCode: 403, statusMessage: "Forbidden" });
|
|
@@ -69,27 +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
|
-
|
|
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
|
+
}
|
|
83
86
|
return currentUser;
|
|
84
87
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
SET {${key}} = ${fieldsToUpdate[key]}, updated_at = CURRENT_TIMESTAMP
|
|
90
|
-
WHERE id = ${id}
|
|
91
|
-
`;
|
|
92
|
-
}
|
|
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}`;
|
|
93
92
|
const updatedUser = await findUserById(id, options);
|
|
94
93
|
if (!updatedUser) {
|
|
95
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;
|
|
@@ -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;
|
package/dist/utils.d.mts
CHANGED
|
@@ -1,4 +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.
|
|
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';
|
|
2
2
|
|
|
3
3
|
interface PasswordValidationResult {
|
|
4
4
|
isValid: boolean;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +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.
|
|
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';
|
|
2
2
|
|
|
3
3
|
interface PasswordValidationResult {
|
|
4
4
|
isValid: boolean;
|
package/package.json
CHANGED