Skip to content

Commit

Permalink
Merge pull request #113 from ReflectionsProjections/dev/devp/sponsor
Browse files Browse the repository at this point in the history
Sponsor Endpoints
  • Loading branch information
divyack2 authored Jul 31, 2024
2 parents b3e1945 + 7e69f88 commit 5397229
Show file tree
Hide file tree
Showing 9 changed files with 480 additions and 90 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/bcrypt": "^5.0.2",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.21",
"@types/express-rate-limit": "^6.0.0",
Expand Down Expand Up @@ -50,6 +51,7 @@
"@types/cors": "^2.8.17",
"aws-sdk": "^2.1604.0",
"axios": "^1.6.8",
"bcrypt": "*",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"crypto": "^1.0.1",
Expand Down
5 changes: 5 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export const Config = {
MAX_RESUME_SIZE_BYTES: 6 * 1024 * 1024,
RESUME_URL_EXPIRY_SECONDS: 60,

HASH_SALT_ROUNDS: 10,
VERIFY_EXP_TIME_MS: 300,

// QR Scanning
QR_HASH_ITERATIONS: 10000,
QR_HASH_SECRET: getEnv("QR_HASH_SECRET"),
Expand All @@ -93,6 +96,8 @@ export const DeviceRedirects: Record<string, string> = {

export const ses = new AWS.SES({
region: Config.S3_REGION,
accessKeyId: Config.S3_ACCESS_KEY,
secretAccessKey: Config.S3_SECRET_KEY,
});

export default Config;
9 changes: 9 additions & 0 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ import {
SpeakerSchema,
SpeakerValidator,
} from "./services/speakers/speakers-schema";
import {
SponsorAuthSchema,
SponsorAuthValidator,
} from "./services/auth/sponsor/sponsor-schema";
import {
CorporateSchema,
CorporateValidator,
Expand Down Expand Up @@ -97,6 +101,11 @@ export const Database = {
NotificationsSchema,
NotificationsValidator
),
AUTH_CODES: initializeModel(
"auth_codes",
SponsorAuthSchema,
SponsorAuthValidator
),
SPEAKERS: initializeModel("speakers", SpeakerSchema, SpeakerValidator),
CORPORATE: initializeModel(
"corporate",
Expand Down
3 changes: 3 additions & 0 deletions src/services/auth/auth-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import RoleChecker from "../../middleware/role-checker";
import { Role } from "../auth/auth-models";
import { AuthRoleChangeRequest } from "./auth-schema";
import { z } from "zod";
import authSponsorRouter from "./sponsor/sponsor-router";

const authStrategies: Record<string, GoogleStrategy> = {};

Expand All @@ -19,6 +20,8 @@ for (const key in DeviceRedirects) {

const authRouter = Router();

authRouter.use("/sponsor", authSponsorRouter);

// Remove role from userId by email address (admin only endpoint)
authRouter.delete(
"/",
Expand Down
6 changes: 0 additions & 6 deletions src/services/auth/auth-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,3 @@ export function isStaff(payload?: JwtPayloadType) {
export function isAdmin(payload?: JwtPayloadType) {
return payload?.roles.includes(Role.Enum.ADMIN);
}

export async function sponsorExists(email: string) {
const response = await Database.CORPORATE.findOne({ email: email });
if (!response) return false;
return true;
}
89 changes: 89 additions & 0 deletions src/services/auth/sponsor/sponsor-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Router } from "express";
import { Database } from "../../../database";
import { StatusCodes } from "http-status-codes";
import { sendEmail } from "../../ses/ses-utils";
import jsonwebtoken from "jsonwebtoken";
import { Config } from "../../../config";
import { Role } from "../../auth/auth-models";
import {
createSixDigitCode,
encryptSixDigitCode,
sponsorExists,
} from "./sponsor-utils";
import * as bcrypt from "bcrypt";
import {
AuthSponsorLoginValidator,
AuthSponsorVerifyValidator,
} from "./sponsor-schema";

const authSponsorRouter = Router();

authSponsorRouter.post("/login", async (req, res, next) => {
try {
const { email } = AuthSponsorLoginValidator.parse(req.body);
if (!(await sponsorExists(email))) {
return res.sendStatus(StatusCodes.UNAUTHORIZED);
}

const sixDigitCode = createSixDigitCode();
const expTime =
Math.floor(Date.now() / 1000) + Config.VERIFY_EXP_TIME_MS;
const hashedVerificationCode = encryptSixDigitCode(sixDigitCode);
await Database.AUTH_CODES.findOneAndUpdate(
{ email },
{
hashedVerificationCode,
expTime,
},
{ upsert: true }
);
await sendEmail(
email,
"R|P Sponsor Email Verification!",
`Here is your verification code: ${sixDigitCode}`
);
return res.sendStatus(StatusCodes.CREATED);
} catch (error) {
next(error);
}
});

authSponsorRouter.post("/verify", async (req, res, next) => {
try {
const { email, sixDigitCode } = AuthSponsorVerifyValidator.parse(
req.body
);
const sponsorData = await Database.AUTH_CODES.findOneAndDelete({
email,
});
if (!sponsorData) {
return res.sendStatus(StatusCodes.UNAUTHORIZED);
}
if (Math.floor(Date.now() / 1000) > sponsorData.expTime) {
return res.sendStatus(StatusCodes.GONE);
}
const match = bcrypt.compareSync(
sixDigitCode,
sponsorData.hashedVerificationCode
);
if (!match) {
return res.sendStatus(StatusCodes.UNAUTHORIZED);
}
const token = jsonwebtoken.sign(
{
email,
role: Role.Enum.CORPORATE,
},
Config.JWT_SIGNING_SECRET,
{
expiresIn:
Math.floor(Date.now() / 1000) + Config.JWT_EXPIRATION_TIME,
}
);
return res.status(StatusCodes.OK).json({ token });
} catch (error) {
next(error);
}
});

export default authSponsorRouter;
23 changes: 23 additions & 0 deletions src/services/auth/sponsor/sponsor-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import mongoose from "mongoose";
import { z } from "zod";

export const SponsorAuthSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
hashedVerificationCode: { type: String, required: true },
expTime: { type: Number, required: true },
});

export const SponsorAuthValidator = z.object({
email: z.string().email(),
hashedVerificationCode: z.string(),
expTime: z.number().int(),
});

export const AuthSponsorLoginValidator = z.object({
email: z.string().email(),
});

export const AuthSponsorVerifyValidator = z.object({
email: z.string().email(),
sixDigitCode: z.string().length(6),
});
28 changes: 28 additions & 0 deletions src/services/auth/sponsor/sponsor-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as bcrypt from "bcrypt";
import { Database } from "../../../database";
import { Config } from "../../../config";

export function createSixDigitCode() {
let result = "";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (let i = 0; i < 6; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}

export function encryptSixDigitCode(sixDigitCode: string): string {
try {
const hash = bcrypt.hashSync(sixDigitCode, Config.HASH_SALT_ROUNDS);
return hash;
} catch (err) {
console.error("Error encrypting the code:", err);
throw err;
}
}

export async function sponsorExists(email: string) {
const response = await Database.CORPORATE.findOne({ email: email });
if (!response) return false;
return true;
}
Loading

0 comments on commit 5397229

Please sign in to comment.