From 53ecda9f7a34111785ac3ea3af18cb33460a44af Mon Sep 17 00:00:00 2001 From: Dries Augustyns Date: Mon, 8 Dec 2025 15:29:04 +0100 Subject: [PATCH] fix: Add additional verification in Oauth controllers --- apps/api/src/controllers/Oauth/Github.ts | 26 ++++++++++++++++++++---- apps/api/src/controllers/Oauth/Google.ts | 22 ++++++++++++++++---- apps/api/src/services/UserService.ts | 4 ++++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/apps/api/src/controllers/Oauth/Github.ts b/apps/api/src/controllers/Oauth/Github.ts index 318248d..6e013f8 100644 --- a/apps/api/src/controllers/Oauth/Github.ts +++ b/apps/api/src/controllers/Oauth/Github.ts @@ -40,6 +40,10 @@ export class Github { } const {code} = req.query; + if (!code || typeof code !== 'string') { + return res.redirect(DASHBOARD_URI + '/auth/login?message=Invalid OAuth callback'); + } + const data = new URLSearchParams({ client_id: GITHUB_OAUTH_CLIENT, client_secret: GITHUB_OAUTH_SECRET, @@ -47,19 +51,33 @@ export class Github { redirect_uri: `${API_URI}/oauth/github/callback`, }); - const {access_token, token_type} = await fetch('https://github.com/login/oauth/access_token', { + const tokenResponse = await fetch('https://github.com/login/oauth/access_token', { method: 'POST', headers: {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'}, body: data, }).then(res => res.json()); + if (!tokenResponse.access_token || !tokenResponse.token_type) { + return res.redirect(DASHBOARD_URI + '/auth/login?message=Failed to authenticate with GitHub'); + } + const emails = await fetch(`https://api.github.com/user/emails`, { - headers: {Authorization: `${token_type} ${access_token}`}, + headers: {Authorization: `${tokenResponse.token_type} ${tokenResponse.access_token}`}, }).then(res => res.json()); - const email = emails.find((e: {primary: boolean; email: string}) => e.primary).email; + if (!Array.isArray(emails) || emails.length === 0) { + return res.redirect(DASHBOARD_URI + '/auth/login?message=Failed to retrieve emails from GitHub'); + } - let user = await UserService.email(email as string); + const primaryEmail = emails.find((e: {primary: boolean; email: string}) => e.primary); + + if (!primaryEmail || !primaryEmail.email || typeof primaryEmail.email !== 'string') { + return res.redirect(DASHBOARD_URI + '/auth/login?message=Failed to retrieve primary email from GitHub'); + } + + const email = primaryEmail.email; + + let user = await UserService.email(email); let isNewUser = false; if (!user) { diff --git a/apps/api/src/controllers/Oauth/Google.ts b/apps/api/src/controllers/Oauth/Google.ts index 59418fe..593612a 100644 --- a/apps/api/src/controllers/Oauth/Google.ts +++ b/apps/api/src/controllers/Oauth/Google.ts @@ -35,6 +35,10 @@ export class Google { } const {code} = req.query; + if (!code || typeof code !== 'string') { + return res.redirect(DASHBOARD_URI + '/auth/login?message=Invalid OAuth callback'); + } + const data = new URLSearchParams({ client_id: GOOGLE_OAUTH_CLIENT, client_secret: GOOGLE_OAUTH_SECRET, @@ -43,15 +47,25 @@ export class Google { grant_type: 'authorization_code', }); - const {access_token} = await fetch('https://oauth2.googleapis.com/token', { + const tokenResponse = await fetch('https://oauth2.googleapis.com/token', { method: 'POST', headers: {'Content-type': 'application/x-www-form-urlencoded'}, body: data, }).then(res => res.json()); - const {email} = await fetch(`https://www.googleapis.com/oauth2/v3/userinfo?access_token=${access_token}`).then( - res => res.json(), - ); + if (!tokenResponse.access_token) { + return res.redirect(DASHBOARD_URI + '/auth/login?message=Failed to authenticate with Google'); + } + + const userInfoResponse = await fetch( + `https://www.googleapis.com/oauth2/v3/userinfo?access_token=${tokenResponse.access_token}`, + ).then(res => res.json()); + + if (!userInfoResponse.email || typeof userInfoResponse.email !== 'string') { + return res.redirect(DASHBOARD_URI + '/auth/login?message=Failed to retrieve email from Google'); + } + + const email = userInfoResponse.email; let user = await UserService.email(email); let isNewUser = false; diff --git a/apps/api/src/services/UserService.ts b/apps/api/src/services/UserService.ts index 7ca1bf1..1ceea64 100644 --- a/apps/api/src/services/UserService.ts +++ b/apps/api/src/services/UserService.ts @@ -52,6 +52,10 @@ export class UserService { } public static async email(email: string) { + if (!email) { + return null; + } + return wrapRedis(Keys.User.email(email), async () => { return prisma.user.findFirst({ where: {