nuxt-users 1.6.7 → 1.7.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 +33 -0
- package/dist/cli.mjs +1 -1
- package/dist/module.d.mts +86 -1
- package/dist/module.d.ts +86 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -6
- package/dist/runtime/components/LogoutLink.vue +2 -2
- package/dist/runtime/components/ProfileInfo.vue +2 -2
- package/dist/runtime/composables/{useAuth.d.ts → useAuthentication.d.ts} +1 -1
- package/dist/runtime/composables/{useAuth.js → useAuthentication.js} +1 -1
- package/dist/runtime/constants.d.ts +1 -0
- package/dist/runtime/constants.js +1 -0
- package/dist/runtime/middleware/authorization.client.js +30 -0
- package/dist/runtime/server/middleware/{auth.server.js → authorization.server.js} +9 -4
- package/dist/runtime/server/services/password.js +35 -2
- package/dist/runtime/utils/permissions.d.ts +22 -0
- package/dist/runtime/utils/permissions.js +36 -0
- package/package.json +11 -4
- package/dist/runtime/middleware/auth.client.js +0 -20
- /package/dist/runtime/middleware/{auth.client.d.ts → authorization.client.d.ts} +0 -0
- /package/dist/runtime/server/middleware/{auth.server.d.ts → authorization.server.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v1.7.0
|
|
5
|
+
|
|
6
|
+
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.7...v1.7.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
|
+
|
|
16
|
+
### 💅 Refactors
|
|
17
|
+
|
|
18
|
+
- Token cleanup task and middleware ([d47a4a5](https://github.com/rrd108/nuxt-users/commit/d47a4a5))
|
|
19
|
+
- Renames `useAuth` to `useAuthentication` ([e611e62](https://github.com/rrd108/nuxt-users/commit/e611e62))
|
|
20
|
+
|
|
21
|
+
### 🏡 Chore
|
|
22
|
+
|
|
23
|
+
- **release:** V1.6.7 ([def2d5c](https://github.com/rrd108/nuxt-users/commit/def2d5c))
|
|
24
|
+
- Bump cli version number ([b4daec6](https://github.com/rrd108/nuxt-users/commit/b4daec6))
|
|
25
|
+
- Fix for linter ([ce1db37](https://github.com/rrd108/nuxt-users/commit/ce1db37))
|
|
26
|
+
|
|
27
|
+
### ✅ Tests
|
|
28
|
+
|
|
29
|
+
- Remove unused test ([3dc23ad](https://github.com/rrd108/nuxt-users/commit/3dc23ad))
|
|
30
|
+
- Fix response ([db3b03c](https://github.com/rrd108/nuxt-users/commit/db3b03c))
|
|
31
|
+
- Fix mysql field type ([2faf0c6](https://github.com/rrd108/nuxt-users/commit/2faf0c6))
|
|
32
|
+
|
|
33
|
+
### ❤️ Contributors
|
|
34
|
+
|
|
35
|
+
- Rrd108 ([@rrd108](https://github.com/rrd108))
|
|
36
|
+
|
|
4
37
|
## v1.6.7
|
|
5
38
|
|
|
6
39
|
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.6...v1.6.7)
|
package/dist/cli.mjs
CHANGED
package/dist/module.d.mts
CHANGED
|
@@ -9,6 +9,90 @@ type DatabaseConfig = {
|
|
|
9
9
|
password?: string;
|
|
10
10
|
database?: string;
|
|
11
11
|
};
|
|
12
|
+
interface RuntimeModuleOptions {
|
|
13
|
+
connector?: {
|
|
14
|
+
name: DatabaseType;
|
|
15
|
+
options: DatabaseConfig;
|
|
16
|
+
};
|
|
17
|
+
tables?: {
|
|
18
|
+
migrations?: string;
|
|
19
|
+
users?: string;
|
|
20
|
+
personalAccessTokens?: string;
|
|
21
|
+
passwordResetTokens?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Mailer configuration options for sending emails (e.g., password resets)
|
|
25
|
+
* Uses nodemailer
|
|
26
|
+
*/
|
|
27
|
+
mailer?: MailerOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Base URL for password reset links
|
|
30
|
+
* @default 'http://localhost:3000' // Example, will be set in module defaults
|
|
31
|
+
*/
|
|
32
|
+
passwordResetBaseUrl?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Skip database checks during module setup to prevent hanging
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
auth?: {
|
|
38
|
+
/**
|
|
39
|
+
* Whitelisted routes that do not require authentication
|
|
40
|
+
* @default ['/login']
|
|
41
|
+
* @example ['/login', '/register']
|
|
42
|
+
*/
|
|
43
|
+
whitelist?: string[];
|
|
44
|
+
/**
|
|
45
|
+
* Token expiration time in minutes
|
|
46
|
+
* @default 1440
|
|
47
|
+
*/
|
|
48
|
+
tokenExpiration?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Role-based permissions configuration
|
|
51
|
+
* @default {}
|
|
52
|
+
* @example {
|
|
53
|
+
* admin: ['*'], // admin can access everything
|
|
54
|
+
* user: ['/profile', '/api/user/profile'],
|
|
55
|
+
* moderator: ['/admin/*', '/api/admin/*']
|
|
56
|
+
* }
|
|
57
|
+
*/
|
|
58
|
+
permissions?: Record<string, string[]>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Password validation configuration
|
|
62
|
+
*/
|
|
63
|
+
passwordValidation?: {
|
|
64
|
+
/**
|
|
65
|
+
* Minimum password length
|
|
66
|
+
* @default 8
|
|
67
|
+
*/
|
|
68
|
+
minLength?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Require uppercase letters
|
|
71
|
+
* @default true
|
|
72
|
+
*/
|
|
73
|
+
requireUppercase?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Require lowercase letters
|
|
76
|
+
* @default true
|
|
77
|
+
*/
|
|
78
|
+
requireLowercase?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Require numbers
|
|
81
|
+
* @default true
|
|
82
|
+
*/
|
|
83
|
+
requireNumbers?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Require special characters
|
|
86
|
+
* @default true
|
|
87
|
+
*/
|
|
88
|
+
requireSpecialChars?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Prevent common passwords
|
|
91
|
+
* @default true
|
|
92
|
+
*/
|
|
93
|
+
preventCommonPasswords?: boolean;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
12
96
|
interface ModuleOptions {
|
|
13
97
|
connector: {
|
|
14
98
|
name: DatabaseType;
|
|
@@ -25,6 +109,7 @@ interface ModuleOptions {
|
|
|
25
109
|
auth: {
|
|
26
110
|
whitelist: string[];
|
|
27
111
|
tokenExpiration: number;
|
|
112
|
+
permissions: Record<string, string[]>;
|
|
28
113
|
};
|
|
29
114
|
passwordValidation: {
|
|
30
115
|
minLength: number;
|
|
@@ -49,6 +134,6 @@ interface MailerOptions {
|
|
|
49
134
|
}
|
|
50
135
|
|
|
51
136
|
declare const defaultOptions: ModuleOptions;
|
|
52
|
-
declare const _default: _nuxt_schema.NuxtModule<
|
|
137
|
+
declare const _default: _nuxt_schema.NuxtModule<RuntimeModuleOptions, RuntimeModuleOptions, false>;
|
|
53
138
|
|
|
54
139
|
export { _default as default, defaultOptions };
|
package/dist/module.d.ts
CHANGED
|
@@ -9,6 +9,90 @@ type DatabaseConfig = {
|
|
|
9
9
|
password?: string;
|
|
10
10
|
database?: string;
|
|
11
11
|
};
|
|
12
|
+
interface RuntimeModuleOptions {
|
|
13
|
+
connector?: {
|
|
14
|
+
name: DatabaseType;
|
|
15
|
+
options: DatabaseConfig;
|
|
16
|
+
};
|
|
17
|
+
tables?: {
|
|
18
|
+
migrations?: string;
|
|
19
|
+
users?: string;
|
|
20
|
+
personalAccessTokens?: string;
|
|
21
|
+
passwordResetTokens?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Mailer configuration options for sending emails (e.g., password resets)
|
|
25
|
+
* Uses nodemailer
|
|
26
|
+
*/
|
|
27
|
+
mailer?: MailerOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Base URL for password reset links
|
|
30
|
+
* @default 'http://localhost:3000' // Example, will be set in module defaults
|
|
31
|
+
*/
|
|
32
|
+
passwordResetBaseUrl?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Skip database checks during module setup to prevent hanging
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
auth?: {
|
|
38
|
+
/**
|
|
39
|
+
* Whitelisted routes that do not require authentication
|
|
40
|
+
* @default ['/login']
|
|
41
|
+
* @example ['/login', '/register']
|
|
42
|
+
*/
|
|
43
|
+
whitelist?: string[];
|
|
44
|
+
/**
|
|
45
|
+
* Token expiration time in minutes
|
|
46
|
+
* @default 1440
|
|
47
|
+
*/
|
|
48
|
+
tokenExpiration?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Role-based permissions configuration
|
|
51
|
+
* @default {}
|
|
52
|
+
* @example {
|
|
53
|
+
* admin: ['*'], // admin can access everything
|
|
54
|
+
* user: ['/profile', '/api/user/profile'],
|
|
55
|
+
* moderator: ['/admin/*', '/api/admin/*']
|
|
56
|
+
* }
|
|
57
|
+
*/
|
|
58
|
+
permissions?: Record<string, string[]>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Password validation configuration
|
|
62
|
+
*/
|
|
63
|
+
passwordValidation?: {
|
|
64
|
+
/**
|
|
65
|
+
* Minimum password length
|
|
66
|
+
* @default 8
|
|
67
|
+
*/
|
|
68
|
+
minLength?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Require uppercase letters
|
|
71
|
+
* @default true
|
|
72
|
+
*/
|
|
73
|
+
requireUppercase?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Require lowercase letters
|
|
76
|
+
* @default true
|
|
77
|
+
*/
|
|
78
|
+
requireLowercase?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Require numbers
|
|
81
|
+
* @default true
|
|
82
|
+
*/
|
|
83
|
+
requireNumbers?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Require special characters
|
|
86
|
+
* @default true
|
|
87
|
+
*/
|
|
88
|
+
requireSpecialChars?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Prevent common passwords
|
|
91
|
+
* @default true
|
|
92
|
+
*/
|
|
93
|
+
preventCommonPasswords?: boolean;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
12
96
|
interface ModuleOptions {
|
|
13
97
|
connector: {
|
|
14
98
|
name: DatabaseType;
|
|
@@ -25,6 +109,7 @@ interface ModuleOptions {
|
|
|
25
109
|
auth: {
|
|
26
110
|
whitelist: string[];
|
|
27
111
|
tokenExpiration: number;
|
|
112
|
+
permissions: Record<string, string[]>;
|
|
28
113
|
};
|
|
29
114
|
passwordValidation: {
|
|
30
115
|
minLength: number;
|
|
@@ -49,6 +134,6 @@ interface MailerOptions {
|
|
|
49
134
|
}
|
|
50
135
|
|
|
51
136
|
declare const defaultOptions: ModuleOptions;
|
|
52
|
-
declare const _default: _nuxt_schema.NuxtModule<
|
|
137
|
+
declare const _default: _nuxt_schema.NuxtModule<RuntimeModuleOptions, RuntimeModuleOptions, false>;
|
|
53
138
|
|
|
54
139
|
export { _default as default, defaultOptions };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -31,9 +31,11 @@ const defaultOptions = {
|
|
|
31
31
|
},
|
|
32
32
|
passwordResetBaseUrl: "http://localhost:3000",
|
|
33
33
|
auth: {
|
|
34
|
-
whitelist: [
|
|
35
|
-
tokenExpiration: 24 * 60
|
|
34
|
+
whitelist: [],
|
|
35
|
+
tokenExpiration: 24 * 60,
|
|
36
36
|
// 24 hours
|
|
37
|
+
permissions: {}
|
|
38
|
+
// Empty by default - whitelist approach
|
|
37
39
|
},
|
|
38
40
|
passwordValidation: {
|
|
39
41
|
minLength: 8,
|
|
@@ -64,7 +66,8 @@ const module = defineNuxtModule({
|
|
|
64
66
|
},
|
|
65
67
|
auth: {
|
|
66
68
|
whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []],
|
|
67
|
-
tokenExpiration: options.auth?.tokenExpiration || defaultOptions.auth.tokenExpiration
|
|
69
|
+
tokenExpiration: options.auth?.tokenExpiration || defaultOptions.auth.tokenExpiration,
|
|
70
|
+
permissions: options.auth?.permissions || defaultOptions.auth.permissions
|
|
68
71
|
}
|
|
69
72
|
};
|
|
70
73
|
nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
|
|
@@ -78,7 +81,8 @@ const module = defineNuxtModule({
|
|
|
78
81
|
preventCommonPasswords: options.passwordValidation?.preventCommonPasswords ?? defaultOptions.passwordValidation.preventCommonPasswords
|
|
79
82
|
},
|
|
80
83
|
auth: {
|
|
81
|
-
whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []]
|
|
84
|
+
whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []],
|
|
85
|
+
permissions: options.auth?.permissions || defaultOptions.auth.permissions
|
|
82
86
|
}
|
|
83
87
|
};
|
|
84
88
|
addPlugin({
|
|
@@ -87,8 +91,8 @@ const module = defineNuxtModule({
|
|
|
87
91
|
});
|
|
88
92
|
addImportsDir(resolver.resolve("./runtime/composables"));
|
|
89
93
|
addRouteMiddleware({
|
|
90
|
-
name: "
|
|
91
|
-
path: resolver.resolve("./runtime/middleware/
|
|
94
|
+
name: "authorization.client",
|
|
95
|
+
path: resolver.resolve("./runtime/middleware/authorization.client"),
|
|
92
96
|
global: true
|
|
93
97
|
});
|
|
94
98
|
addServerHandler({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref } from "vue";
|
|
3
3
|
import { navigateTo } from "#app";
|
|
4
|
-
import {
|
|
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 } =
|
|
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
|
import { useState } from "#app";
|
|
2
2
|
import { computed, readonly } from "vue";
|
|
3
|
-
export const
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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: ${
|
|
39
|
-
|
|
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/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-users",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A comprehensive user management module for Nuxt 3 applications with authentication, database support, and CLI tools",
|
|
3
|
+
"version": "1.7.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:
|
|
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.1.0 || ^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
|
-
});
|
|
File without changes
|
|
File without changes
|