nuxt-users 1.6.7 → 1.8.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,76 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## v1.8.0
5
+
6
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.7...v1.8.0)
7
+
8
+ ### 🚀 Enhancements
9
+
10
+ - Implement RBAC closes #55 ([#55](https://github.com/rrd108/nuxt-users/issues/55))
11
+
12
+ ### 🩹 Fixes
13
+
14
+ - Type errors ([c403645](https://github.com/rrd108/nuxt-users/commit/c403645))
15
+ - **ci:** Incorrect version ([f504cf9](https://github.com/rrd108/nuxt-users/commit/f504cf9))
16
+ - **cli:** Commands to use nuxt-users package ([b435f7c](https://github.com/rrd108/nuxt-users/commit/b435f7c))
17
+
18
+ ### 💅 Refactors
19
+
20
+ - Token cleanup task and middleware ([d47a4a5](https://github.com/rrd108/nuxt-users/commit/d47a4a5))
21
+ - Renames `useAuth` to `useAuthentication` ([e611e62](https://github.com/rrd108/nuxt-users/commit/e611e62))
22
+
23
+ ### 🏡 Chore
24
+
25
+ - **release:** V1.6.7 ([def2d5c](https://github.com/rrd108/nuxt-users/commit/def2d5c))
26
+ - Bump cli version number ([b4daec6](https://github.com/rrd108/nuxt-users/commit/b4daec6))
27
+ - Fix for linter ([ce1db37](https://github.com/rrd108/nuxt-users/commit/ce1db37))
28
+ - **release:** V1.7.0 ([967403d](https://github.com/rrd108/nuxt-users/commit/967403d))
29
+
30
+ ### ✅ Tests
31
+
32
+ - Remove unused test ([3dc23ad](https://github.com/rrd108/nuxt-users/commit/3dc23ad))
33
+ - Fix response ([db3b03c](https://github.com/rrd108/nuxt-users/commit/db3b03c))
34
+ - Fix mysql field type ([2faf0c6](https://github.com/rrd108/nuxt-users/commit/2faf0c6))
35
+ - Refactors test mocks for clarity ([1343482](https://github.com/rrd108/nuxt-users/commit/1343482))
36
+
37
+ ### ❤️ Contributors
38
+
39
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
40
+
41
+ ## v1.7.0
42
+
43
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.7...v1.7.0)
44
+
45
+ ### 🚀 Enhancements
46
+
47
+ - Implement RBAC closes #55 ([#55](https://github.com/rrd108/nuxt-users/issues/55))
48
+
49
+ ### 🩹 Fixes
50
+
51
+ - Type errors ([c403645](https://github.com/rrd108/nuxt-users/commit/c403645))
52
+
53
+ ### 💅 Refactors
54
+
55
+ - Token cleanup task and middleware ([d47a4a5](https://github.com/rrd108/nuxt-users/commit/d47a4a5))
56
+ - Renames `useAuth` to `useAuthentication` ([e611e62](https://github.com/rrd108/nuxt-users/commit/e611e62))
57
+
58
+ ### 🏡 Chore
59
+
60
+ - **release:** V1.6.7 ([def2d5c](https://github.com/rrd108/nuxt-users/commit/def2d5c))
61
+ - Bump cli version number ([b4daec6](https://github.com/rrd108/nuxt-users/commit/b4daec6))
62
+ - Fix for linter ([ce1db37](https://github.com/rrd108/nuxt-users/commit/ce1db37))
63
+
64
+ ### ✅ Tests
65
+
66
+ - Remove unused test ([3dc23ad](https://github.com/rrd108/nuxt-users/commit/3dc23ad))
67
+ - Fix response ([db3b03c](https://github.com/rrd108/nuxt-users/commit/db3b03c))
68
+ - Fix mysql field type ([2faf0c6](https://github.com/rrd108/nuxt-users/commit/2faf0c6))
69
+
70
+ ### ❤️ Contributors
71
+
72
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
73
+
4
74
  ## v1.6.7
5
75
 
6
76
  [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.6...v1.6.7)
package/dist/cli.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { defineCommand, runMain } from 'citty';
3
- import { runMigrations, createUser as createUser$1, createUsersTable as createUsersTable$1, createPersonalAccessTokensTable as createPersonalAccessTokensTable$1, createPasswordResetTokensTable as createPasswordResetTokensTable$1, createMigrationsTable as createMigrationsTable$1 } from '../dist/runtime/server/utils/index.js';
3
+ import { runMigrations, createUser as createUser$1, createUsersTable as createUsersTable$1, createPersonalAccessTokensTable as createPersonalAccessTokensTable$1, createPasswordResetTokensTable as createPasswordResetTokensTable$1, createMigrationsTable as createMigrationsTable$1 } from 'nuxt-users';
4
4
  import { defaultOptions } from './module.mjs';
5
5
  import { loadNuxt } from '@nuxt/kit';
6
6
  import { getPasswordValidationOptions, validatePassword } from './utils.mjs';
7
7
  import 'defu';
8
+ import '../dist/runtime/server/utils/index.js';
8
9
 
9
10
  const getOptionsFromEnv = () => {
10
11
  const connectorName = process.env.DB_CONNECTOR || "sqlite";
@@ -247,7 +248,7 @@ const main = defineCommand({
247
248
  meta: {
248
249
  name: "nuxt-users",
249
250
  description: "CLI for Nuxt Users Module - Manage users, migrations, and database operations",
250
- version: "1.6.6"
251
+ version: "1.7.0"
251
252
  },
252
253
  subCommands: {
253
254
  migrate,
package/dist/module.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ export * from '../dist/runtime/server/utils/index.js';
2
3
 
3
4
  type DatabaseType = 'sqlite' | 'mysql' | 'postgresql';
4
5
  type DatabaseConfig = {
@@ -9,6 +10,90 @@ type DatabaseConfig = {
9
10
  password?: string;
10
11
  database?: string;
11
12
  };
13
+ interface RuntimeModuleOptions {
14
+ connector?: {
15
+ name: DatabaseType;
16
+ options: DatabaseConfig;
17
+ };
18
+ tables?: {
19
+ migrations?: string;
20
+ users?: string;
21
+ personalAccessTokens?: string;
22
+ passwordResetTokens?: string;
23
+ };
24
+ /**
25
+ * Mailer configuration options for sending emails (e.g., password resets)
26
+ * Uses nodemailer
27
+ */
28
+ mailer?: MailerOptions;
29
+ /**
30
+ * Base URL for password reset links
31
+ * @default 'http://localhost:3000' // Example, will be set in module defaults
32
+ */
33
+ passwordResetBaseUrl?: string;
34
+ /**
35
+ * Skip database checks during module setup to prevent hanging
36
+ * @default false
37
+ */
38
+ auth?: {
39
+ /**
40
+ * Whitelisted routes that do not require authentication
41
+ * @default ['/login']
42
+ * @example ['/login', '/register']
43
+ */
44
+ whitelist?: string[];
45
+ /**
46
+ * Token expiration time in minutes
47
+ * @default 1440
48
+ */
49
+ tokenExpiration?: number;
50
+ /**
51
+ * Role-based permissions configuration
52
+ * @default {}
53
+ * @example {
54
+ * admin: ['*'], // admin can access everything
55
+ * user: ['/profile', '/api/user/profile'],
56
+ * moderator: ['/admin/*', '/api/admin/*']
57
+ * }
58
+ */
59
+ permissions?: Record<string, string[]>;
60
+ };
61
+ /**
62
+ * Password validation configuration
63
+ */
64
+ passwordValidation?: {
65
+ /**
66
+ * Minimum password length
67
+ * @default 8
68
+ */
69
+ minLength?: number;
70
+ /**
71
+ * Require uppercase letters
72
+ * @default true
73
+ */
74
+ requireUppercase?: boolean;
75
+ /**
76
+ * Require lowercase letters
77
+ * @default true
78
+ */
79
+ requireLowercase?: boolean;
80
+ /**
81
+ * Require numbers
82
+ * @default true
83
+ */
84
+ requireNumbers?: boolean;
85
+ /**
86
+ * Require special characters
87
+ * @default true
88
+ */
89
+ requireSpecialChars?: boolean;
90
+ /**
91
+ * Prevent common passwords
92
+ * @default true
93
+ */
94
+ preventCommonPasswords?: boolean;
95
+ };
96
+ }
12
97
  interface ModuleOptions {
13
98
  connector: {
14
99
  name: DatabaseType;
@@ -25,6 +110,7 @@ interface ModuleOptions {
25
110
  auth: {
26
111
  whitelist: string[];
27
112
  tokenExpiration: number;
113
+ permissions: Record<string, string[]>;
28
114
  };
29
115
  passwordValidation: {
30
116
  minLength: number;
@@ -49,6 +135,6 @@ interface MailerOptions {
49
135
  }
50
136
 
51
137
  declare const defaultOptions: ModuleOptions;
52
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
138
+ declare const _default: _nuxt_schema.NuxtModule<RuntimeModuleOptions, RuntimeModuleOptions, false>;
53
139
 
54
140
  export { _default as default, defaultOptions };
package/dist/module.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ export * from '../dist/runtime/server/utils/index.js';
2
3
 
3
4
  type DatabaseType = 'sqlite' | 'mysql' | 'postgresql';
4
5
  type DatabaseConfig = {
@@ -9,6 +10,90 @@ type DatabaseConfig = {
9
10
  password?: string;
10
11
  database?: string;
11
12
  };
13
+ interface RuntimeModuleOptions {
14
+ connector?: {
15
+ name: DatabaseType;
16
+ options: DatabaseConfig;
17
+ };
18
+ tables?: {
19
+ migrations?: string;
20
+ users?: string;
21
+ personalAccessTokens?: string;
22
+ passwordResetTokens?: string;
23
+ };
24
+ /**
25
+ * Mailer configuration options for sending emails (e.g., password resets)
26
+ * Uses nodemailer
27
+ */
28
+ mailer?: MailerOptions;
29
+ /**
30
+ * Base URL for password reset links
31
+ * @default 'http://localhost:3000' // Example, will be set in module defaults
32
+ */
33
+ passwordResetBaseUrl?: string;
34
+ /**
35
+ * Skip database checks during module setup to prevent hanging
36
+ * @default false
37
+ */
38
+ auth?: {
39
+ /**
40
+ * Whitelisted routes that do not require authentication
41
+ * @default ['/login']
42
+ * @example ['/login', '/register']
43
+ */
44
+ whitelist?: string[];
45
+ /**
46
+ * Token expiration time in minutes
47
+ * @default 1440
48
+ */
49
+ tokenExpiration?: number;
50
+ /**
51
+ * Role-based permissions configuration
52
+ * @default {}
53
+ * @example {
54
+ * admin: ['*'], // admin can access everything
55
+ * user: ['/profile', '/api/user/profile'],
56
+ * moderator: ['/admin/*', '/api/admin/*']
57
+ * }
58
+ */
59
+ permissions?: Record<string, string[]>;
60
+ };
61
+ /**
62
+ * Password validation configuration
63
+ */
64
+ passwordValidation?: {
65
+ /**
66
+ * Minimum password length
67
+ * @default 8
68
+ */
69
+ minLength?: number;
70
+ /**
71
+ * Require uppercase letters
72
+ * @default true
73
+ */
74
+ requireUppercase?: boolean;
75
+ /**
76
+ * Require lowercase letters
77
+ * @default true
78
+ */
79
+ requireLowercase?: boolean;
80
+ /**
81
+ * Require numbers
82
+ * @default true
83
+ */
84
+ requireNumbers?: boolean;
85
+ /**
86
+ * Require special characters
87
+ * @default true
88
+ */
89
+ requireSpecialChars?: boolean;
90
+ /**
91
+ * Prevent common passwords
92
+ * @default true
93
+ */
94
+ preventCommonPasswords?: boolean;
95
+ };
96
+ }
12
97
  interface ModuleOptions {
13
98
  connector: {
14
99
  name: DatabaseType;
@@ -25,6 +110,7 @@ interface ModuleOptions {
25
110
  auth: {
26
111
  whitelist: string[];
27
112
  tokenExpiration: number;
113
+ permissions: Record<string, string[]>;
28
114
  };
29
115
  passwordValidation: {
30
116
  minLength: number;
@@ -49,6 +135,6 @@ interface MailerOptions {
49
135
  }
50
136
 
51
137
  declare const defaultOptions: ModuleOptions;
52
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
138
+ declare const _default: _nuxt_schema.NuxtModule<RuntimeModuleOptions, RuntimeModuleOptions, false>;
53
139
 
54
140
  export { _default as default, defaultOptions };
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.6.7",
4
+ "version": "1.8.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.1",
7
7
  "unbuild": "3.6.0"
package/dist/module.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { defineNuxtModule, createResolver, addPlugin, addImportsDir, addRouteMiddleware, addServerHandler, addComponent } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
+ export * from '../dist/runtime/server/utils/index.js';
3
4
 
4
5
  const defaultOptions = {
5
6
  connector: {
@@ -31,9 +32,11 @@ const defaultOptions = {
31
32
  },
32
33
  passwordResetBaseUrl: "http://localhost:3000",
33
34
  auth: {
34
- whitelist: ["/login"],
35
- tokenExpiration: 24 * 60
35
+ whitelist: [],
36
+ tokenExpiration: 24 * 60,
36
37
  // 24 hours
38
+ permissions: {}
39
+ // Empty by default - whitelist approach
37
40
  },
38
41
  passwordValidation: {
39
42
  minLength: 8,
@@ -64,7 +67,8 @@ const module = defineNuxtModule({
64
67
  },
65
68
  auth: {
66
69
  whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []],
67
- tokenExpiration: options.auth?.tokenExpiration || defaultOptions.auth.tokenExpiration
70
+ tokenExpiration: options.auth?.tokenExpiration || defaultOptions.auth.tokenExpiration,
71
+ permissions: options.auth?.permissions || defaultOptions.auth.permissions
68
72
  }
69
73
  };
70
74
  nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
@@ -78,7 +82,8 @@ const module = defineNuxtModule({
78
82
  preventCommonPasswords: options.passwordValidation?.preventCommonPasswords ?? defaultOptions.passwordValidation.preventCommonPasswords
79
83
  },
80
84
  auth: {
81
- whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []]
85
+ whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []],
86
+ permissions: options.auth?.permissions || defaultOptions.auth.permissions
82
87
  }
83
88
  };
84
89
  addPlugin({
@@ -87,8 +92,8 @@ const module = defineNuxtModule({
87
92
  });
88
93
  addImportsDir(resolver.resolve("./runtime/composables"));
89
94
  addRouteMiddleware({
90
- name: "auth.client",
91
- path: resolver.resolve("./runtime/middleware/auth.client"),
95
+ name: "authorization.client",
96
+ path: resolver.resolve("./runtime/middleware/authorization.client"),
92
97
  global: true
93
98
  });
94
99
  addServerHandler({
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { ref } from "vue";
3
3
  import { navigateTo } from "#app";
4
- import { useAuth } from "../composables/useAuth";
4
+ import { useAuthentication } from "../composables/useAuthentication";
5
5
  const props = defineProps({
6
6
  redirectTo: { type: String, required: false, default: "/login" },
7
7
  linkText: { type: String, required: false, default: "Logout" },
@@ -16,7 +16,7 @@ const props = defineProps({
16
16
  containerStyle: { type: [String, Object], required: false }
17
17
  });
18
18
  const emit = defineEmits(["success", "click", "error"]);
19
- const { logout } = useAuth();
19
+ const { logout } = useAuthentication();
20
20
  const isLoading = ref(false);
21
21
  const error = ref("");
22
22
  const handleLogout = async (event) => {
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
- import { useAuth } from "../composables/useAuth";
3
- const { user } = useAuth();
2
+ import { useAuthentication } from "../composables/useAuthentication";
3
+ const { user } = useAuthentication();
4
4
  </script>
5
5
 
6
6
  <template>
@@ -1,5 +1,5 @@
1
1
  import type { User, UserWithoutPassword } from '../../types.js';
2
- export declare const useAuth: () => {
2
+ export declare const useAuthentication: () => {
3
3
  user: Readonly<import("vue").Ref<{
4
4
  readonly id: number;
5
5
  readonly email: string;
@@ -1,6 +1,6 @@
1
1
  import { useState } from "#app";
2
2
  import { computed, readonly } from "vue";
3
- export const useAuth = () => {
3
+ export const useAuthentication = () => {
4
4
  const user = useState("user", () => null);
5
5
  const isAuthenticated = computed(() => !!user.value);
6
6
  const login = (userData) => {
@@ -0,0 +1 @@
1
+ export declare const NO_AUTH_PATHS: string[];
@@ -0,0 +1 @@
1
+ export const NO_AUTH_PATHS = ["/login", "/api/auth/login", "/api/auth/forgot-password"];
@@ -0,0 +1,30 @@
1
+ import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig } from "#app";
2
+ import { useAuthentication } from "../composables/useAuthentication.js";
3
+ import { hasPermission, isWhitelisted } from "../utils/permissions.js";
4
+ import { NO_AUTH_PATHS } from "../constants.js";
5
+ export default defineNuxtRouteMiddleware((to) => {
6
+ const { public: { nuxtUsers: publicNuxtUsers } } = useRuntimeConfig();
7
+ const publicOptions = publicNuxtUsers;
8
+ if (NO_AUTH_PATHS.includes(to.path)) {
9
+ console.log(`[Nuxt Users] client.middleware.auth.global: ${to.path}`);
10
+ return;
11
+ }
12
+ if (isWhitelisted(to.path, publicOptions.auth.whitelist)) {
13
+ console.log(`[Nuxt Users] client.middleware.auth.global: Whitelisted: ${to.path}`);
14
+ return;
15
+ }
16
+ const { isAuthenticated, user } = useAuthentication();
17
+ if (!isAuthenticated.value) {
18
+ console.log(`[Nuxt Users] client.middleware.auth.global: Unauthenticated ${to.path}, redirecting to /login`);
19
+ return navigateTo("/login");
20
+ }
21
+ if (!user.value || !hasPermission(user.value.role, to.path, publicOptions.auth.permissions)) {
22
+ console.log(`[Nuxt Users] client.middleware.auth.global: User with role ${user.value?.role} denied access to ${to.path}`);
23
+ return navigateTo("/login");
24
+ }
25
+ console.log("[Nuxt Users] client.middleware.auth.global", {
26
+ isAuthenticated: isAuthenticated.value,
27
+ userRole: user.value?.role,
28
+ to: to.path
29
+ });
30
+ });
@@ -1,7 +1,8 @@
1
1
  import { defineEventHandler, getCookie, sendRedirect } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
3
  import { getCurrentUserFromToken } from "../utils/index.js";
4
- const noAuthPaths = ["/login", "/api/auth/login", "/api/auth/forgot-password"];
4
+ import { hasPermission, isWhitelisted } from "../../utils/permissions.js";
5
+ import { NO_AUTH_PATHS } from "../../constants.js";
5
6
  export default defineEventHandler(async (event) => {
6
7
  const { nuxtUsers } = useRuntimeConfig();
7
8
  const options = nuxtUsers;
@@ -9,11 +10,11 @@ export default defineEventHandler(async (event) => {
9
10
  if (!isPageOrApiRoute) {
10
11
  return;
11
12
  }
12
- if (noAuthPaths.includes(event.path)) {
13
+ if (NO_AUTH_PATHS.includes(event.path)) {
13
14
  console.log("[Nuxt Users] server.middleware.auth.global: /login");
14
15
  return;
15
16
  }
16
- if (options.auth.whitelist.includes(event.path)) {
17
+ if (isWhitelisted(event.path, options.auth.whitelist)) {
17
18
  console.log(`[Nuxt Users] server.middleware.auth.global: whitelisted: ${event.path}`);
18
19
  return;
19
20
  }
@@ -27,6 +28,10 @@ export default defineEventHandler(async (event) => {
27
28
  console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No user found redirecting to login`);
28
29
  return sendRedirect(event, "/login");
29
30
  }
30
- console.log(`[Nuxt Users] server.middleware.auth.global: Authenticated request to ${event.path} for ${user.id}`);
31
+ if (!hasPermission(user.role, event.path, options.auth.permissions)) {
32
+ console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} User ${user.id} with role ${user.role} denied access`);
33
+ return sendRedirect(event, "/login");
34
+ }
35
+ console.log(`[Nuxt Users] server.middleware.auth.global: Authenticated request to ${event.path} for ${user.id} with role ${user.role}`);
31
36
  return;
32
37
  });
@@ -30,13 +30,46 @@ export const sendPasswordResetLink = async (email, options) => {
30
30
  pass: options.mailer.auth.pass
31
31
  }
32
32
  });
33
+ const resetUrl = new URL("/reset-password", options.passwordResetBaseUrl || "http://localhost:3000");
34
+ resetUrl.searchParams.set("token", token);
35
+ resetUrl.searchParams.set("email", email);
36
+ const resetLink = resetUrl.toString();
33
37
  try {
34
38
  await transporter.sendMail({
35
39
  from: options.mailer.defaults?.from || '"Nuxt Users" <noreply@example.com>',
36
40
  to: email,
37
41
  subject: "Password Reset Request",
38
- text: `Please click the following link to reset your password: ${options.passwordResetBaseUrl}`,
39
- html: `<p>Please click the following link to reset your password:</p><p><a href="${options.passwordResetBaseUrl}">${options.passwordResetBaseUrl}</a></p><p>This link will expire in ${TOKEN_EXPIRATION_HOURS} hour(s).</p>`
42
+ text: `Please click the following link to reset your password: ${resetLink}
43
+
44
+ This link will expire in ${TOKEN_EXPIRATION_HOURS} hour(s).
45
+
46
+ If you did not request this password reset, please ignore this email.`,
47
+ html: `
48
+ <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
49
+ <h2>Password Reset Request</h2>
50
+ <p>You have requested to reset your password. Please click the button below to set a new password:</p>
51
+
52
+ <div style="text-align: center; margin: 30px 0;">
53
+ <a href="${resetLink}"
54
+ style="background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block;">
55
+ Reset Password
56
+ </a>
57
+ </div>
58
+
59
+ <p>Or copy and paste the following link into your browser:</p>
60
+ <p style="word-break: break-all; background-color: #f8f9fa; padding: 10px; border-radius: 3px;">
61
+ <a href="${resetLink}">${resetLink}</a>
62
+ </p>
63
+
64
+ <p style="color: #6c757d; font-size: 14px;">
65
+ This link will expire in ${TOKEN_EXPIRATION_HOURS} hour(s).
66
+ </p>
67
+
68
+ <p style="color: #6c757d; font-size: 14px;">
69
+ If you did not request this password reset, please ignore this email. Your password will remain unchanged.
70
+ </p>
71
+ </div>
72
+ `
40
73
  });
41
74
  console.log(`[Nuxt Users] Password reset email sent to ${email}`);
42
75
  } catch (error) {
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Checks if a path matches a pattern (supports wildcards)
3
+ * @param path - The path to check
4
+ * @param pattern - The pattern to match against (supports * wildcard)
5
+ * @returns true if the path matches the pattern
6
+ */
7
+ export declare const pathMatchesPattern: (path: string, pattern: string) => boolean;
8
+ /**
9
+ * Checks if a user has permission to access a specific path
10
+ * @param userRole - The user's role
11
+ * @param path - The path being accessed
12
+ * @param permissions - The permissions configuration
13
+ * @returns true if the user has permission to access the path
14
+ */
15
+ export declare const hasPermission: (userRole: string, path: string, permissions: Record<string, string[]>) => boolean;
16
+ /**
17
+ * Checks if a path is accessible without authentication
18
+ * @param path - The path being accessed
19
+ * @param whitelist - Array of whitelisted paths
20
+ * @returns true if the path is whitelisted
21
+ */
22
+ export declare const isWhitelisted: (path: string, whitelist: string[]) => boolean;
@@ -0,0 +1,36 @@
1
+ export const pathMatchesPattern = (path, pattern) => {
2
+ if (path === pattern) {
3
+ return true;
4
+ }
5
+ if (pattern === "*") {
6
+ return true;
7
+ }
8
+ if ((pattern.match(/\*/g) || []).length > 1) {
9
+ const regexPattern = pattern.replace(/\*/g, "[^/]*");
10
+ const regex = new RegExp(`^${regexPattern}$`);
11
+ return regex.test(path);
12
+ }
13
+ if (pattern.endsWith("/*")) {
14
+ const basePattern = pattern.slice(0, -2);
15
+ return path === basePattern || path.startsWith(basePattern + "/");
16
+ }
17
+ if (pattern.includes("*")) {
18
+ const regexPattern = pattern.replace(/\*/g, "[^/]*");
19
+ const regex = new RegExp(`^${regexPattern}$`);
20
+ return regex.test(path);
21
+ }
22
+ return false;
23
+ };
24
+ export const hasPermission = (userRole, path, permissions) => {
25
+ if (!permissions || Object.keys(permissions).length === 0) {
26
+ return false;
27
+ }
28
+ const rolePermissions = permissions[userRole];
29
+ if (!rolePermissions) {
30
+ return false;
31
+ }
32
+ return rolePermissions.some((pattern) => pathMatchesPattern(path, pattern));
33
+ };
34
+ export const isWhitelisted = (path, whitelist) => {
35
+ return whitelist.some((whitelistedPath) => pathMatchesPattern(path, whitelistedPath));
36
+ };
package/dist/types.d.mts CHANGED
@@ -5,3 +5,5 @@ import type { default as Module } from './module.mjs'
5
5
  export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
6
 
7
7
  export { default, type defaultOptions } from './module.mjs'
8
+
9
+ export * from '../dist/runtime/server/utils/index.js'
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.6.7",
4
- "description": "A comprehensive user management module for Nuxt 3 applications with authentication, database support, and CLI tools",
3
+ "version": "1.8.0",
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",
7
7
  "nuxt3",
8
8
  "nuxt-module",
9
9
  "users",
10
10
  "authentication",
11
+ "authorization",
11
12
  "auth",
12
13
  "user-management",
13
14
  "sqlite",
@@ -82,7 +83,7 @@
82
83
  "docs:dev": "vitepress dev docs",
83
84
  "docs:build": "vitepress build docs",
84
85
  "docs:preview": "vitepress preview docs",
85
- "docs:deploy": "yarn docs:build && ./scripts/deploy-docs.sh"
86
+ "docs:release": "yarn docs:build && ./scripts/deploy-docs.sh"
86
87
  },
87
88
  "dependencies": {
88
89
  "bcrypt": "^6.0.0",
@@ -98,7 +99,13 @@
98
99
  "peerDependencies": {
99
100
  "@nuxt/kit": "^3.17.6",
100
101
  "bcrypt": "^6.0.0",
101
- "nodemailer": "^7.0.5"
102
+ "nodemailer": "^7.0.5",
103
+ "nuxt-api-shield": "^0.8.0"
104
+ },
105
+ "peerDependenciesMeta": {
106
+ "nuxt-api-shield": {
107
+ "optional": true
108
+ }
102
109
  },
103
110
  "devDependencies": {
104
111
  "@nuxt/devtools": "^2.6.2",
@@ -1,20 +0,0 @@
1
- import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig } from "#app";
2
- import { useAuth } from "../composables/useAuth.js";
3
- export default defineNuxtRouteMiddleware((to) => {
4
- const { public: { nuxtUsers: publicNuxtUsers } } = useRuntimeConfig();
5
- const publicOptions = publicNuxtUsers;
6
- if (to.path === "/login") {
7
- console.log("[Nuxt Users] client.middleware.auth.global: /login");
8
- return;
9
- }
10
- if (publicOptions.auth.whitelist.includes(to.path)) {
11
- console.log(`[Nuxt Users] client.middleware.auth.global: Whitelisted: ${to.path}`);
12
- return;
13
- }
14
- const { isAuthenticated } = useAuth();
15
- if (!isAuthenticated.value) {
16
- console.log(`[Nuxt Users] client.middleware.auth.global: Unauthenticated ${to.path}, redirecting to /login`);
17
- return navigateTo("/login");
18
- }
19
- console.log("[Nuxt Users] client.middleware.auth.global", { isAuthenticated: isAuthenticated.value, to: to.path });
20
- });