Ability to change password; Sim time on dashboard

This commit is contained in:
Andrew Lee 2024-12-28 13:09:27 -05:00
parent 39cbc8c286
commit b2748a5c55
Signed by: andrew
SSH key fingerprint: SHA256:bbGg1DYG5CuKl2jo1DqzvUsaTeyvhM3tjCsej5lYMg4
12 changed files with 160 additions and 58 deletions

4
.gitignore vendored
View file

@ -174,4 +174,6 @@ dist
# Finder (MacOS) folder config
.DS_Store
*.db
*.db
.idea/

View file

@ -1,5 +1,5 @@
# bnbso-auth
Web authentication for bnbSO (registering accounts, resetting passwords, changing passwords) using Discord authentication. Designed for FreeSO-based servers.
Web authentication for bnbSO (registering accounts, changing passwords) using Discord authentication. Designed for FreeSO-based servers.
To install dependencies:
@ -10,7 +10,7 @@ bun install
To run:
```bash
bun run index.ts
bun run index.js
```
This project was created using `bun init` in bun v1.1.38. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

View file

@ -86,7 +86,7 @@ app.get("/", async (req, res) => {
}
});
} else {
return res.render('error', { error: 'You must be a member of the bits & Bytes server to access this page.' });
return res.render('error', { error: 'You must be a member of that server to access this page.' });
}
} else {
res.render('index');
@ -105,7 +105,7 @@ app.post("/register", upload.none(), async (req, res) => {
try {
const form = new FormData();
form.append('username', username);
form.append('email', id + '@discord.com');
form.append('email', `${id}@discord.com`);
form.append('password', password);
form.append('key', process.env.REG_KEY);
@ -123,7 +123,7 @@ app.post("/register", upload.none(), async (req, res) => {
console.error("Error inserting user data into database:", err);
return res.render('register', { ...req.user, error: "An error occurred during registration, contact server operator." });
}
return res.render('success');
return res.render('success', { ...req.user, success: "Created account successfully!"});
});
}
} catch (error) {
@ -131,7 +131,62 @@ app.post("/register", upload.none(), async (req, res) => {
return res.render('register', { ...req.user, error: "An error occurred during registration, contact server operator." });
}
} else {
res.redirect("/");
res.status(401).send("Unauthorized.");
}
});
app.get('/password', (req, res) => {
if (req.isAuthenticated()) {
res.render('password');
} else {
res.redirect("/auth/discord");
}
});
app.post('/password/change', upload.none(), async (req, res) => {
if (req.isAuthenticated()) {
const { id } = req.user;
const { currentpassword, newpassword, newpassword2 } = req.body;
if (newpassword !== newpassword2) {
return res.render('password', { ...req.user, error: "Passwords do not match" });
}
try {
db.get(`SELECT * FROM users WHERE discord_id = ?`, [id], async (err, row) => {
if (err) {
console.error("Error querying the database:", err);
return res.render('password', {...req.user, error: "An error occurred while checking user data."});
}
if (row) {
const form = new FormData();
form.append('username', row.fso_username);
form.append('old_password', currentpassword);
form.append('new_password', newpassword);
const response = await axios.post(`${process.env.API_URL}/userapi/password`, form, {
headers: form.getHeaders()
});
if (response.data.error) {
const errorKey = response.data.error_description || "default";
const errorMessage = statusMessages.password_reset_errors[errorKey] || "Something went wrong";
return res.render('password', { ...req.user, error: errorMessage });
}
return res.render('success', { ...req.user, success: "Password changed successfully!" });
}
});
} catch (error) {
console.error("Error during password change:", error);
return res.render('password', { ...req.user, error: "An error occurred during password change, contact server operator." });
}
} else {
res.status(401).send("Unauthorized.");
}
});

View file

@ -1,7 +1,10 @@
{
"name": "bnbso-auth",
"module": "index.ts",
"module": "index.js",
"type": "module",
"scripts": {
"start": "node index.js"
},
"devDependencies": {
"@types/bun": "latest"
},
@ -20,4 +23,4 @@
"passport-discord": "^0.1.4",
"sqlite3": "^5.1.7"
}
}
}

View file

@ -1,10 +1,10 @@
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap');
html, body {
height: 100%;
margin: 0;
padding: 0;
}
}
body {
display: flex;
@ -34,7 +34,7 @@ a:active {
display: inline-block;
text-decoration: none;
color: #fff;
background-color: #00633a;
background-color: #535353;
padding: 10px 20px;
border-radius: 5px;
transition: 0.2s;
@ -43,12 +43,12 @@ a:active {
}
.button:hover {
background-color: #009959;
background-color: #8a8a8a;
color: #fff;
}
.button:active {
background-color: #003620;
background-color: #454545;
color: #fff;
}
@ -139,4 +139,9 @@ button {
.error {
color: #f88c8c;
font-size: 1.5em;
}
}
.success {
color: #a3f88c;
font-size: 1.5em;
}

View file

@ -1,29 +1,28 @@
{
"registration_errors": {
"missing_confirmation_token": "Registration failed: Missing confirmation token.",
"user_short": "Registration failed: Username is too short.",
"user_long": "Registration failed: Username is too long.",
"user_invalid": "Registration failed: Invalid username.",
"pass_required": "Registration failed: Password is required.",
"email_invalid": "Registration failed: Invalid email address.",
"ip_banned": "Registration failed: IP is banned.",
"registrations_too_frequent": "Registration failed: Too many registrations from this IP address.",
"user_exists": "Registration failed: User already exists.",
"smtp_disabled": "Registration failed: SMTP service is disabled.",
"email_taken": "Registration failed: Email address is already taken.",
"confirmation_pending": "Registration failed: Confirmation pending.",
"key_wrong": "Registration failed: Invalid registration key."
"missing_confirmation_token": "Missing confirmation token.",
"user_short": "Username is too short.",
"user_long": "Username is too long.",
"user_invalid": "Invalid username.",
"pass_required": "Password is required.",
"email_invalid": "Invalid email address.",
"ip_banned": "IP is banned.",
"registrations_too_frequent": "Too many registrations from this IP address.",
"user_exists": "User already exists.",
"smtp_disabled": "SMTP service is disabled.",
"email_taken": "Email address is already taken.",
"confirmation_pending": "Confirmation pending.",
"key_wrong": "Invalid registration key."
},
"password_reset_errors": {
"missing_fields": "Password reset failed: Missing required fields.",
"email_invalid": "Password reset failed: Invalid email address.",
"user_invalid": "Password reset failed: User does not exist.",
"incorrect_password": "Password reset failed: Incorrect password.",
"invalid_token": "Password reset failed: Invalid confirmation token."
"missing_fields": "Missing required fields.",
"email_invalid": "Invalid email address.",
"user_invalid": "User does not exist.",
"incorrect_password": "Incorrect password.",
"invalid_token": "Invalid confirmation token."
},
"success_responses": {
"success": "Operation was successful.",
"email_failed": "Email sending failed."
}
}

View file

@ -9,12 +9,44 @@
<body>
<div class="background"></div>
<div class="container">
<img src="img/logo.png" alt="logo" width="200">
<img src="/img/logo.png" alt="logo" width="200">
<h1>Welcome, <%= username %>!</h1>
<h2>FreeSO Username: <%= fso_username %></h2>
<a href="#" class="button">Change Password</a>
<a href="#" class="button">Download bnbSO Client</a>
<h2>bnbSO Username: <%= fso_username %></h2>
<h2 id="simtime"></h2>
<a href="/password" class="button">Change Password</a>
<a href="https://fso-builds.riperiperi.workers.dev" class="button">Download bnbSO Client</a>
<a href="/logout" class="button logout">Logout</a>
</div>
<script>
function updateTSOClock() {
const currentTime = new Date(),
utcMinutes = currentTime.getUTCMinutes(),
utcSeconds = currentTime.getUTCSeconds();
let timePeriod = 'AM', totalSeconds = 0;
if (currentTime.getUTCHours() % 2 === 1) {
totalSeconds = 3600;
timePeriod = 'PM';
}
totalSeconds += utcMinutes * 60 + utcSeconds;
let hour = Math.floor(totalSeconds / 300);
if (hour > 12) {
hour -= 12;
}
if (hour === 0) {
hour = 12;
}
let minute = Math.floor(totalSeconds % 300 / 5);
if (minute < 10) {
minute = '0' + minute;
}
const simTimeElement = document.querySelector('#simtime');
if (simTimeElement) {
simTimeElement.textContent = `${hour}:${minute} ${timePeriod}`;
}
}
setInterval(updateTSOClock, 1000);
updateTSOClock();
</script>
</body>
</html>
</html>

View file

@ -9,9 +9,9 @@
<body>
<div class="background"></div>
<div class="container">
<img src="img/logo.png" alt="logo" width="200">
<img src="/img/logo.png" alt="logo" width="200">
<h1>Oh no! Something went wrong!</h1>
<p><%= error %></p>
</div>
</body>
</html>
</html>

View file

@ -9,10 +9,10 @@
<body>
<div class="background"></div>
<div class="container">
<img src="img/logo.png" alt="logo" width="200">
<img src="/img/logo.png" alt="logo" width="200">
<p>Log into your Discord account to get access to bnbSO.</p>
<p><i>You must be a bits & Bytes member.</i></p>
<a class="button discord" href="/auth/discord">Login with Discord</a>
<a class="button discord" href="/auth/discord">Login with Discord</a>
</div>
</body>
</html>
</html>

View file

@ -9,18 +9,22 @@
<body>
<div class="background"></div>
<div class="container">
<img src="img/logo.png" alt="logo" width="200">
<img src="/img/logo.png" alt="logo" width="200">
<h1>Change Password</h1>
<p>If you have issues changing your password, ask the server operator to change your password.</p>
<form method="post" action="/login">
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<label for="password">Current Password:</label>
<input type="password" id="password" name="password">
<button type="submit">Login</button>
<p>If you have issues changing your password, ask the server operator to reset your password.</p>
<form method="post" action="/password/change">
<label for="currentpassword">Current Password:</label>
<input type="password" id="currentpassword" name="currentpassword">
<label for="newpassword">New Password:</label>
<input type="password" id="newpassword" name="newpassword">
<label for="newpassword2">Confirm New Password:</label>
<input type="password" id="newpassword2" name="newpassword2">
<button type="submit">Change Password</button>
</form>
<a href="/" class="button">Dashboard</a>
<% if (typeof error !== 'undefined') { %>
<div class="error"><%= error %></div>
<% } %>
</div>
</body>
</html>
</html>

View file

@ -9,7 +9,7 @@
<body>
<div class="background"></div>
<div class="container">
<img src="img/logo.png" alt="logo" width="200">
<img src="/img/logo.png" alt="logo" width="200">
<h1>Welcome to bnbSO!</h1>
<p>You will be sending the following information to register your bnbSO account</p>
<p>Please verify that the following information is correct. You can only change your username <b>once</b>.</p>
@ -25,5 +25,6 @@
<% if (typeof error !== 'undefined') { %>
<div class="error"><%= error %></div>
<% } %>
</div>
</body>
</html>
</html>

View file

@ -9,9 +9,10 @@
<body>
<div class="background"></div>
<div class="container">
<img src="img/logo.png" alt="logo" width="200">
<p>Created account successfully!</p>
<img src="/img/logo.png" alt="logo" width="200">
<h1>Success!</h1>
<p class="success"><%= success %></p>
<a href="/">Dashboard</a>
</div>
</body>
</html>
</html>