nuxt-users 1.40.0 → 1.40.3
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/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/NUsersLoginForm.vue +1 -1
- package/dist/runtime/components/NUsersPasswordStrengthIndicator.vue +5 -5
- package/dist/runtime/components/NUsersRegisterForm.vue +1 -1
- package/dist/runtime/components/NUsersUserCard.d.vue.ts +2 -2
- package/dist/runtime/components/NUsersUserCard.vue +20 -6
- package/dist/runtime/components/NUsersUserCard.vue.d.ts +2 -2
- package/dist/runtime/composables/usePublicPaths.js +17 -13
- package/dist/runtime/constants.d.ts +9 -2
- package/dist/runtime/constants.js +11 -2
- package/dist/runtime/middleware/authorization.client.js +5 -5
- package/dist/runtime/server/middleware/authorization.server.js +22 -46
- package/dist/runtime/server/utils/authorization.d.ts +23 -0
- package/dist/runtime/server/utils/authorization.js +33 -0
- package/dist/runtime/server/utils/index.d.ts +1 -0
- package/dist/runtime/server/utils/index.js +1 -0
- package/package.json +4 -3
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -168,7 +168,7 @@ const module$1 = defineNuxtModule({
|
|
|
168
168
|
});
|
|
169
169
|
addImportsDir(resolver.resolve("./runtime/composables"));
|
|
170
170
|
addServerImportsDir(resolver.resolve("./runtime/server/composables"));
|
|
171
|
-
addServerScanDir(resolver.resolve("./runtime/server"));
|
|
171
|
+
addServerScanDir(resolver.resolve("./runtime/server/tasks"));
|
|
172
172
|
addServerHandler({
|
|
173
173
|
middleware: true,
|
|
174
174
|
handler: resolver.resolve("./runtime/server/middleware/authorization.server")
|
|
@@ -136,7 +136,7 @@ const handleForgotPassword = async () => {
|
|
|
136
136
|
name="password"
|
|
137
137
|
:placeholder="props.passwordPlaceholder || t('login.passwordPlaceholder')"
|
|
138
138
|
required
|
|
139
|
-
:minlength="passwordValidation
|
|
139
|
+
:minlength="passwordValidation?.minLength || 8"
|
|
140
140
|
>
|
|
141
141
|
</div>
|
|
142
142
|
</slot>
|
|
@@ -23,35 +23,35 @@ const hasPassword = computed(() => props.password?.length > 0);
|
|
|
23
23
|
const validationRules = computed(() => {
|
|
24
24
|
if (!props.password) return [];
|
|
25
25
|
const rules = [];
|
|
26
|
-
if (moduleOptions.passwordValidation
|
|
26
|
+
if (moduleOptions.passwordValidation?.minLength) {
|
|
27
27
|
const hasMinLength = props.password.length >= moduleOptions.passwordValidation.minLength;
|
|
28
28
|
rules.push({
|
|
29
29
|
text: t("passwordStrength.minLength", [moduleOptions.passwordValidation.minLength]),
|
|
30
30
|
passed: hasMinLength
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
-
if (moduleOptions.passwordValidation
|
|
33
|
+
if (moduleOptions.passwordValidation?.requireUppercase) {
|
|
34
34
|
const hasUppercase = /[A-Z]/.test(props.password);
|
|
35
35
|
rules.push({
|
|
36
36
|
text: t("passwordStrength.uppercase"),
|
|
37
37
|
passed: hasUppercase
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
-
if (moduleOptions.passwordValidation
|
|
40
|
+
if (moduleOptions.passwordValidation?.requireLowercase) {
|
|
41
41
|
const hasLowercase = /[a-z]/.test(props.password);
|
|
42
42
|
rules.push({
|
|
43
43
|
text: t("passwordStrength.lowercase"),
|
|
44
44
|
passed: hasLowercase
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
-
if (moduleOptions.passwordValidation
|
|
47
|
+
if (moduleOptions.passwordValidation?.requireNumbers) {
|
|
48
48
|
const hasNumbers = /\d/.test(props.password);
|
|
49
49
|
rules.push({
|
|
50
50
|
text: t("passwordStrength.number"),
|
|
51
51
|
passed: hasNumbers
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
-
if (moduleOptions.passwordValidation
|
|
54
|
+
if (moduleOptions.passwordValidation?.requireSpecialChars) {
|
|
55
55
|
const hasSpecialChars = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(props.password);
|
|
56
56
|
rules.push({
|
|
57
57
|
text: t("passwordStrength.specialChar"),
|
|
@@ -150,7 +150,7 @@ const handleSubmit = async () => {
|
|
|
150
150
|
name="password"
|
|
151
151
|
:placeholder="props.passwordPlaceholder || t('register.passwordPlaceholder')"
|
|
152
152
|
required
|
|
153
|
-
:minlength="passwordValidation
|
|
153
|
+
:minlength="passwordValidation?.minLength || 8"
|
|
154
154
|
>
|
|
155
155
|
</div>
|
|
156
156
|
</slot>
|
|
@@ -11,11 +11,11 @@ declare var __VLS_1: {
|
|
|
11
11
|
user: any;
|
|
12
12
|
index: number;
|
|
13
13
|
}, __VLS_3: {
|
|
14
|
-
canEdit:
|
|
14
|
+
canEdit: boolean;
|
|
15
15
|
editUser: () => Promise<void>;
|
|
16
16
|
user: any;
|
|
17
17
|
}, __VLS_5: {
|
|
18
|
-
canDelete:
|
|
18
|
+
canDelete: boolean;
|
|
19
19
|
deleteUser: () => Promise<void>;
|
|
20
20
|
user: any;
|
|
21
21
|
};
|
|
@@ -25,9 +25,16 @@ const canEdit = computed(() => {
|
|
|
25
25
|
if (nuxtUsers.auth?.permissions) {
|
|
26
26
|
const userPermissions = nuxtUsers.auth.permissions[currentUser.value.role] || [];
|
|
27
27
|
const apiBasePath = nuxtUsers.apiBasePath;
|
|
28
|
-
return userPermissions.some(
|
|
29
|
-
(
|
|
30
|
-
|
|
28
|
+
return userPermissions.some((permission) => {
|
|
29
|
+
if (typeof permission === "string") {
|
|
30
|
+
return permission === "*" || permission === `${apiBasePath}/*` || permission === `${apiBasePath}/[id].put` || permission === `${apiBasePath}/[id].patch`;
|
|
31
|
+
}
|
|
32
|
+
const pathMatches = permission.path === "*" || permission.path === `${apiBasePath}/*` || permission.path === `${apiBasePath}/[id].put` || permission.path === `${apiBasePath}/[id].patch`;
|
|
33
|
+
if (!pathMatches) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return permission.methods.some((method) => method.toUpperCase() === "PUT" || method.toUpperCase() === "PATCH");
|
|
37
|
+
});
|
|
31
38
|
}
|
|
32
39
|
return false;
|
|
33
40
|
});
|
|
@@ -36,9 +43,16 @@ const canDelete = computed(() => {
|
|
|
36
43
|
if (nuxtUsers.auth?.permissions) {
|
|
37
44
|
const userPermissions = nuxtUsers.auth.permissions[currentUser.value.role] || [];
|
|
38
45
|
const apiBasePath = nuxtUsers.apiBasePath;
|
|
39
|
-
return userPermissions.some(
|
|
40
|
-
(
|
|
41
|
-
|
|
46
|
+
return userPermissions.some((permission) => {
|
|
47
|
+
if (typeof permission === "string") {
|
|
48
|
+
return permission === "*" || permission === `${apiBasePath}/*` || permission === `${apiBasePath}/[id].delete`;
|
|
49
|
+
}
|
|
50
|
+
const pathMatches = permission.path === "*" || permission.path === `${apiBasePath}/*` || permission.path === `${apiBasePath}/[id].delete`;
|
|
51
|
+
if (!pathMatches) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return permission.methods.some((method) => method.toUpperCase() === "DELETE");
|
|
55
|
+
});
|
|
42
56
|
}
|
|
43
57
|
return false;
|
|
44
58
|
});
|
|
@@ -11,11 +11,11 @@ declare var __VLS_1: {
|
|
|
11
11
|
user: any;
|
|
12
12
|
index: number;
|
|
13
13
|
}, __VLS_3: {
|
|
14
|
-
canEdit:
|
|
14
|
+
canEdit: boolean;
|
|
15
15
|
editUser: () => Promise<void>;
|
|
16
16
|
user: any;
|
|
17
17
|
}, __VLS_5: {
|
|
18
|
-
canDelete:
|
|
18
|
+
canDelete: boolean;
|
|
19
19
|
deleteUser: () => Promise<void>;
|
|
20
20
|
user: any;
|
|
21
21
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRuntimeConfig } from "#app";
|
|
2
|
-
import {
|
|
2
|
+
import { PUBLIC_PAGES, PUBLIC_API_ENDPOINTS, AUTHENTICATED_AUTO_ACCESS_ENDPOINTS } from "../constants.js";
|
|
3
3
|
import { hasPermission, isWhitelisted } from "../utils/permissions.js";
|
|
4
4
|
import { useAuthentication } from "./useAuthentication.js";
|
|
5
5
|
export const usePublicPaths = () => {
|
|
@@ -8,13 +8,13 @@ export const usePublicPaths = () => {
|
|
|
8
8
|
const apiBasePath = config.apiBasePath || "/api/nuxt-users";
|
|
9
9
|
const { user } = useAuthentication();
|
|
10
10
|
const getPublicPaths = () => {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
11
|
+
const publicPages = [...PUBLIC_PAGES];
|
|
12
|
+
const publicApiEndpoints = PUBLIC_API_ENDPOINTS.map((endpoint) => `${endpoint.methods.join(", ")}: ${apiBasePath}${endpoint.path}`);
|
|
13
13
|
const whitelistedPaths = config.auth?.whitelist || [];
|
|
14
14
|
const customPasswordResetPath = config.passwordResetUrl && config.passwordResetUrl !== "/reset-password" ? config.passwordResetUrl : null;
|
|
15
15
|
const allPublicPaths = [
|
|
16
|
-
...
|
|
17
|
-
...
|
|
16
|
+
...publicPages,
|
|
17
|
+
...publicApiEndpoints,
|
|
18
18
|
...whitelistedPaths
|
|
19
19
|
];
|
|
20
20
|
if (customPasswordResetPath) {
|
|
@@ -25,8 +25,8 @@ export const usePublicPaths = () => {
|
|
|
25
25
|
all: allPublicPaths,
|
|
26
26
|
// Categorized public paths
|
|
27
27
|
builtIn: {
|
|
28
|
-
pages:
|
|
29
|
-
api:
|
|
28
|
+
pages: publicPages,
|
|
29
|
+
api: publicApiEndpoints
|
|
30
30
|
},
|
|
31
31
|
whitelist: whitelistedPaths,
|
|
32
32
|
customPasswordResetPath,
|
|
@@ -69,11 +69,11 @@ export const usePublicPaths = () => {
|
|
|
69
69
|
if (path.startsWith("/_")) {
|
|
70
70
|
return true;
|
|
71
71
|
}
|
|
72
|
-
if (
|
|
72
|
+
if (PUBLIC_PAGES.includes(path)) {
|
|
73
73
|
return true;
|
|
74
74
|
}
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
75
|
+
const publicApiEndpoints = PUBLIC_API_ENDPOINTS.map((endpoint) => `${endpoint.methods.join(", ")}: ${apiBasePath}${endpoint.path}`);
|
|
76
|
+
if (publicApiEndpoints.includes(path)) {
|
|
77
77
|
return true;
|
|
78
78
|
}
|
|
79
79
|
if (config.passwordResetUrl && config.passwordResetUrl !== "/reset-password" && path === config.passwordResetUrl) {
|
|
@@ -83,6 +83,10 @@ export const usePublicPaths = () => {
|
|
|
83
83
|
return true;
|
|
84
84
|
}
|
|
85
85
|
if (user.value) {
|
|
86
|
+
const autoWhitelistedApiEndpoints = AUTHENTICATED_AUTO_ACCESS_ENDPOINTS.map((endpoint) => `${endpoint.methods.join(", ")}: ${apiBasePath}${endpoint.path}`);
|
|
87
|
+
if (autoWhitelistedApiEndpoints.includes(path)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
86
90
|
return hasPermission(user.value.role, path, method, config.auth?.permissions || {});
|
|
87
91
|
}
|
|
88
92
|
return false;
|
|
@@ -94,11 +98,11 @@ export const usePublicPaths = () => {
|
|
|
94
98
|
if (path.startsWith("/_")) {
|
|
95
99
|
return true;
|
|
96
100
|
}
|
|
97
|
-
if (
|
|
101
|
+
if (PUBLIC_PAGES.includes(path)) {
|
|
98
102
|
return true;
|
|
99
103
|
}
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
104
|
+
const publicApiEndpoints = PUBLIC_API_ENDPOINTS.map((endpoint) => `${endpoint.methods.join(", ")}: ${apiBasePath}${endpoint.path}`);
|
|
105
|
+
if (publicApiEndpoints.includes(path)) {
|
|
102
106
|
return true;
|
|
103
107
|
}
|
|
104
108
|
if (config.passwordResetUrl && config.passwordResetUrl !== "/reset-password" && path === config.passwordResetUrl) {
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare const
|
|
1
|
+
export declare const PUBLIC_PAGES: string[];
|
|
2
|
+
export declare const PUBLIC_API_ENDPOINTS: {
|
|
3
|
+
path: string;
|
|
4
|
+
methods: string[];
|
|
5
|
+
}[];
|
|
6
|
+
export declare const AUTHENTICATED_AUTO_ACCESS_ENDPOINTS: {
|
|
7
|
+
path: string;
|
|
8
|
+
methods: string[];
|
|
9
|
+
}[];
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
export const
|
|
2
|
-
export const
|
|
1
|
+
export const PUBLIC_PAGES = ["/login", "/reset-password"];
|
|
2
|
+
export const PUBLIC_API_ENDPOINTS = [
|
|
3
|
+
{ path: "/password/forgot", methods: ["POST"] },
|
|
4
|
+
{ path: "/password/reset", methods: ["POST"] },
|
|
5
|
+
{ path: "/session", methods: ["POST", "DELETE"] }
|
|
6
|
+
// Login and logout endpoints
|
|
7
|
+
];
|
|
8
|
+
export const AUTHENTICATED_AUTO_ACCESS_ENDPOINTS = [
|
|
9
|
+
{ path: "/me", methods: ["GET", "PATCH"] },
|
|
10
|
+
{ path: "/password", methods: ["PATCH"] }
|
|
11
|
+
];
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig } from "#app";
|
|
2
2
|
import { useAuthentication } from "../composables/useAuthentication.js";
|
|
3
3
|
import { hasPermission, isWhitelisted } from "../utils/permissions.js";
|
|
4
|
-
import {
|
|
4
|
+
import { PUBLIC_PAGES, AUTHENTICATED_AUTO_ACCESS_ENDPOINTS } from "../constants.js";
|
|
5
5
|
export default defineNuxtRouteMiddleware(async (to) => {
|
|
6
6
|
const { public: { nuxtUsers: publicNuxtUsers } } = useRuntimeConfig();
|
|
7
7
|
const publicOptions = publicNuxtUsers;
|
|
8
8
|
const base = publicOptions.apiBasePath || "/api/nuxt-users";
|
|
9
|
-
if (
|
|
9
|
+
if (PUBLIC_PAGES.includes(to.path)) {
|
|
10
10
|
console.log(`[Nuxt Users] client.middleware.auth.global: ${to.path}`);
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
|
-
const openApiPaths =
|
|
13
|
+
const openApiPaths = AUTHENTICATED_AUTO_ACCESS_ENDPOINTS.map((endpoint) => `${endpoint.methods.join(", ")}: ${base}${endpoint.path}`);
|
|
14
14
|
if (openApiPaths.includes(to.path)) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
if (isWhitelisted(to.path, publicOptions.auth
|
|
17
|
+
if (isWhitelisted(to.path, publicOptions.auth?.whitelist || [])) {
|
|
18
18
|
console.log(`[Nuxt Users] client.middleware.auth.global: Whitelisted: ${to.path}`);
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
@@ -33,7 +33,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|
|
33
33
|
console.log(`[Nuxt Users] client.middleware.auth.global: Unauthenticated ${to.path}, redirecting to /login`);
|
|
34
34
|
return navigateTo("/login");
|
|
35
35
|
}
|
|
36
|
-
if (!user.value || !hasPermission(user.value.role, to.path, "GET", publicOptions.auth
|
|
36
|
+
if (!user.value || !hasPermission(user.value.role, to.path, "GET", publicOptions.auth?.permissions || {})) {
|
|
37
37
|
console.log(`[Nuxt Users] client.middleware.auth.global: User with role ${user.value?.role} denied access to ${to.path}`);
|
|
38
38
|
return navigateTo("/login");
|
|
39
39
|
}
|
|
@@ -1,82 +1,58 @@
|
|
|
1
|
-
import { defineEventHandler, getCookie
|
|
1
|
+
import { defineEventHandler, getCookie } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "#imports";
|
|
3
|
-
import { getCurrentUserFromToken } from "../utils/index.js";
|
|
3
|
+
import { getCurrentUserFromToken, handleUnauthenticatedRequest, handleInvalidToken, handleUnauthorizedRequest } from "../utils/index.js";
|
|
4
4
|
import { hasPermission, isWhitelisted } from "../../utils/permissions.js";
|
|
5
|
-
import {
|
|
5
|
+
import { PUBLIC_PAGES, PUBLIC_API_ENDPOINTS, AUTHENTICATED_AUTO_ACCESS_ENDPOINTS } from "../../constants.js";
|
|
6
6
|
export default defineEventHandler(async (event) => {
|
|
7
7
|
const { nuxtUsers } = useRuntimeConfig();
|
|
8
8
|
const options = nuxtUsers;
|
|
9
9
|
const base = options.apiBasePath || "/api/nuxt-users";
|
|
10
|
-
const
|
|
10
|
+
const isApiRoute = event.path.startsWith("/api/");
|
|
11
|
+
const isMeEndpoint = event.path === `${base}/me`;
|
|
12
|
+
const isPageOrApiRoute = !event.path.includes(".") && (event.path === "/" || isApiRoute || !event.path.startsWith("/_"));
|
|
11
13
|
if (!isPageOrApiRoute) {
|
|
12
14
|
return;
|
|
13
15
|
}
|
|
14
|
-
const
|
|
16
|
+
const publicAuthPaths = [...PUBLIC_PAGES];
|
|
15
17
|
if (options.passwordResetUrl && options.passwordResetUrl !== "/reset-password") {
|
|
16
|
-
|
|
18
|
+
publicAuthPaths.push(options.passwordResetUrl);
|
|
17
19
|
}
|
|
18
|
-
if (
|
|
20
|
+
if (publicAuthPaths.includes(event.path)) {
|
|
19
21
|
console.debug(`[Nuxt Users] authorization: NO_AUTH_PATH: ${event.path}`);
|
|
20
22
|
return;
|
|
21
23
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
for (const endpoint of PUBLIC_API_ENDPOINTS) {
|
|
25
|
+
if (event.path === `${base}${endpoint.path}` && endpoint.methods.includes(event.method)) {
|
|
26
|
+
console.debug(`[Nuxt Users] authorization: Public API endpoint ${event.path} (${event.method})`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
25
29
|
}
|
|
26
30
|
if (isWhitelisted(event.path, options.auth.whitelist)) {
|
|
27
31
|
console.debug(`[Nuxt Users] authorization: whitelisted: ${event.path}`);
|
|
28
32
|
return;
|
|
29
33
|
}
|
|
30
34
|
const token = getCookie(event, "auth_token");
|
|
31
|
-
const isMeEndpoint = event.path === `${base}/me`;
|
|
32
35
|
if (!token) {
|
|
33
|
-
|
|
34
|
-
if (isMeEndpoint) {
|
|
35
|
-
console.debug(`[Nuxt Users] authorization: ${event.path} No token found - expected for auth check`);
|
|
36
|
-
} else {
|
|
37
|
-
console.warn(`[Nuxt Users] authorization: ${event.path} No token found - API request rejected`);
|
|
38
|
-
}
|
|
39
|
-
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
|
40
|
-
}
|
|
41
|
-
if (!event.path.startsWith("/api/")) {
|
|
42
|
-
console.debug(`[Nuxt Users] authorization: ${event.path} No token found - letting client handle page redirect`);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
36
|
+
return handleUnauthenticatedRequest(event, isApiRoute, isMeEndpoint);
|
|
45
37
|
}
|
|
46
38
|
const user = await getCurrentUserFromToken(token, options);
|
|
47
39
|
if (!user) {
|
|
48
|
-
|
|
49
|
-
if (isMeEndpoint) {
|
|
50
|
-
console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - expected for auth check`);
|
|
51
|
-
} else {
|
|
52
|
-
console.warn(`[Nuxt Users] authorization: ${event.path} Invalid token - API request rejected`);
|
|
53
|
-
}
|
|
54
|
-
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
|
55
|
-
}
|
|
56
|
-
if (!event.path.startsWith("/api/")) {
|
|
57
|
-
console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - letting client handle page redirect`);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
40
|
+
return handleInvalidToken(event, isApiRoute, isMeEndpoint);
|
|
60
41
|
}
|
|
61
42
|
if (!event.context) {
|
|
62
43
|
event.context = {};
|
|
63
44
|
}
|
|
64
45
|
event.context.nuxtUsers = event.context.nuxtUsers || {};
|
|
65
46
|
event.context.nuxtUsers.user = user;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
if (!hasPermission(user.role, event.path, event.method, options.auth.permissions)) {
|
|
71
|
-
if (event.path.startsWith("/api/")) {
|
|
72
|
-
console.warn(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - API request rejected`);
|
|
73
|
-
throw createError({ statusCode: 403, statusMessage: "Forbidden" });
|
|
74
|
-
}
|
|
75
|
-
if (!event.path.startsWith("/api/")) {
|
|
76
|
-
console.debug(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - letting client handle page redirect`);
|
|
47
|
+
for (const endpoint of AUTHENTICATED_AUTO_ACCESS_ENDPOINTS) {
|
|
48
|
+
if (event.path === `${base}${endpoint.path}` && endpoint.methods.includes(event.method)) {
|
|
49
|
+
console.debug(`[Nuxt Users] authorization: Auto-whitelisted endpoint ${event.path} for authenticated user ${user.id}`);
|
|
77
50
|
return;
|
|
78
51
|
}
|
|
79
52
|
}
|
|
53
|
+
if (!hasPermission(user.role, event.path, event.method, options.auth.permissions)) {
|
|
54
|
+
return handleUnauthorizedRequest(event, user, isApiRoute);
|
|
55
|
+
}
|
|
80
56
|
console.debug(`[Nuxt Users] authorization: Authenticated request to ${event.path} for ${user.id} with role ${user.role}`);
|
|
81
57
|
return;
|
|
82
58
|
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type H3Event } from 'h3';
|
|
2
|
+
import type { UserWithoutPassword } from 'nuxt-users/utils';
|
|
3
|
+
/**
|
|
4
|
+
* Handles unauthenticated requests (no token found)
|
|
5
|
+
* @param event - H3 event object
|
|
6
|
+
* @param isApiRoute - Whether this is an API route
|
|
7
|
+
* @param isMeEndpoint - Whether this is the /me endpoint (used for auth status checks)
|
|
8
|
+
*/
|
|
9
|
+
export declare const handleUnauthenticatedRequest: (event: H3Event, isApiRoute: boolean, isMeEndpoint: boolean) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Handles invalid token requests
|
|
12
|
+
* @param event - H3 event object
|
|
13
|
+
* @param isApiRoute - Whether this is an API route
|
|
14
|
+
* @param isMeEndpoint - Whether this is the /me endpoint (used for auth status checks)
|
|
15
|
+
*/
|
|
16
|
+
export declare const handleInvalidToken: (event: H3Event, isApiRoute: boolean, isMeEndpoint: boolean) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Handles unauthorized requests (user lacks required permissions)
|
|
19
|
+
* @param event - H3 event object
|
|
20
|
+
* @param user - Authenticated user object
|
|
21
|
+
* @param isApiRoute - Whether this is an API route
|
|
22
|
+
*/
|
|
23
|
+
export declare const handleUnauthorizedRequest: (event: H3Event, user: UserWithoutPassword, isApiRoute: boolean) => void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createError } from "h3";
|
|
2
|
+
export const handleUnauthenticatedRequest = (event, isApiRoute, isMeEndpoint) => {
|
|
3
|
+
if (isApiRoute) {
|
|
4
|
+
if (isMeEndpoint) {
|
|
5
|
+
console.debug(`[Nuxt Users] authorization: ${event.path} No token found - expected for auth check`);
|
|
6
|
+
} else {
|
|
7
|
+
console.warn(`[Nuxt Users] authorization: ${event.path} No token found - API request rejected`);
|
|
8
|
+
}
|
|
9
|
+
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
|
10
|
+
}
|
|
11
|
+
console.debug(`[Nuxt Users] authorization: ${event.path} No token found - letting client handle page redirect`);
|
|
12
|
+
return;
|
|
13
|
+
};
|
|
14
|
+
export const handleInvalidToken = (event, isApiRoute, isMeEndpoint) => {
|
|
15
|
+
if (isApiRoute) {
|
|
16
|
+
if (isMeEndpoint) {
|
|
17
|
+
console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - expected for auth check`);
|
|
18
|
+
} else {
|
|
19
|
+
console.warn(`[Nuxt Users] authorization: ${event.path} Invalid token - API request rejected`);
|
|
20
|
+
}
|
|
21
|
+
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
|
22
|
+
}
|
|
23
|
+
console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - letting client handle page redirect`);
|
|
24
|
+
return;
|
|
25
|
+
};
|
|
26
|
+
export const handleUnauthorizedRequest = (event, user, isApiRoute) => {
|
|
27
|
+
if (isApiRoute) {
|
|
28
|
+
console.warn(`[Nuxt Users] authorization: ${event.method}: ${event.path} User ${user.id} with role ${user.role} denied access - API request rejected`);
|
|
29
|
+
throw createError({ statusCode: 403, statusMessage: "Forbidden" });
|
|
30
|
+
}
|
|
31
|
+
console.debug(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - letting client handle page redirect`);
|
|
32
|
+
return;
|
|
33
|
+
};
|
|
@@ -7,3 +7,4 @@ export { createPasswordResetTokensTable } from './create-password-reset-tokens-t
|
|
|
7
7
|
export { createMigrationsTable } from './create-migrations-table.js';
|
|
8
8
|
export { runMigrations, getAppliedMigrations } from './migrate.js';
|
|
9
9
|
export { createGoogleOAuth2Client, getGoogleAuthUrl, getGoogleUserFromCode, findOrCreateGoogleUser, createAuthTokenForUser, generateSecurePassword } from './google-oauth.js';
|
|
10
|
+
export { handleUnauthenticatedRequest, handleInvalidToken, handleUnauthorizedRequest } from './authorization.js';
|
|
@@ -7,3 +7,4 @@ export { createPasswordResetTokensTable } from "./create-password-reset-tokens-t
|
|
|
7
7
|
export { createMigrationsTable } from "./create-migrations-table.js";
|
|
8
8
|
export { runMigrations, getAppliedMigrations } from "./migrate.js";
|
|
9
9
|
export { createGoogleOAuth2Client, getGoogleAuthUrl, getGoogleUserFromCode, findOrCreateGoogleUser, createAuthTokenForUser, generateSecurePassword } from "./google-oauth.js";
|
|
10
|
+
export { handleUnauthenticatedRequest, handleInvalidToken, handleUnauthorizedRequest } from "./authorization.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-users",
|
|
3
|
-
"version": "1.40.
|
|
3
|
+
"version": "1.40.3",
|
|
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",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"postbuild": "node scripts/post-build.js",
|
|
65
65
|
"check:translations": "tsx scripts/check-translations.ts",
|
|
66
66
|
"check:locale-consistency": "tsx scripts/check-locale-consistency.ts",
|
|
67
|
-
"check:
|
|
67
|
+
"check:npm_token": "if [ -z \"$NPM_TOKEN\" ]; then echo '❌ NPM_TOKEN environment variable is not set. Please set it to your npm token.'; exit 1; fi",
|
|
68
68
|
"prerelease": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for deployment' && exit 1)",
|
|
69
|
-
"release": "yarn check:
|
|
69
|
+
"release": "yarn check:npm_token && yarn lint && yarn check:translations && yarn check:locale-consistency && yarn test && yarn prepack && dotenv -- changelogen --release && dotenv -- npm publish --registry https://registry.npmjs.org/ && git push --follow-tags",
|
|
70
70
|
"lint": "eslint .",
|
|
71
71
|
"lint:fix": "eslint . --fix",
|
|
72
72
|
"pretest": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for testing' && exit 1)",
|
|
@@ -130,6 +130,7 @@
|
|
|
130
130
|
"@types/nodemailer": "^6.4.17",
|
|
131
131
|
"@types/pg": "^8.15.4",
|
|
132
132
|
"changelogen": "^0.6.2",
|
|
133
|
+
"dotenv-cli": "^11.0.0",
|
|
133
134
|
"eslint": "^9.30.1",
|
|
134
135
|
"nuxt": "^4.0.0",
|
|
135
136
|
"tsx": "^4.20.3",
|