Skip to content

Commit

Permalink
Adds sendSignInLink mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
HeetShah committed Nov 20, 2023
1 parent bc1d195 commit 75e7f5c
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ backend/typescript/serviceAccount.json
package-lock.json
.gitignore
package.json
backend/typescript/graphql/sampleData/users.json
1 change: 1 addition & 0 deletions backend/typescript/graphql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const graphQLMiddlewares = {
deleteUserByEmail: authorizedByAdmin(),
logout: isAuthorizedByUserId("userId"),
resetPassword: isAuthorizedByEmail("email"),
sendSignInLink: authorizedByAllRoles(),
},
};

Expand Down
15 changes: 13 additions & 2 deletions backend/typescript/graphql/resolvers/authResolvers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// import { CookieOptions } from "express";

import * as firebaseAdmin from "firebase-admin";
import nodemailerConfig from "../../nodemailer.config";
import AuthService from "../../services/implementations/authService";
Expand All @@ -15,7 +14,10 @@ import IReviewService from "../../services/interfaces/reviewService";
import ReviewService from "../../services/implementations/reviewService";

const userService: IUserService = new UserService();
const emailService: IEmailService = new EmailService(nodemailerConfig);
const emailService: IEmailService = new EmailService(
nodemailerConfig,
"UW Blueprint Internal Tools Team",
);
const authService: IAuthService = new AuthService(userService, emailService);
const reviewService: IReviewService = new ReviewService();

Expand Down Expand Up @@ -142,6 +144,15 @@ const authResolvers = {
await authService.resetPassword(email);
return true;
},
sendSignInLink: async (
_parent: undefined,
{ email }: { email: string },
): Promise<boolean> => {
await authService.sendSignInLink(email).catch((err) => {
throw err;
});
return true;
},
},
};

Expand Down
1 change: 1 addition & 0 deletions backend/typescript/graphql/types/authType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const authType = gql`
refresh(refreshToken: String!): String!
logout(userId: ID!): ID
resetPassword(email: String!): Boolean!
sendSignInLink(email: String!): Boolean!
}
`;

Expand Down
63 changes: 62 additions & 1 deletion backend/typescript/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ApolloServer } from "apollo-server-express";
import { sequelize } from "./models";
import schema from "./graphql";
import Application from "./models/application.model";
import memeberData from "./graphql/sampleData/members.json";
import firebaseAuthUsers from "./graphql/sampleData/users.json";

const CORS_ALLOW_LIST = [
"http://localhost:3000",
Expand Down Expand Up @@ -63,6 +65,65 @@ admin.initializeApp({
const db = admin.database();
const ref = db.ref("studentApplications");

app.get("/diff", async (req, res) => {
const currentTerm = memeberData.term;
const currentTermMembers: string[] = [];

// const teamToMembers : Record<string, string[]> = {};
memeberData.members.forEach((member) => {
if (member.term === currentTerm) {
currentTermMembers.push(member.name);
// if (teamToMembers[member.teams[0]]) {
// teamToMembers[member.teams[0]].push(member.name);
// } else {
// teamToMembers[member.teams[0]] = [member.name];
// }
}
});

// const teamToMemberSize : Record<string, number> = {};
// (Object.keys(teamToMembers)).forEach((team) => {
// teamToMemberSize[team] = teamToMembers[team].length;
// }
// )

const firebaseUsers: Record<string, string | undefined> = {};
firebaseAuthUsers.forEach((user) => {
firebaseUsers[user.uid] = user.displayName;
});

// see if all currentTermMembers have their name in firebase_users
const missingMembersFromFirebaseAuth: string[] = [];

currentTermMembers.forEach((member) => {
if (!Object.values(firebaseUsers).includes(member)) {
missingMembersFromFirebaseAuth.push(member);
}
});

res.status(200).json({
currentTerm,
currentTermMembers,
firebaseUsers,
missingMembersFromFirebaseAuth,
});
});

app.get("/authUsers", async (req, res) => {
try {
admin
.auth()
.listUsers()
.then((data) => {
res.status(200).json(data.users);
});
} catch (error) {
res
.status(500)
.send("An error occurred while retrieving the applications.");
}
});

app.get("/termApplications", async (req, res) => {
ref
.orderByChild("term")
Expand Down Expand Up @@ -103,13 +164,13 @@ app.get("/applications/:id", async (req, res) => {
res.status(404).send("Student application not found.");
}
} catch (error) {
console.error(error);
res
.status(500)
.send("An error occurred while retrieving the student application.");
}
});

app.listen({ port: process.env.PORT || 5000 }, () => {
// eslint-disable-next-line no-console
console.info(`Server is listening on port ${process.env.PORT || 5000}!`);
});
49 changes: 49 additions & 0 deletions backend/typescript/services/implementations/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,55 @@ class AuthService implements IAuthService {
return false;
}
}

async sendSignInLink(email: string): Promise<boolean> {
if (!this.emailService) {
const errorMessage =
"Attempted to call sendEmailVerificationLink but this instance of AuthService does not have an EmailService instance";
Logger.error(errorMessage);
throw new Error(errorMessage);
}

if (!email.endsWith("@uwblueprint.org")) {
const errorMessage = `Attempted to call sendEmailVerificationLink with an email, ${email}, that does not end with @uwblueprint.org`;
Logger.error(errorMessage);
throw new Error(errorMessage);
}

try {
await firebaseAdmin
.auth()
.generateSignInWithEmailLink(email, {
url: `${process.env.FIREBASE_REQUEST_URI}/admin`,
handleCodeInApp: true,
})
.then((link) => {
const emailBody = `
Hello,
<br><br>
We noticed that you are a current UW Blueprint member but do not have an account in our internal recruitment tool. Please click the following link to sign in with your blueprint email.
<br><br>
<a href=${link}>Sign into internal recruitment tool</a>`;

return this.emailService?.sendEmail(
email,
"Sign into internal recruitment tool",
emailBody,
);
})
.catch((error) => {
Logger.error("Failed to send email sign in link to user with email");
throw error;
});

return true;
} catch (error) {
Logger.error(
`Failed to generate email sign in link for user with email ${email} ${error}`,
);
throw error;
}
}
}

export default AuthService;
8 changes: 8 additions & 0 deletions backend/typescript/services/interfaces/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ interface IAuthService {
accessToken: string,
requestedEmail: string,
): Promise<boolean>;

/**
* Sends an email to the input with a sign in link to the application. This will be used to create a user in firebase for current term BP members who are not in the database.
* @param email email of user to be created
* @throws Error if unable to generate link or send email
* @returns true if email sent successfully
*/
sendSignInLink(email: string): Promise<boolean>;
}

export default IAuthService;

0 comments on commit 75e7f5c

Please sign in to comment.