added authentication and swagger

This commit is contained in:
Kai Ritthaler 2025-05-09 21:08:38 +02:00 committed by Rudi Regentonne
parent c311e3adda
commit 64c0d79438
8 changed files with 447 additions and 56 deletions

View file

@ -3,42 +3,107 @@ import { PrismaClient } from "@prisma/client";
import { UserLoginDto, userLoginSchema } from "../schemas/userSchemas";
import jwt from "jsonwebtoken";
import dotenv from "dotenv";
import { string } from "zod";
import bcrypt from "bcryptjs";
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!;
function generateAccessToken(username: string) {
return jwt.sign({ username }, JWT_SECRET, { expiresIn: "1800s" });
const JWT_SECRET: string = process.env.TOKEN_SECRET!; // this secret is used to sign the JWT token
// 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) {
return jwt.sign(
{ username: username, role: "user", sub: userId },
JWT_SECRET,
{ expiresIn: "1800s", issuer: "VogelApi" }
); //TODO: change role to user role
}
// Endpoint to register a new user
export const registerUser = async (req: Request, res: Response) => {
const userRequest = await req.body;
const user = await prisma.user.create({ data: userRequest });
console.log(user.username);
res.json({ message: "User registered successfully", data: req.body });
const { username, password, email } = await req.body; //gets the data from the request body
if (!username || !password || !email) {
// check if username, password and email are provided and
res
.status(400)
.json({ message: "Username, password and email are required" });
}
const existingUser = 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(400).json({ message: `User "${username}" already exists` });
}
const hashedPassword = await bcrypt.hash(password, 10); // hash the password with bcrypt
if (!hashedPassword) {
// check if the password was hashed successfully
res.status(500).json({ message: "Server Error" });
}
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 = await prisma.user.create({ data: userData }); // create a new user in the database
if (!user) {
// check if the user was created successfully
res.status(500).json({ message: "Server Error" });
}
res.json({ message: "User registered successfully" });
res.status(201).json({
message: "user created",
data: { username: username, email: email },
}); // return the user object with the username and email
};
export const loginUser = (req: Request, res: Response) => {
const token: string = generateAccessToken(req.body.username);
res.json({ message: "User logged in successfully", data: req.body, token });
// Endpoint to login a user (unfinished)
export const loginUser = async (req: Request, res: Response) => {
const { username, password } = req.body; // get the data from the request body
if (!username || !password) {
// check if username and password are provided
res.status(400).json({ message: "Username and password are required" });
}
const user = 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(400).json({ 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(401).json({ message: "Invalid password" });
}
const token: string = generateAccessToken(user.username, user.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.json({ message: "User logged in successfully" });
};
// Endpoint to get user data
export const getUser = async (req: Request, res: Response) => {
const username = req.body.username;
console.log(username, req.body);
const username: string = req.query.username as string;
if (!username) {
res.status(400).json({ message: "Username is required" });
}
const user = await prisma.user.findUnique({
where: {
username: username,
},
});
if (!user) {
res.status(404).json({ message: "User not found" });
res.status(404).json({ message: `User "${username}" not found` });
return;
}
res.json({ message: "User found", data: user });
res.json({
message: "User found",
data: { username: user.username, email: user.email },
});
};

View file

@ -1,15 +1,19 @@
import express, { NextFunction, Request, Response } from "express";
import jwt from "jsonwebtoken";
import jwt, { TokenExpiredError } from "jsonwebtoken";
import dotenv from "dotenv";
import { string } from "zod";
dotenv.config();
// imports the JWT secret
const JWT_SECRET: string = process.env.TOKEN_SECRET!;
if (!JWT_SECRET) console.log("no JWT secret");
// create an interface for the JWT payload
// this interface is used to define the structure of the JWT payload
interface JwtPayload {
username: string;
iat: number;
exp: number;
}
// extend the Express Request interface to include the user property
// this is used to store the JWT payload in the request object
declare global {
namespace Express {
interface Request {
@ -17,27 +21,26 @@ declare global {
}
}
}
// Middleware function to authenticate the JWT token
export function authenticateToken() {
return (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
const authHeader = req.headers["authorization"]; // get the authorization header from the request
const token = authHeader && authHeader.split(" ")[1]; // split the header to get the token
if (token == null) res.sendStatus(401);
if (token == null)
res.sendStatus(401); // if there is no token, return 401 Unauthorized
else {
jwt.verify(
token,
process.env.TOKEN_SECRET as string,
(err: any, user: any) => {
console.log(err);
if (err) res.sendStatus(403);
req.user = user;
next();
},
);
jwt.verify(token, JWT_SECRET, (err: any, user: any) => {
// verify the token with the secret
console.log(err);
if (err) {
if (err instanceof TokenExpiredError)
// check if the error is expired and return 401
res.status(401).json({ message: "Token expired" });
else res.status(403).json({ message: "Invalid token" });
}
next();
});
}
};
}

View file

@ -14,17 +14,100 @@ import {
loginUser,
getUser,
} from "../controllers/userController";
/**
* @swagger
* components:
* schemas:
* UserRegistrationDto:
* type: object
* required:
* - username
* - email
* - password
* properties:
* username:
* type: string
* email:
* type: string
* format: email
* password:
* type: string
* format: password
* UserLoginDto:
* type: object
* required:
* - username
* - password
* properties:
* username:
* type: string
* password:
* type: string
*/
/**
* @swagger
* /api/user/register:
* post:
* summary: Register a new user
* tags: [User]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/UserRegistrationDto'
* responses:
* 201:
* description: Benutzer erfolgreich registriert
* 400:
* description: Ungültige Eingabedaten
*/
userRouter.post(
"/register",
validateData(userRegistrationSchema),
registerUser,
registerUser
);
/**
* @swagger
* /api/user/login:
* post:
* summary: Log in a user
* tags: [User]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/UserLoginDto'
* responses:
* 200:
* description: Login erfolgreich
* 401:
* description: Ungültige Anmeldedaten
*/
userRouter.post("/login", validateData(userLoginSchema), loginUser);
userRouter.get(
"/getUser",
authenticateToken(),
validateData(userLoginSchema),
getUser,
);
/**
* @swagger
* /api/user/getUser/{username}:
* get:
* summary: Get user data
* tags: [User]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: username
* required: true
* schema:
* type: string
* description: Der Benutzername, nach dem gesucht werden soll
* responses:
* 200:
* description: Login erfolgreich
* 401:
* description: Ungültige Anmeldedaten
*/
userRouter.get("/getUser/:username", authenticateToken(), getUser);
export default userRouter;

View file

@ -2,14 +2,14 @@
import { z } from "zod";
export const userRegistrationSchema = z.object({
username: z.string(),
username: z.string().regex(/^\S*$/, "Username must not contain spaces"), // No whitespaces allowed,
email: z.string().email(),
password: z.string().min(8),
});
export const userLoginSchema = z.object({
username: z.string(),
password: z.string().min(8),
username: z.string().regex(/^\S*$/, "Username must not contain spaces"), // No whitespaces allowed,
password: z.string(),
});
// DTO-Typen aus den Schemas ableiten
export type UserRegistrationDto = z.infer<typeof userRegistrationSchema>;

View file

@ -1,4 +1,4 @@
import express, { Request, Response } from "express";
import express, { Request, Response, Application } from "express";
import dotenv from "dotenv";
import userRouter from "./routes/userRoutes";
@ -9,9 +9,50 @@ dotenv.config();
const app = express();
const port = 3000;
//swagger configuration
import swaggerJSDoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";
const options = {
definition: {
openapi: "3.1.0",
info: {
title: "VogelApi",
version: "0.0.1",
description:
"This is a simple CRUD API application made with Express and documented with Swagger",
},
servers: [
{
url: "http://localhost:3000",
},
],
components: {
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT", // Optional, for documentation purposes
},
},
},
security: [
{
bearerAuth: [], // Apply globally if needed
},
],
},
apis: ["src/routes/*.ts"], // Hier werden alle Routen-Dateien mit Swagger-Kommentaren geladen
};
const specs = swaggerJSDoc(options);
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(specs));
app.use(bodyParser.json());
app.use("/api/user", userRouter);
// Sample route
app.get("/api/hello", (req, res) => {
res.send("Hello World!");
});
app.listen(port, () => {
console.log(`Server läuft auf http://localhost:${port}`);
});