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.
- package/CHANGELOG.md +50 -0
- package/dist/cli.mjs +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +26 -21
- package/dist/runtime/components/LoginForm.vue +6 -3
- package/dist/runtime/components/LoginForm.vue.d.ts +4 -4
- package/dist/runtime/components/LogoutLink.vue +21 -7
- package/dist/runtime/components/LogoutLink.vue.d.ts +22 -5
- package/dist/runtime/components/PasswordStrengthIndicator.vue +38 -27
- package/dist/runtime/components/ProfileInfo.vue +33 -0
- package/dist/runtime/components/ResetPasswordForm.vue +89 -96
- package/dist/runtime/components/ResetPasswordForm.vue.d.ts +15 -1
- package/dist/runtime/composables/useAuth.d.ts +2 -2
- package/dist/runtime/composables/useAuth.js +2 -2
- package/dist/runtime/composables/usePasswordValidation.d.ts +1 -1
- package/dist/runtime/middleware/auth.client.d.ts +2 -0
- package/dist/runtime/middleware/auth.client.js +20 -0
- package/dist/runtime/plugin.js +1 -46
- package/dist/runtime/server/api/{login.post.js → auth/login.post.js} +1 -1
- package/dist/runtime/server/api/{logout.get.js → auth/logout.get.js} +1 -1
- package/dist/runtime/server/api/auth/reset-password.post.js +0 -3
- package/dist/runtime/server/api/{update-password.post.js → auth/update-password.post.js} +3 -3
- package/dist/runtime/server/api/user/profile.get.d.ts +4 -0
- package/dist/runtime/server/api/{profile.get.js → user/profile.get.js} +2 -3
- package/dist/runtime/server/middleware/auth.server.d.ts +2 -0
- package/dist/runtime/server/middleware/auth.server.js +32 -0
- package/dist/runtime/server/utils/user.d.ts +2 -2
- package/dist/runtime/server/utils/user.js +7 -2
- package/package.json +1 -1
- package/dist/runtime/components/ForgotPasswordForm.vue +0 -77
- package/dist/runtime/components/ProfileForm.vue +0 -240
- package/dist/runtime/components/ProfileForm.vue.d.ts +0 -20
- package/dist/runtime/server/api/profile.get.d.ts +0 -11
- /package/dist/runtime/components/{ForgotPasswordForm.vue.d.ts → ProfileInfo.vue.d.ts} +0 -0
- /package/dist/runtime/server/api/{login.post.d.ts → auth/login.post.d.ts} +0 -0
- /package/dist/runtime/server/api/{logout.get.d.ts → auth/logout.get.d.ts} +0 -0
- /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,
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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(() =>
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
34
|
+
await $fetch(props.updatePasswordEndpoint, {
|
|
59
35
|
method: "POST",
|
|
60
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
50
|
+
isPasswordLoading.value = false;
|
|
92
51
|
}
|
|
93
52
|
};
|
|
94
53
|
</script>
|
|
95
54
|
|
|
96
55
|
<template>
|
|
97
56
|
<div>
|
|
98
|
-
<h2>
|
|
99
|
-
|
|
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
|
|
76
|
+
<label
|
|
77
|
+
for="newPassword"
|
|
78
|
+
class="form-label"
|
|
79
|
+
>New Password</label>
|
|
102
80
|
<input
|
|
103
|
-
id="
|
|
104
|
-
v-model="
|
|
81
|
+
id="newPassword"
|
|
82
|
+
v-model="passwordForm.newPassword"
|
|
105
83
|
type="password"
|
|
106
|
-
|
|
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 &&
|
|
87
|
+
:class="{ error: passwordValidation.errors.value.length > 0 && passwordForm.newPassword }"
|
|
88
|
+
:disabled="isPasswordLoading"
|
|
111
89
|
>
|
|
90
|
+
|
|
112
91
|
<PasswordStrengthIndicator
|
|
113
|
-
: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
|
|
107
|
+
<label
|
|
108
|
+
for="newPasswordConfirmation"
|
|
109
|
+
class="form-label"
|
|
110
|
+
>Confirm New Password</label>
|
|
127
111
|
<input
|
|
128
|
-
id="
|
|
129
|
-
v-model="
|
|
112
|
+
id="newPasswordConfirmation"
|
|
113
|
+
v-model="passwordForm.newPasswordConfirmation"
|
|
130
114
|
type="password"
|
|
131
|
-
|
|
132
|
-
placeholder="Confirm new password"
|
|
115
|
+
class="form-input"
|
|
133
116
|
required
|
|
134
|
-
:
|
|
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
|
-
|
|
137
|
+
class="btn btn-primary"
|
|
138
|
+
:disabled="isPasswordLoading"
|
|
140
139
|
>
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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<"
|
|
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,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
|
+
});
|
package/dist/runtime/plugin.js
CHANGED
|
@@ -1,55 +1,10 @@
|
|
|
1
|
-
import {
|
|
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 "
|
|
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 "
|
|
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 "
|
|
5
|
-
import { validatePassword, getPasswordValidationOptions } from "
|
|
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,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getCookie } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "#imports";
|
|
3
|
-
import { getCurrentUserFromToken } from "
|
|
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
|
-
|
|
22
|
-
return { user: userWithoutPassword };
|
|
21
|
+
return { user };
|
|
23
22
|
});
|
|
@@ -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
|
-
|
|
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,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>
|