diff --git a/CHANGELOG.md b/CHANGELOG.md index 19a135b7b..2b5b3642d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) +## [16.2.1] - 2023-10-06 + +- Slight refactors logic to code for social providers to make it consistent across all providers + ## [16.2.0] - 2023-09-29 ### Changes diff --git a/lib/build/recipe/thirdparty/providers/bitbucket.js b/lib/build/recipe/thirdparty/providers/bitbucket.js index 500aae8a4..81317479a 100644 --- a/lib/build/recipe/thirdparty/providers/bitbucket.js +++ b/lib/build/recipe/thirdparty/providers/bitbucket.js @@ -21,6 +21,7 @@ var __importDefault = Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("./utils"); const custom_1 = __importDefault(require("./custom")); +const logger_1 = require("../../../logger"); function Bitbucket(input) { if (input.config.name === undefined) { input.config.name = "Bitbucket"; @@ -63,16 +64,40 @@ function Bitbucket(input) { undefined, headers ); - rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken; + if (userInfoFromAccessToken.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + } + rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.response; const userInfoFromEmail = await utils_1.doGetRequest( "https://api.bitbucket.org/2.0/user/emails", undefined, headers ); - rawUserInfoFromProvider.fromUserInfoAPI.email = userInfoFromEmail; + if (userInfoFromEmail.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${ + userInfoFromEmail.status + } and body ${await userInfoFromEmail.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + userInfoFromEmail.status + } and body ${await userInfoFromEmail.response.clone().text()}` + ); + } + rawUserInfoFromProvider.fromUserInfoAPI.email = userInfoFromEmail.response; let email = undefined; let isVerified = false; - for (const emailInfo of userInfoFromEmail.values) { + for (const emailInfo of userInfoFromEmail.response.values) { if (emailInfo.is_primary) { email = emailInfo.email; isVerified = emailInfo.is_confirmed; diff --git a/lib/build/recipe/thirdparty/providers/custom.js b/lib/build/recipe/thirdparty/providers/custom.js index 4d4a4bd2b..c82ca14b5 100644 --- a/lib/build/recipe/thirdparty/providers/custom.js +++ b/lib/build/recipe/thirdparty/providers/custom.js @@ -10,6 +10,7 @@ const utils_1 = require("./utils"); const pkce_challenge_1 = __importDefault(require("pkce-challenge")); const configUtils_1 = require("./configUtils"); const jose_1 = require("jose"); +const logger_1 = require("../../../logger"); const DEV_OAUTH_AUTHORIZATION_URL = "https://supertokens.io/dev/oauth/redirect-to-provider"; exports.DEV_OAUTH_REDIRECT_URL = "https://supertokens.io/dev/oauth/redirect-to-app"; // If Third Party login is used with one of the following development keys, then the dev authorization url and the redirect url will be used. @@ -250,7 +251,20 @@ function NewProvider(input) { accessTokenAPIParams["redirect_uri"] = exports.DEV_OAUTH_REDIRECT_URL; } /* Transformation needed for dev keys END */ - return await utils_1.doPostRequest(tokenAPIURL, accessTokenAPIParams); + const tokenResponse = await utils_1.doPostRequest(tokenAPIURL, accessTokenAPIParams); + if (tokenResponse.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${ + tokenResponse.status + } and body ${await tokenResponse.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + tokenResponse.status + } and body ${await tokenResponse.response.clone().text()}` + ); + } + return tokenResponse.response; }, getUserInfo: async function ({ oAuthTokens, userContext }) { const accessToken = oAuthTokens["access_token"]; @@ -313,7 +327,19 @@ function NewProvider(input) { queryParams, headers ); - rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken; + if (userInfoFromAccessToken.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + } + rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.response; } const userInfoResult = getSupertokensUserInfoResultFromRawUserInfo(impl.config, rawUserInfoFromProvider); return { diff --git a/lib/build/recipe/thirdparty/providers/github.js b/lib/build/recipe/thirdparty/providers/github.js index 14b22ce21..85e9d3bbb 100644 --- a/lib/build/recipe/thirdparty/providers/github.js +++ b/lib/build/recipe/thirdparty/providers/github.js @@ -5,22 +5,8 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * You may not use this file except in compliance with the License. You may - * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -const cross_fetch_1 = __importDefault(require("cross-fetch")); const custom_1 = __importDefault(require("./custom")); +const utils_1 = require("./utils"); function getSupertokensUserInfoFromRawUserInfoResponseForGithub(rawUserInfoResponse) { if (rawUserInfoResponse.fromUserInfoAPI === undefined) { throw new Error("rawUserInfoResponse.fromUserInfoAPI is not available"); @@ -58,24 +44,23 @@ function Github(input) { const basicAuthToken = Buffer.from( `${clientConfig.clientId}:${clientConfig.clientSecret === undefined ? "" : clientConfig.clientSecret}` ).toString("base64"); - const applicationsResponse = await cross_fetch_1.default( + const applicationResponse = await utils_1.doPostRequest( `https://api.github.com/applications/${clientConfig.clientId}/token`, { - headers: { - Authorization: `Basic ${basicAuthToken}`, - "Content-Type": "application/json", - }, - method: "POST", - body: JSON.stringify({ - access_token: accessToken, - }), + access_token: accessToken, + }, + { + Authorization: `Basic ${basicAuthToken}`, + "Content-Type": "application/json", } ); - if (applicationsResponse.status !== 200) { + if (applicationResponse.status !== 200) { throw new Error("Invalid access token"); } - const body = await applicationsResponse.json(); - if (body.app === undefined || body.app.client_id !== clientConfig.clientId) { + if ( + applicationResponse.response.app === undefined || + applicationResponse.response.app.client_id !== clientConfig.clientId + ) { throw new Error("Access token does not belong to your application"); } }; @@ -96,18 +81,20 @@ function Github(input) { Accept: "application/vnd.github.v3+json", }; const rawResponse = {}; - const emailInfoResp = await cross_fetch_1.default("https://api.github.com/user/emails", { headers }); + const emailInfoResp = await utils_1.doGetRequest("https://api.github.com/user/emails", undefined, headers); if (emailInfoResp.status >= 400) { - throw new Error(`Getting userInfo failed with ${emailInfoResp.status}: ${await emailInfoResp.text()}`); + throw new Error( + `Getting userInfo failed with ${emailInfoResp.status}: ${await emailInfoResp.response.text()}` + ); } - const emailInfo = await emailInfoResp.json(); - rawResponse.emails = emailInfo; - const userInfoResp = await cross_fetch_1.default("https://api.github.com/user", { headers }); + rawResponse.emails = emailInfoResp.response; + const userInfoResp = await utils_1.doGetRequest("https://api.github.com/user", undefined, headers); if (userInfoResp.status >= 400) { - throw new Error(`Getting userInfo failed with ${userInfoResp.status}: ${await userInfoResp.text()}`); + throw new Error( + `Getting userInfo failed with ${userInfoResp.status}: ${await userInfoResp.response.text()}` + ); } - const userInfo = await userInfoResp.json(); - rawResponse.user = userInfo; + rawResponse.user = userInfoResp.response; const rawUserInfoFromProvider = { fromUserInfoAPI: rawResponse, fromIdTokenPayload: {}, diff --git a/lib/build/recipe/thirdparty/providers/linkedin.js b/lib/build/recipe/thirdparty/providers/linkedin.js index 9e1e482a8..e09b5ba93 100644 --- a/lib/build/recipe/thirdparty/providers/linkedin.js +++ b/lib/build/recipe/thirdparty/providers/linkedin.js @@ -5,6 +5,21 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +const logger_1 = require("../../../logger"); const custom_1 = __importDefault(require("./custom")); const utils_1 = require("./utils"); function Linkedin(input) { @@ -44,19 +59,44 @@ function Linkedin(input) { undefined, headers ); - rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken; + rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.response; + if (userInfoFromAccessToken.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + } const emailAPIURL = "https://api.linkedin.com/v2/emailAddress"; const userInfoFromEmail = await utils_1.doGetRequest( emailAPIURL, { q: "members", projection: "(elements*(handle~))" }, headers ); - if (userInfoFromEmail.elements && userInfoFromEmail.elements.length > 0) { - rawUserInfoFromProvider.fromUserInfoAPI.email = userInfoFromEmail.elements[0]["handle~"].emailAddress; + if (userInfoFromEmail.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${ + userInfoFromEmail.status + } and body ${await userInfoFromEmail.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + userInfoFromEmail.status + } and body ${await userInfoFromEmail.response.clone().text()}` + ); + } + if (userInfoFromEmail.response.elements && userInfoFromEmail.response.elements.length > 0) { + rawUserInfoFromProvider.fromUserInfoAPI.email = + userInfoFromEmail.response.elements[0]["handle~"].emailAddress; } rawUserInfoFromProvider.fromUserInfoAPI = Object.assign( Object.assign({}, rawUserInfoFromProvider.fromUserInfoAPI), - userInfoFromEmail + userInfoFromEmail.response ); return { thirdPartyUserId: rawUserInfoFromProvider.fromUserInfoAPI.id, diff --git a/lib/build/recipe/thirdparty/providers/twitter.js b/lib/build/recipe/thirdparty/providers/twitter.js index 61818eb30..3164b30d3 100644 --- a/lib/build/recipe/thirdparty/providers/twitter.js +++ b/lib/build/recipe/thirdparty/providers/twitter.js @@ -36,6 +36,21 @@ var __importStar = return result; }; Object.defineProperty(exports, "__esModule", { value: true }); +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +const logger_1 = require("../../../logger"); const custom_1 = __importStar(require("./custom")); const utils_1 = require("./utils"); function Twitter(input) { @@ -101,9 +116,26 @@ function Twitter(input) { }, originalImplementation.config.tokenEndpointBodyParams ); - return await utils_1.doPostRequest(originalImplementation.config.tokenEndpoint, twitterOauthTokenParams, { - Authorization: `Basic ${basicAuthToken}`, - }); + const tokenResponse = await utils_1.doPostRequest( + originalImplementation.config.tokenEndpoint, + twitterOauthTokenParams, + { + Authorization: `Basic ${basicAuthToken}`, + } + ); + if (tokenResponse.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${ + tokenResponse.status + } and body ${await tokenResponse.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + tokenResponse.status + } and body ${await tokenResponse.response.clone().text()}` + ); + } + return tokenResponse.response; }; if (oOverride !== undefined) { originalImplementation = oOverride(originalImplementation); diff --git a/lib/build/recipe/thirdparty/providers/utils.d.ts b/lib/build/recipe/thirdparty/providers/utils.d.ts index fe9b9e048..88b1077c9 100644 --- a/lib/build/recipe/thirdparty/providers/utils.d.ts +++ b/lib/build/recipe/thirdparty/providers/utils.d.ts @@ -9,7 +9,10 @@ export declare function doGetRequest( headers?: { [key: string]: string; } -): Promise; +): Promise<{ + response: any; + status: number; +}>; export declare function doPostRequest( url: string, params: { @@ -18,7 +21,10 @@ export declare function doPostRequest( headers?: { [key: string]: string; } -): Promise; +): Promise<{ + response: any; + status: number; +}>; export declare function verifyIdTokenFromJWKSEndpointAndGetPayload( idToken: string, jwks: jose.JWTVerifyGetKey, diff --git a/lib/build/recipe/thirdparty/providers/utils.js b/lib/build/recipe/thirdparty/providers/utils.js index 7e5896f2c..55a635feb 100644 --- a/lib/build/recipe/thirdparty/providers/utils.js +++ b/lib/build/recipe/thirdparty/providers/utils.js @@ -59,15 +59,12 @@ async function doGetRequest(url, queryParams, headers) { let response = await cross_fetch_1.default(finalURL.toString(), { headers: headers, }); - if (response.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${response.status} and body ${await response.clone().text()}` - ); - throw new Error(`Received response with status ${response.status} and body ${await response.clone().text()}`); - } const respData = await response.clone().json(); logger_1.logDebugMessage(`Received response with status ${response.status} and body ${JSON.stringify(respData)}`); - return respData; + return { + response: respData, + status: response.status, + }; } exports.doGetRequest = doGetRequest; async function doPostRequest(url, params, headers) { @@ -85,15 +82,12 @@ async function doPostRequest(url, params, headers) { body, headers, }); - if (response.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${response.status} and body ${await response.clone().text()}` - ); - throw new Error(`Received response with status ${response.status} and body ${await response.clone().text()}`); - } const respData = await response.clone().json(); logger_1.logDebugMessage(`Received response with status ${response.status} and body ${JSON.stringify(respData)}`); - return respData; + return { + response: respData, + status: response.status, + }; } exports.doPostRequest = doPostRequest; async function verifyIdTokenFromJWKSEndpointAndGetPayload(idToken, jwks, otherOptions) { @@ -114,8 +108,16 @@ async function getOIDCDiscoveryInfo(issuer) { const oidcInfo = await doGetRequest( normalizedDomain.getAsStringDangerous() + normalizedPath.getAsStringDangerous() ); - oidcInfoMap[issuer] = oidcInfo; - return oidcInfo; + if (oidcInfo.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${oidcInfo.status} and body ${await oidcInfo.response.clone().text()}` + ); + throw new Error( + `Received response with status ${oidcInfo.status} and body ${await oidcInfo.response.clone().text()}` + ); + } + oidcInfoMap[issuer] = oidcInfo.response; + return oidcInfo.response; } async function discoverOIDCEndpoints(config) { if (config.oidcDiscoveryEndpoint !== undefined) { diff --git a/lib/build/version.d.ts b/lib/build/version.d.ts index b4855d369..cc19076ea 100644 --- a/lib/build/version.d.ts +++ b/lib/build/version.d.ts @@ -1,4 +1,4 @@ // @ts-nocheck -export declare const version = "16.2.0"; +export declare const version = "16.2.1"; export declare const cdiSupported: string[]; export declare const dashboardVersion = "0.8"; diff --git a/lib/build/version.js b/lib/build/version.js index f924b34b1..d11fe63e0 100644 --- a/lib/build/version.js +++ b/lib/build/version.js @@ -15,7 +15,7 @@ exports.dashboardVersion = exports.cdiSupported = exports.version = void 0; * License for the specific language governing permissions and limitations * under the License. */ -exports.version = "16.2.0"; +exports.version = "16.2.1"; exports.cdiSupported = ["4.0"]; // Note: The actual script import for dashboard uses v{DASHBOARD_VERSION} exports.dashboardVersion = "0.8"; diff --git a/lib/ts/recipe/thirdparty/providers/bitbucket.ts b/lib/ts/recipe/thirdparty/providers/bitbucket.ts index 0767aa6e8..e7374ca3e 100644 --- a/lib/ts/recipe/thirdparty/providers/bitbucket.ts +++ b/lib/ts/recipe/thirdparty/providers/bitbucket.ts @@ -17,6 +17,7 @@ import { ProviderInput, TypeProvider } from "../types"; import { doGetRequest } from "./utils"; import NewProvider from "./custom"; +import { logDebugMessage } from "../../../logger"; export default function Bitbucket(input: ProviderInput): TypeProvider { if (input.config.name === undefined) { @@ -74,7 +75,20 @@ export default function Bitbucket(input: ProviderInput): TypeProvider { undefined, headers ); - rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken; + + if (userInfoFromAccessToken.status >= 400) { + logDebugMessage( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + } + rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.response; const userInfoFromEmail = await doGetRequest( "https://api.bitbucket.org/2.0/user/emails", @@ -82,11 +96,24 @@ export default function Bitbucket(input: ProviderInput): TypeProvider { headers ); - rawUserInfoFromProvider.fromUserInfoAPI.email = userInfoFromEmail; + if (userInfoFromEmail.status >= 400) { + logDebugMessage( + `Received response with status ${ + userInfoFromEmail.status + } and body ${await userInfoFromEmail.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + userInfoFromEmail.status + } and body ${await userInfoFromEmail.response.clone().text()}` + ); + } + + rawUserInfoFromProvider.fromUserInfoAPI.email = userInfoFromEmail.response; let email = undefined; let isVerified = false; - for (const emailInfo of userInfoFromEmail.values) { + for (const emailInfo of userInfoFromEmail.response.values) { if (emailInfo.is_primary) { email = emailInfo.email; isVerified = emailInfo.is_confirmed; diff --git a/lib/ts/recipe/thirdparty/providers/custom.ts b/lib/ts/recipe/thirdparty/providers/custom.ts index 2564049c2..449a67eb2 100644 --- a/lib/ts/recipe/thirdparty/providers/custom.ts +++ b/lib/ts/recipe/thirdparty/providers/custom.ts @@ -3,6 +3,7 @@ import { doGetRequest, doPostRequest, verifyIdTokenFromJWKSEndpointAndGetPayload import pkceChallenge from "pkce-challenge"; import { getProviderConfigForClient } from "./configUtils"; import { JWTVerifyGetKey, createRemoteJWKSet } from "jose"; +import { logDebugMessage } from "../../../logger"; const DEV_OAUTH_AUTHORIZATION_URL = "https://supertokens.io/dev/oauth/redirect-to-provider"; export const DEV_OAUTH_REDIRECT_URL = "https://supertokens.io/dev/oauth/redirect-to-app"; @@ -269,7 +270,22 @@ export default function NewProvider(input: ProviderInput): TypeProvider { } /* Transformation needed for dev keys END */ - return await doPostRequest(tokenAPIURL, accessTokenAPIParams); + const tokenResponse = await doPostRequest(tokenAPIURL, accessTokenAPIParams); + + if (tokenResponse.status >= 400) { + logDebugMessage( + `Received response with status ${ + tokenResponse.status + } and body ${await tokenResponse.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + tokenResponse.status + } and body ${await tokenResponse.response.clone().text()}` + ); + } + + return tokenResponse.response; }, getUserInfo: async function ({ oAuthTokens, userContext }): Promise { @@ -340,7 +356,21 @@ export default function NewProvider(input: ProviderInput): TypeProvider { } const userInfoFromAccessToken = await doGetRequest(impl.config.userInfoEndpoint, queryParams, headers); - rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken; + + if (userInfoFromAccessToken.status >= 400) { + logDebugMessage( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + } + + rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.response; } const userInfoResult = getSupertokensUserInfoResultFromRawUserInfo(impl.config, rawUserInfoFromProvider); diff --git a/lib/ts/recipe/thirdparty/providers/github.ts b/lib/ts/recipe/thirdparty/providers/github.ts index e9307658d..648be0d49 100644 --- a/lib/ts/recipe/thirdparty/providers/github.ts +++ b/lib/ts/recipe/thirdparty/providers/github.ts @@ -12,9 +12,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -import fetch from "cross-fetch"; import { ProviderInput, TypeProvider, UserInfo } from "../types"; import NewProvider from "./custom"; +import { doGetRequest, doPostRequest } from "./utils"; function getSupertokensUserInfoFromRawUserInfoResponseForGithub(rawUserInfoResponse: { fromIdTokenPayload?: any; @@ -64,27 +64,25 @@ export default function Github(input: ProviderInput): TypeProvider { `${clientConfig.clientId}:${clientConfig.clientSecret === undefined ? "" : clientConfig.clientSecret}` ).toString("base64"); - const applicationsResponse = await fetch( + const applicationResponse = await doPostRequest( `https://api.github.com/applications/${clientConfig.clientId}/token`, { - headers: { - Authorization: `Basic ${basicAuthToken}`, - "Content-Type": "application/json", - }, - method: "POST", - body: JSON.stringify({ - access_token: accessToken, - }), + access_token: accessToken, + }, + { + Authorization: `Basic ${basicAuthToken}`, + "Content-Type": "application/json", } ); - if (applicationsResponse.status !== 200) { + if (applicationResponse.status !== 200) { throw new Error("Invalid access token"); } - const body = await applicationsResponse.json(); - - if (body.app === undefined || body.app.client_id !== clientConfig.clientId) { + if ( + applicationResponse.response.app === undefined || + applicationResponse.response.app.client_id !== clientConfig.clientId + ) { throw new Error("Access token does not belong to your application"); } }; @@ -111,19 +109,25 @@ export default function Github(input: ProviderInput): TypeProvider { }; const rawResponse: { [key: string]: any } = {}; - const emailInfoResp = await fetch("https://api.github.com/user/emails", { headers }); + const emailInfoResp = await doGetRequest("https://api.github.com/user/emails", undefined, headers); + if (emailInfoResp.status >= 400) { - throw new Error(`Getting userInfo failed with ${emailInfoResp.status}: ${await emailInfoResp.text()}`); + throw new Error( + `Getting userInfo failed with ${emailInfoResp.status}: ${await emailInfoResp.response.text()}` + ); } - const emailInfo = await emailInfoResp.json(); - rawResponse.emails = emailInfo; - const userInfoResp = await fetch("https://api.github.com/user", { headers }); + rawResponse.emails = emailInfoResp.response; + + const userInfoResp = await doGetRequest("https://api.github.com/user", undefined, headers); + if (userInfoResp.status >= 400) { - throw new Error(`Getting userInfo failed with ${userInfoResp.status}: ${await userInfoResp.text()}`); + throw new Error( + `Getting userInfo failed with ${userInfoResp.status}: ${await userInfoResp.response.text()}` + ); } - const userInfo = await userInfoResp.json(); - rawResponse.user = userInfo; + + rawResponse.user = userInfoResp.response; const rawUserInfoFromProvider = { fromUserInfoAPI: rawResponse, diff --git a/lib/ts/recipe/thirdparty/providers/linkedin.ts b/lib/ts/recipe/thirdparty/providers/linkedin.ts index 4472eac17..6759576c7 100644 --- a/lib/ts/recipe/thirdparty/providers/linkedin.ts +++ b/lib/ts/recipe/thirdparty/providers/linkedin.ts @@ -12,6 +12,7 @@ * License for the specific language governing permissions and limitations * under the License. */ +import { logDebugMessage } from "../../../logger"; import { ProviderInput, TypeProvider } from "../types"; import NewProvider from "./custom"; import { doGetRequest } from "./utils"; @@ -63,7 +64,20 @@ export default function Linkedin(input: ProviderInput): TypeProvider { }; const userInfoFromAccessToken = await doGetRequest("https://api.linkedin.com/v2/me", undefined, headers); - rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken; + rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.response; + + if (userInfoFromAccessToken.status >= 400) { + logDebugMessage( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + userInfoFromAccessToken.status + } and body ${await userInfoFromAccessToken.response.clone().text()}` + ); + } const emailAPIURL = "https://api.linkedin.com/v2/emailAddress"; const userInfoFromEmail = await doGetRequest( @@ -72,12 +86,26 @@ export default function Linkedin(input: ProviderInput): TypeProvider { headers ); - if (userInfoFromEmail.elements && userInfoFromEmail.elements.length > 0) { - rawUserInfoFromProvider.fromUserInfoAPI.email = userInfoFromEmail.elements[0]["handle~"].emailAddress; + if (userInfoFromEmail.status >= 400) { + logDebugMessage( + `Received response with status ${ + userInfoFromEmail.status + } and body ${await userInfoFromEmail.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + userInfoFromEmail.status + } and body ${await userInfoFromEmail.response.clone().text()}` + ); + } + + if (userInfoFromEmail.response.elements && userInfoFromEmail.response.elements.length > 0) { + rawUserInfoFromProvider.fromUserInfoAPI.email = + userInfoFromEmail.response.elements[0]["handle~"].emailAddress; } rawUserInfoFromProvider.fromUserInfoAPI = { ...rawUserInfoFromProvider.fromUserInfoAPI, - ...userInfoFromEmail, + ...userInfoFromEmail.response, }; return { diff --git a/lib/ts/recipe/thirdparty/providers/twitter.ts b/lib/ts/recipe/thirdparty/providers/twitter.ts index 44b5ea6a3..c39b0d6ea 100644 --- a/lib/ts/recipe/thirdparty/providers/twitter.ts +++ b/lib/ts/recipe/thirdparty/providers/twitter.ts @@ -12,6 +12,7 @@ * License for the specific language governing permissions and limitations * under the License. */ +import { logDebugMessage } from "../../../logger"; import { ProviderInput, TypeProvider } from "../types"; import NewProvider, { DEV_OAUTH_REDIRECT_URL, @@ -92,9 +93,28 @@ export default function Twitter(input: ProviderInput): TypeProvider { ...originalImplementation.config.tokenEndpointBodyParams, }; - return await doPostRequest(originalImplementation.config.tokenEndpoint!, twitterOauthTokenParams, { - Authorization: `Basic ${basicAuthToken}`, - }); + const tokenResponse = await doPostRequest( + originalImplementation.config.tokenEndpoint!, + twitterOauthTokenParams, + { + Authorization: `Basic ${basicAuthToken}`, + } + ); + + if (tokenResponse.status >= 400) { + logDebugMessage( + `Received response with status ${ + tokenResponse.status + } and body ${await tokenResponse.response.clone().text()}` + ); + throw new Error( + `Received response with status ${ + tokenResponse.status + } and body ${await tokenResponse.response.clone().text()}` + ); + } + + return tokenResponse.response; }; if (oOverride !== undefined) { diff --git a/lib/ts/recipe/thirdparty/providers/utils.ts b/lib/ts/recipe/thirdparty/providers/utils.ts index 28dcf6e26..546ea6a4c 100644 --- a/lib/ts/recipe/thirdparty/providers/utils.ts +++ b/lib/ts/recipe/thirdparty/providers/utils.ts @@ -10,7 +10,10 @@ export async function doGetRequest( url: string, queryParams?: { [key: string]: string }, headers?: { [key: string]: string } -): Promise { +): Promise<{ + response: any; + status: number; +}> { logDebugMessage( `GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}` ); @@ -26,21 +29,23 @@ export async function doGetRequest( headers: headers, }); - if (response.status >= 400) { - logDebugMessage(`Received response with status ${response.status} and body ${await response.clone().text()}`); - throw new Error(`Received response with status ${response.status} and body ${await response.clone().text()}`); - } const respData = await response.clone().json(); logDebugMessage(`Received response with status ${response.status} and body ${JSON.stringify(respData)}`); - return respData; + return { + response: respData, + status: response.status, + }; } export async function doPostRequest( url: string, params: { [key: string]: any }, headers?: { [key: string]: string } -): Promise { +): Promise<{ + response: any; + status: number; +}> { if (headers === undefined) { headers = {}; } @@ -59,14 +64,13 @@ export async function doPostRequest( headers, }); - if (response.status >= 400) { - logDebugMessage(`Received response with status ${response.status} and body ${await response.clone().text()}`); - throw new Error(`Received response with status ${response.status} and body ${await response.clone().text()}`); - } const respData = await response.clone().json(); logDebugMessage(`Received response with status ${response.status} and body ${JSON.stringify(respData)}`); - return respData; + return { + response: respData, + status: response.status, + }; } export async function verifyIdTokenFromJWKSEndpointAndGetPayload( @@ -96,8 +100,17 @@ async function getOIDCDiscoveryInfo(issuer: string): Promise { normalizedDomain.getAsStringDangerous() + normalizedPath.getAsStringDangerous() ); - oidcInfoMap[issuer] = oidcInfo; - return oidcInfo; + if (oidcInfo.status >= 400) { + logDebugMessage( + `Received response with status ${oidcInfo.status} and body ${await oidcInfo.response.clone().text()}` + ); + throw new Error( + `Received response with status ${oidcInfo.status} and body ${await oidcInfo.response.clone().text()}` + ); + } + + oidcInfoMap[issuer] = oidcInfo.response; + return oidcInfo.response; } export async function discoverOIDCEndpoints(config: ProviderConfigForClientType): Promise { diff --git a/lib/ts/version.ts b/lib/ts/version.ts index b7ad51705..833c06f8a 100644 --- a/lib/ts/version.ts +++ b/lib/ts/version.ts @@ -12,7 +12,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -export const version = "16.2.0"; +export const version = "16.2.1"; export const cdiSupported = ["4.0"]; diff --git a/package-lock.json b/package-lock.json index 4f03f222c..6ae0b980a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supertokens-node", - "version": "16.2.0", + "version": "16.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "supertokens-node", - "version": "16.2.0", + "version": "16.2.1", "license": "Apache-2.0", "dependencies": { "content-type": "^1.0.5", diff --git a/package.json b/package.json index c45de4c30..781309e76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "supertokens-node", - "version": "16.2.0", + "version": "16.2.1", "description": "NodeJS driver for SuperTokens core", "main": "index.js", "scripts": { diff --git a/test/thirdparty/provider.test.js b/test/thirdparty/provider.test.js index 0202e7eac..2a7dcfe88 100644 --- a/test/thirdparty/provider.test.js +++ b/test/thirdparty/provider.test.js @@ -1147,6 +1147,7 @@ describe(`providerTest: ${printPath("[test/thirdparty/provider.test.js]")}`, fun it("Test that sign in up works if validateAccessToken does not throw", async function () { const connectionURI = await startST(); + let overridenValidateCalled = false; STExpress.init({ supertokens: { connectionURI, @@ -1165,26 +1166,19 @@ describe(`providerTest: ${printPath("[test/thirdparty/provider.test.js]")}`, fun override: (original) => { return { ...original, - exchangeAuthCodeForOAuthTokens: async (input) => { - return { - access_token: "accesstoken", - id_token: "idtoken", - }; - }, - getUserInfo: async function ({ oAuthTokens }) { - const time = Date.now(); - return { - thirdPartyUserId: "" + time, - email: { - id: `johndoeprovidertest+${time}@supertokens.com`, - isVerified: true, - }, - }; - }, }; }, config: { + tokenEndpoint: "http://localhost:8083/tokenendpoint", + userInfoEndpoint: "http://localhost:8083/userinfo", thirdPartyId: "custom", + userInfoMap: { + fromUserInfoAPI: { + userId: "userId", + email: "email", + emailVerified: "emailVerified", + }, + }, clients: [ { clientId: "test2", @@ -1192,6 +1186,7 @@ describe(`providerTest: ${printPath("[test/thirdparty/provider.test.js]")}`, fun }, ], validateAccessToken: async ({ accessToken }) => { + overridenValidateCalled = true; if (accessToken === "accesstoken") { return; } @@ -1210,6 +1205,21 @@ describe(`providerTest: ${printPath("[test/thirdparty/provider.test.js]")}`, fun app.use(middleware()); + app.post("/tokenendpoint", async (req, res) => { + res.json({ + access_token: "accesstoken", + id_token: "idtoken", + }); + }); + + app.get("/userinfo", async (req, res) => { + res.json({ + userId: "testiserid", + email: "testinguser@supertokens.com", + emailVerified: "true", + }); + }); + app.use(errorHandler()); // default error handler for app @@ -1217,6 +1227,8 @@ describe(`providerTest: ${printPath("[test/thirdparty/provider.test.js]")}`, fun res.status(500).send(err.message); }); + const server = app.listen(8083); + let response = await new Promise((resolve) => request(app) .post("/auth/signinup") @@ -1230,6 +1242,8 @@ describe(`providerTest: ${printPath("[test/thirdparty/provider.test.js]")}`, fun }, }) .end((err, res) => { + console.log(err); + console.log(res); if (err) { resolve(undefined); } else { @@ -1241,7 +1255,10 @@ describe(`providerTest: ${printPath("[test/thirdparty/provider.test.js]")}`, fun }) ); + assert.equal(overridenValidateCalled, true); assert.strictEqual(response.status, 200); assert.strictEqual(response.body.status, "OK"); + + server.close(); }); });