aboutsummaryrefslogtreecommitdiff
path: root/bot/src/api
diff options
context:
space:
mode:
authorAndrew Lee <andrew@alee14.me>2025-03-24 15:42:10 -0400
committerAndrew Lee <andrew@alee14.me>2025-03-24 15:42:10 -0400
commitad768e2b25b58d62a44aa2daeb1429a651d488e5 (patch)
treecadfaee0b8998c4d0d13a2a03bf18cc55e495264 /bot/src/api
parent0453bafa63ccd1057279a1be9286b3e7ebcb62d2 (diff)
downloadAleeBot-ad768e2b25b58d62a44aa2daeb1429a651d488e5.tar.gz
AleeBot-ad768e2b25b58d62a44aa2daeb1429a651d488e5.tar.bz2
AleeBot-ad768e2b25b58d62a44aa2daeb1429a651d488e5.zip
Added JWT on API; Added back settings on Discord
Diffstat (limited to 'bot/src/api')
-rw-r--r--bot/src/api/routes/auth.js73
-rw-r--r--bot/src/api/routes/quotes.js9
-rw-r--r--bot/src/api/routes/settings.js9
-rw-r--r--bot/src/api/server.js9
4 files changed, 89 insertions, 11 deletions
diff --git a/bot/src/api/routes/auth.js b/bot/src/api/routes/auth.js
new file mode 100644
index 0000000..224a2d1
--- /dev/null
+++ b/bot/src/api/routes/auth.js
@@ -0,0 +1,73 @@
+import { Router } from 'express';
+import jwt from 'jsonwebtoken';
+import bcrypt from 'bcrypt';
+import dotenv from 'dotenv';
+
+dotenv.config();
+
+// Check if required environment variables are set
+const requiredEnvVars = ['JWT_SECRET', 'AUTH_USERNAME', 'AUTH_PASSWORD_HASH'];
+const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
+if (missingVars.length > 0) {
+ console.error(`Missing required environment variables: ${missingVars.join(', ')}`);
+ console.error('For AUTH_PASSWORD_HASH, run bcrypt with the round of 10');
+}
+
+export function authRouter() {
+ const router = Router();
+
+ // Login endpoint
+ router.post('/login', async (req, res) => {
+ try {
+ const { username, password } = req.body;
+
+ if (!username || !password) {
+ return res.status(400).json({ error: 'Username and password are required' });
+ }
+
+ // Check against environment variables
+ if (username !== process.env.API_USERNAME) {
+ return res.status(401).json({ error: 'Invalid credentials' });
+ }
+
+ // Verify password
+ const isPasswordValid = await bcrypt.compare(password, process.env.API_PASSWORD_HASH);
+ if (!isPasswordValid) {
+ return res.status(401).json({ error: 'Invalid credentials' });
+ }
+
+ // Generate JWT token
+ const token = jwt.sign(
+ { username: username },
+ process.env.JWT_SECRET,
+ { expiresIn: '12h' }
+ );
+
+ res.json({ token });
+ } catch (error) {
+ console.error('Login error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+ });
+
+ return router;
+}
+
+// Middleware to verify JWT token
+export function verifyToken(req, res, next) {
+ const authHeader = req.headers.authorization;
+
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
+ return res.status(401).json({ error: 'No token provided' });
+ }
+
+ const token = authHeader.split(' ')[1];
+
+ try {
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
+ req.user = decoded;
+ next();
+ } catch {
+ return res.status(403).json({ error: 'Invalid or expired token' });
+ }
+}
diff --git a/bot/src/api/routes/quotes.js b/bot/src/api/routes/quotes.js
index d39bb28..7f9f255 100644
--- a/bot/src/api/routes/quotes.js
+++ b/bot/src/api/routes/quotes.js
@@ -1,9 +1,10 @@
import { Router } from 'express';
import { pendingQuote, quote as newQuote } from '../../models/quote.js';
+import { verifyToken } from './auth.js';
export const quoteRouter = Router();
-quoteRouter.get('/quotes/pending', async (req, res) => {
+quoteRouter.get('/quotes/pending', verifyToken, async (req, res) => {
try {
const quotes = await pendingQuote.findAll();
res.json(quotes);
@@ -13,7 +14,7 @@ quoteRouter.get('/quotes/pending', async (req, res) => {
}
});
-quoteRouter.post('/quotes/add', async (req, res) => {
+quoteRouter.post('/quotes/add', verifyToken, async (req, res) => {
const { author, authorImage, quote, year, submitterID } = req.body;
try {
await newQuote.create({
@@ -30,7 +31,7 @@ quoteRouter.post('/quotes/add', async (req, res) => {
}
});
-quoteRouter.post('/quotes/approve', async (req, res) => {
+quoteRouter.post('/quotes/approve', verifyToken, async (req, res) => {
const { id } = req.body;
try {
const quote = await pendingQuote.findByPk(id);
@@ -53,7 +54,7 @@ quoteRouter.post('/quotes/approve', async (req, res) => {
}
});
-quoteRouter.post('/quotes/reject', async (req, res) => {
+quoteRouter.post('/quotes/reject', verifyToken, async (req, res) => {
const { id } = req.body;
try {
const quote = await pendingQuote.findByPk(id);
diff --git a/bot/src/api/routes/settings.js b/bot/src/api/routes/settings.js
index ce28acd..bdef633 100644
--- a/bot/src/api/routes/settings.js
+++ b/bot/src/api/routes/settings.js
@@ -1,11 +1,12 @@
import { ChannelType } from 'discord.js';
import { Router } from 'express';
import { guildSettings } from '../../models/guild-settings.js';
+import { verifyToken } from './auth.js';
export function settingsRouter(client) {
const router = Router();
- router.get('/settings/guild/:id', async (req, res) => {
+ router.get('/settings/guild/:id', verifyToken, async (req, res) => {
try {
const settings = await guildSettings.findOne({ where: { guildID: req.params.id } });
@@ -19,7 +20,6 @@ export function settingsRouter(client) {
const channelInfo = {
name: channel.name,
id: channel.id,
- position: channel.position,
category: channel.parent ? channel.parent.name : 'No Category'
};
@@ -42,9 +42,10 @@ export function settingsRouter(client) {
}
});
- router.post('/settings/guild', async (req, res) => {
+ router.post('/settings/guild/:id', verifyToken, async (req, res) => {
try {
- const { guildID, ...newSettings } = req.body;
+ const guildID = req.params.id;
+ const { ...newSettings } = req.body;
const [updated] = await guildSettings.update(newSettings, { where: { guildID: guildID } });
if (updated) {
const updatedSettings = await guildSettings.findOne({ where: { guildID: guildID } });
diff --git a/bot/src/api/server.js b/bot/src/api/server.js
index 15211eb..9ad2026 100644
--- a/bot/src/api/server.js
+++ b/bot/src/api/server.js
@@ -6,6 +6,7 @@ import { readFileSync } from 'node:fs';
import { quoteRouter } from './routes/quotes.js';
import { settingsRouter } from './routes/settings.js';
+import { authRouter, verifyToken } from './routes/auth.js';
const app = express();
@@ -15,11 +16,13 @@ export const apiServer = (client) => {
app.use('/api', quoteRouter);
app.use('/api', settingsRouter(client));
+ app.use('/api', authRouter());
app.get('/api/version', (req, res) => {
const { version } = JSON.parse(readFileSync('./package.json', 'utf-8'));
res.json({
- version: version
+ api_version: '1.0',
+ ab_version: version
});
});
@@ -30,7 +33,7 @@ export const apiServer = (client) => {
});
});
- app.get('/api/servers', (req, res) => {
+ app.get('/api/servers', verifyToken, (req, res) => {
const guildsInfo = [];
if (client.guilds.cache.size === 0) {
@@ -52,7 +55,7 @@ export const apiServer = (client) => {
});
- app.post('/api/leave', (req, res) => {
+ app.post('/api/leave', verifyToken, (req, res) => {
const { id } = req.body;
let guild = client.guilds.cache.get(id);