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 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
- - 🔐 User authentication with bcrypt password hashing
8
- - 🗄️ Database support (SQLite, MySQL, PostgreSQL)
9
- - 🛠️ CLI commands for database management
10
- - 🔑 Password Reset Functionality
11
- - 📦 Zero-config setup with sensible defaults
12
- - 🔧 TypeScript support
13
- - 🚀 Nuxt 3 & 4 compatibility
14
-
15
- ## Quick Start
16
-
17
- ```bash
18
- # Install the module
19
- npm install nuxt-users
20
-
21
- # Add to your nuxt.config.ts
22
- export default defineNuxtConfig({
23
- modules: ['nuxt-users'],
24
- nuxtUsers: {
25
- connector: {
26
- name: 'sqlite',
27
- options: {
28
- path: './data/default.sqlite3'
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: [docs/](/docs/)**
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
@@ -247,7 +247,7 @@ const main = defineCommand({
247
247
  meta: {
248
248
  name: "nuxt-users",
249
249
  description: "CLI for Nuxt Users Module - Manage users, migrations, and database operations",
250
- version: "1.11.0"
250
+ version: "1.12.0"
251
251
  },
252
252
  subCommands: {
253
253
  migrate,
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.B5ttEpIO.mjs';
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.B5ttEpIO.js';
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.12.0",
4
+ "version": "1.13.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.0"
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,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
+ });
@@ -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 currentUser = await findUserById(id, options);
73
- if (!currentUser) {
74
- throw new Error("User not found.");
75
- }
76
- const fieldsToUpdate = { ...userData };
77
- delete fieldsToUpdate.id;
78
- delete fieldsToUpdate.created_at;
79
- delete fieldsToUpdate.updated_at;
80
- delete fieldsToUpdate.last_login_at;
81
- delete fieldsToUpdate.password;
82
- if (Object.keys(fieldsToUpdate).length === 0) {
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
- const dirtyFields = Object.keys(fieldsToUpdate).filter((key) => currentUser[key] != fieldsToUpdate[key]);
86
- for (const key of dirtyFields) {
87
- await db.sql`
88
- UPDATE {${usersTable}}
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 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;
@@ -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.B5ttEpIO.mjs';
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.B5ttEpIO.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';
2
2
 
3
3
  interface PasswordValidationResult {
4
4
  isValid: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.12.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",