updated primary to userId

main
Netcup Gituser 2023-12-17 22:53:58 +01:00
parent 822c07aff2
commit 52fc971305
12 changed files with 153 additions and 50 deletions

View File

@ -49,10 +49,6 @@ app.use("/api/v1/user", userRoutes);
app.use("/api/v1/users", usersRoutes); app.use("/api/v1/users", usersRoutes);
app.use("/api/v1/admin", adminRoutes); app.use("/api/v1/admin", adminRoutes);
app.use("/api/v1/events", eventsRoutes); 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); const specs = swaggerJsDoc(options);
app.use("/api-docs", swaggerUI.serve, swaggerUI.setup(specs)); app.use("/api-docs", swaggerUI.serve, swaggerUI.setup(specs));

22
package-lock.json generated
View File

@ -16,7 +16,8 @@
"mongoose": "^8.0.2", "mongoose": "^8.0.2",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"swagger-jsdoc": "^6.2.8", "swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0" "swagger-ui-express": "^5.0.0",
"uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
@ -25,6 +26,7 @@
"@types/node": "^20.10.2", "@types/node": "^20.10.2",
"@types/swagger-jsdoc": "^6.0.4", "@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.6", "@types/swagger-ui-express": "^4.1.6",
"@types/uuid": "^9.0.7",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"nodemon": "^3.0.2", "nodemon": "^3.0.2",
"typescript": "^5.3.2" "typescript": "^5.3.2"
@ -263,6 +265,12 @@
"@types/serve-static": "*" "@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": { "node_modules/@types/webidl-conversions": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
@ -2373,6 +2381,18 @@
"node": ">= 0.4.0" "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": { "node_modules/validator": {
"version": "13.11.0", "version": "13.11.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",

View File

@ -19,7 +19,8 @@
"mongoose": "^8.0.2", "mongoose": "^8.0.2",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"swagger-jsdoc": "^6.2.8", "swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0" "swagger-ui-express": "^5.0.0",
"uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
@ -28,6 +29,7 @@
"@types/node": "^20.10.2", "@types/node": "^20.10.2",
"@types/swagger-jsdoc": "^6.0.4", "@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.6", "@types/swagger-ui-express": "^4.1.6",
"@types/uuid": "^9.0.7",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"nodemon": "^3.0.2", "nodemon": "^3.0.2",
"typescript": "^5.3.2" "typescript": "^5.3.2"

View File

@ -6,21 +6,24 @@ import {
decodeBase64, decodeBase64,
matchPassword, matchPassword,
getUserSession, getUserSession,
getUser,
} from "../utils/utils"; } from "../utils/utils";
import { isUsernameValid, isPasswordValid } from "../validation/validation"; import { isUsernameValid, isPasswordValid } from "../validation/validation";
import { Session } from "../models/session"; 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) { export async function SignUp(req: Request, res: Response) {
try { try {
const { AccountName, Username, Password } = req.body; let { AccountName, Username, Password } = req.body;
if (!AccountName || !Username || !Password) { if (!AccountName || !Username || !Password) {
return res.status(400).json({ status: "err" }); return res.status(400).json({ status: "err" });
} }
const existingUser = await User.findOne({ AccountName }) AccountName = AccountName.toLowerCase();
.select(MONGODB_IGNORED_FIELDS)
const existingUser = await User.findOne({ accountName: AccountName })
.select(`accountName -_id`)
.lean(); .lean();
if (existingUser) { if (existingUser) {
@ -42,9 +45,9 @@ export async function SignUp(req: Request, res: Response) {
user user
.save() .save()
.then(() => saveSession(res, AccountName, Username)) .then(() => saveSession(res, user.userId, Username))
.catch((err) => { .catch((err) => {
console.log(err); console.error("error on signup saving session:", err);
res.status(500).json({ status: "err" }); res.status(500).json({ status: "err" });
}); });
} catch (error) { } catch (error) {
@ -62,7 +65,7 @@ export async function Login(req: Request, res: Response) {
} }
const user = await User.findOne({ accountName: AccountName }) const user = await User.findOne({ accountName: AccountName })
.select(`accountName username password -_id`) .select(`userId accountName username password -_id`)
.lean(); .lean();
if (!user) { if (!user) {
@ -85,7 +88,7 @@ export async function Login(req: Request, res: Response) {
return res.status(401).json({ status: "err" }); return res.status(401).json({ status: "err" });
} }
saveSession(res, user.accountName, user.username); saveSession(res, user.userId, user.username);
} catch (error) { } catch (error) {
console.error("error on login:", error); console.error("error on login:", error);
res.status(500).json({ status: "err" }); 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" }); return res.status(400).json({ status: "err" });
} }
const session = await getUserSession(req, "accountName"); const session = await getUserSession(req, "userId");
if (!session) { if (!session) {
return res.status(401).json({ status: "err" }); return res.status(401).json({ status: "err" });
} }
await User.updateOne( await User.updateOne(
{ accountName: session.accountName }, { userId: session.userId },
{ $set: { username: newUsername } } { $set: { username: newUsername } }
); );
@ -128,52 +131,46 @@ export async function ChangePassword(req: Request, res: Response) {
return res.status(400).json({ status: "err" }); 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 || !session.userId) {
if (!session) {
return res.status(401).json({ status: "err" }); return res.status(401).json({ status: "err" });
} }
const accountName = session.accountName;
const decodedCurrentPassword = decodeBase64(cp); 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)) { if (!isPasswordValid(decodedCurrentPassword)) {
return res.status(400).json({ status: "err" }); 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( const isPasswordMatching = await matchPassword(
decodedCurrentPassword, decodedCurrentPassword,
existingUser.password existingUser.password
); );
const decodedNewPassword = decodeBase64(np);
if (!isPasswordMatching || !isPasswordValid(decodedNewPassword)) { if (!isPasswordMatching || !isPasswordValid(decodedNewPassword)) {
return res.status(401).json({ status: "err" }); return res.status(401).json({ status: "err" });
} }
await User.updateOne( await User.updateOne(
{ accountName: accountName }, { userId: userId },
{ $set: { password: await hashPassword(decodedNewPassword) } } { $set: { password: await hashPassword(decodedNewPassword) } }
); );
res.status(200).json({ status: "ok" }); res.status(200).json({ status: "ok" });
// delete all sessions // delete all sessions
await Session.deleteMany({ accountName: accountName }); await Session.deleteMany({ userId: userId });
} catch (error) { } catch (error) {
console.error("error on changing password", error); console.error("error on changing password", error);
res.status(500).json({ status: "err" }); res.status(500).json({ status: "err" });
@ -196,3 +193,53 @@ export async function Logout(req: Request, res: Response) {
res.status(500).json({ status: "err" }); 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" });
}
}

View File

@ -4,10 +4,16 @@ import { MONGODB_IGNORED_FIELDS_USER } from "../utils/constants";
export async function GetUserProfile(req: Request, res: Response) { export async function GetUserProfile(req: Request, res: Response) {
try { try {
const { userId } = req.params;
if (!userId) {
return res.status(400).json({ status: "err" });
}
const user = await User.findOne({ const user = await User.findOne({
accountName: req.params.accountName, id: userId,
}) })
.select(`-accountName ${MONGODB_IGNORED_FIELDS_USER}`) .select(`-id ${MONGODB_IGNORED_FIELDS_USER}`)
.lean(); .lean();
if (!user) { if (!user) {

View File

@ -2,7 +2,7 @@ import { Request } from "express";
import { Session } from "../models/session"; import { Session } from "../models/session";
import { User } from "../models/user"; import { User } from "../models/user";
import { HEADER_X_AUTHORIZATION } from "../utils/constants"; 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) { export async function sessionProtection(req: Request, res: any, next: any) {
const session = await getUserSession(req); 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 }) const session = await Session.findOne({ sessionId: xAuthorization })
.select("sessionId accountName -_id") .select("sessionId userId -_id")
.lean(); .lean();
if (!session) { if (!session || !session.userId) {
return res.status(401).json({ status: "err" }); return res.status(401).json({ status: "err" });
} }
const user = await User.findOne({ accountName: session.accountName }) const user = await getUser(session.userId, "isAdmin");
.select("isAdmin -_id")
.lean();
if (!user || !user.isAdmin) { if (!user || !user.isAdmin) {
return res.status(401).json({ status: "err" }); return res.status(401).json({ status: "err" });

View File

@ -3,7 +3,7 @@ import { DEFAULT_SESSION_EXPIRATION } from "../utils/constants";
export const sessionSchema = new Schema({ export const sessionSchema = new Schema({
sessionId: String, sessionId: String,
accountName: String, userId: String,
expiresAt: { expiresAt: {
type: Date, type: Date,
default: new Date(Date.now() + DEFAULT_SESSION_EXPIRATION), default: new Date(Date.now() + DEFAULT_SESSION_EXPIRATION),

View File

@ -1,6 +1,8 @@
import mongoose, { InferSchemaType, Schema } from "mongoose"; import mongoose, { InferSchemaType, Schema } from "mongoose";
import { v4 as uuidv4 } from "uuid";
export interface IUser extends Document { export interface IUser extends Document {
userId: string;
accountName: string; accountName: string;
username: string; username: string;
password: string; password: string;
@ -8,9 +10,16 @@ export interface IUser extends Document {
following: number; following: number;
visited: number; visited: number;
isAdmin: boolean; isAdmin: boolean;
createdAt: Date;
} }
export const userSchema = new Schema({ export const userSchema = new Schema({
userId: {
type: String,
required: true,
unique: true,
default: () => uuidv4(),
},
accountName: String, accountName: String,
username: String, username: String,
password: String, password: String,
@ -30,6 +39,10 @@ export const userSchema = new Schema({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
createdAt: {
type: Date,
default: Date.now(),
},
}); });
export type User = InferSchemaType<typeof userSchema>; export type User = InferSchemaType<typeof userSchema>;

View File

@ -130,5 +130,7 @@ router.post("/login", userController.Login);
router.post("/username", sessionProtection, userController.ChangeUsername); router.post("/username", sessionProtection, userController.ChangeUsername);
router.post("/password", sessionProtection, userController.ChangePassword); router.post("/password", sessionProtection, userController.ChangePassword);
router.post("/logout", sessionProtection, userController.Logout); router.post("/logout", sessionProtection, userController.Logout);
router.get("/check/:accountName", userController.IsAccountNameAvailable);
router.get("/", sessionProtection, userController.GetUserProfile);
export default router; export default router;

View File

@ -3,6 +3,6 @@ const router = express.Router();
import * as usersController from "../controllers/usersController"; import * as usersController from "../controllers/usersController";
import { sessionProtection } from "../middleware/authMiddleware"; import { sessionProtection } from "../middleware/authMiddleware";
router.get("/:accountName", sessionProtection, usersController.GetUserProfile); router.get("/:userId", sessionProtection, usersController.GetUserProfile);
export default router; export default router;

View File

@ -12,6 +12,7 @@ export const MONGODB_IGNORED_FIELDS_USER: string =
// Header name for the session ID // Header name for the session ID
export const HEADER_X_AUTHORIZATION: string = "x-authorization"; export const HEADER_X_AUTHORIZATION: string = "x-authorization";
// Regular expressions and length requirements
export const USERNAME_MIN_LENGTH: number = 2; export const USERNAME_MIN_LENGTH: number = 2;
export const USERNAME_MAX_LENGTH: number = 24; export const USERNAME_MAX_LENGTH: number = 24;
export const USERNAME_REGEX: RegExp = /^[a-zA-Z0-9_]+$/; // Alphanumeric and underscore export const USERNAME_REGEX: RegExp = /^[a-zA-Z0-9_]+$/; // Alphanumeric and underscore

View File

@ -2,11 +2,12 @@ import crypto from "crypto";
import { Session } from "../models/session"; import { Session } from "../models/session";
import { Request, Response } from "express"; import { Request, Response } from "express";
import bcrypt from "bcrypt"; 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( export async function saveSession(
res: Response, res: Response,
accountName: string, userId: string,
username: string username: string
) { ) {
try { try {
@ -16,14 +17,16 @@ export async function saveSession(
// Create a new session document // Create a new session document
const session = new Session({ const session = new Session({
sessionId: sessionId, sessionId: sessionId,
accountName: accountName, // Assuming you have the user information in req.user userId: userId,
}); });
// Save the session to MongoDB // Save the session to MongoDB
await session.save(); await session.save();
// Respond with the session ID // 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) { } catch (error) {
console.error("Error saving session:", error); console.error("Error saving session:", error);
res.status(500).json({ status: "err" }); res.status(500).json({ status: "err" });
@ -43,19 +46,34 @@ export function decodeBase64(value: string) {
} }
export async function getUserSession(req: Request, select?: string) { export async function getUserSession(req: Request, select?: string) {
// Get the session ID from the request headers
const sessionId = req.get(HEADER_X_AUTHORIZATION); const sessionId = req.get(HEADER_X_AUTHORIZATION);
if (!sessionId) { if (!sessionId) {
return null; return null;
} }
// Find the session in MongoDB
const session = await Session.findOne({ sessionId }) const session = await Session.findOne({ sessionId })
.select(`sessionId -_id ${select}`) .select(`sessionId -_id ${select}`)
.lean(); .lean();
// Return the session
if (!session) { if (!session) {
return null; return null;
} }
return session; 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;
}