Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .env.example

This file was deleted.

35 changes: 18 additions & 17 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,37 @@ require("dotenv").config();
const express = require("express");
const morgan = require("morgan");
const path = require("path");
const jwt = require("jsonwebtoken");
const cookieParser = require("cookie-parser");
const app = express();
const apiRouter = require("./api");
const { router: authRouter } = require("./auth");
const spotifyRouter = require("./auth/spotify");
const { db } = require("./database");
const cors = require("cors");

const PORT = process.env.PORT || 8080;
const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:3000";

// body parser middleware
app.use(express.json());

app.use(
cors({
origin: FRONTEND_URL,
credentials: true,
})
);
const corsOptions = {
origin: [
"http://localhost:3000",
"http://127.0.0.1:3000",
"https://capstone-2-frontend-one.vercel.app",
],
credentials: true,
methods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "Cookie"],
};

// cookie parser middleware
app.use(cors(corsOptions));
app.use(cookieParser());
app.use(morgan("dev"));
app.use(express.static(path.join(__dirname, "public")));
app.use("/api", apiRouter);
app.use("/auth", authRouter);
app.use("/auth/spotify", spotifyRouter);

app.use(morgan("dev")); // logging middleware
app.use(express.static(path.join(__dirname, "public"))); // serve static files from public folder
app.use("/api", apiRouter); // mount api router
app.use("/auth", authRouter); // mount auth router

// error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.sendStatus(500);
Expand All @@ -51,4 +52,4 @@ const runApp = async () => {

runApp();

module.exports = app;
module.exports = app;
99 changes: 40 additions & 59 deletions auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,25 @@ const jwt = require("jsonwebtoken");
const { User } = require("../database");

const router = express.Router();

const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";

// Middleware to authenticate JWT tokens
const cookieSettings = {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
maxAge: 24 * 60 * 60 * 1000,
path: "/",
};

const authenticateJWT = (req, res, next) => {
const token = req.cookies.token;
let token = req.cookies.token;

if (!token && req.headers.authorization) {
const authHeader = req.headers.authorization;
if (authHeader.startsWith('Bearer ')) {
token = authHeader.substring(7);
}
}

if (!token) {
return res.status(401).send({ error: "Access token required" });
Expand All @@ -23,7 +36,6 @@ const authenticateJWT = (req, res, next) => {
});
};

// Auth0 authentication route
router.post("/auth0", async (req, res) => {
try {
const { auth0Id, email, username } = req.body;
Expand All @@ -32,30 +44,24 @@ router.post("/auth0", async (req, res) => {
return res.status(400).send({ error: "Auth0 ID is required" });
}

// Try to find existing user by auth0Id first
let user = await User.findOne({ where: { auth0Id } });

if (!user && email) {
// If no user found by auth0Id, try to find by email
user = await User.findOne({ where: { email } });

if (user) {
// Update existing user with auth0Id
user.auth0Id = auth0Id;
await user.save();
}
}

if (!user) {
// Create new user if not found
const userData = {
auth0Id,
email: email || null,
username: username || email?.split("@")[0] || `user_${Date.now()}`, // Use email prefix as username if no username provided
passwordHash: null, // Auth0 users don't have passwords
username: username || email?.split("@")[0] || `user_${Date.now()}`,
passwordHash: null,
};

// Ensure username is unique
let finalUsername = userData.username;
let counter = 1;
while (await User.findOne({ where: { username: finalUsername } })) {
Expand All @@ -67,7 +73,6 @@ router.post("/auth0", async (req, res) => {
user = await User.create(userData);
}

// Generate JWT token with auth0Id included
const token = jwt.sign(
{
id: user.id,
Expand All @@ -79,15 +84,11 @@ router.post("/auth0", async (req, res) => {
{ expiresIn: "24h" }
);

res.cookie("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 24 * 60 * 60 * 1000, // 24 hours
});
res.cookie("token", token, cookieSettings);

res.send({
message: "Auth0 authentication successful",
token: token,
user: {
id: user.id,
username: user.username,
Expand All @@ -96,39 +97,30 @@ router.post("/auth0", async (req, res) => {
},
});
} catch (error) {
console.error("Auth0 authentication error:", error);
res.sendStatus(500);
res.status(500).send({ error: "Internal server error" });
}
});

// Signup route
router.post("/signup", async (req, res) => {
try {
const { username, password } = req.body;

if (!username || !password) {
return res
.status(400)
.send({ error: "Username and password are required" });
return res.status(400).send({ error: "Username and password are required" });
}

if (password.length < 6) {
return res
.status(400)
.send({ error: "Password must be at least 6 characters long" });
return res.status(400).send({ error: "Password must be at least 6 characters long" });
}

// Check if user already exists
const existingUser = await User.findOne({ where: { username } });
if (existingUser) {
return res.status(409).send({ error: "Username already exists" });
}

// Create new user
const passwordHash = User.hashPassword(password);
const user = await User.create({ username, passwordHash });

// Generate JWT token
const token = jwt.sign(
{
id: user.id,
Expand All @@ -140,46 +132,35 @@ router.post("/signup", async (req, res) => {
{ expiresIn: "24h" }
);

res.cookie("token", token, {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 24 * 60 * 60 * 1000, // 24 hours
});
res.cookie("token", token, cookieSettings);

res.send({
message: "User created successfully",
token: token,
user: { id: user.id, username: user.username },
});
} catch (error) {
console.error("Signup error:", error);
res.sendStatus(500);
res.status(500).send({ error: "Internal server error" });
}
});

// Login route
router.post("/login", async (req, res) => {
try {
const { username, password } = req.body;

if (!username || !password) {
res.status(400).send({ error: "Username and password are required" });
return;
return res.status(400).send({ error: "Username and password are required" });
}

// Find user
const user = await User.findOne({ where: { username } });
user.checkPassword(password);
if (!user) {
return res.status(401).send({ error: "Invalid credentials" });
}

// Check password
if (!user.checkPassword(password)) {
return res.status(401).send({ error: "Invalid credentials" });
}

// Generate JWT token
const token = jwt.sign(
{
id: user.id,
Expand All @@ -191,35 +172,35 @@ router.post("/login", async (req, res) => {
{ expiresIn: "24h" }
);

res.cookie("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 24 * 60 * 60 * 1000, // 24 hours
});
res.cookie("token", token, cookieSettings);

res.send({
message: "Login successful",
token: token,
user: { id: user.id, username: user.username },
});
} catch (error) {
console.error("Login error:", error);
res.sendStatus(500);
res.status(500).send({ error: "Internal server error" });
}
});

// Logout route
router.post("/logout", (req, res) => {
res.clearCookie("token");
res.send({ message: "Logout successful" });
});

// Get current user route (protected)
router.get("/me", (req, res) => {
const token = req.cookies.token;
let token = req.cookies.token;

if (!token && req.headers.authorization) {
const authHeader = req.headers.authorization;
if (authHeader.startsWith('Bearer ')) {
token = authHeader.substring(7);
}
}

if (!token) {
return res.send({});
return res.send({ user: null });
}

jwt.verify(token, JWT_SECRET, (err, user) => {
Expand All @@ -230,4 +211,4 @@ router.get("/me", (req, res) => {
});
});

module.exports = { router, authenticateJWT };
module.exports = { router, authenticateJWT };
Loading