Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed user service #88

Merged
merged 9 commits into from
Oct 15, 2023
Merged
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
2 changes: 1 addition & 1 deletion src/database/attendee-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class AttendeeProfile {
public avatarUrl: string;

@prop({ required: true })
public discordName: string;
public discordTag: string;

@prop({ required: true })
public points: number;
Expand Down
1 change: 1 addition & 0 deletions src/services/auth/auth-models.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface ProfileData {
id?: string;
login?: string;
email: string;
displayName?: string;
}
Expand Down
8 changes: 5 additions & 3 deletions src/services/auth/auth-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,16 @@ authRouter.get(
const redirect: string = Constants.REDIRECT_MAPPINGS.get(device) ?? Constants.DEFAULT_REDIRECT;

data.id = data.id ?? user.id;
data.displayName = user.displayName;
data.displayName = data.displayName ?? data.login;

try {
// Load in the payload with the actual values stored in the database
const payload: JwtPayload = await getJwtPayloadFromProfile(user.provider, data);
console.log(data, payload);
const userId: string = payload.id;
await UserInfoModel.findOneAndUpdate(
{ userId: data.id },
{ email: data.email, name: data.displayName, userId: payload.id },
{ userId: userId },
{ email: data.email, name: data.displayName, userId: userId },
{ upsert: true },
);

Expand Down
17 changes: 13 additions & 4 deletions src/services/profile/profile-formats.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { AttendeeProfile } from "../../database/attendee-db.js";
export interface ProfileFormat {
userId: string;
avatarUrl: string;
discordTag: string;
displayName: string;
points: number;
}

export function isValidProfileModel(profile: AttendeeProfile): boolean {
export function isValidProfileFormat(profile: ProfileFormat): boolean {
if (!profile) {
return false;
}
if (!profile.avatarUrl || !profile.discordName || !profile.displayName) {

if (!profile.userId || !profile.avatarUrl || !profile.discordTag || !profile.displayName) {
return false;
}

if (
typeof profile.discordName !== "string" ||
typeof profile.userId !== "string" ||
typeof profile.discordTag !== "string" ||
typeof profile.displayName !== "string" ||
typeof profile.avatarUrl !== "string"
) {
Expand Down
84 changes: 50 additions & 34 deletions src/services/profile/profile-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { Query } from "mongoose";
import { LeaderboardEntry } from "./profile-models.js";

import { JwtPayload } from "../auth/auth-models.js";
import { strongJwtVerification, weakJwtVerification } from "../../middleware/verify-jwt.js";
import { isValidProfileModel } from "./profile-formats.js";
import { strongJwtVerification } from "../../middleware/verify-jwt.js";
import { ProfileFormat, isValidProfileFormat } from "./profile-formats.js";
import { hasElevatedPerms } from "../auth/auth-lib.js";

const profileRouter: Router = Router();

Expand All @@ -34,7 +35,7 @@ profileRouter.use(cors({ origin: "*" }));
"points": 2021,
},
{
"displayName": "test2"
"displayName": "patrick"
"points": 2020,
},
]
Expand Down Expand Up @@ -79,17 +80,20 @@ profileRouter.get("/leaderboard/", async (req: Request, res: Response) => {
* @apiGroup Profile
* @apiDescription Retrieve the user profile based on their authentication.
*
* @apiSuccess (200: Success) {Json} user User's profile information.
* @apiSuccess (200: Success) {string} userID ID of the user
* @apiSuccess (200: Success) {string} displayName Publicly-visible display name for the user
* @apiSuccess (200: Success) {string} discordTag Discord tag for the user
* @apiSuccess (200: Success) {string} avatarUrl URL that contains the user avatar
* @apiSuccess (200: Success) {number} points Points that the user has
* @apiSuccessExample Example Success Response:
* HTTP/1.1 200 OK
* {
* "_id": "12345",
* "displayName": "Illinois",
* "discordName": "hackillinois",
* "userId": "google12345"
* "displayName": "hackillinois",
* "discordTag": "discordtag",
* "avatarUrl": "na",
* "points": 0,
* "userId": "abcde",
* "foodWave": 0
* }
*
* @apiError (404: Not Found) {String} UserNotFound The user's profile was not found.
Expand All @@ -104,13 +108,14 @@ profileRouter.get("/leaderboard/", async (req: Request, res: Response) => {
*/

profileRouter.get("/", strongJwtVerification, async (_: Request, res: Response) => {
const decodedData: JwtPayload = res.locals.payload as JwtPayload;
const payload: JwtPayload = res.locals.payload as JwtPayload;

const userId: string = decodedData.id;
const userId: string = payload.id;
console.log(userId);
const user: AttendeeProfile | null = await AttendeeProfileModel.findOne({ userId: userId });

if (!user) {
return res.status(Constants.NOT_FOUND).send({ error: "UserNotFound" });
return res.status(Constants.BAD_REQUEST).send({ error: "UserNotFound" });
}

return res.status(Constants.SUCCESS).send(user);
Expand All @@ -123,16 +128,21 @@ profileRouter.get("/", strongJwtVerification, async (_: Request, res: Response)
*
* @apiParam {String} USERID User's unique ID.
*
* @apiSuccess (200: Success) {Json} user User's profile information.
* @apiSuccess (200: Success) {string} userID ID of the user
* @apiSuccess (200: Success) {string} displayName Publicly-visible display name for the user
* @apiSuccess (200: Success) {string} discordTag Discord tag for the user
* @apiSuccess (200: Success) {string} avatarUrl URL that contains the user avatar
* @apiSuccess (200: Success) {number} points Points that the user has
*
* @apiSuccessExample Example Success Response:
* HTTP/1.1 200 OK
* {
* "_id": "12345",
* "displayName": "Hackk",
* "discordName": "hackillinois",
* "userId": "google12345",
* "displayName": "Hack",
* "discordTag": "hackillinois",
* "avatarUrl": "na",
* "points": 0,
* "userId": "abcde",
* }
*
* @apiError (404: Not Found) {String} UserNotFound The user's profile was not found.
Expand All @@ -146,17 +156,23 @@ profileRouter.get("/", strongJwtVerification, async (_: Request, res: Response)
* {"error": "InternalError"}
*/

profileRouter.get("/id/:USERID", weakJwtVerification, async (req: Request, res: Response) => {
profileRouter.get("/id/:USERID", strongJwtVerification, async (req: Request, res: Response) => {
const userId: string | undefined = req.params.USERID;
const payload: JwtPayload = res.locals.payload as JwtPayload;

if (!userId) {
return res.status(Constants.BAD_REQUEST).send({ error: "InvalidParams" });
return res.redirect("/user/");
}

// Trying to perform elevated operation (getting someone else's profile without elevated perms)
if (userId != payload.id && !hasElevatedPerms(payload)) {
return res.status(Constants.FORBIDDEN).send({ error: "Forbidden" });
}

const user: AttendeeProfile | null = await AttendeeProfileModel.findOne({ userId: userId });

if (!user) {
return res.status(Constants.NOT_FOUND).send({ error: "UserNotFound" });
return res.status(Constants.BAD_REQUEST).send({ error: "UserNotFound" });
}

return res.status(Constants.SUCCESS).send(user);
Expand All @@ -172,16 +188,21 @@ profileRouter.get("/id/:USERID", weakJwtVerification, async (req: Request, res:
* @apiBody {String} discord User's Discord username.
* @apiBody {String} avatarUrl User's avatar URL.
*
* @apiSuccess (200: Success) {Json} user Created user's profile information.
* @apiSuccess (200: Success) {string} userID ID of the user
* @apiSuccess (200: Success) {string} displayName Publicly-visible display name for the user
* @apiSuccess (200: Success) {string} discordTag Discord tag for the user
* @apiSuccess (200: Success) {string} avatarUrl URL that contains the user avatar
* @apiSuccess (200: Success) {number} points Points that the user has
*
* @apiSuccessExample Example Success Response:
* HTTP/1.1 200 OK
* {
* "_id": "abc12345",
* "displayName": "Illinois",
* "discordName": "HackIllinois",
* "userId": "github12345",
* "displayName": "Hack",
* "discord": "HackIllinois",
* "avatarUrl": "na",
* "points": 0,
* "userId": "12345",
* }
*
* @apiError (400: Bad Request) {String} UserAlreadyExists The user profile already exists.
Expand All @@ -194,28 +215,23 @@ profileRouter.get("/id/:USERID", weakJwtVerification, async (req: Request, res:
* HTTP/1.1 500 Internal Server Error
* {"error": "InternalError"}
*/

profileRouter.post("/", strongJwtVerification, async (req: Request, res: Response) => {
const profile: AttendeeProfile = req.body as AttendeeProfile;
const profile: ProfileFormat = req.body as ProfileFormat;
profile.points = Constants.DEFAULT_POINT_VALUE;

if (!isValidProfileModel(profile)) {
return res.status(Constants.BAD_REQUEST).send({ error: "InvalidPostData" });
if (!isValidProfileFormat(profile)) {
return res.status(Constants.BAD_REQUEST).send({ error: "InvalidParams" });
}

const decodedData: JwtPayload = res.locals.payload as JwtPayload;

profile.userId = decodedData.id;
profile.points = Constants.DEFAULT_POINT_VALUE;

// Ensure that user doesn't already exist before creating
const user: AttendeeProfile | null = await AttendeeProfileModel.findOne({ userId: profile.userId });

if (user) {
return res.status(Constants.FAILURE).send({ error: "UserAlreadyExists" });
}

const profileMetadata: AttendeeMetadata = new AttendeeMetadata(profile.userId, Constants.DEFAULT_FOOD_WAVE);

// Create a metadata object, and return it
try {
const profileMetadata: AttendeeMetadata = new AttendeeMetadata(profile.userId, Constants.DEFAULT_FOOD_WAVE);
const newProfile = await AttendeeProfileModel.create(profile);
await AttendeeMetadataModel.create(profileMetadata);
return res.status(Constants.SUCCESS).send(newProfile);
Expand Down
2 changes: 1 addition & 1 deletion src/services/user/user-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ userRouter.get("/qr/:USERID", strongJwtVerification, async (req: Request, res: R
userRouter.get("/:USERID", strongJwtVerification, async (req: Request, res: Response) => {
// If no target user, exact same as next route
if (!req.params.USERID) {
res.redirect("/");
return res.redirect("/");
}

const targetUser: string = req.params.USERID ?? "";
Expand Down
Loading