-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add OAuth2Client recipe (#877)
* feat: add initial oauth2 client apis * feat: Add an api to get login info * fix: merge issues and FE path * fix: WIP fix for CSRF and redirection issues * fix: OAuth2 fixes and test-server updates (#871) * feat: update oauth2 login info endpoint types to match our general patterns * fix: make login flow work * fix: circular dependency * feat: Add OAuth2Client recipe * fix: PR changes * fix: PR changes * fix: PR changes * fix: use correct userContext type --------- Co-authored-by: Mihaly Lengyel <[email protected]>
- Loading branch information
Showing
53 changed files
with
2,196 additions
and
317 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// @ts-nocheck | ||
import { APIInterface, APIOptions } from "../"; | ||
import { UserContext } from "../../../types"; | ||
export default function authorisationUrlAPI( | ||
apiImplementation: APIInterface, | ||
_tenantId: string, | ||
options: APIOptions, | ||
userContext: UserContext | ||
): Promise<boolean>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
"use strict"; | ||
/* Copyright (c) 2024, 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. | ||
*/ | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function (mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const utils_1 = require("../../../utils"); | ||
const error_1 = __importDefault(require("../../../error")); | ||
async function authorisationUrlAPI(apiImplementation, _tenantId, options, userContext) { | ||
if (apiImplementation.authorisationUrlGET === undefined) { | ||
return false; | ||
} | ||
// TODO: Check if we can rename `redirectURIOnProviderDashboard` to a more suitable name | ||
const redirectURIOnProviderDashboard = options.req.getKeyValueFromQuery("redirectURIOnProviderDashboard"); | ||
if (redirectURIOnProviderDashboard === undefined || typeof redirectURIOnProviderDashboard !== "string") { | ||
throw new error_1.default({ | ||
type: error_1.default.BAD_INPUT_ERROR, | ||
message: "Please provide the redirectURIOnProviderDashboard as a GET param", | ||
}); | ||
} | ||
let result = await apiImplementation.authorisationUrlGET({ | ||
redirectURIOnProviderDashboard, | ||
options, | ||
userContext, | ||
}); | ||
utils_1.send200Response(options.res, result); | ||
return true; | ||
} | ||
exports.default = authorisationUrlAPI; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// @ts-nocheck | ||
import { APIInterface } from "../"; | ||
export default function getAPIInterface(): APIInterface; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
"use strict"; | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function (mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const session_1 = __importDefault(require("../../session")); | ||
function getAPIInterface() { | ||
return { | ||
authorisationUrlGET: async function ({ options, redirectURIOnProviderDashboard, userContext }) { | ||
const providerConfig = await options.recipeImplementation.getProviderConfig({ userContext }); | ||
const authUrl = await options.recipeImplementation.getAuthorisationRedirectURL({ | ||
providerConfig, | ||
redirectURIOnProviderDashboard, | ||
userContext, | ||
}); | ||
return Object.assign({ status: "OK" }, authUrl); | ||
}, | ||
signInPOST: async function (input) { | ||
const { options, tenantId, userContext } = input; | ||
const providerConfig = await options.recipeImplementation.getProviderConfig({ userContext }); | ||
let oAuthTokensToUse = {}; | ||
if ("redirectURIInfo" in input && input.redirectURIInfo !== undefined) { | ||
oAuthTokensToUse = await options.recipeImplementation.exchangeAuthCodeForOAuthTokens({ | ||
providerConfig, | ||
redirectURIInfo: input.redirectURIInfo, | ||
userContext, | ||
}); | ||
} else if ("oAuthTokens" in input && input.oAuthTokens !== undefined) { | ||
oAuthTokensToUse = input.oAuthTokens; | ||
} else { | ||
throw Error("should never come here"); | ||
} | ||
const { userId, rawUserInfoFromProvider } = await options.recipeImplementation.getUserInfo({ | ||
providerConfig, | ||
oAuthTokens: oAuthTokensToUse, | ||
userContext, | ||
}); | ||
const { user, recipeUserId } = await options.recipeImplementation.signIn({ | ||
userId, | ||
tenantId, | ||
rawUserInfoFromProvider, | ||
oAuthTokens: oAuthTokensToUse, | ||
userContext, | ||
}); | ||
const session = await session_1.default.createNewSession( | ||
options.req, | ||
options.res, | ||
tenantId, | ||
recipeUserId, | ||
undefined, | ||
undefined, | ||
userContext | ||
); | ||
return { | ||
status: "OK", | ||
user, | ||
session, | ||
oAuthTokens: oAuthTokensToUse, | ||
rawUserInfoFromProvider, | ||
}; | ||
}, | ||
}; | ||
} | ||
exports.default = getAPIInterface; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// @ts-nocheck | ||
import { APIInterface, APIOptions } from ".."; | ||
import { UserContext } from "../../../types"; | ||
export default function signInAPI( | ||
apiImplementation: APIInterface, | ||
tenantId: string, | ||
options: APIOptions, | ||
userContext: UserContext | ||
): Promise<boolean>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
"use strict"; | ||
/* Copyright (c) 2024, 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. | ||
*/ | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function (mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const error_1 = __importDefault(require("../../../error")); | ||
const utils_1 = require("../../../utils"); | ||
const session_1 = __importDefault(require("../../session")); | ||
async function signInAPI(apiImplementation, tenantId, options, userContext) { | ||
if (apiImplementation.signInPOST === undefined) { | ||
return false; | ||
} | ||
const bodyParams = await options.req.getJSONBody(); | ||
let redirectURIInfo; | ||
let oAuthTokens; | ||
if (bodyParams.redirectURIInfo !== undefined) { | ||
if (bodyParams.redirectURIInfo.redirectURIOnProviderDashboard === undefined) { | ||
throw new error_1.default({ | ||
type: error_1.default.BAD_INPUT_ERROR, | ||
message: "Please provide the redirectURIOnProviderDashboard in request body", | ||
}); | ||
} | ||
redirectURIInfo = bodyParams.redirectURIInfo; | ||
} else if (bodyParams.oAuthTokens !== undefined) { | ||
oAuthTokens = bodyParams.oAuthTokens; | ||
} else { | ||
throw new error_1.default({ | ||
type: error_1.default.BAD_INPUT_ERROR, | ||
message: "Please provide one of redirectURIInfo or oAuthTokens in the request body", | ||
}); | ||
} | ||
let session = await session_1.default.getSession( | ||
options.req, | ||
options.res, | ||
{ | ||
sessionRequired: false, | ||
overrideGlobalClaimValidators: () => [], | ||
}, | ||
userContext | ||
); | ||
if (session !== undefined) { | ||
tenantId = session.getTenantId(); | ||
} | ||
let result = await apiImplementation.signInPOST({ | ||
tenantId, | ||
redirectURIInfo, | ||
oAuthTokens, | ||
session, | ||
options, | ||
userContext, | ||
}); | ||
if (result.status === "OK") { | ||
utils_1.send200Response( | ||
options.res, | ||
Object.assign( | ||
{ status: result.status }, | ||
utils_1.getBackwardsCompatibleUserInfo(options.req, result, userContext) | ||
) | ||
); | ||
} else { | ||
utils_1.send200Response(options.res, result); | ||
} | ||
return true; | ||
} | ||
exports.default = signInAPI; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// @ts-nocheck | ||
export declare const AUTHORISATION_API = "/oauth2client/authorisationurl"; | ||
export declare const SIGN_IN_API = "/oauth2client/signin"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
"use strict"; | ||
/* Copyright (c) 2024, 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. | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.SIGN_IN_API = exports.AUTHORISATION_API = void 0; | ||
exports.AUTHORISATION_API = "/oauth2client/authorisationurl"; | ||
exports.SIGN_IN_API = "/oauth2client/signin"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// @ts-nocheck | ||
import Recipe from "./recipe"; | ||
import { RecipeInterface, APIInterface, APIOptions, OAuthTokens } from "./types"; | ||
export default class Wrapper { | ||
static init: typeof Recipe.init; | ||
static getAuthorisationRedirectURL( | ||
redirectURIOnProviderDashboard: string, | ||
userContext?: Record<string, any> | ||
): Promise<{ | ||
urlWithQueryParams: string; | ||
pkceCodeVerifier?: string | undefined; | ||
}>; | ||
static exchangeAuthCodeForOAuthTokens( | ||
redirectURIInfo: { | ||
redirectURIOnProviderDashboard: string; | ||
redirectURIQueryParams: any; | ||
pkceCodeVerifier?: string | undefined; | ||
}, | ||
userContext?: Record<string, any> | ||
): Promise<import("./types").OAuthTokenResponse>; | ||
static getUserInfo( | ||
oAuthTokens: OAuthTokens, | ||
userContext?: Record<string, any> | ||
): Promise<import("./types").UserInfo>; | ||
} | ||
export declare let init: typeof Recipe.init; | ||
export declare let getAuthorisationRedirectURL: typeof Wrapper.getAuthorisationRedirectURL; | ||
export declare let exchangeAuthCodeForOAuthTokens: typeof Wrapper.exchangeAuthCodeForOAuthTokens; | ||
export declare let getUserInfo: typeof Wrapper.getUserInfo; | ||
export type { RecipeInterface, APIInterface, APIOptions }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
"use strict"; | ||
/* Copyright (c) 2024, 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. | ||
*/ | ||
var __importDefault = | ||
(this && this.__importDefault) || | ||
function (mod) { | ||
return mod && mod.__esModule ? mod : { default: mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getUserInfo = exports.exchangeAuthCodeForOAuthTokens = exports.getAuthorisationRedirectURL = exports.init = void 0; | ||
const utils_1 = require("../../utils"); | ||
const recipe_1 = __importDefault(require("./recipe")); | ||
class Wrapper { | ||
static async getAuthorisationRedirectURL(redirectURIOnProviderDashboard, userContext) { | ||
const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl; | ||
const providerConfig = await recipeInterfaceImpl.getProviderConfig({ | ||
userContext: utils_1.getUserContext(userContext), | ||
}); | ||
return await recipeInterfaceImpl.getAuthorisationRedirectURL({ | ||
providerConfig, | ||
redirectURIOnProviderDashboard, | ||
userContext: utils_1.getUserContext(userContext), | ||
}); | ||
} | ||
static async exchangeAuthCodeForOAuthTokens(redirectURIInfo, userContext) { | ||
const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl; | ||
const providerConfig = await recipeInterfaceImpl.getProviderConfig({ | ||
userContext: utils_1.getUserContext(userContext), | ||
}); | ||
return await recipeInterfaceImpl.exchangeAuthCodeForOAuthTokens({ | ||
providerConfig, | ||
redirectURIInfo, | ||
userContext: utils_1.getUserContext(userContext), | ||
}); | ||
} | ||
static async getUserInfo(oAuthTokens, userContext) { | ||
const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl; | ||
const providerConfig = await recipeInterfaceImpl.getProviderConfig({ | ||
userContext: utils_1.getUserContext(userContext), | ||
}); | ||
return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getUserInfo({ | ||
providerConfig, | ||
oAuthTokens, | ||
userContext: utils_1.getUserContext(userContext), | ||
}); | ||
} | ||
} | ||
exports.default = Wrapper; | ||
Wrapper.init = recipe_1.default.init; | ||
exports.init = Wrapper.init; | ||
exports.getAuthorisationRedirectURL = Wrapper.getAuthorisationRedirectURL; | ||
exports.exchangeAuthCodeForOAuthTokens = Wrapper.exchangeAuthCodeForOAuthTokens; | ||
exports.getUserInfo = Wrapper.getUserInfo; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// @ts-nocheck | ||
import RecipeModule from "../../recipeModule"; | ||
import { NormalisedAppinfo, APIHandled, RecipeListFunction, HTTPMethod, UserContext } from "../../types"; | ||
import { TypeInput, TypeNormalisedInput, RecipeInterface, APIInterface } from "./types"; | ||
import STError from "../../error"; | ||
import NormalisedURLPath from "../../normalisedURLPath"; | ||
import type { BaseRequest, BaseResponse } from "../../framework"; | ||
export default class Recipe extends RecipeModule { | ||
private static instance; | ||
static RECIPE_ID: string; | ||
config: TypeNormalisedInput; | ||
recipeInterfaceImpl: RecipeInterface; | ||
apiImpl: APIInterface; | ||
isInServerlessEnv: boolean; | ||
constructor( | ||
recipeId: string, | ||
appInfo: NormalisedAppinfo, | ||
isInServerlessEnv: boolean, | ||
config: TypeInput, | ||
_recipes: {} | ||
); | ||
static init(config: TypeInput): RecipeListFunction; | ||
static getInstanceOrThrowError(): Recipe; | ||
static reset(): void; | ||
getAPIsHandled: () => APIHandled[]; | ||
handleAPIRequest: ( | ||
id: string, | ||
tenantId: string, | ||
req: BaseRequest, | ||
res: BaseResponse, | ||
_path: NormalisedURLPath, | ||
_method: HTTPMethod, | ||
userContext: UserContext | ||
) => Promise<boolean>; | ||
handleError: (err: STError, _request: BaseRequest, _response: BaseResponse) => Promise<void>; | ||
getAllCORSHeaders: () => string[]; | ||
isErrorFromThisRecipe: (err: any) => err is STError; | ||
} |
Oops, something went wrong.