nuxt-users 1.29.1 → 1.30.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 +30 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +13 -29
- package/dist/runtime/components/NUsersList.vue +58 -5
- package/dist/runtime/components/NUsersUserCard.vue +24 -14
- package/dist/runtime/components/NUsersUserCard.vue.d.ts +9 -1
- package/dist/runtime/server/middleware/authorization.server.js +10 -10
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v1.30.0
|
|
5
|
+
|
|
6
|
+
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.29.1...v1.30.0)
|
|
7
|
+
|
|
8
|
+
### 🚀 Enhancements
|
|
9
|
+
|
|
10
|
+
- Extends NUsersList with customizable user actions ([1ea4f61](https://github.com/rrd108/nuxt-users/commit/1ea4f61))
|
|
11
|
+
- Adds filtering functionality to user list ([c3d25a9](https://github.com/rrd108/nuxt-users/commit/c3d25a9))
|
|
12
|
+
|
|
13
|
+
### 🩹 Fixes
|
|
14
|
+
|
|
15
|
+
- Add missing endpoint ([a0a371c](https://github.com/rrd108/nuxt-users/commit/a0a371c))
|
|
16
|
+
- Auto whitelist /me endpoint ([5453da9](https://github.com/rrd108/nuxt-users/commit/5453da9))
|
|
17
|
+
- Check peer dependencies on install ([9e6d596](https://github.com/rrd108/nuxt-users/commit/9e6d596))
|
|
18
|
+
- Missing connector.options closes #96 ([#96](https://github.com/rrd108/nuxt-users/issues/96))
|
|
19
|
+
- Using user provided properties ([07dd6a2](https://github.com/rrd108/nuxt-users/commit/07dd6a2))
|
|
20
|
+
|
|
21
|
+
### 🏡 Chore
|
|
22
|
+
|
|
23
|
+
- **release:** V1.29.1 ([b8f72c2](https://github.com/rrd108/nuxt-users/commit/b8f72c2))
|
|
24
|
+
- Change console.logs ([7918808](https://github.com/rrd108/nuxt-users/commit/7918808))
|
|
25
|
+
|
|
26
|
+
### ✅ Tests
|
|
27
|
+
|
|
28
|
+
- Add integration tests runner ([e6cfbfb](https://github.com/rrd108/nuxt-users/commit/e6cfbfb))
|
|
29
|
+
|
|
30
|
+
### ❤️ Contributors
|
|
31
|
+
|
|
32
|
+
- Rrd108 ([@rrd108](https://github.com/rrd108))
|
|
33
|
+
|
|
4
34
|
## v1.29.1
|
|
5
35
|
|
|
6
36
|
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.29.0...v1.29.1)
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -61,19 +61,12 @@ const module = defineNuxtModule({
|
|
|
61
61
|
const runtimeConfigOptions = defu(options, nuxt.options.runtimeConfig.nuxtUsers || {}, defaultOptions);
|
|
62
62
|
nuxt.options.runtimeConfig.nuxtUsers = {
|
|
63
63
|
...runtimeConfigOptions,
|
|
64
|
-
apiBasePath: options.apiBasePath || defaultOptions.apiBasePath,
|
|
65
|
-
passwordResetUrl: options.passwordResetUrl || defaultOptions.passwordResetUrl,
|
|
66
|
-
tables: {
|
|
67
|
-
migrations: options.tables?.migrations || defaultOptions.tables.migrations,
|
|
68
|
-
users: options.tables?.users || defaultOptions.tables.users,
|
|
69
|
-
personalAccessTokens: options.tables?.personalAccessTokens || defaultOptions.tables.personalAccessTokens,
|
|
70
|
-
passwordResetTokens: options.tables?.passwordResetTokens || defaultOptions.tables.passwordResetTokens
|
|
71
|
-
},
|
|
72
64
|
auth: {
|
|
65
|
+
...runtimeConfigOptions.auth,
|
|
73
66
|
whitelist: (() => {
|
|
74
|
-
const combinedWhitelist = [...defaultOptions.auth?.whitelist || [], ...
|
|
67
|
+
const combinedWhitelist = [...defaultOptions.auth?.whitelist || [], ...runtimeConfigOptions.auth?.whitelist || []];
|
|
75
68
|
if (combinedWhitelist.includes("/register")) {
|
|
76
|
-
const apiBasePath =
|
|
69
|
+
const apiBasePath = runtimeConfigOptions.apiBasePath || defaultOptions.apiBasePath;
|
|
77
70
|
const registrationEndpoints = [
|
|
78
71
|
"/confirm-email",
|
|
79
72
|
// Page route for email confirmation
|
|
@@ -89,27 +82,17 @@ const module = defineNuxtModule({
|
|
|
89
82
|
});
|
|
90
83
|
}
|
|
91
84
|
return combinedWhitelist;
|
|
92
|
-
})()
|
|
93
|
-
|
|
94
|
-
permissions: options.auth?.permissions || defaultOptions.auth.permissions
|
|
95
|
-
},
|
|
96
|
-
hardDelete: options.hardDelete ?? defaultOptions.hardDelete
|
|
85
|
+
})()
|
|
86
|
+
}
|
|
97
87
|
};
|
|
98
88
|
nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
|
|
99
89
|
nuxt.options.runtimeConfig.public.nuxtUsers = {
|
|
100
|
-
passwordValidation:
|
|
101
|
-
minLength: options.passwordValidation?.minLength || defaultOptions.passwordValidation.minLength,
|
|
102
|
-
requireUppercase: options.passwordValidation?.requireUppercase ?? defaultOptions.passwordValidation.requireUppercase,
|
|
103
|
-
requireLowercase: options.passwordValidation?.requireLowercase ?? defaultOptions.passwordValidation.requireLowercase,
|
|
104
|
-
requireNumbers: options.passwordValidation?.requireNumbers ?? defaultOptions.passwordValidation.requireNumbers,
|
|
105
|
-
requireSpecialChars: options.passwordValidation?.requireSpecialChars ?? defaultOptions.passwordValidation.requireSpecialChars,
|
|
106
|
-
preventCommonPasswords: options.passwordValidation?.preventCommonPasswords ?? defaultOptions.passwordValidation.preventCommonPasswords
|
|
107
|
-
},
|
|
90
|
+
passwordValidation: runtimeConfigOptions.passwordValidation,
|
|
108
91
|
auth: {
|
|
109
92
|
whitelist: (() => {
|
|
110
|
-
const combinedWhitelist = [...defaultOptions.auth?.whitelist || [], ...
|
|
93
|
+
const combinedWhitelist = [...defaultOptions.auth?.whitelist || [], ...runtimeConfigOptions.auth?.whitelist || []];
|
|
111
94
|
if (combinedWhitelist.includes("/register")) {
|
|
112
|
-
const apiBasePath =
|
|
95
|
+
const apiBasePath = runtimeConfigOptions.apiBasePath || defaultOptions.apiBasePath;
|
|
113
96
|
const registrationEndpoints = [
|
|
114
97
|
"/confirm-email",
|
|
115
98
|
// Page route for email confirmation
|
|
@@ -126,9 +109,9 @@ const module = defineNuxtModule({
|
|
|
126
109
|
}
|
|
127
110
|
return combinedWhitelist;
|
|
128
111
|
})(),
|
|
129
|
-
permissions:
|
|
112
|
+
permissions: runtimeConfigOptions.auth?.permissions || defaultOptions.auth.permissions
|
|
130
113
|
},
|
|
131
|
-
apiBasePath:
|
|
114
|
+
apiBasePath: runtimeConfigOptions.apiBasePath || defaultOptions.apiBasePath
|
|
132
115
|
};
|
|
133
116
|
addPlugin({
|
|
134
117
|
src: resolver.resolve("./runtime/plugin"),
|
|
@@ -221,9 +204,10 @@ const module = defineNuxtModule({
|
|
|
221
204
|
nitroConfig.experimental.database = true;
|
|
222
205
|
nitroConfig.database = nitroConfig.database || {};
|
|
223
206
|
if (!nitroConfig.database.default) {
|
|
224
|
-
const
|
|
207
|
+
const finalConnectorConfig = nuxt.options.runtimeConfig.nuxtUsers;
|
|
208
|
+
const connectorOptions = { ...finalConnectorConfig.connector.options };
|
|
225
209
|
let nitroConnector;
|
|
226
|
-
switch (
|
|
210
|
+
switch (finalConnectorConfig.connector.name) {
|
|
227
211
|
case "sqlite":
|
|
228
212
|
nitroConnector = "better-sqlite3";
|
|
229
213
|
break;
|
|
@@ -2,19 +2,37 @@
|
|
|
2
2
|
import { onMounted, computed, ref } from "vue";
|
|
3
3
|
import { useUsers } from "../composables/useUsers";
|
|
4
4
|
import { defaultDisplayFields, defaultFieldLabels } from "nuxt-users/utils";
|
|
5
|
-
defineProps({
|
|
5
|
+
const props = defineProps({
|
|
6
6
|
displayFields: { type: Array, required: false, default: () => defaultDisplayFields },
|
|
7
|
-
fieldLabels: { type: Object, required: false, default: () => defaultFieldLabels }
|
|
7
|
+
fieldLabels: { type: Object, required: false, default: () => defaultFieldLabels },
|
|
8
|
+
filter: { type: Object, required: false, default: () => ({}) }
|
|
8
9
|
});
|
|
9
10
|
const emit = defineEmits(["editClick", "delete"]);
|
|
10
11
|
const usersComposable = ref(null);
|
|
11
|
-
const
|
|
12
|
+
const allUsers = computed(() => usersComposable.value?.users ?? []);
|
|
12
13
|
const pagination = computed(() => usersComposable.value?.pagination ?? null);
|
|
13
14
|
const loading = computed(() => usersComposable.value?.loading ?? false);
|
|
14
15
|
const error = computed(() => usersComposable.value?.error ?? null);
|
|
16
|
+
const users = computed(() => {
|
|
17
|
+
if (!props.filter || Object.keys(props.filter).length === 0) {
|
|
18
|
+
return allUsers.value;
|
|
19
|
+
}
|
|
20
|
+
return allUsers.value.filter((user) => {
|
|
21
|
+
return Object.entries(props.filter).every(([key, value]) => {
|
|
22
|
+
if (value === void 0 || value === null || value === "") {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
const userValue = user[key];
|
|
26
|
+
if (typeof value === "string" && typeof userValue === "string") {
|
|
27
|
+
return userValue.toLowerCase().includes(value.toLowerCase());
|
|
28
|
+
}
|
|
29
|
+
return userValue === value;
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
15
33
|
onMounted(async () => {
|
|
16
34
|
usersComposable.value = useUsers();
|
|
17
|
-
if (
|
|
35
|
+
if (allUsers.value.length === 0) {
|
|
18
36
|
usersComposable.value?.fetchUsers();
|
|
19
37
|
}
|
|
20
38
|
});
|
|
@@ -78,6 +96,8 @@ const handleFetchUsers = (page, limit) => {
|
|
|
78
96
|
name="user"
|
|
79
97
|
:user="user"
|
|
80
98
|
:index="index"
|
|
99
|
+
:edit-user="() => $emit('editClick', user)"
|
|
100
|
+
:delete-user="() => handleDelete(user)"
|
|
81
101
|
>
|
|
82
102
|
<NUsersUserCard
|
|
83
103
|
:user="user"
|
|
@@ -86,7 +106,40 @@ const handleFetchUsers = (page, limit) => {
|
|
|
86
106
|
:field-labels="fieldLabels"
|
|
87
107
|
@edit-click="$emit('editClick', user)"
|
|
88
108
|
@delete="handleDelete(user)"
|
|
89
|
-
|
|
109
|
+
>
|
|
110
|
+
<template #editButton="{ canEdit, editUser: editUserFn, user: userData }">
|
|
111
|
+
<slot
|
|
112
|
+
name="editButton"
|
|
113
|
+
:can-edit="canEdit"
|
|
114
|
+
:edit-user="editUserFn"
|
|
115
|
+
:user="userData"
|
|
116
|
+
>
|
|
117
|
+
<button
|
|
118
|
+
v-if="canEdit"
|
|
119
|
+
class="n-users-edit-btn"
|
|
120
|
+
@click="editUserFn"
|
|
121
|
+
>
|
|
122
|
+
Edit
|
|
123
|
+
</button>
|
|
124
|
+
</slot>
|
|
125
|
+
</template>
|
|
126
|
+
<template #deleteButton="{ canDelete, deleteUser: deleteUserFn, user: userData }">
|
|
127
|
+
<slot
|
|
128
|
+
name="deleteButton"
|
|
129
|
+
:can-delete="canDelete"
|
|
130
|
+
:delete-user="deleteUserFn"
|
|
131
|
+
:user="userData"
|
|
132
|
+
>
|
|
133
|
+
<button
|
|
134
|
+
v-if="canDelete"
|
|
135
|
+
class="n-users-delete-btn"
|
|
136
|
+
@click="deleteUserFn"
|
|
137
|
+
>
|
|
138
|
+
Delete
|
|
139
|
+
</button>
|
|
140
|
+
</slot>
|
|
141
|
+
</template>
|
|
142
|
+
</NUsersUserCard>
|
|
90
143
|
</slot>
|
|
91
144
|
</li>
|
|
92
145
|
</TransitionGroup>
|
|
@@ -75,24 +75,34 @@ const deleteUser = async (user) => {
|
|
|
75
75
|
<div
|
|
76
76
|
class="n-users-user-card-actions"
|
|
77
77
|
>
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
<slot
|
|
79
|
+
name="editButton"
|
|
80
|
+
:can-edit="canEdit"
|
|
81
|
+
:edit-user="() => editUser(user)"
|
|
82
|
+
:user="user"
|
|
82
83
|
>
|
|
83
|
-
<
|
|
84
|
+
<button
|
|
85
|
+
v-if="canEdit"
|
|
86
|
+
class="n-users-edit-btn"
|
|
87
|
+
@click="editUser(user)"
|
|
88
|
+
>
|
|
84
89
|
Edit
|
|
85
|
-
</
|
|
86
|
-
</
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
</button>
|
|
91
|
+
</slot>
|
|
92
|
+
<slot
|
|
93
|
+
name="deleteButton"
|
|
94
|
+
:can-delete="canDelete"
|
|
95
|
+
:delete-user="() => deleteUser(user)"
|
|
96
|
+
:user="user"
|
|
91
97
|
>
|
|
92
|
-
<
|
|
98
|
+
<button
|
|
99
|
+
v-if="canDelete"
|
|
100
|
+
class="n-users-delete-btn"
|
|
101
|
+
@click="deleteUser(user)"
|
|
102
|
+
>
|
|
93
103
|
Delete
|
|
94
|
-
</
|
|
95
|
-
</
|
|
104
|
+
</button>
|
|
105
|
+
</slot>
|
|
96
106
|
</div>
|
|
97
107
|
</slot>
|
|
98
108
|
</template>
|
|
@@ -8,7 +8,15 @@ interface Props {
|
|
|
8
8
|
declare var __VLS_1: {
|
|
9
9
|
user: User;
|
|
10
10
|
index: number;
|
|
11
|
-
}, __VLS_3: {
|
|
11
|
+
}, __VLS_3: {
|
|
12
|
+
canEdit: any;
|
|
13
|
+
editUser: () => Promise<void>;
|
|
14
|
+
user: User;
|
|
15
|
+
}, __VLS_5: {
|
|
16
|
+
canDelete: any;
|
|
17
|
+
deleteUser: () => Promise<void>;
|
|
18
|
+
user: User;
|
|
19
|
+
};
|
|
12
20
|
type __VLS_Slots = {} & {
|
|
13
21
|
userCard?: (props: typeof __VLS_1) => any;
|
|
14
22
|
} & {
|
|
@@ -16,7 +16,7 @@ export default defineEventHandler(async (event) => {
|
|
|
16
16
|
noAuthPaths.push(options.passwordResetUrl);
|
|
17
17
|
}
|
|
18
18
|
if (noAuthPaths.includes(event.path)) {
|
|
19
|
-
console.
|
|
19
|
+
console.debug(`[Nuxt Users] authorization: NO_AUTH_PATH: ${event.path}`);
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
const openApiPaths = NO_AUTH_API_PATHS.map((path) => `${base}${path}`);
|
|
@@ -24,42 +24,42 @@ export default defineEventHandler(async (event) => {
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
if (isWhitelisted(event.path, options.auth.whitelist)) {
|
|
27
|
-
console.
|
|
27
|
+
console.debug(`[Nuxt Users] authorization: whitelisted: ${event.path}`);
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
const token = getCookie(event, "auth_token");
|
|
31
31
|
if (!token) {
|
|
32
32
|
if (event.path.startsWith("/api/")) {
|
|
33
|
-
console.
|
|
33
|
+
console.warn(`[Nuxt Users] authorization: ${event.path} No token found - API request rejected`);
|
|
34
34
|
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
|
35
35
|
} else {
|
|
36
|
-
console.
|
|
36
|
+
console.debug(`[Nuxt Users] authorization: ${event.path} No token found - letting client handle page redirect`);
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
const user = await getCurrentUserFromToken(token, options);
|
|
41
41
|
if (!user) {
|
|
42
42
|
if (event.path.startsWith("/api/")) {
|
|
43
|
-
console.
|
|
43
|
+
console.warn(`[Nuxt Users] authorization: ${event.path} Invalid token - API request rejected`);
|
|
44
44
|
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
|
45
45
|
} else {
|
|
46
|
-
console.
|
|
46
|
+
console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - letting client handle page redirect`);
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
if (event.path === `${base}/me`) {
|
|
51
|
-
console.
|
|
51
|
+
console.debug(`[Nuxt Users] authorization: Auto-whitelisted /me endpoint for authenticated user ${user.id}`);
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
if (!hasPermission(user.role, event.path, event.method, options.auth.permissions)) {
|
|
55
55
|
if (event.path.startsWith("/api/")) {
|
|
56
|
-
console.
|
|
56
|
+
console.warn(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - API request rejected`);
|
|
57
57
|
throw createError({ statusCode: 403, statusMessage: "Forbidden" });
|
|
58
58
|
} else {
|
|
59
|
-
console.
|
|
59
|
+
console.debug(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - letting client handle page redirect`);
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
console.
|
|
63
|
+
console.debug(`[Nuxt Users] authorization: Authenticated request to ${event.path} for ${user.id} with role ${user.role}`);
|
|
64
64
|
return;
|
|
65
65
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-users",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.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",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"prepack": "nuxt-module-build build && node scripts/post-build.js",
|
|
57
|
-
"postinstall": "node -e \"try { require('fs').chmodSync('./dist/cli.mjs', '755') } catch(e) {}\"",
|
|
57
|
+
"postinstall": "node -e \"try { require('fs').chmodSync('./dist/cli.mjs', '755') } catch(e) {}\" && node scripts/check-peer-dependencies.js",
|
|
58
58
|
"predev": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for development' && exit 1)",
|
|
59
59
|
"dev": "yarn dev:prepare && nuxi dev playground",
|
|
60
60
|
"dev:build": "nuxi build playground",
|