mirror of
https://github.com/Alee14/bnbso-auth.git
synced 2025-01-22 10:41:57 -05:00
Initial commit
This commit is contained in:
commit
4811a3eabc
16 changed files with 641 additions and 0 deletions
175
.gitignore
vendored
Normal file
175
.gitignore
vendored
Normal file
|
@ -0,0 +1,175 @@
|
|||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
16
README.md
Normal file
16
README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# bnbso-auth
|
||||
Web authentication for bnbSO (registering accounts, resetting passwords, changing passwords) using Discord authentication. Programmed for FreeSO-based servers.
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.1.38. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
BIN
bun.lockb
Executable file
BIN
bun.lockb
Executable file
Binary file not shown.
140
index.js
Normal file
140
index.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
import express from "express";
|
||||
import session from "express-session";
|
||||
import multer from "multer";
|
||||
import passport from "passport";
|
||||
import { Strategy as DiscordStrategy } from "passport-discord";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from 'url';
|
||||
import dotenv from "dotenv";
|
||||
import axios from "axios";
|
||||
import FormData from "form-data";
|
||||
import fs from "fs";
|
||||
dotenv.config();
|
||||
|
||||
// Load error messages from JSON file
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const statusMessages = JSON.parse(fs.readFileSync(path.join(__dirname, 'status.json'), 'utf8'));
|
||||
|
||||
const upload = multer();
|
||||
const app = express();
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.json());
|
||||
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
|
||||
|
||||
// Passport session setup
|
||||
passport.serializeUser((user, done) => done(null, user));
|
||||
passport.deserializeUser((obj, done) => done(null, obj));
|
||||
|
||||
// Configure Passport Discord strategy
|
||||
passport.use(
|
||||
new DiscordStrategy(
|
||||
{
|
||||
clientID: process.env.CLIENT_ID,
|
||||
clientSecret: process.env.CLIENT_SECRET,
|
||||
callbackURL: process.env.REDIRECT_URI,
|
||||
scope: ["identify", "email", "guilds"],
|
||||
},
|
||||
(accessToken, refreshToken, profile, done) => {
|
||||
return done(null, profile);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Middleware
|
||||
app.use(
|
||||
session({
|
||||
secret: process.env.SESSION_SECRET,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
})
|
||||
);
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// Routes
|
||||
app.get("/", async (req, res) => {
|
||||
if (req.isAuthenticated()) {
|
||||
const { id, username, email, guilds } = req.user;
|
||||
const isInGuild = guilds.some((guild) => guild.id === process.env.GUILD_ID);
|
||||
|
||||
if (isInGuild) {
|
||||
let userExists = true;
|
||||
if (userExists) {
|
||||
return res.render('login');
|
||||
} else {
|
||||
return res.render('register', req.user);
|
||||
}
|
||||
} else {
|
||||
return res.render('error', { error: 'You must be a member of the bits & Bytes server to access this page.' });
|
||||
}
|
||||
} else {
|
||||
res.render('index');
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/register", upload.none(), async (req, res) => {
|
||||
if (req.isAuthenticated()) {
|
||||
const { id } = req.user;
|
||||
const { username, email, password, password2 } = req.body;
|
||||
|
||||
if (password !== password2) {
|
||||
return res.render('register', { ...req.user, error: "Passwords do not match" });
|
||||
}
|
||||
|
||||
try {
|
||||
const form = new FormData();
|
||||
form.append('username', username);
|
||||
form.append('email', email);
|
||||
form.append('password', password);
|
||||
|
||||
const response = await axios.post(`${process.env.API_URL}/userapi/registration`, form, {
|
||||
headers: form.getHeaders()
|
||||
});
|
||||
|
||||
if (response.data.error) {
|
||||
const errorKey = response.data.error_description || "default";
|
||||
const errorMessage = statusMessages.registration_errors[errorKey] || "Something went wrong";
|
||||
return res.render('register', { ...req.user, error: errorMessage });
|
||||
} else {
|
||||
console.log(`Discord ID: ${id}`)
|
||||
return res.render('success');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during registration:", error);
|
||||
return res.render('register', { ...req.user, error: "An error occurred during registration" });
|
||||
}
|
||||
} else {
|
||||
res.redirect("/");
|
||||
}
|
||||
});
|
||||
|
||||
app.get(
|
||||
"/auth/discord",
|
||||
passport.authenticate("discord", { scope: ["identify", "email", "guilds"] })
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/callback",
|
||||
passport.authenticate("discord", { failureRedirect: "/" }),
|
||||
(req, res) => {
|
||||
res.redirect("/");
|
||||
}
|
||||
);
|
||||
|
||||
app.get("/logout", (req, res) => {
|
||||
req.logout((err) => {
|
||||
if (err) return next(err);
|
||||
res.redirect("/");
|
||||
});
|
||||
});
|
||||
|
||||
const port = 3000;
|
||||
app.listen(PORT, () => console.log(`Server running on http://localhost:${port}`));
|
24
package.json
Normal file
24
package.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "bnbso-auth",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"tailwindcss": "^3.4.17"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"dotenv": "^16.4.7",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.2",
|
||||
"express-session": "^1.18.1",
|
||||
"form-data": "^4.0.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"passport": "^0.7.0",
|
||||
"passport-discord": "^0.1.4",
|
||||
"sqlite3": "^5.1.7"
|
||||
}
|
||||
}
|
112
public/css/style.css
Normal file
112
public/css/style.css
Normal file
|
@ -0,0 +1,112 @@
|
|||
@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;
|
||||
font-family: "Open Sans", sans-serif;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #77a6ff;
|
||||
transition: 0.2s;;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
color: #a5c4ff;
|
||||
}
|
||||
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
color: #2773ff;
|
||||
}
|
||||
|
||||
.discord {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
background-color: #5865F2;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
transition: 0.2s;
|
||||
margin-top: 10px;
|
||||
|
||||
}
|
||||
|
||||
.discord:hover {
|
||||
background-color: #727dee;
|
||||
}
|
||||
|
||||
.discord:active {
|
||||
background-color: #4e5bd1;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 3em;
|
||||
text-align: center;
|
||||
background-color: #313131;
|
||||
color: #ffffff;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('/img/background.webp');
|
||||
background-size: cover;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.background::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
form label,
|
||||
form input,
|
||||
form button {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=email],
|
||||
input[type=password] {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #00633a;
|
||||
color: white;
|
||||
padding: 14px 20px;
|
||||
margin: 8px 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: rgb(248, 140, 140);
|
||||
font-size: 1.5em;
|
||||
}
|
BIN
public/img/background.webp
Normal file
BIN
public/img/background.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 643 KiB |
BIN
public/img/logo.png
Normal file
BIN
public/img/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
29
status.json
Normal file
29
status.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"success_responses": {
|
||||
"success": "Operation was successful.",
|
||||
"email_failed": "Email sending failed."
|
||||
}
|
||||
}
|
||||
|
9
tailwind.config.js
Normal file
9
tailwind.config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./views/**/*.{html,js,ejs}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
17
views/error.ejs
Normal file
17
views/error.ejs
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<title>bnbSO</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="background"></div>
|
||||
<div class="container">
|
||||
<img src="img/logo.png" alt="logo" width="200">
|
||||
<h1>Oh no! Something went wrong!</h1>
|
||||
<p><%= error %></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
17
views/index.ejs
Normal file
17
views/index.ejs
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<title>bnbSO</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="background"></div>
|
||||
<div class="container">
|
||||
<img src="img/logo.png" alt="logo" width="200">
|
||||
<p>You must be a member of the bits & Bytes Discord server to join bnbSO.</p>
|
||||
<a class="discord" href="/auth/discord">Login with Discord</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
26
views/login.ejs
Normal file
26
views/login.ejs
Normal file
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<title>bnbSO Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="background"></div>
|
||||
<div class="container">
|
||||
<img src="img/logo.png" alt="logo" width="200">
|
||||
<h1>Login</h1>
|
||||
<form method="post" action="/login">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password">
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<a href="#">Forgot Password</a>
|
||||
<% if (typeof error !== 'undefined') { %>
|
||||
<div class="error"><%= error %></div>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
31
views/register.ejs
Normal file
31
views/register.ejs
Normal file
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<title>bnbSO Register</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="background"></div>
|
||||
<div class="container">
|
||||
<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 this <b>once</b>.</p>
|
||||
<form method="post" action="/register">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" value="<%= username %>">
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email" value="<%= email %>">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password">
|
||||
<label for="password2">Confirm Password:</label>
|
||||
<input type="password" id="password2" name="password2">
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
<% if (typeof error !== 'undefined') { %>
|
||||
<div class="error"><%= error %></div>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
18
views/success.ejs
Normal file
18
views/success.ejs
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<title>bnbSO</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="background"></div>
|
||||
<div class="container">
|
||||
<img src="img/logo.png" alt="logo" width="200">
|
||||
<p>Created account successfully!</p>
|
||||
<p>Check on your inbox for the confirmation code.</p>
|
||||
<a href="/">Home</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue