Skip to content

Commit

Permalink
Set username and calendarId cookie (#100)
Browse files Browse the repository at this point in the history
* create username and calendarId cookie

* refactor

* refactor: use enum

* add try catch to handle for getUserData

* log formatting
  • Loading branch information
prakashchoudhary07 authored Aug 28, 2023
1 parent 936d3ef commit a8a0ec2
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 88 deletions.
78 changes: 49 additions & 29 deletions controllers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,46 @@ import { NextFunction, Request, Response } from 'express';
import logger from '../utils/logger';
import * as authService from '../services/authService';
import { apiResponse } from '../@types/apiReponse';
import { Users } from '@prisma/client';

enum COOKIES_KEYS {
CALENDAR_ID = 'calendar-id',
USERNAME = 'username',
}

/**
* Makes authentication call to google statergy
*
* Sets cookies for
* @param res {Object}
* @param user {Users & { calendarId: number }}
*/
const setCookies = (
res: Response,
user: Users & { calendarId: number }
): void => {
const COOKIE_OPTIONS: any = {
domain: config.get('userAccessToken.cookieDomain'),
expires: new Date(Date.now() + config.get('userAccessToken.ttl') * 1000),
httpOnly: true,
secure: true,
sameSite: 'lax',
};

const token = authService.generateAuthToken({ userId: user.id });

res.cookie(config.get('userAccessToken.cookieName'), token, COOKIE_OPTIONS);
res.cookie(COOKIES_KEYS.CALENDAR_ID, user.calendarId, {
...COOKIE_OPTIONS,
httpOnly: false,
});
res.cookie(COOKIES_KEYS.USERNAME, user.username ?? '', {
...COOKIE_OPTIONS,
httpOnly: false,
});
};

/**
* Makes authentication call to google strategy
*
* @param req {Object} - Express request object
* @param res {Object} - Express response object
Expand Down Expand Up @@ -40,11 +77,13 @@ const googleAuthCallback = (

let rCalUiUrl = new URL(config.get('services.rCalUi.baseUrl'));

try {
rCalUiUrl = new URL(redirectURL);
} catch (error) {
logger.error('Invalid redirect URL provided');
logger.error(error);
if (redirectURL) {
try {
rCalUiUrl = new URL(redirectURL);
} catch (error) {
logger.error('Invalid redirect URL provided');
logger.error(error);
}
}

try {
Expand All @@ -54,20 +93,10 @@ const googleAuthCallback = (
return res.boom(Boom.unauthorized('User cannot be authenticated'));
}

const userData = await authService.loginOrSignupWithGoogle(user._json);

const token = authService.generateAuthToken({ userId: userData?.id });
const data = await authService.loginOrSignupWithGoogle(user._json);

// respond with a cookie
res.cookie(config.get('userAccessToken.cookieName'), token, {
domain: config.get('userAccessToken.cookieDomain'),
expires: new Date(
Date.now() + config.get('userAccessToken.ttl') * 1000
),
httpOnly: true,
secure: true,
sameSite: 'lax',
});
setCookies(res, data);

return res.redirect(rCalUiUrl.href);
})(req, res, next);
Expand All @@ -92,19 +121,10 @@ const microsoftAuthCallback = (
logger.error(err);
return res.boom(Boom.unauthorized('User cannot be authenticated'));
}
const userData = await authService.loginOrSignupWithMicrosoft(user._json);
const token = authService.generateAuthToken({ userId: userData?.id });
const data = await authService.loginOrSignupWithMicrosoft(user._json);

// respond with a cookie
res.cookie(config.get('userAccessToken.cookieName'), token, {
domain: config.get('userAccessToken.cookieDomain'),
expires: new Date(
Date.now() + config.get('userAccessToken.ttl') * 1000
),
httpOnly: true,
secure: true,
sameSite: 'lax',
});
setCookies(res, data);

return res.redirect(rCalUiUrl.href);
})(req, res, next);
Expand Down
166 changes: 107 additions & 59 deletions services/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import jwt from 'jsonwebtoken';
import { GoogleOAuthJson, MicrosoftOAuthJson } from '../@types/providers';
import prisma from '../prisma/prisma';
import { jwtPayload } from '../@types/services';
import { Users } from '@prisma/client';
import { Calendar, Users } from '@prisma/client';

/**
* Generates the JWT
Expand Down Expand Up @@ -39,47 +39,117 @@ const decodeAuthToken = (token: string): any => {
return jwt.decode(token);
};

/**
*
* Returns user details with calendarId
* @param email string
* @returns Promise<Users & { calendarId: number }
*/
const getUserData = async (
email: string
): Promise<(Users & { calendarId: number }) | undefined> => {
try {
const user = await prisma.users.findUniqueOrThrow({
where: {
email,
},
include: {
Calendar: {
where: {
isDeleted: false,
isPrimary: true,
},
select: {
id: true,
},
},
},
});
return { ...user, calendarId: user.Calendar[0].id };
} catch (error) {
logger.info('Could not find user', error);
return undefined;
}
};

/**
*
* Creates new user in DB
* @param user GoogleOAuthJson | MicrosoftOAuthJson
* @returns Promise<{ user: Users; calendar: Calendar }
*/
const createNewUser = async (
user: GoogleOAuthJson | MicrosoftOAuthJson
): Promise<{ user: Users; calendar: Calendar }> => {
let data!: {
email: string;
firstname: string;
lastname: string;
emailVerified: boolean;
googleProfileId?: string;
microsoftProfileId?: string;
};
if ('email' in user) {
logger.info(
`User with email ${user.email} does not exist. Creating new account from Google`
);
data = {
email: user.email,
firstname: user.given_name,
lastname: user.family_name,
emailVerified: true,
googleProfileId: user?.sub,
};
} else if ('mail' in user) {
logger.info(
`User with email ${user.mail} does not exist. Creating new account from Microsoft`
);
data = {
email: user.mail ?? user.userPrincipalName,
firstname: user.givenName,
lastname: user.surname,
emailVerified: true,
microsoftProfileId: user.id,
};
}

const createdUser: Users | undefined = await prisma.users.create({ data });

if (!createdUser) {
logger.error(`Failed to create user.`);
throw new Error('Failed to create user');
}

logger.info(`Creating users default calender.`);

const createdCalendar = await prisma.calendar.create({
data: {
name: createdUser.email,
ownerId: createdUser.id,
isPrimary: true,
},
});

return { user: createdUser, calendar: createdCalendar };
};

/**
* Login or signUp with Google
* @param googleProfile{Object} : Google profile response from Google OAuth2.0
*/
const loginOrSignupWithGoogle = async (
googleProfile: GoogleOAuthJson
): Promise<Users> => {
): Promise<Users & { calendarId: number }> => {
try {
const user = await prisma.users.findUnique({
where: {
email: googleProfile?.email,
},
});
const user = await getUserData(googleProfile.email);

if (user) {
return user;
} else {
logger.info(
`User with email ${googleProfile?.email} does not exist. Creating new account.`
);
const createdUser = await prisma.users.create({
data: {
email: googleProfile?.email,
firstname: googleProfile?.given_name,
lastname: googleProfile?.family_name,
emailVerified: true,
googleProfileId: googleProfile?.sub,
},
});

logger.info(`Creating users default calender.`);
const { user: createdUser, calendar: createdCalendar } =
await createNewUser(googleProfile);

await prisma.calendar.create({
data: {
name: createdUser.email,
ownerId: createdUser.id,
isPrimary: true,
},
});

return createdUser;
return { ...createdUser, calendarId: createdCalendar.id };
}
} catch (err: any) {
logger.error('loginOrSignupWithGoogle:: Error in authenticating user', {
Expand All @@ -96,41 +166,19 @@ const loginOrSignupWithGoogle = async (
*/
const loginOrSignupWithMicrosoft = async (
microsoftProfile: MicrosoftOAuthJson
): Promise<Users> => {
): Promise<Users & { calendarId: number }> => {
try {
const user = await prisma.users.findUnique({
where: {
email: microsoftProfile.mail ?? microsoftProfile?.userPrincipalName,
},
});
const user = await getUserData(
microsoftProfile.mail ?? microsoftProfile?.userPrincipalName
);

if (user) {
return user;
} else {
logger.info(
`User with email ${microsoftProfile?.userPrincipalName} does not exist. Creating new account.`
);
const createdUser = await prisma.users.create({
data: {
email: microsoftProfile.mail ?? microsoftProfile?.userPrincipalName,
firstname: microsoftProfile?.givenName,
lastname: microsoftProfile?.surname,
emailVerified: true,
microsoftProfileId: microsoftProfile?.id,
},
});

logger.info(`Creating users default calender.`);

await prisma.calendar.create({
data: {
name: createdUser.email,
ownerId: createdUser.id,
isPrimary: true,
},
});
const { user: createdUser, calendar: createdCalendar } =
await createNewUser(microsoftProfile);

return createdUser;
return { ...createdUser, calendarId: createdCalendar.id };
}
} catch (err: any) {
logger.error('loginOrSignupWithGoogle:: Error in authenticating user', {
Expand Down

0 comments on commit a8a0ec2

Please sign in to comment.