Add forms for changing username, email and password
Signed-off-by: Aelita4 <kontakt@mikorosa.pl>
This commit is contained in:
parent
eafea0c1f5
commit
2945828935
|
@ -2,8 +2,21 @@
|
||||||
"Header": {
|
"Header": {
|
||||||
"user": "user {}"
|
"user": "user {}"
|
||||||
},
|
},
|
||||||
|
"Label": {
|
||||||
|
"userCreationDate": "Account created at {}",
|
||||||
|
"newUsernamePlaceholder": "new username",
|
||||||
|
"newEmailPlaceholder": "new email",
|
||||||
|
"newPasswordPlaceholder": "new password",
|
||||||
|
"newPasswordVerifyPlaceholder": "verify",
|
||||||
|
"passwordPlaceholder": "password",
|
||||||
|
"oldPasswordPlaceholder": "old password"
|
||||||
|
},
|
||||||
"Link": {
|
"Link": {
|
||||||
"logout": "Log out",
|
"build": "[build]",
|
||||||
"build": "[build]"
|
|
||||||
|
"changeUsername": "Change username",
|
||||||
|
"changeEmail": "Change email",
|
||||||
|
"changePassword": "Change password",
|
||||||
|
"logout": "[log out]"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
---
|
||||||
|
import type { ObjectId } from "mongodb";
|
||||||
|
import { Users } from "../../../../lib/db/mongodb";
|
||||||
|
import { getUserById, getUserByNickOrEmail } from "../../../../lib/db/users";
|
||||||
|
import validateAccessToken from "../../../../lib/utils/validateAccessToken";
|
||||||
|
import { hash, compare } from "bcrypt";
|
||||||
|
|
||||||
|
if(Astro.request.method === "PATCH") {
|
||||||
|
const response = await validateAccessToken(Astro.request);
|
||||||
|
if(response instanceof Response) return response;
|
||||||
|
|
||||||
|
const { path } = Astro.params;
|
||||||
|
|
||||||
|
const body = await Astro.request.json();
|
||||||
|
|
||||||
|
if(!body || !body['password']) return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 400,
|
||||||
|
message: "Bad Request"
|
||||||
|
}), { status: 400 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const user = await getUserById(response.user as ObjectId);
|
||||||
|
if(!user) return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 404,
|
||||||
|
message: "Not Found"
|
||||||
|
}), { status: 404 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if(!(await compare(body['password'], user.password))) return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 401,
|
||||||
|
message: "Unauthorized"
|
||||||
|
}), { status: 401 }
|
||||||
|
);
|
||||||
|
|
||||||
|
switch(path) {
|
||||||
|
case 'username':
|
||||||
|
if(!body['newUsername']) return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 400,
|
||||||
|
message: "Bad Request"
|
||||||
|
}), { status: 400 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if(await getUserByNickOrEmail(body['newUsername'])) return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 409,
|
||||||
|
message: "Conflict"
|
||||||
|
}), { status: 409 }
|
||||||
|
);
|
||||||
|
|
||||||
|
(await Users()).updateOne({ _id: user._id }, { $set: { username: body['newUsername'] } })
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 200,
|
||||||
|
message: "OK"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
case 'email':
|
||||||
|
if(!body['newEmail']) return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 400,
|
||||||
|
message: "Bad Request"
|
||||||
|
}), { status: 400 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if(await getUserByNickOrEmail(body['newEmail'])) return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 409,
|
||||||
|
message: "Conflict"
|
||||||
|
}), { status: 409 }
|
||||||
|
);
|
||||||
|
|
||||||
|
(await Users()).updateOne({ _id: user._id }, { $set: { email: body['newEmail'] } })
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 200,
|
||||||
|
message: "OK"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
case 'password':
|
||||||
|
if(!body['newPassword']) return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 400,
|
||||||
|
message: "Bad Request"
|
||||||
|
}), { status: 400 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const newPassword = await hash(body['newPassword'], 10);
|
||||||
|
|
||||||
|
(await Users()).updateOne({ _id: user._id }, { $set: { password: newPassword } })
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 200,
|
||||||
|
message: "OK"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 400,
|
||||||
|
message: "Bad Request"
|
||||||
|
}), { status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 405,
|
||||||
|
message: "Method Not Allowed"
|
||||||
|
}), { status: 405 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
---
|
|
@ -0,0 +1,230 @@
|
||||||
|
---
|
||||||
|
import Layout from '../../layouts/Layout.astro';
|
||||||
|
import NavBar from '../../components/NavBar.astro';
|
||||||
|
import { getUserByNickOrEmail, getUserResources, updateUserResources } from '../../lib/db/users';
|
||||||
|
import { getHighestWeightedLanguage, getLocales } from '../../lib/lang/langDriver';
|
||||||
|
import ResourceBar from '../../components/ResourceBar.astro';
|
||||||
|
import format from '../../lib/utils/format';
|
||||||
|
|
||||||
|
const loggedToken = Astro.cookies.get('sessionToken')?.value ?? null;
|
||||||
|
const username = Astro.cookies.get('username')?.value ?? "";
|
||||||
|
|
||||||
|
if(loggedToken === null || username === "") return Astro.redirect('/');
|
||||||
|
|
||||||
|
const locale = getHighestWeightedLanguage(Astro.request.headers.get('accept-language'));
|
||||||
|
|
||||||
|
const user = await getUserByNickOrEmail(username);
|
||||||
|
|
||||||
|
const langGame = await getLocales(locale, 'game');
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="Profile">
|
||||||
|
<NavBar loggedIn="true" active="profile" />
|
||||||
|
<ResourceBar loggedIn="true" />
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
<h3>{format(langGame['Label_userCreationDate'], user?.createdAt.toISOString().slice(0, 19).replace(/-/g, "/").replace("T", " ").toString() ?? "")}</h3>
|
||||||
|
<a href="/logout" class="a-button">{langGame['Link_logout']}</a>
|
||||||
|
</div>
|
||||||
|
<form id="changeUsernameForm" class="data-form">
|
||||||
|
<input class="data-form-input" type="text" name="username" placeholder={langGame['Label_newUsernamePlaceholder']} />
|
||||||
|
<input class="data-form-input" type="password" name="password" placeholder={langGame['Label_passwordPlaceholder']} />
|
||||||
|
<input class="data-form-button" type="button" value={langGame['Link_changeUsername']} />
|
||||||
|
</form>
|
||||||
|
<form id="changeEmailForm" class="data-form">
|
||||||
|
<input class="data-form-input" type="email" name="email" placeholder={langGame['Label_newEmailPlaceholder']} />
|
||||||
|
<input class="data-form-input" type="password" name="password" placeholder={langGame['Label_passwordPlaceholder']} />
|
||||||
|
<input class="data-form-button" type="button" value={langGame['Link_changeEmail']} />
|
||||||
|
</form>
|
||||||
|
<form id="changePasswordForm" class="data-form">
|
||||||
|
<input class="data-form-input" type="password" name="oldPassword" placeholder={langGame['Label_oldPasswordPlaceholder']} />
|
||||||
|
<input class="data-form-input" type="password" name="password1" placeholder={langGame['Label_newPasswordPlaceholder']} />
|
||||||
|
<input class="data-form-input" type="password" name="password2" placeholder={langGame['Label_newPasswordVerifyPlaceholder']} />
|
||||||
|
<input class="data-form-button" type="button" value={langGame['Link_changePassword']} />
|
||||||
|
</form>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-form-input {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-form-button {
|
||||||
|
color: red;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
width: 800px;
|
||||||
|
max-width: calc(100% - 2rem);
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.astro-a {
|
||||||
|
position: absolute;
|
||||||
|
top: -32px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translatex(-50%);
|
||||||
|
width: 220px;
|
||||||
|
height: auto;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gradient {
|
||||||
|
background-image: var(--accent-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-size: 400%;
|
||||||
|
background-position: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border: 1px solid rgba(var(--accent-light), 25%);
|
||||||
|
background: linear-gradient(rgba(var(--accent-dark), 66%), rgba(var(--accent-dark), 33%));
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions code {
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
background: rgba(var(--accent-light), 12%);
|
||||||
|
color: rgb(var(--accent-light));
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.3em 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions strong {
|
||||||
|
color: rgb(var(--accent-light));
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-card-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-button {
|
||||||
|
text-decoration: none;
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-button:hover {
|
||||||
|
color: lime;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
const changeUsernameForm = document.getElementById('changeUsernameForm');
|
||||||
|
if(changeUsernameForm === null) console.error("changeUsernameForm is null");
|
||||||
|
else {
|
||||||
|
changeUsernameForm.querySelector('input[type="button"]')?.addEventListener("click", async () => {
|
||||||
|
const newUsername = (changeUsernameForm.querySelector('input[name="username"]') as HTMLInputElement).value;
|
||||||
|
const password = (changeUsernameForm.querySelector('input[name="password"]') as HTMLInputElement).value;
|
||||||
|
|
||||||
|
const request = await fetch('/api/auth/changeUserData/username', {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
newUsername,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request.json();
|
||||||
|
console.log(response);
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeEmailForm = document.getElementById('changeEmailForm');
|
||||||
|
if(changeEmailForm === null) console.error("changeEmailForm is null");
|
||||||
|
else {
|
||||||
|
changeEmailForm.querySelector('input[type="button"]')?.addEventListener("click", async () => {
|
||||||
|
const newEmail = (changeEmailForm.querySelector('input[name="email"]') as HTMLInputElement).value;
|
||||||
|
const password = (changeEmailForm.querySelector('input[name="password"]') as HTMLInputElement).value;
|
||||||
|
|
||||||
|
const request = await fetch('/api/auth/changeUserData/email', {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
newEmail,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request.json();
|
||||||
|
console.log(response);
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const changePasswordForm = document.getElementById('changePasswordForm');
|
||||||
|
if(changePasswordForm === null) console.error("changePasswordForm is null");
|
||||||
|
else {
|
||||||
|
changePasswordForm.querySelector('input[type="button"]')?.addEventListener("click", async () => {
|
||||||
|
const oldPassword = (changePasswordForm.querySelector('input[name="oldPassword"]') as HTMLInputElement).value;
|
||||||
|
const password1 = (changePasswordForm.querySelector('input[name="password1"]') as HTMLInputElement).value;
|
||||||
|
const password2 = (changePasswordForm.querySelector('input[name="password2"]') as HTMLInputElement).value;
|
||||||
|
|
||||||
|
if(password1 !== password2) return alert("Passwords don't match");
|
||||||
|
else {
|
||||||
|
const request = await fetch('/api/auth/changeUserData/password', {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
password: oldPassword,
|
||||||
|
newPassword: password1
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request.json();
|
||||||
|
console.log(response);
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
Loading…
Reference in New Issue