Skip to content

Commit

Permalink
feat: add tryLinkingWithSessionUser, forceFreshAuth and small test fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
porcellus committed Aug 4, 2024
1 parent 7ce9e7c commit ee0b186
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 25 deletions.
1 change: 1 addition & 0 deletions lib/build/recipe/oauth2provider/api/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function getAPIImplementation() {
recipeImplementation: options.recipeImplementation,
loginChallenge,
session,
isDirectCall: true,
userContext,
});
return utils_1.handleInternalRedirects({
Expand Down
17 changes: 17 additions & 0 deletions lib/build/recipe/oauth2provider/api/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var __importDefault =
return mod && mod.__esModule ? mod : { default: mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const set_cookie_parser_1 = __importDefault(require("set-cookie-parser"));
const utils_1 = require("../../../utils");
const session_1 = __importDefault(require("../../session"));
const error_1 = __importDefault(require("../../../error"));
Expand Down Expand Up @@ -54,6 +55,22 @@ async function login(apiImplementation, options, userContext) {
if ("status" in response) {
utils_1.send200Response(options.res, response);
} else {
if (response.setCookie) {
const cookieStr = set_cookie_parser_1.default.splitCookiesString(response.setCookie);
const cookies = set_cookie_parser_1.default.parse(cookieStr);
for (const cookie of cookies) {
options.res.setCookie(
cookie.name,
cookie.value,
cookie.domain,
!!cookie.secure,
!!cookie.httpOnly,
new Date(cookie.expires).getTime(),
cookie.path || "/",
cookie.sameSite
);
}
}
options.res.original.redirect(response.redirectTo);
}
return true;
Expand Down
2 changes: 2 additions & 0 deletions lib/build/recipe/oauth2provider/api/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export declare function loginGET({
loginChallenge,
session,
setCookie,
isDirectCall,
userContext,
}: {
recipeImplementation: RecipeInterface;
loginChallenge: string;
session?: SessionContainerInterface;
setCookie?: string;
userContext: UserContext;
isDirectCall: boolean;
}): Promise<{
redirectTo: string;
setCookie: string | undefined;
Expand Down
50 changes: 38 additions & 12 deletions lib/build/recipe/oauth2provider/api/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,51 @@ var __importDefault =
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleInternalRedirects = exports.loginGET = void 0;
const supertokens_1 = __importDefault(require("../../../supertokens"));
const session_1 = require("../../session");
const constants_1 = require("../constants");
const set_cookie_parser_1 = __importDefault(require("set-cookie-parser"));
// API implementation for the loginGET function.
// Extracted for use in both apiImplementation and handleInternalRedirects.
async function loginGET({ recipeImplementation, loginChallenge, session, setCookie, userContext }) {
async function loginGET({ recipeImplementation, loginChallenge, session, setCookie, isDirectCall, userContext }) {
var _a;
const request = await recipeImplementation.getLoginRequest({
const loginRequest = await recipeImplementation.getLoginRequest({
challenge: loginChallenge,
userContext,
});
const queryParams = new URLSearchParams({
loginChallenge,
});
if ((_a = request.oidcContext) === null || _a === void 0 ? void 0 : _a.login_hint) {
queryParams.set("hint", request.oidcContext.login_hint);
const sessionInfo =
session !== undefined
? await session_1.getSessionInformation(
session === null || session === void 0 ? void 0 : session.getHandle()
)
: undefined;
if (!sessionInfo) {
session = undefined;
}
if (request.skip) {
const incomingAuthUrlQueryParams = new URLSearchParams(loginRequest.requestUrl.split("?")[1]);
const promptParam = incomingAuthUrlQueryParams.get("prompt");
const maxAgeParam = incomingAuthUrlQueryParams.get("max_age");
if (loginRequest.skip) {
const accept = await recipeImplementation.acceptLoginRequest({
challenge: loginChallenge,
identityProviderSessionId: session === null || session === void 0 ? void 0 : session.getHandle(),
subject: request.subject,
subject: loginRequest.subject,
userContext,
});
return { redirectTo: accept.redirectTo, setCookie };
} else if (session && (!request.subject || session.getUserId() === request.subject)) {
} else if (
session &&
(["", undefined].includes(loginRequest.subject) || session.getUserId() === loginRequest.subject) &&
(promptParam !== "login" || isDirectCall) &&
(maxAgeParam === null ||
(maxAgeParam === "0" && isDirectCall) ||
Number.parseInt(maxAgeParam) * 1000 > Date.now() - sessionInfo.timeCreated)
) {
const accept = await recipeImplementation.acceptLoginRequest({
challenge: loginChallenge,
subject: session.getUserId(),
identityProviderSessionId: session.getHandle(),
remember: true,
rememberFor: 3600,
userContext,
});
return { redirectTo: accept.redirectTo, setCookie };
Expand All @@ -48,8 +64,17 @@ async function loginGET({ recipeImplementation, loginChallenge, session, setCook
})
.getAsStringDangerous();
const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous();
const queryParamsForAuthPage = new URLSearchParams({
loginChallenge,
});
if ((_a = loginRequest.oidcContext) === null || _a === void 0 ? void 0 : _a.login_hint) {
queryParamsForAuthPage.set("hint", loginRequest.oidcContext.login_hint);
}
if (session !== undefined) {
queryParamsForAuthPage.set("forceFreshAuth", "true");
}
return {
redirectTo: websiteDomain + websiteBasePath + `?${queryParams.toString()}`,
redirectTo: websiteDomain + websiteBasePath + `?${queryParamsForAuthPage.toString()}`,
setCookie,
};
}
Expand Down Expand Up @@ -81,7 +106,7 @@ function mergeSetCookieHeaders(setCookie1, setCookie2) {
if (!setCookie2 || setCookie1 === setCookie2) {
return setCookie1;
}
return `${setCookie1};${setCookie2}`;
return `${setCookie1}, ${setCookie2}`;
}
function isInternalRedirect(redirectTo) {
const { apiDomain, apiBasePath } = supertokens_1.default.getInstanceOrThrowError().appInfo;
Expand Down Expand Up @@ -120,6 +145,7 @@ async function handleInternalRedirects({ response, recipeImplementation, session
loginChallenge,
session,
setCookie: response.setCookie,
isDirectCall: false,
userContext,
});
response = {
Expand Down
1 change: 1 addition & 0 deletions lib/build/recipe/oauth2provider/recipeImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload,
new normalisedURLPath_1.default(`/recipe/oauth2/pub/auth`),
input.params,
{
// TODO: if session is not set also clear the oauth2 cookie
Cookie: `${input.cookies}`,
},
input.userContext
Expand Down
1 change: 1 addition & 0 deletions lib/build/recipe/oauth2provider/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ export declare type APIInterface = {
}) => Promise<
| {
redirectTo: string;
setCookie: string | undefined;
}
| GeneralErrorResponse
>);
Expand Down
1 change: 1 addition & 0 deletions lib/ts/recipe/oauth2provider/api/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function getAPIImplementation(): APIInterface {
recipeImplementation: options.recipeImplementation,
loginChallenge,
session,
isDirectCall: true,
userContext,
});
return handleInternalRedirects({
Expand Down
17 changes: 17 additions & 0 deletions lib/ts/recipe/oauth2provider/api/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* under the License.
*/

import setCookieParser from "set-cookie-parser";
import { send200Response } from "../../../utils";
import { APIInterface, APIOptions } from "..";
import Session from "../../session";
Expand Down Expand Up @@ -54,6 +55,22 @@ export default async function login(
if ("status" in response) {
send200Response(options.res, response);
} else {
if (response.setCookie) {
const cookieStr = setCookieParser.splitCookiesString(response.setCookie);
const cookies = setCookieParser.parse(cookieStr);
for (const cookie of cookies) {
options.res.setCookie(
cookie.name,
cookie.value,
cookie.domain,
!!cookie.secure,
!!cookie.httpOnly,
new Date(cookie.expires!).getTime(),
cookie.path || "/",
cookie.sameSite as any
);
}
}
options.res.original.redirect(response.redirectTo);
}
return true;
Expand Down
49 changes: 37 additions & 12 deletions lib/ts/recipe/oauth2provider/api/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SuperTokens from "../../../supertokens";
import { UserContext } from "../../../types";
import { getSessionInformation } from "../../session";
import { SessionContainerInterface } from "../../session/types";
import { AUTH_PATH, LOGIN_PATH } from "../constants";
import { RecipeInterface } from "../types";
Expand All @@ -12,41 +13,52 @@ export async function loginGET({
loginChallenge,
session,
setCookie,
isDirectCall,
userContext,
}: {
recipeImplementation: RecipeInterface;
loginChallenge: string;
session?: SessionContainerInterface;
setCookie?: string;
userContext: UserContext;
isDirectCall: boolean;
}) {
const request = await recipeImplementation.getLoginRequest({
const loginRequest = await recipeImplementation.getLoginRequest({
challenge: loginChallenge,
userContext,
});

const queryParams = new URLSearchParams({
loginChallenge,
});

if (request.oidcContext?.login_hint) {
queryParams.set("hint", request.oidcContext.login_hint);
const sessionInfo = session !== undefined ? await getSessionInformation(session?.getHandle()) : undefined;
if (!sessionInfo) {
session = undefined;
}

if (request.skip) {
const incomingAuthUrlQueryParams = new URLSearchParams(loginRequest.requestUrl.split("?")[1]);
const promptParam = incomingAuthUrlQueryParams.get("prompt");
const maxAgeParam = incomingAuthUrlQueryParams.get("max_age");
if (loginRequest.skip) {
const accept = await recipeImplementation.acceptLoginRequest({
challenge: loginChallenge,
identityProviderSessionId: session?.getHandle(),
subject: request.subject,
subject: loginRequest.subject,
userContext,
});

return { redirectTo: accept.redirectTo, setCookie };
} else if (session && (!request.subject || session.getUserId() === request.subject)) {
} else if (
session &&
(["", undefined].includes(loginRequest.subject) || session.getUserId() === loginRequest.subject) &&
(promptParam !== "login" || isDirectCall) &&
(maxAgeParam === null ||
(maxAgeParam === "0" && isDirectCall) ||
Number.parseInt(maxAgeParam) * 1000 > Date.now() - sessionInfo!.timeCreated)
) {
const accept = await recipeImplementation.acceptLoginRequest({
challenge: loginChallenge,
subject: session.getUserId(),
identityProviderSessionId: session.getHandle(),
remember: true,
rememberFor: 3600,
userContext,
});
return { redirectTo: accept.redirectTo, setCookie };
Expand All @@ -60,8 +72,20 @@ export async function loginGET({
.getAsStringDangerous();
const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous();

const queryParamsForAuthPage = new URLSearchParams({
loginChallenge,
});

if (loginRequest.oidcContext?.login_hint) {
queryParamsForAuthPage.set("hint", loginRequest.oidcContext.login_hint);
}

if (session !== undefined) {
queryParamsForAuthPage.set("forceFreshAuth", "true");
}

return {
redirectTo: websiteDomain + websiteBasePath + `?${queryParams.toString()}`,
redirectTo: websiteDomain + websiteBasePath + `?${queryParamsForAuthPage.toString()}`,
setCookie,
};
}
Expand Down Expand Up @@ -98,7 +122,7 @@ function mergeSetCookieHeaders(setCookie1?: string, setCookie2?: string): string
if (!setCookie2 || setCookie1 === setCookie2) {
return setCookie1;
}
return `${setCookie1};${setCookie2}`;
return `${setCookie1}, ${setCookie2}`;
}

function isInternalRedirect(redirectTo: string): boolean {
Expand Down Expand Up @@ -154,6 +178,7 @@ export async function handleInternalRedirects({
loginChallenge,
session,
setCookie: response.setCookie,
isDirectCall: false,
userContext,
});

Expand Down
1 change: 1 addition & 0 deletions lib/ts/recipe/oauth2provider/recipeImplementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ export default function getRecipeInterface(
new NormalisedURLPath(`/recipe/oauth2/pub/auth`),
input.params,
{
// TODO: if session is not set also clear the oauth2 cookie
Cookie: `${input.cookies}`,
},
input.userContext
Expand Down
2 changes: 1 addition & 1 deletion lib/ts/recipe/oauth2provider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ export type APIInterface = {
options: APIOptions;
session?: SessionContainerInterface;
userContext: UserContext;
}) => Promise<{ redirectTo: string } | GeneralErrorResponse>);
}) => Promise<{ redirectTo: string; setCookie: string | undefined } | GeneralErrorResponse>);

authGET:
| undefined
Expand Down

0 comments on commit ee0b186

Please sign in to comment.