mirror of
https://github.com/bubblecup-12/VogelSocialMedia.git
synced 2025-07-10 21:28:51 +00:00
269 lines
8.4 KiB
TypeScript
269 lines
8.4 KiB
TypeScript
import express, { Request, Response } from "express";
|
|
import {
|
|
PrismaClient,
|
|
User,
|
|
RefreshToken,
|
|
} from "../../prisma/app/generated/prisma/client";
|
|
import { UserLoginDto, UserRegistrationDto } from "../schemas/userSchemas";
|
|
import jwt, { TokenExpiredError } from "jsonwebtoken";
|
|
import dotenv from "dotenv";
|
|
import bcrypt from "bcryptjs";
|
|
import { StatusCodes } from "http-status-codes";
|
|
import { RefreshTokenPayload } from "../types/tokens";
|
|
const app = express();
|
|
app.use(express.json());
|
|
const prisma = new PrismaClient();
|
|
// load environment variables from .env file
|
|
dotenv.config();
|
|
const JWT_SECRET: string = process.env.TOKEN_SECRET!; // this secret is used to sign the JWT token
|
|
const REFRESH_TOKEN_SECRET: string = process.env.REFRESH_TOKEN_SECRET!;
|
|
|
|
// Generate a JWT token with the username as payload and a secret from the environment variables which expires in 1800 seconds (30 minutes)
|
|
function generateAccessToken(
|
|
username: string,
|
|
userId: string,
|
|
role: string,
|
|
refreshTokenId: string
|
|
) {
|
|
return jwt.sign(
|
|
{ username: username, role: role, sub: userId, jti: refreshTokenId },
|
|
JWT_SECRET,
|
|
{
|
|
expiresIn: "1800s",
|
|
issuer: "VogelApi",
|
|
}
|
|
);
|
|
}
|
|
|
|
async function generateRefreshToken(
|
|
userId: string
|
|
): Promise<{ token: string; id: string }> {
|
|
const expiresAt = new Date(Date.now() + 100 * 60 * 60 * 1000); // 100 h
|
|
let refreshToken: RefreshToken;
|
|
{
|
|
refreshToken = await prisma.refreshToken.create({
|
|
data: {
|
|
expiresAt: expiresAt,
|
|
user: {
|
|
connect: {
|
|
id: userId,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
return {
|
|
token: jwt.sign({ jti: refreshToken.id }, REFRESH_TOKEN_SECRET, {
|
|
expiresIn: "100h",
|
|
}),
|
|
id: refreshToken.id,
|
|
};
|
|
}
|
|
|
|
// Endpoint to register a new user
|
|
export const registerUser = async (req: Request, res: Response) => {
|
|
const { username, password, email }: UserRegistrationDto = await req.body; //gets the data from the request body
|
|
|
|
const existingUser: User | null = await prisma.user.findUnique({
|
|
// check if the user already exists
|
|
where: {
|
|
username: username,
|
|
},
|
|
});
|
|
if (existingUser) {
|
|
// if the user already exists, return an error message
|
|
res.status(StatusCodes.BAD_REQUEST).json({
|
|
error: "Invalid data",
|
|
details: [{ message: `User "${username}" already exists` }],
|
|
});
|
|
return;
|
|
}
|
|
const existingEmailUser: User | null = await prisma.user.findUnique({
|
|
// check if the email is already in use
|
|
where: {
|
|
email: email,
|
|
},
|
|
});
|
|
if (existingEmailUser) {
|
|
// if the email is already in use, return an error message
|
|
res.status(StatusCodes.BAD_REQUEST).json({
|
|
error: "Invalid data",
|
|
details: [{ message: `User with "${email}" already exists` }],
|
|
});
|
|
return;
|
|
}
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 10); // hash the password with bcrypt
|
|
if (!hashedPassword) {
|
|
// check if the password was hashed successfully
|
|
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
error: "Server error",
|
|
details: [{ message: "Server Error" }],
|
|
});
|
|
return;
|
|
}
|
|
const userData = {
|
|
// create a new user object with the data from the request body and the hashed password
|
|
username: username,
|
|
email: email,
|
|
password: hashedPassword,
|
|
};
|
|
const user: User | null = await prisma.user.create({ data: userData }); // create a new user in the database
|
|
if (!user) {
|
|
// check if the user was created successfully
|
|
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
error: "Server error",
|
|
details: [{ message: "Server Error while creating user" }],
|
|
});
|
|
return;
|
|
}
|
|
const refreshToken = await generateRefreshToken(user.id);
|
|
res.set("Refresh-Token", refreshToken.token);
|
|
const token: string = generateAccessToken(
|
|
user.username,
|
|
user.id,
|
|
user.role,
|
|
refreshToken.id
|
|
); // generate a JWT token with the username and userId as payload
|
|
res.set("Authorization", `Bearer ${token}`); // set the token in the response header
|
|
res.status(StatusCodes.CREATED).json({
|
|
message: "user created",
|
|
data: { username: username, email: email },
|
|
}); // return the user object with the username and email
|
|
};
|
|
|
|
// Endpoint to login a user (unfinished)
|
|
export const loginUser = async (req: Request, res: Response) => {
|
|
const { username, password }: UserLoginDto = req.body; // get the data from the request body
|
|
|
|
const user: User | null = await prisma.user.findUnique({
|
|
// check if the user exists
|
|
where: {
|
|
username: username,
|
|
},
|
|
});
|
|
if (!user) {
|
|
// if the user does not exist, return an error message
|
|
res.status(StatusCodes.NOT_FOUND).json({
|
|
error: "user not found",
|
|
details: [{ message: `User "${username}" not found` }],
|
|
});
|
|
return;
|
|
}
|
|
const isPasswordValid = await bcrypt.compare(password, user.password); // compare the password with the hashed password in the database
|
|
if (!isPasswordValid) {
|
|
// if the password is not valid, return an error message
|
|
res.status(StatusCodes.UNAUTHORIZED).json({
|
|
error: "invalid credentials",
|
|
details: [{ message: "Invalid password" }],
|
|
});
|
|
return;
|
|
}
|
|
const refreshToken = await generateRefreshToken(user.id);
|
|
res.set("Refresh-Token", refreshToken.token);
|
|
const token: string = generateAccessToken(
|
|
user.username,
|
|
user.id,
|
|
user.role,
|
|
refreshToken.id
|
|
); // generate a JWT token with the username and userId as payload
|
|
res.set("Authorization", `Bearer ${token}`); // set the token in the response header
|
|
res.status(StatusCodes.OK).json({ message: "User logged in successfully" });
|
|
};
|
|
|
|
export const refreshToken = async (req: Request, res: Response) => {
|
|
const refreshToken: string | undefined = req.headers[
|
|
"refresh-token"
|
|
] as string;
|
|
if (!refreshToken) {
|
|
res.status(StatusCodes.UNAUTHORIZED).json({
|
|
error: "Unauthorized",
|
|
details: [{ message: "No token provided" }],
|
|
});
|
|
return;
|
|
}
|
|
|
|
await jwt.verify(
|
|
refreshToken,
|
|
REFRESH_TOKEN_SECRET,
|
|
async (err: any, decoded: any) => {
|
|
if (err) {
|
|
if (err instanceof jwt.TokenExpiredError) {
|
|
res.status(StatusCodes.UNAUTHORIZED).json({
|
|
error: "Refreshtoken expired",
|
|
details: [{ message: "Refreshtoken has expired" }],
|
|
});
|
|
return;
|
|
}
|
|
res.status(StatusCodes.FORBIDDEN).json({
|
|
error: "Invalid refreshtoken",
|
|
details: [{ message: "Refreshtoken is invalid" }],
|
|
});
|
|
return;
|
|
}
|
|
|
|
const payload = decoded as RefreshTokenPayload;
|
|
try {
|
|
const now = new Date();
|
|
const storedToken = await prisma.refreshToken.findUnique({
|
|
where: {
|
|
id: payload.jti,
|
|
expiresAt: {
|
|
gt: now, // expiresAt > now
|
|
},
|
|
},
|
|
include: { user: true },
|
|
});
|
|
if (!storedToken) {
|
|
res.status(StatusCodes.UNAUTHORIZED).json({
|
|
error: "InvalidRefreshToken",
|
|
details: [
|
|
{ message: "Refresh token is invalid or no longer exists" },
|
|
],
|
|
});
|
|
return;
|
|
}
|
|
const existingToken = await prisma.refreshToken.findUnique({
|
|
where: { id: payload.jti },
|
|
});
|
|
|
|
if (existingToken) {
|
|
await prisma.refreshToken.deleteMany({
|
|
where: { id: payload.jti },
|
|
});
|
|
}
|
|
const refreshToken = await generateRefreshToken(storedToken.user.id);
|
|
res.set("Refresh-Token", refreshToken.token);
|
|
const token: string = generateAccessToken(
|
|
storedToken.user.username,
|
|
storedToken.user.id,
|
|
storedToken.user.role,
|
|
refreshToken.id
|
|
); // generate a JWT token with the username and userId as payload
|
|
res.set("Authorization", `Bearer ${token}`); // set the token in the response header
|
|
res.status(StatusCodes.OK).send();
|
|
} catch (error) {
|
|
console.log(error);
|
|
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
error: "Server error",
|
|
details: [{ message: "Server Error" }],
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
export const logout = async (req: Request, res: Response) => {
|
|
const jti: string = req.user!.jti as string;
|
|
try {
|
|
await prisma.refreshToken.delete({ where: { id: jti } });
|
|
res.status(StatusCodes.NO_CONTENT).send();
|
|
} catch (err) {
|
|
console.log(err);
|
|
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
|
error: "Server error",
|
|
details: [{ message: "Server Error" }],
|
|
});
|
|
}
|
|
};
|