Skip to content

Commit

Permalink
Feat/updating verification flow (#1990)
Browse files Browse the repository at this point in the history
* Feat : Added API to verify external-account

* Tests : Updated fixures & external-account response & payload for tests

* Test : Added tests for external-account/link API

* Test : Added more tests to inc code coverage

---------

Co-authored-by: Achintya Chatterjee <[email protected]>
  • Loading branch information
joyguptaa and Achintya-Chatterjee authored Apr 4, 2024
1 parent 0a199eb commit 2fc51c6
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 9 deletions.
30 changes: 30 additions & 0 deletions controllers/external-accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,35 @@ const getExternalAccountData = async (req, res) => {
return res.boom.serverUnavailable(SOMETHING_WENT_WRONG);
}
};
const linkExternalAccount = async (req, res) => {
try {
const { id: userId, roles } = req.userData;

const externalAccountData = await externalAccountsModel.fetchExternalAccountData(req.query, req.params.token);
if (!externalAccountData.id) {
return res.boom.notFound("No data found");
}

const attributes = externalAccountData.attributes;
if (attributes.expiry && attributes.expiry < Date.now()) {
return res.boom.unauthorized("Token Expired. Please generate it again");
}

await addOrUpdate(
{
roles: { ...roles, in_discord: true },
discordId: attributes.discordId,
discordJoinedAt: attributes.discordJoinedAt,
},
userId
);

return res.status(204).json({ message: "Your discord profile has been linked successfully" });
} catch (error) {
logger.error(`Error getting external account data: ${error}`);
return res.boom.serverUnavailable(SOMETHING_WENT_WRONG);
}
};

/**
* @deprecated
Expand Down Expand Up @@ -222,6 +251,7 @@ const newSyncExternalAccountData = async (req, res) => {
module.exports = {
addExternalAccountData,
getExternalAccountData,
linkExternalAccount,
syncExternalAccountData,
newSyncExternalAccountData,
externalAccountsUsersPostHandler,
Expand Down
41 changes: 35 additions & 6 deletions middlewares/validators/external-accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@ const joi = require("joi");
const { EXTERNAL_ACCOUNTS_POST_ACTIONS } = require("../../constants/external-accounts");

const externalAccountData = async (req, res, next) => {
const schema = joi.object().strict().keys({
type: joi.string().required(),
token: joi.string().required(),
attributes: joi.object().strict().required(),
});
const schema = joi
.object()
.strict()
.keys({
type: joi.string().required(),
token: joi.string().required(),
attributes: {
userName: joi.string().required(),
discriminator: joi.string().required(),
userAvatar: joi.string().required(),
discordId: joi.string().required(),
discordJoinedAt: joi.string().required(),
expiry: joi.number().required(),
},
});

try {
await schema.validateAsync(req.body);
Expand Down Expand Up @@ -35,4 +45,23 @@ const postExternalAccountsUsers = async (req, res, next) => {
res.boom.badRequest(error.details[0].message);
}
};
module.exports = { externalAccountData, postExternalAccountsUsers };

const linkDiscord = async (req, res, next) => {
const { token } = req.params;

const schema = joi.object({
token: joi.string().required(),
});

const validationOptions = { abortEarly: false };

try {
await schema.validateAsync({ token }, validationOptions);
next();
} catch (error) {
logger.error(`Error retrieving event: ${error}`);
res.boom.badRequest(error.details.map((detail) => detail.message));
}
};

module.exports = { externalAccountData, postExternalAccountsUsers, linkDiscord };
1 change: 1 addition & 0 deletions routes/external-accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const { authorizeAndAuthenticate } = require("../middlewares/authorizeUsersAndSe

router.post("/", validator.externalAccountData, authorizeBot.verifyDiscordBot, externalAccount.addExternalAccountData);
router.get("/:token", authenticate, externalAccount.getExternalAccountData);
router.patch("/link/:token", authenticate, validator.linkDiscord, externalAccount.linkExternalAccount);
router.patch("/discord-sync", authenticate, authorizeRoles([SUPERUSER]), externalAccount.syncExternalAccountData);
router.post(
"/users",
Expand Down
16 changes: 16 additions & 0 deletions test/fixtures/external-accounts/external-accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ module.exports = () => {
type: "discord",
token: "<TOKEN>",
attributes: {
userName: "<USER_NAME>",
discriminator: "<DISCRIMINATOR>",
userAvatar: "<USER_AVATAR>",
discordId: "<DISCORD_ID>",
discordJoinedAt: "<DISCORD_JOINED_AT>",
expiry: 1674041460211,
},
},
Expand All @@ -13,23 +17,35 @@ module.exports = () => {
type: "discord",
token: 123,
attributes: {
userName: "<USER_NAME>",
discriminator: "<DISCRIMINATOR>",
userAvatar: "<USER_AVATAR>",
discordId: "<DISCORD_ID>",
discordJoinedAt: "<DISCORD_JOINED_AT>",
expiry: 1674041460211,
},
},
{
type: "discord",
token: "<TOKEN>",
attributes: {
userName: "<USER_NAME>",
discriminator: "<DISCRIMINATOR>",
userAvatar: "<USER_AVATAR>",
discordId: "<DISCORD_ID>",
discordJoinedAt: "<DISCORD_JOINED_AT>",
expiry: Date.now() + 600000,
},
},
{
type: "discord",
token: "<TOKEN_1>",
attributes: {
userName: "<USER_NAME>",
discriminator: "<DISCRIMINATOR>",
userAvatar: "<USER_AVATAR>",
discordId: "<DISCORD_ID>",
discordJoinedAt: "<DISCORD_JOINED_AT>",
expiry: Date.now() - 600000,
},
},
Expand Down
1 change: 0 additions & 1 deletion test/fixtures/user/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ module.exports = () => {
linkedin_id: "sagarbajpai",
github_id: "sagarbajpai",
github_display_name: "Sagar Bajpai",
discordJoinedAt: "2023-04-06T01:47:34.488000+00:00",
phone: "1234567890",
email: "[email protected]",
status: "active",
Expand Down
110 changes: 110 additions & 0 deletions test/integration/external-accounts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -425,4 +425,114 @@ describe("External Accounts", function () {
});
});
});

describe("PATCH /external-accounts/link/:token", function () {
let newUserJWT;

beforeEach(async function () {
const userId = await addUser(userData[3]);
newUserJWT = authService.generateAuthToken({ userId });
await externalAccountsModel.addExternalAccountData(externalAccountData[2]);
await externalAccountsModel.addExternalAccountData(externalAccountData[3]);
});

afterEach(async function () {
Sinon.restore();
await cleanDb();
});

it("Should return 404 when token is not provided in path variable", async function () {
const res = await chai.request(app).patch("/external-accounts/link").set("Cookie", `${cookieName}=${newUserJWT}`);
expect(res).to.have.status(404);
expect(res.body.message).to.equal("Not Found");
});

it("Should return 404 when no data found", function (done) {
chai
.request(app)
.get("/external-accounts/<TOKEN_2>")
.set("Authorization", `Bearer ${newUserJWT}`)
.end((err, res) => {
if (err) {
return done(err);
}
expect(res).to.have.status(404);
expect(res.body).to.have.property("message");
expect(res.body.message).to.equal("No data found");

return done();
});
});

it("Should return 401 when token is expired", function (done) {
chai
.request(app)
.get("/external-accounts/<TOKEN_1>")
.set("Authorization", `Bearer ${newUserJWT}`)
.end((err, res) => {
if (err) {
return done(err);
}
expect(res).to.have.status(401);
expect(res.body).to.be.an("object");
expect(res.body).to.eql({
statusCode: 401,
error: "Unauthorized",
message: "Token Expired. Please generate it again",
});

return done();
});
});

it("Should return 401 when user is not authenticated", function (done) {
chai
.request(app)
.get("/external-accounts/<TOKEN>")
.end((err, res) => {
if (err) {
return done(err);
}
expect(res).to.have.status(401);
expect(res.body).to.be.an("object");
expect(res.body).to.eql({
statusCode: 401,
error: "Unauthorized",
message: "Unauthenticated User",
});

return done();
});
});

it("Should return 204 when valid action is provided", async function () {
await externalAccountsModel.addExternalAccountData(externalAccountData[2]);
const getUserResponseBeforeUpdate = await chai
.request(app)
.get("/users/self")
.set("cookie", `${cookieName}=${newUserJWT}`);

expect(getUserResponseBeforeUpdate).to.have.status(200);
expect(getUserResponseBeforeUpdate.body.roles.in_discord).to.equal(false);
expect(getUserResponseBeforeUpdate.body).to.not.have.property("discordId");
expect(getUserResponseBeforeUpdate.body).to.not.have.property("discordJoinedAt");

const response = await chai
.request(app)
.patch(`/external-accounts/link/${externalAccountData[2].token}`)
.query({ action: EXTERNAL_ACCOUNTS_POST_ACTIONS.DISCORD_USERS_SYNC })
.set("Cookie", `${cookieName}=${newUserJWT}`);

expect(response).to.have.status(204);

const updatedUserDetails = await chai
.request(app)
.get("/users/self")
.set("cookie", `${cookieName}=${newUserJWT}`);

expect(updatedUserDetails.body.roles.in_discord).to.equal(true);
expect(updatedUserDetails.body).to.have.property("discordId");
expect(updatedUserDetails.body).to.have.property("discordJoinedAt");
});
});
});
33 changes: 31 additions & 2 deletions test/unit/middlewares/external-accounts-validator.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const Sinon = require("sinon");
const { externalAccountData, postExternalAccountsUsers } = require("../../../middlewares/validators/external-accounts");
const {
externalAccountData,
postExternalAccountsUsers,
linkDiscord,
} = require("../../../middlewares/validators/external-accounts");
const { EXTERNAL_ACCOUNTS_POST_ACTIONS } = require("../../../constants/external-accounts");
const { expect } = require("chai");

Expand All @@ -10,7 +14,14 @@ describe("Middleware | Validators | external accounts", function () {
body: {
type: "some type",
token: "some token",
attributes: {},
attributes: {
userName: "some name",
discriminator: "some discriminator",
userAvatar: "some avatar",
discordId: "some id",
discordJoinedAt: "some date",
expiry: Date.now(),
},
},
};
const res = {};
Expand Down Expand Up @@ -62,4 +73,22 @@ describe("Middleware | Validators | external accounts", function () {
expect(res.boom.badRequest.callCount).to.be.equal(1);
});
});

describe("linkDiscord", function () {
it("should call next with a valid token", async function () {
const req = { params: { token: "validToken" } };
const res = {};
const nextSpy = Sinon.spy();
await linkDiscord(req, res, nextSpy);
expect(nextSpy.calledOnce).to.be.equal(true);
});

it("should throw an error when token is empty", async function () {
const req = { params: { token: "" } };
const res = { boom: { badRequest: Sinon.spy() } };
const nextSpy = Sinon.spy();
await linkDiscord(req, res, nextSpy);
expect(res.boom.badRequest.calledOnce).to.be.equal(true);
});
});
});
4 changes: 4 additions & 0 deletions test/unit/models/external-accounts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ describe("External Accounts", function () {
expect(response.token).to.equal(externalAccountData[2].token);
expect(response.attributes).to.be.eql({
discordId: externalAccountData[2].attributes.discordId,
discordJoinedAt: externalAccountData[2].attributes.discordJoinedAt,
expiry: externalAccountData[2].attributes.expiry,
userName: externalAccountData[2].attributes.userName,
discriminator: externalAccountData[2].attributes.discriminator,
userAvatar: externalAccountData[2].attributes.userAvatar,
});
});

Expand Down

0 comments on commit 2fc51c6

Please sign in to comment.