diff --git a/index.ts b/index.ts index b33e525..ab5dcab 100644 --- a/index.ts +++ b/index.ts @@ -49,10 +49,6 @@ app.use("/api/v1/user", userRoutes); app.use("/api/v1/users", usersRoutes); app.use("/api/v1/admin", adminRoutes); app.use("/api/v1/events", eventsRoutes); -app.use("/api/v1/appstart", (req, res, next) => { - console.log("appstart"); - res.status(200).json({ status: "ok" }); -}); const specs = swaggerJsDoc(options); app.use("/api-docs", swaggerUI.serve, swaggerUI.setup(specs)); diff --git a/package-lock.json b/package-lock.json index 50265b0..dbf9ef0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "mongoose": "^8.0.2", "morgan": "^1.10.0", "swagger-jsdoc": "^6.2.8", - "swagger-ui-express": "^5.0.0" + "swagger-ui-express": "^5.0.0", + "uuid": "^9.0.1" }, "devDependencies": { "@types/bcrypt": "^5.0.2", @@ -25,6 +26,7 @@ "@types/node": "^20.10.2", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.6", + "@types/uuid": "^9.0.7", "concurrently": "^8.2.2", "nodemon": "^3.0.2", "typescript": "^5.3.2" @@ -263,6 +265,12 @@ "@types/serve-static": "*" } }, + "node_modules/@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -2373,6 +2381,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/validator": { "version": "13.11.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", diff --git a/package.json b/package.json index 4110a14..afaac3a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "mongoose": "^8.0.2", "morgan": "^1.10.0", "swagger-jsdoc": "^6.2.8", - "swagger-ui-express": "^5.0.0" + "swagger-ui-express": "^5.0.0", + "uuid": "^9.0.1" }, "devDependencies": { "@types/bcrypt": "^5.0.2", @@ -28,6 +29,7 @@ "@types/node": "^20.10.2", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.6", + "@types/uuid": "^9.0.7", "concurrently": "^8.2.2", "nodemon": "^3.0.2", "typescript": "^5.3.2" diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index 82611cd..e464dbf 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -6,21 +6,24 @@ import { decodeBase64, matchPassword, getUserSession, + getUser, } from "../utils/utils"; import { isUsernameValid, isPasswordValid } from "../validation/validation"; import { Session } from "../models/session"; -import { MONGODB_IGNORED_FIELDS } from "../utils/constants"; +import { MONGODB_IGNORED_FIELDS_USER } from "../utils/constants"; export async function SignUp(req: Request, res: Response) { try { - const { AccountName, Username, Password } = req.body; + let { AccountName, Username, Password } = req.body; if (!AccountName || !Username || !Password) { return res.status(400).json({ status: "err" }); } - const existingUser = await User.findOne({ AccountName }) - .select(MONGODB_IGNORED_FIELDS) + AccountName = AccountName.toLowerCase(); + + const existingUser = await User.findOne({ accountName: AccountName }) + .select(`accountName -_id`) .lean(); if (existingUser) { @@ -42,9 +45,9 @@ export async function SignUp(req: Request, res: Response) { user .save() - .then(() => saveSession(res, AccountName, Username)) + .then(() => saveSession(res, user.userId, Username)) .catch((err) => { - console.log(err); + console.error("error on signup saving session:", err); res.status(500).json({ status: "err" }); }); } catch (error) { @@ -62,7 +65,7 @@ export async function Login(req: Request, res: Response) { } const user = await User.findOne({ accountName: AccountName }) - .select(`accountName username password -_id`) + .select(`userId accountName username password -_id`) .lean(); if (!user) { @@ -85,7 +88,7 @@ export async function Login(req: Request, res: Response) { return res.status(401).json({ status: "err" }); } - saveSession(res, user.accountName, user.username); + saveSession(res, user.userId, user.username); } catch (error) { console.error("error on login:", error); res.status(500).json({ status: "err" }); @@ -100,14 +103,14 @@ export async function ChangeUsername(req: Request, res: Response) { return res.status(400).json({ status: "err" }); } - const session = await getUserSession(req, "accountName"); + const session = await getUserSession(req, "userId"); if (!session) { return res.status(401).json({ status: "err" }); } await User.updateOne( - { accountName: session.accountName }, + { userId: session.userId }, { $set: { username: newUsername } } ); @@ -128,52 +131,46 @@ export async function ChangePassword(req: Request, res: Response) { return res.status(400).json({ status: "err" }); } - console.log(cp, np); + const session = await getUserSession(req, "userId"); - const session = await getUserSession(req, "accountName"); - - if (!session) { + if (!session || !session.userId) { return res.status(401).json({ status: "err" }); } - const accountName = session.accountName; - const decodedCurrentPassword = decodeBase64(cp); - const existingUser = await User.findOne({ - accountName: accountName, - }) - .select(`password -_id`) - .lean(); - - if (!existingUser) { - return res.status(400).json({ status: 1 }); - } - - const decodedNewPassword = decodeBase64(np); - if (!isPasswordValid(decodedCurrentPassword)) { return res.status(400).json({ status: "err" }); } + const userId = session.userId; + + const existingUser = await getUser(userId); + + if (!existingUser) { + return res.status(400).json({ status: 1 }); + } + const isPasswordMatching = await matchPassword( decodedCurrentPassword, existingUser.password ); + const decodedNewPassword = decodeBase64(np); + if (!isPasswordMatching || !isPasswordValid(decodedNewPassword)) { return res.status(401).json({ status: "err" }); } await User.updateOne( - { accountName: accountName }, + { userId: userId }, { $set: { password: await hashPassword(decodedNewPassword) } } ); res.status(200).json({ status: "ok" }); // delete all sessions - await Session.deleteMany({ accountName: accountName }); + await Session.deleteMany({ userId: userId }); } catch (error) { console.error("error on changing password", error); res.status(500).json({ status: "err" }); @@ -196,3 +193,53 @@ export async function Logout(req: Request, res: Response) { res.status(500).json({ status: "err" }); } } + +export async function IsAccountNameAvailable(req: Request, res: Response) { + try { + let { accountName } = req.params; + + accountName = accountName.toLowerCase(); + + if (!accountName) { + return res.status(400).json({ status: "err" }); + } + + const existingUser = await User.findOne({ accountName: accountName }) + .select(`accountName -_id`) + .lean(); + + if (existingUser) { + return res.status(422).json({ status: "not found" }); + } + + res.status(200).json({ status: "ok" }); + } catch (error) { + console.error("error on checking account name availability", error); + res.status(500).json({ status: "err" }); + } +} + +// appstart +export async function GetUserProfile(req: Request, res: Response) { + try { + const session = await getUserSession(req, "userId"); + + if (!session || !session.userId) { + return res.status(401).json({ status: "err" }); + } + + const user = await getUser( + session.userId, + `${MONGODB_IGNORED_FIELDS_USER} -createdAt` + ); + + if (!user) { + return res.status(404).json({ status: "err" }); + } + + res.json(user); + } catch (error) { + console.error("error on get user profile:", error); + res.status(500).json({ status: "err" }); + } +} diff --git a/src/controllers/usersController.ts b/src/controllers/usersController.ts index df94be6..1079043 100644 --- a/src/controllers/usersController.ts +++ b/src/controllers/usersController.ts @@ -4,10 +4,16 @@ import { MONGODB_IGNORED_FIELDS_USER } from "../utils/constants"; export async function GetUserProfile(req: Request, res: Response) { try { + const { userId } = req.params; + + if (!userId) { + return res.status(400).json({ status: "err" }); + } + const user = await User.findOne({ - accountName: req.params.accountName, + id: userId, }) - .select(`-accountName ${MONGODB_IGNORED_FIELDS_USER}`) + .select(`-id ${MONGODB_IGNORED_FIELDS_USER}`) .lean(); if (!user) { diff --git a/src/middleware/authMiddleware.ts b/src/middleware/authMiddleware.ts index 0f2d80f..d1d4ddc 100644 --- a/src/middleware/authMiddleware.ts +++ b/src/middleware/authMiddleware.ts @@ -2,7 +2,7 @@ import { Request } from "express"; import { Session } from "../models/session"; import { User } from "../models/user"; import { HEADER_X_AUTHORIZATION } from "../utils/constants"; -import { getUserSession } from "../utils/utils"; +import { getUser, getUserSession } from "../utils/utils"; export async function sessionProtection(req: Request, res: any, next: any) { const session = await getUserSession(req); @@ -22,16 +22,14 @@ export async function adminProtection(req: Request, res: any, next: any) { } const session = await Session.findOne({ sessionId: xAuthorization }) - .select("sessionId accountName -_id") + .select("sessionId userId -_id") .lean(); - if (!session) { + if (!session || !session.userId) { return res.status(401).json({ status: "err" }); } - const user = await User.findOne({ accountName: session.accountName }) - .select("isAdmin -_id") - .lean(); + const user = await getUser(session.userId, "isAdmin"); if (!user || !user.isAdmin) { return res.status(401).json({ status: "err" }); diff --git a/src/models/session.ts b/src/models/session.ts index 459250a..0c2279a 100644 --- a/src/models/session.ts +++ b/src/models/session.ts @@ -3,7 +3,7 @@ import { DEFAULT_SESSION_EXPIRATION } from "../utils/constants"; export const sessionSchema = new Schema({ sessionId: String, - accountName: String, + userId: String, expiresAt: { type: Date, default: new Date(Date.now() + DEFAULT_SESSION_EXPIRATION), diff --git a/src/models/user.ts b/src/models/user.ts index a0c0e61..84af69f 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -1,6 +1,8 @@ import mongoose, { InferSchemaType, Schema } from "mongoose"; +import { v4 as uuidv4 } from "uuid"; export interface IUser extends Document { + userId: string; accountName: string; username: string; password: string; @@ -8,9 +10,16 @@ export interface IUser extends Document { following: number; visited: number; isAdmin: boolean; + createdAt: Date; } export const userSchema = new Schema({ + userId: { + type: String, + required: true, + unique: true, + default: () => uuidv4(), + }, accountName: String, username: String, password: String, @@ -30,6 +39,10 @@ export const userSchema = new Schema({ type: Boolean, default: false, }, + createdAt: { + type: Date, + default: Date.now(), + }, }); export type User = InferSchemaType; diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index 5dc7c7d..5e02933 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -130,5 +130,7 @@ router.post("/login", userController.Login); router.post("/username", sessionProtection, userController.ChangeUsername); router.post("/password", sessionProtection, userController.ChangePassword); router.post("/logout", sessionProtection, userController.Logout); +router.get("/check/:accountName", userController.IsAccountNameAvailable); +router.get("/", sessionProtection, userController.GetUserProfile); export default router; diff --git a/src/routes/usersRoutes.ts b/src/routes/usersRoutes.ts index 449399a..f87c2bf 100644 --- a/src/routes/usersRoutes.ts +++ b/src/routes/usersRoutes.ts @@ -3,6 +3,6 @@ const router = express.Router(); import * as usersController from "../controllers/usersController"; import { sessionProtection } from "../middleware/authMiddleware"; -router.get("/:accountName", sessionProtection, usersController.GetUserProfile); +router.get("/:userId", sessionProtection, usersController.GetUserProfile); export default router; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 733734e..466a781 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -12,6 +12,7 @@ export const MONGODB_IGNORED_FIELDS_USER: string = // Header name for the session ID export const HEADER_X_AUTHORIZATION: string = "x-authorization"; +// Regular expressions and length requirements export const USERNAME_MIN_LENGTH: number = 2; export const USERNAME_MAX_LENGTH: number = 24; export const USERNAME_REGEX: RegExp = /^[a-zA-Z0-9_]+$/; // Alphanumeric and underscore diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b2c10bd..ea18b14 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -2,11 +2,12 @@ import crypto from "crypto"; import { Session } from "../models/session"; import { Request, Response } from "express"; import bcrypt from "bcrypt"; -import { HEADER_X_AUTHORIZATION } from "./constants"; +import { HEADER_X_AUTHORIZATION, MONGODB_IGNORED_FIELDS } from "./constants"; +import { User } from "../models/user"; export async function saveSession( res: Response, - accountName: string, + userId: string, username: string ) { try { @@ -16,14 +17,16 @@ export async function saveSession( // Create a new session document const session = new Session({ sessionId: sessionId, - accountName: accountName, // Assuming you have the user information in req.user + userId: userId, }); // Save the session to MongoDB await session.save(); // Respond with the session ID - res.status(200).json({ XAuthorization: sessionId, Username: username }); + res + .status(200) + .json({ XAuthorization: sessionId, UserId: userId, Username: username }); } catch (error) { console.error("Error saving session:", error); res.status(500).json({ status: "err" }); @@ -43,19 +46,34 @@ export function decodeBase64(value: string) { } export async function getUserSession(req: Request, select?: string) { + // Get the session ID from the request headers const sessionId = req.get(HEADER_X_AUTHORIZATION); if (!sessionId) { return null; } + // Find the session in MongoDB const session = await Session.findOne({ sessionId }) .select(`sessionId -_id ${select}`) .lean(); + // Return the session if (!session) { return null; } return session; } + +export async function getUser(userId: string, select?: string) { + const user = await User.findOne({ userId: userId }) + .select(select ? select : "") + .lean(); + + if (!user) { + return null; + } + + return user; +}