Skip to content

Commit 5c00318

Browse files
Add redirectURL params in github/login route (#937)
* feat: add redirectURL params in github/loign route * Fix tests * chore:fix-test * chore: add new test coverage * fix endswith edgecase
1 parent 53b80cd commit 5c00318

File tree

4 files changed

+129
-9
lines changed

4 files changed

+129
-9
lines changed

controllers/auth.js

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,45 @@ const QrCodeAuthModel = require("../models/qrCodeAuth");
44
const authService = require("../services/authService");
55
const { SOMETHING_WENT_WRONG, DATA_ADDED_SUCCESSFULLY, BAD_REQUEST } = require("../constants/errorMessages");
66

7+
/**
8+
* Makes authentication call to GitHub statergy
9+
*
10+
* @param req {Object} - Express request object
11+
* @param res {Object} - Express response object
12+
* @param next {Function} - Express middleware function
13+
*/
14+
const githubAuthLogin = (req, res, next) => {
15+
const redirectURL = req.query.redirectURL;
16+
return passport.authenticate("github", {
17+
scope: ["user:email"],
18+
state: redirectURL,
19+
})(req, res, next);
20+
};
21+
722
/**
823
* Fetches the user info from GitHub and authenticates User
924
*
1025
* @param req {Object} - Express request object
1126
* @param res {Object} - Express response object
1227
* @param next {Function} - Express middleware function
1328
*/
14-
const githubAuth = (req, res, next) => {
29+
const githubAuthCallback = (req, res, next) => {
1530
let userData;
1631
const rdsUiUrl = new URL(config.get("services.rdsUi.baseUrl"));
17-
let authRedirectionUrl = req.query.state ?? rdsUiUrl;
18-
32+
let authRedirectionUrl = rdsUiUrl;
33+
if ("state" in req.query) {
34+
try {
35+
const redirectUrl = new URL(req.query.state);
36+
if (`.${redirectUrl.hostname}`.endsWith(`.${rdsUiUrl.hostname}`)) {
37+
// Matching *.realdevsquad.com
38+
authRedirectionUrl = redirectUrl;
39+
} else {
40+
logger.error(`Malicious redirect URL provided URL: ${redirectUrl}, Will redirect to RDS`);
41+
}
42+
} catch (error) {
43+
logger.error("Invalid redirect URL provided", error);
44+
}
45+
}
1946
try {
2047
return passport.authenticate("github", { session: false }, async (err, accessToken, user) => {
2148
if (err) {
@@ -137,7 +164,8 @@ const fetchUserDeviceInfo = async (req, res) => {
137164
};
138165

139166
module.exports = {
140-
githubAuth,
167+
githubAuthLogin,
168+
githubAuthCallback,
141169
signout,
142170
storeUserDeviceInfo,
143171
updateAuthStatus,

routes/auth.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
const express = require("express");
2-
const passport = require("passport");
32
const router = express.Router();
43
const auth = require("../controllers/auth");
54
const authenticate = require("../middlewares/authenticate");
65
const userDeviceInfoValidator = require("../middlewares/validators/qrCodeAuth");
76
const qrCodeAuthValidator = require("../middlewares/validators/qrCodeAuth");
87

9-
router.get("/github/login", passport.authenticate("github", { scope: ["user:email"] }));
8+
router.get("/github/login", auth.githubAuthLogin);
109

11-
router.get("/github/callback", auth.githubAuth);
10+
router.get("/github/callback", auth.githubAuthCallback);
1211

1312
router.get("/signout", auth.signout);
1413

test/integration/auth.test.js

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const chaiHttp = require("chai-http");
55
const passport = require("passport");
66
const app = require("../../server");
77
const cleanDb = require("../utils/cleanDb");
8+
const { generateGithubAuthRedirectUrl } = require("..//utils/github");
89
const { addUserToDBForTest } = require("../../utils/users");
910
const userData = require("../fixtures/user/user")();
1011

@@ -20,6 +21,25 @@ describe("auth", function () {
2021
sinon.restore();
2122
});
2223

24+
it("should return github call back URL", async function () {
25+
const githubOauthURL = generateGithubAuthRedirectUrl({});
26+
const res = await chai.request(app).get("/auth/github/login").redirects(0);
27+
expect(res).to.have.status(302);
28+
expect(res.headers.location).to.equal(githubOauthURL);
29+
});
30+
31+
it("should return github call back URL with redirectUrl", async function () {
32+
const RDS_MEMBERS_SITE_URL = "https://members.realdevsquad.com";
33+
const githubOauthURL = generateGithubAuthRedirectUrl({ state: RDS_MEMBERS_SITE_URL });
34+
const res = await chai
35+
.request(app)
36+
.get("/auth/github/login")
37+
.query({ redirectURL: RDS_MEMBERS_SITE_URL })
38+
.redirects(0);
39+
expect(res).to.have.status(302);
40+
expect(res.headers.location).to.equal(githubOauthURL);
41+
});
42+
2343
it("should redirect the user to new sign up flow if they are have incomplte user details true", async function () {
2444
const redirectURL = "https://my.realdevsquad.com/new-signup";
2545
sinon.stub(passport, "authenticate").callsFake((strategy, options, callback) => {
@@ -38,8 +58,7 @@ describe("auth", function () {
3858
// same data should be return from github and same data should be added there
3959
it("should redirect the request to the goto page on successful login, if user has incomplete user details false", async function () {
4060
await addUserToDBForTest(userData[0]);
41-
const rdsUiUrl = config.get("services.rdsUi.baseUrl");
42-
61+
const rdsUiUrl = new URL(config.get("services.rdsUi.baseUrl")).href;
4362
sinon.stub(passport, "authenticate").callsFake((strategy, options, callback) => {
4463
callback(null, "accessToken", githubUserInfo[0]);
4564
return (req, res, next) => {};
@@ -54,6 +73,59 @@ describe("auth", function () {
5473
expect(res.headers.location).to.equal(rdsUiUrl);
5574
});
5675

76+
it("should redirect the request to the redirect URL provided on successful login, if user has incomplete user details false", async function () {
77+
await addUserToDBForTest(userData[0]);
78+
const rdsUrl = new URL("https://dashboard.realdevsquad.com").href;
79+
sinon.stub(passport, "authenticate").callsFake((strategy, options, callback) => {
80+
callback(null, "accessToken", githubUserInfo[0]);
81+
return (req, res, next) => {};
82+
});
83+
84+
const res = await chai
85+
.request(app)
86+
.get(`/auth/github/callback`)
87+
.query({ code: "codeReturnedByGithub", state: rdsUrl })
88+
.redirects(0);
89+
expect(res).to.have.status(302);
90+
expect(res.headers.location).to.equal(rdsUrl);
91+
});
92+
93+
it("should redirect the realdevsquad.com if non RDS URL provided, any url that is other than *.realdevsqud.com is invalid", async function () {
94+
await addUserToDBForTest(userData[0]);
95+
const invalidRedirectUrl = new URL("https://google.com").href;
96+
const rdsUiUrl = new URL(config.get("services.rdsUi.baseUrl")).href;
97+
sinon.stub(passport, "authenticate").callsFake((strategy, options, callback) => {
98+
callback(null, "accessToken", githubUserInfo[0]);
99+
return (req, res, next) => {};
100+
});
101+
102+
const res = await chai
103+
.request(app)
104+
.get(`/auth/github/callback`)
105+
.query({ code: "codeReturnedByGithub", state: invalidRedirectUrl })
106+
.redirects(0);
107+
expect(res).to.have.status(302);
108+
expect(res.headers.location).to.equal(rdsUiUrl);
109+
});
110+
111+
it("should redirect the realdevsquad.com if invalid redirect URL provided", async function () {
112+
await addUserToDBForTest(userData[0]);
113+
const invalidRedirectUrl = "invalidURL";
114+
const rdsUiUrl = new URL(config.get("services.rdsUi.baseUrl")).href;
115+
sinon.stub(passport, "authenticate").callsFake((strategy, options, callback) => {
116+
callback(null, "accessToken", githubUserInfo[0]);
117+
return (req, res, next) => {};
118+
});
119+
120+
const res = await chai
121+
.request(app)
122+
.get(`/auth/github/callback`)
123+
.query({ code: "codeReturnedByGithub", state: invalidRedirectUrl })
124+
.redirects(0);
125+
expect(res).to.have.status(302);
126+
expect(res.headers.location).to.equal(rdsUiUrl);
127+
});
128+
57129
it("should send a cookie with JWT in the response", function (done) {
58130
const rdsUiUrl = new URL(config.get("services.rdsUi.baseUrl"));
59131

test/utils/github.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const defaultClientId = config.get("githubOauth.clientId");
2+
3+
const generateGithubAuthRedirectUrl = function ({
4+
baseUrl = "https://github.com/login/oauth/authorize",
5+
responseType = "code",
6+
redirectUri = "http://localhost:3000/auth/github/callback",
7+
scope = "user:email",
8+
state = "",
9+
clientId = defaultClientId,
10+
}) {
11+
const encodedBaseUrl = encodeURI(baseUrl);
12+
const encodedRedirectUri = encodeURIComponent(redirectUri);
13+
const encodedScope = encodeURIComponent(scope);
14+
let encodedUrl = `${encodedBaseUrl}?response_type=${responseType}&redirect_uri=${encodedRedirectUri}&scope=${encodedScope}`;
15+
if (state) {
16+
encodedUrl += `&state=${encodeURIComponent(state)}`;
17+
}
18+
return `${encodedUrl}&client_id=${clientId}`;
19+
};
20+
21+
module.exports = { generateGithubAuthRedirectUrl };

0 commit comments

Comments
 (0)