nuxt-users 1.6.4 → 1.6.6

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/cli.mjs +1 -1
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +26 -21
  5. package/dist/runtime/components/LoginForm.vue +6 -3
  6. package/dist/runtime/components/LoginForm.vue.d.ts +4 -4
  7. package/dist/runtime/components/LogoutLink.vue +21 -7
  8. package/dist/runtime/components/LogoutLink.vue.d.ts +22 -5
  9. package/dist/runtime/components/PasswordStrengthIndicator.vue +38 -27
  10. package/dist/runtime/components/ProfileInfo.vue +33 -0
  11. package/dist/runtime/components/ResetPasswordForm.vue +89 -96
  12. package/dist/runtime/components/ResetPasswordForm.vue.d.ts +15 -1
  13. package/dist/runtime/composables/useAuth.d.ts +2 -2
  14. package/dist/runtime/composables/useAuth.js +2 -2
  15. package/dist/runtime/composables/usePasswordValidation.d.ts +1 -1
  16. package/dist/runtime/middleware/auth.client.d.ts +2 -0
  17. package/dist/runtime/middleware/auth.client.js +20 -0
  18. package/dist/runtime/plugin.js +1 -46
  19. package/dist/runtime/server/api/{login.post.js → auth/login.post.js} +1 -1
  20. package/dist/runtime/server/api/{logout.get.js → auth/logout.get.js} +1 -1
  21. package/dist/runtime/server/api/auth/reset-password.post.js +0 -3
  22. package/dist/runtime/server/api/{update-password.post.js → auth/update-password.post.js} +3 -3
  23. package/dist/runtime/server/api/user/profile.get.d.ts +4 -0
  24. package/dist/runtime/server/api/{profile.get.js → user/profile.get.js} +2 -3
  25. package/dist/runtime/server/middleware/auth.server.d.ts +2 -0
  26. package/dist/runtime/server/middleware/auth.server.js +32 -0
  27. package/dist/runtime/server/utils/user.d.ts +2 -2
  28. package/dist/runtime/server/utils/user.js +7 -2
  29. package/package.json +1 -1
  30. package/dist/runtime/components/ForgotPasswordForm.vue +0 -77
  31. package/dist/runtime/components/ProfileForm.vue +0 -240
  32. package/dist/runtime/components/ProfileForm.vue.d.ts +0 -20
  33. package/dist/runtime/server/api/profile.get.d.ts +0 -11
  34. /package/dist/runtime/components/{ForgotPasswordForm.vue.d.ts → ProfileInfo.vue.d.ts} +0 -0
  35. /package/dist/runtime/server/api/{login.post.d.ts → auth/login.post.d.ts} +0 -0
  36. /package/dist/runtime/server/api/{logout.get.d.ts → auth/logout.get.d.ts} +0 -0
  37. /package/dist/runtime/server/api/{update-password.post.d.ts → auth/update-password.post.d.ts} +0 -0
@@ -1,118 +1,98 @@
1
1
  <script setup>
2
- import { ref, reactive, onMounted, computed, watch } from "vue";
3
- import { useRoute, useRouter } from "#app";
2
+ import { ref, watch } from "vue";
4
3
  import { usePasswordValidation } from "../composables/usePasswordValidation";
5
4
  import { useRuntimeConfig } from "#imports";
6
5
  import PasswordStrengthIndicator from "./PasswordStrengthIndicator.vue";
7
- const formData = reactive({
8
- password: "",
9
- password_confirm: ""
6
+ const props = defineProps({
7
+ apiEndpoint: { type: String, required: false, default: "/api/user/profile" },
8
+ updatePasswordEndpoint: { type: String, required: false, default: "/api/auth/update-password" }
10
9
  });
11
- const message = ref("");
12
- const loading = ref(false);
13
- const isError = ref(false);
14
- const formInvalidDueToToken = ref(false);
15
- const route = useRoute();
16
- const router = useRouter();
17
- const tokenFromUrl = ref(null);
18
- const emailFromUrl = ref(null);
19
- onMounted(() => {
20
- if (typeof route.query.token === "string") {
21
- tokenFromUrl.value = route.query.token;
22
- }
23
- if (typeof route.query.email === "string") {
24
- emailFromUrl.value = decodeURIComponent(route.query.email);
25
- }
26
- if (!tokenFromUrl.value || !emailFromUrl.value) {
27
- message.value = "Invalid or missing password reset token or email in URL. Cannot reset password.";
28
- isError.value = true;
29
- formInvalidDueToToken.value = true;
30
- }
10
+ const emit = defineEmits(["success", "error", "password-error", "password-updated"]);
11
+ const isPasswordLoading = ref(false);
12
+ const passwordError = ref("");
13
+ const passwordSuccess = ref("");
14
+ const passwordForm = ref({
15
+ currentPassword: "",
16
+ newPassword: "",
17
+ newPasswordConfirmation: ""
31
18
  });
32
- const canSubmit = computed(() => !!tokenFromUrl.value && !!emailFromUrl.value && !formInvalidDueToToken.value);
33
19
  const { public: { nuxtUsers } } = useRuntimeConfig();
34
20
  const moduleOptions = nuxtUsers;
35
21
  const passwordValidation = usePasswordValidation(moduleOptions);
36
- watch(() => formData.password, (newPassword) => {
22
+ watch(() => passwordForm.value.newPassword, (newPassword) => {
37
23
  if (newPassword) {
38
24
  passwordValidation.validate(newPassword);
39
25
  } else {
40
26
  passwordValidation.clearValidation();
41
27
  }
42
28
  });
43
- const handleResetPassword = async () => {
44
- if (!canSubmit.value) {
45
- message.value = "Token or email is missing or invalid. Cannot reset password.";
46
- isError.value = true;
47
- return;
48
- }
49
- if (formData.password !== formData.password_confirm) {
50
- message.value = "Passwords do not match.";
51
- isError.value = true;
52
- return;
53
- }
54
- loading.value = true;
55
- message.value = "";
56
- isError.value = false;
29
+ const updatePassword = async () => {
30
+ isPasswordLoading.value = true;
31
+ passwordError.value = "";
32
+ passwordSuccess.value = "";
57
33
  try {
58
- const response = await fetch("/api/reset-password", {
34
+ await $fetch(props.updatePasswordEndpoint, {
59
35
  method: "POST",
60
- headers: {
61
- "Content-Type": "application/json",
62
- "Accept": "application/json"
63
- },
64
- body: JSON.stringify({
65
- token: tokenFromUrl.value,
66
- email: emailFromUrl.value,
67
- password: formData.password,
68
- password_confirmation: formData.password_confirm
69
- })
36
+ body: passwordForm.value
70
37
  });
71
- const responseData = await response.json();
72
- if (!response.ok) {
73
- throw new Error(responseData.message || responseData.statusMessage || "An error occurred.");
74
- }
75
- message.value = responseData.message + " Redirecting to login...";
76
- isError.value = false;
77
- formData.password = "";
78
- formData.password_confirm = "";
79
- setTimeout(() => {
80
- router.push("/login");
81
- }, 3e3);
38
+ passwordSuccess.value = "Password updated successfully";
39
+ emit("password-updated");
40
+ passwordForm.value = {
41
+ currentPassword: "",
42
+ newPassword: "",
43
+ newPasswordConfirmation: ""
44
+ };
82
45
  } catch (err) {
83
- if (err instanceof Error) {
84
- message.value = err.message;
85
- } else {
86
- message.value = "Failed to reset password.";
87
- }
88
- console.error("[Nuxt Users] Reset Password Form Error:", err);
89
- isError.value = true;
46
+ const errorMessage = err && typeof err === "object" && "data" in err && err.data && typeof err.data === "object" && "statusMessage" in err.data ? String(err.data.statusMessage) : err instanceof Error ? err.message : "Failed to update password";
47
+ passwordError.value = errorMessage;
48
+ emit("password-error", errorMessage);
90
49
  } finally {
91
- loading.value = false;
50
+ isPasswordLoading.value = false;
92
51
  }
93
52
  };
94
53
  </script>
95
54
 
96
55
  <template>
97
56
  <div>
98
- <h2>Reset Password</h2>
99
- <form @submit.prevent="handleResetPassword">
57
+ <h2>Change Password</h2>
58
+
59
+ <form @submit.prevent="updatePassword">
60
+ <div class="form-group">
61
+ <label
62
+ for="currentPassword"
63
+ class="form-label"
64
+ >Current Password</label>
65
+ <input
66
+ id="currentPassword"
67
+ v-model="passwordForm.currentPassword"
68
+ type="password"
69
+ class="form-input"
70
+ required
71
+ :disabled="isPasswordLoading"
72
+ >
73
+ </div>
74
+
100
75
  <div class="form-group">
101
- <label for="password">New Password</label>
76
+ <label
77
+ for="newPassword"
78
+ class="form-label"
79
+ >New Password</label>
102
80
  <input
103
- id="password"
104
- v-model="formData.password"
81
+ id="newPassword"
82
+ v-model="passwordForm.newPassword"
105
83
  type="password"
106
- name="password"
107
- placeholder="Enter new password"
84
+ class="form-input"
108
85
  required
109
86
  :minlength="moduleOptions.passwordValidation?.minLength || 8"
110
- :class="{ error: passwordValidation.errors.value.length > 0 && formData.password }"
87
+ :class="{ error: passwordValidation.errors.value.length > 0 && passwordForm.newPassword }"
88
+ :disabled="isPasswordLoading"
111
89
  >
90
+
112
91
  <PasswordStrengthIndicator
113
- :password="formData.password"
92
+ :password="passwordForm.newPassword"
114
93
  :validation-result="passwordValidation.validationResult.value"
115
94
  />
95
+
116
96
  <!-- Password requirements -->
117
97
  <small class="form-help">
118
98
  Password must contain at least {{ moduleOptions.passwordValidation?.minLength || 8 }} characters
@@ -122,35 +102,48 @@ const handleResetPassword = async () => {
122
102
  <span v-if="moduleOptions.passwordValidation?.requireSpecialChars">, and special characters</span>.
123
103
  </small>
124
104
  </div>
105
+
125
106
  <div class="form-group">
126
- <label for="password_confirm">Confirm New Password</label>
107
+ <label
108
+ for="newPasswordConfirmation"
109
+ class="form-label"
110
+ >Confirm New Password</label>
127
111
  <input
128
- id="password_confirm"
129
- v-model="formData.password_confirm"
112
+ id="newPasswordConfirmation"
113
+ v-model="passwordForm.newPasswordConfirmation"
130
114
  type="password"
131
- name="password_confirm"
132
- placeholder="Confirm new password"
115
+ class="form-input"
133
116
  required
134
- :minlength="moduleOptions.passwordValidation?.minLength || 8"
117
+ :disabled="isPasswordLoading"
135
118
  >
136
119
  </div>
120
+
121
+ <div
122
+ v-if="passwordError"
123
+ class="error-message"
124
+ >
125
+ {{ passwordError }}
126
+ </div>
127
+
128
+ <div
129
+ v-if="passwordSuccess"
130
+ class="success-message"
131
+ >
132
+ {{ passwordSuccess }}
133
+ </div>
134
+
137
135
  <button
138
136
  type="submit"
139
- :disabled="loading || formInvalidDueToToken"
137
+ class="btn btn-primary"
138
+ :disabled="isPasswordLoading"
140
139
  >
141
- {{ loading ? "Resetting..." : "Reset Password" }}
140
+ <span v-if="isPasswordLoading">Updating...</span>
141
+ <span v-else>Update Password</span>
142
142
  </button>
143
- <p
144
- v-if="message"
145
- :class="{ error: isError, success: !isError }"
146
- class="form-message"
147
- >
148
- {{ message }}
149
- </p>
150
143
  </form>
151
144
  </div>
152
145
  </template>
153
146
 
154
147
  <style scoped>
155
- .error{color:red}.success{color:green}.form-message{font-size:.9rem;margin-top:1em}.form-group{margin-bottom:1em}label{display:block;font-weight:700;margin-bottom:.25em}input[type=password]{border:1px solid var(--color-border-dark);border-radius:4px;box-sizing:border-box;padding:.75em;width:100%}input[type=password].error{border-color:#dc3545}.form-help{color:#6c757d;display:block;font-size:.875rem;margin-top:4px}button[type=submit]{background-color:var(--color-gray-800);border:none;border-radius:4px;color:#fff;cursor:pointer;padding:.75em 1.5em}button[type=submit]:disabled{background-color:var(--color-gray-500);cursor:not-allowed}
148
+ div{background-color:var(--color-bg-primary);border-bottom-left-radius:.5em;border-bottom-right-radius:.5em;margin:0 auto;max-width:42em;padding:1em}h2{border-bottom:.1em solid var(--color-border)}form{gap:1em}.form-group,form{display:flex;flex-direction:column}.form-group{gap:.5em;width:90%}.form-label{color:var(--color-text-primary);font-size:1rem;font-weight:500}.form-input{border:1px solid var(--color-border);border-radius:.5em;font-size:1rem;padding:.5em 1em;transition:border-color .2s,box-shadow .2s}.form-input:focus{border-color:var(--color-primary);box-shadow:0 0 0 .1em rgba(var(--color-primary-rgb),.1);outline:none}.form-input:disabled{background-color:var(--color-bg-secondary);cursor:not-allowed}.form-help{color:var(--color-text-secondary);font-size:.75rem;margin-top:.25em}.btn{align-self:flex-start;border:none;border-radius:.5em;cursor:pointer;font-size:1rem;font-weight:500;padding:.5em 1em;transition:background-color .2s,transform .1s}.btn:disabled{cursor:not-allowed;opacity:.6;transform:none}.btn-primary{background-color:var(--color-primary);color:#fff}.btn-primary:hover:not(:disabled){background-color:var(--color-primary-dark);transform:translateY(-1px)}.error-message{background-color:var(--color-error-bg);border:1px solid var(--color-error-border);border-radius:.5em;color:var(--color-error);font-size:.875rem;padding:12px}.success-message{background-color:#f0fff4;border:1px solid #9ae6b4;border-radius:6px;color:#38a169;font-size:.875rem;padding:12px}
156
149
  </style>
@@ -1,2 +1,16 @@
1
- declare const _default: import("vue").DefineComponent<{}, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
1
+ import type { UserWithoutPassword, ResetPasswordFormProps } from '../../types.js';
2
+ declare const _default: import("vue").DefineComponent<ResetPasswordFormProps, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
3
+ success: (user: UserWithoutPassword) => any;
4
+ error: (error: string) => any;
5
+ "password-error": (error: string) => any;
6
+ "password-updated": () => any;
7
+ }, string, import("vue").PublicProps, Readonly<ResetPasswordFormProps> & Readonly<{
8
+ onSuccess?: ((user: UserWithoutPassword) => any) | undefined;
9
+ onError?: ((error: string) => any) | undefined;
10
+ "onPassword-error"?: ((error: string) => any) | undefined;
11
+ "onPassword-updated"?: (() => any) | undefined;
12
+ }>, {
13
+ apiEndpoint: string;
14
+ updatePasswordEndpoint: string;
15
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
2
16
  export default _default;
@@ -1,15 +1,15 @@
1
1
  import type { User, UserWithoutPassword } from '../../types.js';
2
2
  export declare const useAuth: () => {
3
3
  user: Readonly<import("vue").Ref<{
4
- readonly email: string;
5
4
  readonly id: number;
5
+ readonly email: string;
6
6
  readonly name: string;
7
7
  readonly role: string;
8
8
  readonly created_at: string;
9
9
  readonly updated_at: string;
10
10
  } | null, {
11
- readonly email: string;
12
11
  readonly id: number;
12
+ readonly email: string;
13
13
  readonly name: string;
14
14
  readonly role: string;
15
15
  readonly created_at: string;
@@ -12,7 +12,7 @@ export const useAuth = () => {
12
12
  };
13
13
  const logout = async () => {
14
14
  try {
15
- await $fetch("/api/logout", {
15
+ await $fetch("/api/auth/logout", {
16
16
  method: "GET"
17
17
  });
18
18
  user.value = null;
@@ -26,7 +26,7 @@ export const useAuth = () => {
26
26
  };
27
27
  const fetchUser = async () => {
28
28
  try {
29
- const response = await $fetch("/api/profile", {
29
+ const response = await $fetch("/api/user/profile", {
30
30
  method: "GET"
31
31
  });
32
32
  user.value = response.user;
@@ -25,7 +25,7 @@ export declare const usePasswordValidation: (moduleOptions?: ModuleOptions, opti
25
25
  isValid: import("vue").ComputedRef<boolean>;
26
26
  errors: import("vue").ComputedRef<string[]>;
27
27
  hints: import("vue").ComputedRef<string[]>;
28
- strength: import("vue").ComputedRef<"strong" | "weak" | "medium">;
28
+ strength: import("vue").ComputedRef<"weak" | "medium" | "strong">;
29
29
  score: import("vue").ComputedRef<number>;
30
30
  strengthColor: import("vue").ComputedRef<string>;
31
31
  strengthText: import("vue").ComputedRef<string>;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("#app").RouteMiddleware;
2
+ export default _default;
@@ -0,0 +1,20 @@
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
+ });
@@ -1,55 +1,10 @@
1
- import { addRouteMiddleware, defineNuxtPlugin, useRuntimeConfig, navigateTo } from "#app";
1
+ import { defineNuxtPlugin, useRuntimeConfig } from "#app";
2
2
  import { checkTableExists } from "./server/utils/index.js";
3
- import { useAuth } from "./composables/useAuth.js";
4
3
  export default defineNuxtPlugin(async (_nuxtApp) => {
5
4
  const { nuxtUsers } = useRuntimeConfig();
6
- const { public: { nuxtUsers: publicNuxtUsers } } = useRuntimeConfig();
7
5
  const options = nuxtUsers;
8
- const publicOptions = publicNuxtUsers;
9
6
  const hasMigrationsTable = await checkTableExists(options, options.tables.migrations);
10
7
  if (!hasMigrationsTable) {
11
8
  console.warn("[Nuxt Users] \u26A0\uFE0F Migrations table does not exist, you should run the migration script to create it by running: npx nuxt-users migrate");
12
9
  }
13
- const { initializeUser } = useAuth();
14
- if (import.meta.client) {
15
- await initializeUser();
16
- }
17
- addRouteMiddleware(
18
- "auth.global",
19
- (to, from) => {
20
- if (to.path === "/login") {
21
- return;
22
- }
23
- if (import.meta.server) {
24
- console.log("[Nuxt Users] \u{1F510} Auth middleware: SSR - allowing request to proceed");
25
- return;
26
- }
27
- const { user } = useAuth();
28
- if (user.value || publicOptions.auth?.whitelist?.includes(to.path)) {
29
- console.log("[Nuxt Users] \u{1F510} Auth middleware: Access allowed");
30
- return;
31
- }
32
- console.log(`[Nuxt Users] \u{1F510} Auth middleware: Redirecting from ${from.path} to login - no user and not whitelisted`);
33
- return navigateTo("/login");
34
- },
35
- { global: true }
36
- );
37
- if (import.meta.client) {
38
- addRouteMiddleware(
39
- "auth.client",
40
- (to, from) => {
41
- if (to.path === "/login") {
42
- return;
43
- }
44
- const { user } = useAuth();
45
- if (user.value || publicOptions.auth?.whitelist?.includes(to.path)) {
46
- console.log("[Nuxt Users] \u{1F510} Client auth middleware: Access allowed");
47
- return;
48
- }
49
- console.log(`[Nuxt Users] \u{1F510} Client auth middleware: Redirecting from ${from.path} to login - no user and not whitelisted`);
50
- return navigateTo("/login");
51
- },
52
- { global: true }
53
- );
54
- }
55
10
  });
@@ -2,7 +2,7 @@ import { createError, defineEventHandler, readBody, setCookie } from "h3";
2
2
  import bcrypt from "bcrypt";
3
3
  import crypto from "node:crypto";
4
4
  import { useRuntimeConfig } from "#imports";
5
- import { useDb } from "../utils/index.js";
5
+ import { useDb } from "../../utils/index.js";
6
6
  export default defineEventHandler(async (event) => {
7
7
  const body = await readBody(event);
8
8
  const { email, password } = body;
@@ -1,6 +1,6 @@
1
1
  import { defineEventHandler, getCookie, setCookie } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { useDb } from "../utils/index.js";
3
+ import { useDb } from "../../utils/index.js";
4
4
  export default defineEventHandler(async (event) => {
5
5
  const { nuxtUsers } = useRuntimeConfig();
6
6
  const options = nuxtUsers;
@@ -5,9 +5,6 @@ import { validatePassword, getPasswordValidationOptions } from "../../../../util
5
5
  export default defineEventHandler(async (event) => {
6
6
  const body = await readBody(event);
7
7
  const { token, email, password, password_confirmation } = body;
8
- if (!token || typeof token !== "string") {
9
- throw createError({ statusCode: 400, statusMessage: "Token is required." });
10
- }
11
8
  if (!email || typeof email !== "string") {
12
9
  throw createError({ statusCode: 400, statusMessage: "Email is required." });
13
10
  }
@@ -1,8 +1,8 @@
1
1
  import { createError, defineEventHandler, getCookie, readBody } from "h3";
2
2
  import bcrypt from "bcrypt";
3
3
  import { useRuntimeConfig } from "#imports";
4
- import { getCurrentUserFromToken, updateUserPassword } from "../utils/index.js";
5
- import { validatePassword, getPasswordValidationOptions } from "../../../utils";
4
+ import { getCurrentUserFromToken, updateUserPassword } from "../../utils/index.js";
5
+ import { validatePassword, getPasswordValidationOptions } from "../../../../utils";
6
6
  export default defineEventHandler(async (event) => {
7
7
  const { nuxtUsers } = useRuntimeConfig();
8
8
  const options = nuxtUsers;
@@ -13,7 +13,7 @@ export default defineEventHandler(async (event) => {
13
13
  statusMessage: "Unauthorized - No authentication token found"
14
14
  });
15
15
  }
16
- const user = await getCurrentUserFromToken(token, options);
16
+ const user = await getCurrentUserFromToken(token, options, true);
17
17
  if (!user) {
18
18
  throw createError({
19
19
  statusCode: 401,
@@ -0,0 +1,4 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ user: import("../../../../types.js").UserWithoutPassword;
3
+ }>>;
4
+ export default _default;
@@ -1,6 +1,6 @@
1
1
  import { createError, defineEventHandler, getCookie } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { getCurrentUserFromToken } from "../utils/index.js";
3
+ import { getCurrentUserFromToken } from "../../utils/index.js";
4
4
  export default defineEventHandler(async (event) => {
5
5
  const { nuxtUsers } = useRuntimeConfig();
6
6
  const options = nuxtUsers;
@@ -18,6 +18,5 @@ export default defineEventHandler(async (event) => {
18
18
  statusMessage: "Unauthorized - Invalid authentication token"
19
19
  });
20
20
  }
21
- const { password: _, ...userWithoutPassword } = user;
22
- return { user: userWithoutPassword };
21
+ return { user };
23
22
  });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
2
+ export default _default;
@@ -0,0 +1,32 @@
1
+ import { defineEventHandler, getCookie, sendRedirect } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { getCurrentUserFromToken } from "../utils/index.js";
4
+ const noAuthPaths = ["/login", "/api/auth/login", "/api/auth/forgot-password"];
5
+ export default defineEventHandler(async (event) => {
6
+ const { nuxtUsers } = useRuntimeConfig();
7
+ const options = nuxtUsers;
8
+ const isPageOrApiRoute = !event.path.includes(".") && (event.path === "/" || event.path.startsWith("/api/") || !event.path.startsWith("/_"));
9
+ if (!isPageOrApiRoute) {
10
+ return;
11
+ }
12
+ if (noAuthPaths.includes(event.path)) {
13
+ console.log("[Nuxt Users] server.middleware.auth.global: /login");
14
+ return;
15
+ }
16
+ if (options.auth.whitelist.includes(event.path)) {
17
+ console.log(`[Nuxt Users] server.middleware.auth.global: whitelisted: ${event.path}`);
18
+ return;
19
+ }
20
+ const token = getCookie(event, "auth_token");
21
+ if (!token) {
22
+ console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No token found redirecting to login`);
23
+ return sendRedirect(event, "/login");
24
+ }
25
+ const user = await getCurrentUserFromToken(token, options);
26
+ if (!user) {
27
+ console.log(`[Nuxt Users] server.middleware.auth.global: ${event.path} No user found redirecting to login`);
28
+ return sendRedirect(event, "/login");
29
+ }
30
+ console.log(`[Nuxt Users] server.middleware.auth.global: Authenticated request to ${event.path} for ${user.id}`);
31
+ return;
32
+ });
@@ -1,4 +1,4 @@
1
- import type { ModuleOptions, User } from '../../../types.js';
1
+ import type { ModuleOptions, User, UserWithoutPassword } from '../../../types.js';
2
2
  interface CreateUserParams {
3
3
  email: string;
4
4
  name: string;
@@ -23,5 +23,5 @@ export declare const hasAnyUsers: (options: ModuleOptions) => Promise<boolean>;
23
23
  /**
24
24
  * Gets the current user from an authentication token.
25
25
  */
26
- export declare const getCurrentUserFromToken: (token: string, options: ModuleOptions) => Promise<User | null>;
26
+ export declare const getCurrentUserFromToken: <T extends boolean = false>(token: string, options: ModuleOptions, withPass?: T) => Promise<T extends true ? User | null : UserWithoutPassword | null>;
27
27
  export {};
@@ -71,7 +71,7 @@ export const hasAnyUsers = async (options) => {
71
71
  return false;
72
72
  }
73
73
  };
74
- export const getCurrentUserFromToken = async (token, options) => {
74
+ export const getCurrentUserFromToken = async (token, options, withPass = false) => {
75
75
  const db = await useDb(options);
76
76
  const personalAccessTokensTable = options.tables.personalAccessTokens;
77
77
  const usersTable = options.tables.users;
@@ -90,5 +90,10 @@ export const getCurrentUserFromToken = async (token, options) => {
90
90
  if (userResult.rows.length === 0) {
91
91
  return null;
92
92
  }
93
- return userResult.rows[0];
93
+ const user = userResult.rows[0];
94
+ if (withPass === true) {
95
+ return user;
96
+ }
97
+ const { password: _, ...userWithoutPassword } = user;
98
+ return userWithoutPassword;
94
99
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "A comprehensive user management module for Nuxt 3 applications with authentication, database support, and CLI tools",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -1,77 +0,0 @@
1
- <script setup>
2
- import { ref, reactive } from "vue";
3
- const formData = reactive({
4
- email: ""
5
- });
6
- const message = ref("");
7
- const loading = ref(false);
8
- const isError = ref(false);
9
- const handleForgotPassword = async () => {
10
- loading.value = true;
11
- message.value = "";
12
- isError.value = false;
13
- try {
14
- const response = await fetch("/api/forgot-password", {
15
- method: "POST",
16
- headers: {
17
- "Content-Type": "application/json",
18
- "Accept": "application/json"
19
- },
20
- body: JSON.stringify({ email: formData.email })
21
- });
22
- const responseData = await response.json();
23
- if (!response.ok) {
24
- throw new Error(responseData.message || responseData.statusMessage || "An error occurred.");
25
- }
26
- message.value = responseData.message;
27
- isError.value = false;
28
- formData.email = "";
29
- } catch (err) {
30
- if (err instanceof Error) {
31
- message.value = err.message;
32
- } else {
33
- message.value = "Failed to send password reset link.";
34
- }
35
- console.error("[Nuxt Users] Forgot Password Form Error:", err);
36
- isError.value = true;
37
- } finally {
38
- loading.value = false;
39
- }
40
- };
41
- </script>
42
-
43
- <template>
44
- <div>
45
- <h2>Forgot Password</h2>
46
- <form @submit.prevent="handleForgotPassword">
47
- <div class="form-group">
48
- <label for="email">Email</label>
49
- <input
50
- id="email"
51
- v-model="formData.email"
52
- type="email"
53
- name="email"
54
- placeholder="your@email.com"
55
- required
56
- >
57
- </div>
58
- <button
59
- type="submit"
60
- :disabled="loading"
61
- >
62
- {{ loading ? "Sending..." : "Send Password Reset Link" }}
63
- </button>
64
- <p
65
- v-if="message"
66
- :class="{ error: isError, success: !isError }"
67
- class="form-message"
68
- >
69
- {{ message }}
70
- </p>
71
- </form>
72
- </div>
73
- </template>
74
-
75
- <style scoped>
76
- .error{color:red}.success{color:green}.form-message{font-size:.9rem;margin-top:1em}.form-group{margin-bottom:1em}label{display:block;font-weight:700;margin-bottom:.25em}input[type=email]{border:1px solid var(--color-border-dark);border-radius:4px;box-sizing:border-box;padding:.75em;width:100%}button[type=submit]{background-color:var(--color-gray-800);border:none;border-radius:4px;color:#fff;cursor:pointer;padding:.75em 1.5em}button[type=submit]:disabled{background-color:var(--color-gray-500);cursor:not-allowed}
77
- </style>