Skip to content

Commit

Permalink
fix: better error handling in oauth flows (#862)
Browse files Browse the repository at this point in the history
  • Loading branch information
porcellus authored Oct 27, 2024
1 parent 9eab039 commit 76968e6
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 8 deletions.
3 changes: 2 additions & 1 deletion lib/build/index2.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions lib/build/oauth2providerprebuiltui.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ const AuthPageInner: React.FC<AuthPageProps> = (props) => {
}
},
() => {
return clearQueryParams(["loginChallenge"]);
clearQueryParams(["loginChallenge"]);
setError("SOMETHING_WENT_WRONG_ERROR");
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import SuperTokens from "../../../../../superTokens";
import UI from "../../../../../ui";
import { useUserContext } from "../../../../../usercontext";
import { getQueryParams, getTenantIdFromQueryParams, useRethrowInRender } from "../../../../../utils";
import { SessionContext } from "../../../../session";
import { doesSessionExist, SessionContext } from "../../../../session";
import OAuth2Provider from "../../../recipe";
import { OAuth2LogoutScreenTheme } from "../../themes/oauth2LogoutScreen";
import { defaultTranslationsOAuth2Provider } from "../../themes/translations";
Expand Down Expand Up @@ -75,7 +75,16 @@ export const OAuth2LogoutScreen: React.FC<Prop> = (props) => {
userContext
);
} catch (err: any) {
rethrowInRender(err);
if (!(await doesSessionExist(userContext))) {
void SuperTokens.getInstanceOrThrow()
.redirectToAuth({
userContext,
redirectBack: false,
})
.catch(rethrowInRender);
} else {
rethrowInRender(err);
}
}
}, [logoutChallenge, navigate, props.recipe, userContext, rethrowInRender]);

Expand Down
93 changes: 93 additions & 0 deletions test/end-to-end/oauth2provider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
waitForSTElement,
isOauth2Supported,
setupBrowser,
getGeneralError,
} from "../helpers";
import fetch from "isomorphic-fetch";

Expand All @@ -48,6 +49,7 @@ import {
TEST_SERVER_BASE_URL,
SIGN_OUT_API,
TEST_APPLICATION_SERVER_BASE_URL,
SOMETHING_WENT_WRONG_ERROR,
} from "../constants";

// We do no thave to use a separate domain for the oauth2 client, since the way we are testing
Expand Down Expand Up @@ -120,6 +122,36 @@ describe("SuperTokens OAuth2Provider", function () {
await removeOAuth2ClientInfo(page);
});

it("should clear invalid/expired loginChallenge from the url and show an error", async function () {
const { client } = await createOAuth2Client({
scope: "offline_access profile openid email",
redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`],
tokenEndpointAuthMethod: "none",
grantTypes: ["authorization_code", "refresh_token"],
responseTypes: ["code", "id_token"],
});

await setOAuth2ClientInfo(page, client.clientId);

await Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`),
]);

let loginButton = await getOAuth2LoginButton(page);
await loginButton.click();

await waitForUrl(page, "/auth");

await Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
page.goto(page.url().replace("loginChallenge=", "loginChallenge=nooope")),
]);

const error = await getGeneralError(page);
assert.strictEqual(error, SOMETHING_WENT_WRONG_ERROR);
});

it("should successfully complete the OAuth2 flow", async function () {
const { client } = await createOAuth2Client({
scope: "offline_access profile openid email",
Expand Down Expand Up @@ -165,6 +197,67 @@ describe("SuperTokens OAuth2Provider", function () {
await waitForUrl(page, "/oauth/callback");
});

it("should handle invalid logoutChallenge in the OAuth2 Logout flow", async function () {
const postLogoutRedirectUri = `${TEST_CLIENT_BASE_URL}/oauth/login?logout=true`;

const { client } = await createOAuth2Client({
scope: "offline_access profile openid email",
redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`],
postLogoutRedirectUris: [postLogoutRedirectUri],
accessTokenStrategy: "jwt",
tokenEndpointAuthMethod: "none",
grantTypes: ["authorization_code", "refresh_token"],
responseTypes: ["code", "id_token"],
skipConsent: true,
});

await setOAuth2ClientInfo(
page,
client.clientId,
undefined,
undefined,
undefined,
JSON.stringify({
post_logout_redirect_uri: postLogoutRedirectUri,
})
);

await Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`),
]);

let loginButton = await getOAuth2LoginButton(page);
await loginButton.click();

await waitForUrl(page, "/auth");

await toggleSignInSignUp(page);
const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() });
await signUp(page, fieldValues, postValues, "emailpassword");

await waitForUrl(page, "/oauth/callback");

// Logout
const logoutButton = await getOAuth2LogoutButton(page, "redirect");
await logoutButton.click();

await waitForUrl(page, "/auth/oauth/logout");

await Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
page.goto(page.url().replace("logoutChallenge=", "logoutChallenge=nooope")),
]);

// Click the Logout button on the provider website
const stLogoutButton = await waitForSTElement(page, "[data-supertokens~=button]");
await stLogoutButton.click();

// Ensure the we get redirected to the auth page
await waitForUrl(page, "/auth/");
await waitForSTElement(page);
});

it("should successfully complete the OAuth2 Logout flow", async function () {
const postLogoutRedirectUri = `${TEST_CLIENT_BASE_URL}/oauth/login?logout=true`;

Expand Down

0 comments on commit 76968e6

Please sign in to comment.