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 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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.29.1",
4
+ "version": "1.30.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.0"
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 || [], ...options.auth?.whitelist || []];
67
+ const combinedWhitelist = [...defaultOptions.auth?.whitelist || [], ...runtimeConfigOptions.auth?.whitelist || []];
75
68
  if (combinedWhitelist.includes("/register")) {
76
- const apiBasePath = options.apiBasePath || defaultOptions.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
- tokenExpiration: options.auth?.tokenExpiration || defaultOptions.auth.tokenExpiration,
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 || [], ...options.auth?.whitelist || []];
93
+ const combinedWhitelist = [...defaultOptions.auth?.whitelist || [], ...runtimeConfigOptions.auth?.whitelist || []];
111
94
  if (combinedWhitelist.includes("/register")) {
112
- const apiBasePath = options.apiBasePath || defaultOptions.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: options.auth?.permissions || defaultOptions.auth.permissions
112
+ permissions: runtimeConfigOptions.auth?.permissions || defaultOptions.auth.permissions
130
113
  },
131
- apiBasePath: options.apiBasePath || defaultOptions.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 connectorOptions = { ...runtimeConfigOptions.connector.options };
207
+ const finalConnectorConfig = nuxt.options.runtimeConfig.nuxtUsers;
208
+ const connectorOptions = { ...finalConnectorConfig.connector.options };
225
209
  let nitroConnector;
226
- switch (runtimeConfigOptions.connector.name) {
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 users = computed(() => usersComposable.value?.users ?? []);
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 (users.value.length === 0) {
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
- <button
79
- v-if="canEdit"
80
- class="n-users-edit-btn"
81
- @click="editUser(user)"
78
+ <slot
79
+ name="editButton"
80
+ :can-edit="canEdit"
81
+ :edit-user="() => editUser(user)"
82
+ :user="user"
82
83
  >
83
- <slot name="editButton">
84
+ <button
85
+ v-if="canEdit"
86
+ class="n-users-edit-btn"
87
+ @click="editUser(user)"
88
+ >
84
89
  Edit
85
- </slot>
86
- </button>
87
- <button
88
- v-if="canDelete"
89
- class="n-users-delete-btn"
90
- @click="deleteUser(user)"
90
+ </button>
91
+ </slot>
92
+ <slot
93
+ name="deleteButton"
94
+ :can-delete="canDelete"
95
+ :delete-user="() => deleteUser(user)"
96
+ :user="user"
91
97
  >
92
- <slot name="deleteButton">
98
+ <button
99
+ v-if="canDelete"
100
+ class="n-users-delete-btn"
101
+ @click="deleteUser(user)"
102
+ >
93
103
  Delete
94
- </slot>
95
- </button>
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: {}, __VLS_5: {};
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.log(`[Nuxt Users] server.middleware.auth.global: NO_AUTH_PATH: ${event.path}`);
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.log(`[Nuxt Users] server.middleware.auth.global: whitelisted: ${event.path}`);
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.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No token found - API request rejected`);
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.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No token found - letting client handle page redirect`);
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.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} Invalid token - API request rejected`);
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.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} Invalid token - letting client handle page redirect`);
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.log(`[Nuxt Users] server.middleware.auth.global: Auto-whitelisted /me endpoint for authenticated user ${user.id}`);
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.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} User ${user.id} with role ${user.role} denied access - API request rejected`);
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.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} User ${user.id} with role ${user.role} denied access - letting client handle page redirect`);
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.log(`[Nuxt Users] server.middleware.auth.global: Authenticated request to ${event.path} for ${user.id} with role ${user.role}`);
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.29.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",