From 03f39145dc72dcca55b594ac3e23eb6e503b5d82 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Sun, 9 Jun 2024 17:16:45 +0200 Subject: [PATCH 01/35] feat: add boilerplate for oauth2 recipe --- CHANGELOG.md | 4 + .../recipe/oauth2/api/implementation.d.ts | 3 + lib/build/recipe/oauth2/api/implementation.js | 20 +++ lib/build/recipe/oauth2/constants.d.ts | 2 + lib/build/recipe/oauth2/constants.js | 18 +++ lib/build/recipe/oauth2/index.d.ts | 8 ++ lib/build/recipe/oauth2/index.js | 27 ++++ lib/build/recipe/oauth2/recipe.d.ts | 32 +++++ lib/build/recipe/oauth2/recipe.js | 99 +++++++++++++++ .../recipe/oauth2/recipeImplementation.d.ts | 9 ++ .../recipe/oauth2/recipeImplementation.js | 20 +++ lib/build/recipe/oauth2/types.d.ts | 31 +++++ lib/build/recipe/oauth2/types.js | 16 +++ lib/build/recipe/oauth2/utils.d.ts | 9 ++ lib/build/recipe/oauth2/utils.js | 30 +++++ lib/ts/recipe/oauth2/api/implementation.ts | 20 +++ lib/ts/recipe/oauth2/constants.ts | 16 +++ lib/ts/recipe/oauth2/index.ts | 25 ++++ lib/ts/recipe/oauth2/recipe.ts | 120 ++++++++++++++++++ lib/ts/recipe/oauth2/recipeImplementation.ts | 26 ++++ lib/ts/recipe/oauth2/types.ts | 50 ++++++++ lib/ts/recipe/oauth2/utils.ts | 34 +++++ recipe/oauth2/index.d.ts | 10 ++ recipe/oauth2/index.js | 6 + recipe/oauth2/types/index.d.ts | 10 ++ recipe/oauth2/types/index.js | 6 + test/oauth2/config.test.js | 45 +++++++ 27 files changed, 696 insertions(+) create mode 100644 lib/build/recipe/oauth2/api/implementation.d.ts create mode 100644 lib/build/recipe/oauth2/api/implementation.js create mode 100644 lib/build/recipe/oauth2/constants.d.ts create mode 100644 lib/build/recipe/oauth2/constants.js create mode 100644 lib/build/recipe/oauth2/index.d.ts create mode 100644 lib/build/recipe/oauth2/index.js create mode 100644 lib/build/recipe/oauth2/recipe.d.ts create mode 100644 lib/build/recipe/oauth2/recipe.js create mode 100644 lib/build/recipe/oauth2/recipeImplementation.d.ts create mode 100644 lib/build/recipe/oauth2/recipeImplementation.js create mode 100644 lib/build/recipe/oauth2/types.d.ts create mode 100644 lib/build/recipe/oauth2/types.js create mode 100644 lib/build/recipe/oauth2/utils.d.ts create mode 100644 lib/build/recipe/oauth2/utils.js create mode 100644 lib/ts/recipe/oauth2/api/implementation.ts create mode 100644 lib/ts/recipe/oauth2/constants.ts create mode 100644 lib/ts/recipe/oauth2/index.ts create mode 100644 lib/ts/recipe/oauth2/recipe.ts create mode 100644 lib/ts/recipe/oauth2/recipeImplementation.ts create mode 100644 lib/ts/recipe/oauth2/types.ts create mode 100644 lib/ts/recipe/oauth2/utils.ts create mode 100644 recipe/oauth2/index.d.ts create mode 100644 recipe/oauth2/index.js create mode 100644 recipe/oauth2/types/index.d.ts create mode 100644 recipe/oauth2/types/index.js create mode 100644 test/oauth2/config.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c5bd65564..6696800e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## UNRELEASED + +- Added OAuth2 recipe + ## [18.0.0] - 2024-05-23 ### Breaking change diff --git a/lib/build/recipe/oauth2/api/implementation.d.ts b/lib/build/recipe/oauth2/api/implementation.d.ts new file mode 100644 index 000000000..0218549fa --- /dev/null +++ b/lib/build/recipe/oauth2/api/implementation.d.ts @@ -0,0 +1,3 @@ +// @ts-nocheck +import { APIInterface } from "../types"; +export default function getAPIImplementation(): APIInterface; diff --git a/lib/build/recipe/oauth2/api/implementation.js b/lib/build/recipe/oauth2/api/implementation.js new file mode 100644 index 000000000..4a0dd5c82 --- /dev/null +++ b/lib/build/recipe/oauth2/api/implementation.js @@ -0,0 +1,20 @@ +"use strict"; +/* 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. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +function getAPIImplementation() { + return {}; +} +exports.default = getAPIImplementation; diff --git a/lib/build/recipe/oauth2/constants.d.ts b/lib/build/recipe/oauth2/constants.d.ts new file mode 100644 index 000000000..0d55262a7 --- /dev/null +++ b/lib/build/recipe/oauth2/constants.d.ts @@ -0,0 +1,2 @@ +// @ts-nocheck +export declare const OAUTH2_BASE_PATH = "/oauth2/"; diff --git a/lib/build/recipe/oauth2/constants.js b/lib/build/recipe/oauth2/constants.js new file mode 100644 index 000000000..e8dc43a0d --- /dev/null +++ b/lib/build/recipe/oauth2/constants.js @@ -0,0 +1,18 @@ +"use strict"; +/* 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. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OAUTH2_BASE_PATH = void 0; +exports.OAUTH2_BASE_PATH = "/oauth2/"; diff --git a/lib/build/recipe/oauth2/index.d.ts b/lib/build/recipe/oauth2/index.d.ts new file mode 100644 index 000000000..b3b1542de --- /dev/null +++ b/lib/build/recipe/oauth2/index.d.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +import Recipe from "./recipe"; +import { APIInterface, RecipeInterface, APIOptions } from "./types"; +export default class Wrapper { + static init: typeof Recipe.init; +} +export declare let init: typeof Recipe.init; +export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/build/recipe/oauth2/index.js b/lib/build/recipe/oauth2/index.js new file mode 100644 index 000000000..fb52eae5a --- /dev/null +++ b/lib/build/recipe/oauth2/index.js @@ -0,0 +1,27 @@ +"use strict"; +/* 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. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.init = void 0; +const recipe_1 = __importDefault(require("./recipe")); +class Wrapper {} +exports.default = Wrapper; +Wrapper.init = recipe_1.default.init; +exports.init = Wrapper.init; diff --git a/lib/build/recipe/oauth2/recipe.d.ts b/lib/build/recipe/oauth2/recipe.d.ts new file mode 100644 index 000000000..c979f3d0c --- /dev/null +++ b/lib/build/recipe/oauth2/recipe.d.ts @@ -0,0 +1,32 @@ +// @ts-nocheck +import error from "../../error"; +import type { BaseRequest, BaseResponse } from "../../framework"; +import NormalisedURLPath from "../../normalisedURLPath"; +import RecipeModule from "../../recipeModule"; +import { APIHandled, HTTPMethod, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; +import { APIInterface, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; +export default class Recipe extends RecipeModule { + static RECIPE_ID: string; + private static instance; + config: TypeNormalisedInput; + recipeInterfaceImpl: RecipeInterface; + apiImpl: APIInterface; + isInServerlessEnv: boolean; + constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config?: TypeInput); + static getInstanceOrThrowError(): Recipe; + static init(config?: TypeInput): RecipeListFunction; + static reset(): void; + getAPIsHandled(): APIHandled[]; + handleAPIRequest: ( + _id: string, + _tenantId: string | undefined, + _req: BaseRequest, + _res: BaseResponse, + _path: NormalisedURLPath, + _method: HTTPMethod, + _userContext: UserContext + ) => Promise; + handleError(error: error, _: BaseRequest, __: BaseResponse, _userContext: UserContext): Promise; + getAllCORSHeaders(): string[]; + isErrorFromThisRecipe(err: any): err is error; +} diff --git a/lib/build/recipe/oauth2/recipe.js b/lib/build/recipe/oauth2/recipe.js new file mode 100644 index 000000000..44be8d270 --- /dev/null +++ b/lib/build/recipe/oauth2/recipe.js @@ -0,0 +1,99 @@ +"use strict"; +/* 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. + */ +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 querier_1 = require("../../querier"); +const recipeModule_1 = __importDefault(require("../../recipeModule")); +const implementation_1 = __importDefault(require("./api/implementation")); +const recipeImplementation_1 = __importDefault(require("./recipeImplementation")); +const utils_1 = require("./utils"); +const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); +class Recipe extends recipeModule_1.default { + constructor(recipeId, appInfo, isInServerlessEnv, config) { + super(recipeId, appInfo); + this.handleAPIRequest = async (_id, _tenantId, _req, _res, _path, _method, _userContext) => { + // let options = { + // config: this.config, + // recipeId: this.getRecipeId(), + // isInServerlessEnv: this.isInServerlessEnv, + // recipeImplementation: this.recipeInterfaceImpl, + // req, + // res, + // }; + throw new Error("Not implemented"); + }; + this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); + this.isInServerlessEnv = isInServerlessEnv; + { + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default( + querier_1.Querier.getNewInstanceOrThrowError(recipeId), + this.config, + appInfo + ) + ); + this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); + } + { + let builder = new supertokens_js_override_1.default(implementation_1.default()); + this.apiImpl = builder.override(this.config.override.apis).build(); + } + } + /* Init functions */ + static getInstanceOrThrowError() { + if (Recipe.instance !== undefined) { + return Recipe.instance; + } + throw new Error("Initialisation not done. Did you forget to call the Jwt.init function?"); + } + static init(config) { + return (appInfo, isInServerlessEnv) => { + if (Recipe.instance === undefined) { + Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); + return Recipe.instance; + } else { + throw new Error("JWT recipe has already been initialised. Please check your code for bugs."); + } + }; + } + static reset() { + if (process.env.TEST_MODE !== "testing") { + throw new Error("calling testing function in non testing env"); + } + Recipe.instance = undefined; + } + /* RecipeModule functions */ + getAPIsHandled() { + return []; + } + handleError(error, _, __, _userContext) { + throw error; + } + getAllCORSHeaders() { + return []; + } + isErrorFromThisRecipe(err) { + return error_1.default.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; + } +} +exports.default = Recipe; +Recipe.RECIPE_ID = "oauth2"; +Recipe.instance = undefined; diff --git a/lib/build/recipe/oauth2/recipeImplementation.d.ts b/lib/build/recipe/oauth2/recipeImplementation.d.ts new file mode 100644 index 000000000..513f5df92 --- /dev/null +++ b/lib/build/recipe/oauth2/recipeImplementation.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import { Querier } from "../../querier"; +import { NormalisedAppinfo } from "../../types"; +import { RecipeInterface, TypeNormalisedInput } from "./types"; +export default function getRecipeInterface( + _querier: Querier, + _config: TypeNormalisedInput, + _appInfo: NormalisedAppinfo +): RecipeInterface; diff --git a/lib/build/recipe/oauth2/recipeImplementation.js b/lib/build/recipe/oauth2/recipeImplementation.js new file mode 100644 index 000000000..67c4636f5 --- /dev/null +++ b/lib/build/recipe/oauth2/recipeImplementation.js @@ -0,0 +1,20 @@ +"use strict"; +/* 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. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +function getRecipeInterface(_querier, _config, _appInfo) { + return {}; +} +exports.default = getRecipeInterface; diff --git a/lib/build/recipe/oauth2/types.d.ts b/lib/build/recipe/oauth2/types.d.ts new file mode 100644 index 000000000..f2e26d16d --- /dev/null +++ b/lib/build/recipe/oauth2/types.d.ts @@ -0,0 +1,31 @@ +// @ts-nocheck +import type { BaseRequest, BaseResponse } from "../../framework"; +import OverrideableBuilder from "supertokens-js-override"; +export declare type TypeInput = { + override?: { + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; +export declare type TypeNormalisedInput = { + override: { + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; +export declare type APIOptions = { + recipeImplementation: RecipeInterface; + config: TypeNormalisedInput; + recipeId: string; + isInServerlessEnv: boolean; + req: BaseRequest; + res: BaseResponse; +}; +export declare type RecipeInterface = {}; +export declare type APIInterface = {}; diff --git a/lib/build/recipe/oauth2/types.js b/lib/build/recipe/oauth2/types.js new file mode 100644 index 000000000..a098ca1d7 --- /dev/null +++ b/lib/build/recipe/oauth2/types.js @@ -0,0 +1,16 @@ +"use strict"; +/* 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. + */ +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/build/recipe/oauth2/utils.d.ts b/lib/build/recipe/oauth2/utils.d.ts new file mode 100644 index 000000000..4025b1b44 --- /dev/null +++ b/lib/build/recipe/oauth2/utils.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import { NormalisedAppinfo } from "../../types"; +import Recipe from "./recipe"; +import { TypeInput, TypeNormalisedInput } from "./types"; +export declare function validateAndNormaliseUserInput( + _: Recipe, + __: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput; diff --git a/lib/build/recipe/oauth2/utils.js b/lib/build/recipe/oauth2/utils.js new file mode 100644 index 000000000..435b4e471 --- /dev/null +++ b/lib/build/recipe/oauth2/utils.js @@ -0,0 +1,30 @@ +"use strict"; +/* 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. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.validateAndNormaliseUserInput = void 0; +function validateAndNormaliseUserInput(_, __, config) { + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); + return { + override, + }; +} +exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput; diff --git a/lib/ts/recipe/oauth2/api/implementation.ts b/lib/ts/recipe/oauth2/api/implementation.ts new file mode 100644 index 000000000..3cba9819d --- /dev/null +++ b/lib/ts/recipe/oauth2/api/implementation.ts @@ -0,0 +1,20 @@ +/* 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. + */ + +import { APIInterface } from "../types"; + +export default function getAPIImplementation(): APIInterface { + return {}; +} diff --git a/lib/ts/recipe/oauth2/constants.ts b/lib/ts/recipe/oauth2/constants.ts new file mode 100644 index 000000000..172ca6987 --- /dev/null +++ b/lib/ts/recipe/oauth2/constants.ts @@ -0,0 +1,16 @@ +/* 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. + */ + +export const OAUTH2_BASE_PATH = "/oauth2/"; diff --git a/lib/ts/recipe/oauth2/index.ts b/lib/ts/recipe/oauth2/index.ts new file mode 100644 index 000000000..d0c7eb45e --- /dev/null +++ b/lib/ts/recipe/oauth2/index.ts @@ -0,0 +1,25 @@ +/* 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. + */ + +import Recipe from "./recipe"; +import { APIInterface, RecipeInterface, APIOptions } from "./types"; + +export default class Wrapper { + static init = Recipe.init; +} + +export let init = Wrapper.init; + +export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/ts/recipe/oauth2/recipe.ts b/lib/ts/recipe/oauth2/recipe.ts new file mode 100644 index 000000000..72aa025ae --- /dev/null +++ b/lib/ts/recipe/oauth2/recipe.ts @@ -0,0 +1,120 @@ +/* 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. + */ + +import SuperTokensError from "../../error"; +import error from "../../error"; +import type { BaseRequest, BaseResponse } from "../../framework"; +import NormalisedURLPath from "../../normalisedURLPath"; +import { Querier } from "../../querier"; +import RecipeModule from "../../recipeModule"; +import { APIHandled, HTTPMethod, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; +import APIImplementation from "./api/implementation"; +import RecipeImplementation from "./recipeImplementation"; +import { APIInterface, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; +import { validateAndNormaliseUserInput } from "./utils"; +import OverrideableBuilder from "supertokens-js-override"; + +export default class Recipe extends RecipeModule { + static RECIPE_ID = "oauth2"; + private static instance: Recipe | undefined = undefined; + + config: TypeNormalisedInput; + recipeInterfaceImpl: RecipeInterface; + apiImpl: APIInterface; + isInServerlessEnv: boolean; + + constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config?: TypeInput) { + super(recipeId, appInfo); + this.config = validateAndNormaliseUserInput(this, appInfo, config); + this.isInServerlessEnv = isInServerlessEnv; + + { + let builder = new OverrideableBuilder( + RecipeImplementation(Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo) + ); + this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); + } + { + let builder = new OverrideableBuilder(APIImplementation()); + this.apiImpl = builder.override(this.config.override.apis).build(); + } + } + + /* Init functions */ + + static getInstanceOrThrowError(): Recipe { + if (Recipe.instance !== undefined) { + return Recipe.instance; + } + throw new Error("Initialisation not done. Did you forget to call the Jwt.init function?"); + } + + static init(config?: TypeInput): RecipeListFunction { + return (appInfo, isInServerlessEnv) => { + if (Recipe.instance === undefined) { + Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); + return Recipe.instance; + } else { + throw new Error("JWT recipe has already been initialised. Please check your code for bugs."); + } + }; + } + + static reset() { + if (process.env.TEST_MODE !== "testing") { + throw new Error("calling testing function in non testing env"); + } + Recipe.instance = undefined; + } + + /* RecipeModule functions */ + + getAPIsHandled(): APIHandled[] { + return []; + } + + handleAPIRequest = async ( + _id: string, + _tenantId: string | undefined, + _req: BaseRequest, + _res: BaseResponse, + _path: NormalisedURLPath, + _method: HTTPMethod, + _userContext: UserContext + ): Promise => { + // let options = { + // config: this.config, + // recipeId: this.getRecipeId(), + // isInServerlessEnv: this.isInServerlessEnv, + // recipeImplementation: this.recipeInterfaceImpl, + // req, + // res, + // }; + + throw new Error("Not implemented"); + }; + + handleError(error: error, _: BaseRequest, __: BaseResponse, _userContext: UserContext): Promise { + throw error; + } + + getAllCORSHeaders(): string[] { + return []; + } + + isErrorFromThisRecipe(err: any): err is error { + return SuperTokensError.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; + } +} diff --git a/lib/ts/recipe/oauth2/recipeImplementation.ts b/lib/ts/recipe/oauth2/recipeImplementation.ts new file mode 100644 index 000000000..1bd119bb2 --- /dev/null +++ b/lib/ts/recipe/oauth2/recipeImplementation.ts @@ -0,0 +1,26 @@ +/* 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. + */ + +import { Querier } from "../../querier"; +import { NormalisedAppinfo } from "../../types"; +import { RecipeInterface, TypeNormalisedInput } from "./types"; + +export default function getRecipeInterface( + _querier: Querier, + _config: TypeNormalisedInput, + _appInfo: NormalisedAppinfo +): RecipeInterface { + return {}; +} diff --git a/lib/ts/recipe/oauth2/types.ts b/lib/ts/recipe/oauth2/types.ts new file mode 100644 index 000000000..31bfdbf94 --- /dev/null +++ b/lib/ts/recipe/oauth2/types.ts @@ -0,0 +1,50 @@ +/* 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. + */ + +import type { BaseRequest, BaseResponse } from "../../framework"; +import OverrideableBuilder from "supertokens-js-override"; + +export type TypeInput = { + override?: { + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; + +export type TypeNormalisedInput = { + override: { + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; + +export type APIOptions = { + recipeImplementation: RecipeInterface; + config: TypeNormalisedInput; + recipeId: string; + isInServerlessEnv: boolean; + req: BaseRequest; + res: BaseResponse; +}; + +export type RecipeInterface = {}; + +export type APIInterface = {}; diff --git a/lib/ts/recipe/oauth2/utils.ts b/lib/ts/recipe/oauth2/utils.ts new file mode 100644 index 000000000..d59cd4537 --- /dev/null +++ b/lib/ts/recipe/oauth2/utils.ts @@ -0,0 +1,34 @@ +/* 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. + */ + +import { NormalisedAppinfo } from "../../types"; +import Recipe from "./recipe"; +import { APIInterface, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; + +export function validateAndNormaliseUserInput( + _: Recipe, + __: NormalisedAppinfo, + config?: TypeInput +): TypeNormalisedInput { + let override = { + functions: (originalImplementation: RecipeInterface) => originalImplementation, + apis: (originalImplementation: APIInterface) => originalImplementation, + ...config?.override, + }; + + return { + override, + }; +} diff --git a/recipe/oauth2/index.d.ts b/recipe/oauth2/index.d.ts new file mode 100644 index 000000000..e82d1a6b5 --- /dev/null +++ b/recipe/oauth2/index.d.ts @@ -0,0 +1,10 @@ +export * from "../../lib/build/recipe/oauth2"; +/** + * 'export *' does not re-export a default. + * import NextJS from "supertokens-node/nextjs"; + * the above import statement won't be possible unless either + * - user add "esModuleInterop": true in their tsconfig.json file + * - we do the following change: + */ +import * as _default from "../../lib/build/recipe/oauth2"; +export default _default; diff --git a/recipe/oauth2/index.js b/recipe/oauth2/index.js new file mode 100644 index 000000000..01b7d12b6 --- /dev/null +++ b/recipe/oauth2/index.js @@ -0,0 +1,6 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +exports.__esModule = true; +__export(require("../../lib/build/recipe/oauth2")); diff --git a/recipe/oauth2/types/index.d.ts b/recipe/oauth2/types/index.d.ts new file mode 100644 index 000000000..6b7f5a64c --- /dev/null +++ b/recipe/oauth2/types/index.d.ts @@ -0,0 +1,10 @@ +export * from "../../../lib/build/recipe/oauth2/types"; +/** + * 'export *' does not re-export a default. + * import NextJS from "supertokens-node/nextjs"; + * the above import statement won't be possible unless either + * - user add "esModuleInterop": true in their tsconfig.json file + * - we do the following change: + */ +import * as _default from "../../../lib/build/recipe/oauth2/types"; +export default _default; diff --git a/recipe/oauth2/types/index.js b/recipe/oauth2/types/index.js new file mode 100644 index 000000000..5f9654705 --- /dev/null +++ b/recipe/oauth2/types/index.js @@ -0,0 +1,6 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +exports.__esModule = true; +__export(require("../../../lib/build/recipe/oauth2/types")); diff --git a/test/oauth2/config.test.js b/test/oauth2/config.test.js new file mode 100644 index 000000000..88c15957f --- /dev/null +++ b/test/oauth2/config.test.js @@ -0,0 +1,45 @@ +let assert = require("assert"); + +const { printPath, setupST, startST, killAllST, cleanST } = require("../utils"); +let { ProcessState } = require("../../lib/build/processState"); +let STExpress = require("../../"); +const OAuth2Recipe = require("../../lib/build/recipe/OAuth2/recipe").default; +let { Querier } = require("../../lib/build/querier"); +const { maxVersion } = require("../../lib/build/utils"); + +describe(`configTest: ${printPath("[test/oauth2/config.test.js]")}`, function () { + beforeEach(async function () { + await killAllST(); + await setupST(); + ProcessState.getInstance().reset(); + }); + + after(async function () { + await killAllST(); + await cleanST(); + }); + + it("Test that the recipe initializes without a config obj", async function () { + const connectionURI = await startST(); + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [OAuth2Recipe.init()], + }); + + // Only run for version >= 2.9 + let querier = Querier.getNewInstanceOrThrowError(undefined); + let apiVersion = await querier.getAPIVersion(); + if (maxVersion(apiVersion, "2.8") === "2.8") { + return; + } + + let OAuth2Recipe = await OAuth2Recipe.getInstanceOrThrowError(); + }); +}); From 5a215b91458f1c1c9145a79e860e19c75ebbff0a Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Wed, 12 Jun 2024 22:09:38 +0200 Subject: [PATCH 02/35] feat: add a temporary solution to query hydra (until core impl) from recipe funcs --- lib/build/querier.js | 14 ++++++++++++++ lib/ts/querier.ts | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/build/querier.js b/lib/build/querier.js index 3a2f7db69..aa53a5266 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -4,6 +4,7 @@ var __importDefault = function (mod) { return mod && mod.__esModule ? mod : { default: mod }; }; +var _a, _b; Object.defineProperty(exports, "__esModule", { value: true }); exports.Querier = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. @@ -26,6 +27,10 @@ const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); const processState_1 = require("./processState"); const constants_1 = require("./constants"); const logger_1 = require("./logger"); +const hydraPubDomain = (_a = process.env.HYDRA_PUB) !== null && _a !== void 0 ? _a : "http://localhost:4444"; // This will be used as a domain for paths starting with hydraPubPathPrefix +const hydraAdmDomain = (_b = process.env.HYDRA_ADM) !== null && _b !== void 0 ? _b : "http://localhost:4445"; // This will be used as a domain for paths starting with hydraAdmPathPrefix +const hydraPubPathPrefix = "/recipe/oauth2/pub"; // Replaced with "/oauth2" when sending the request (/recipe/oauth2/pub/token -> /oauth2/token) +const hydraAdmPathPrefix = "/recipe/oauth2/admin"; // Replaced with "/admin" when sending the request (/recipe/oauth2/admin/clients -> /admin/clients) class Querier { // we have rIdToCore so that recipes can force change the rId sent to core. This is a hack until the core is able // to support multiple rIds per API @@ -373,6 +378,15 @@ class Querier { } let currentDomain = this.__hosts[Querier.lastTriedIndex].domain.getAsStringDangerous(); let currentBasePath = this.__hosts[Querier.lastTriedIndex].basePath.getAsStringDangerous(); + let strPath = path.getAsStringDangerous(); + if (strPath.startsWith(hydraPubPathPrefix)) { + currentDomain = hydraPubDomain; + strPath.replace(hydraPubPathPrefix, "/oauth2"); + } + if (strPath.startsWith(hydraAdmPathPrefix)) { + currentDomain = hydraAdmDomain; + strPath.replace(hydraAdmPathPrefix, "/admin"); + } const url = currentDomain + currentBasePath + path.getAsStringDangerous(); const maxRetries = 5; if (retryInfoMap === undefined) { diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index b4255b4db..7d02f2377 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -22,6 +22,11 @@ import { logDebugMessage } from "./logger"; import { UserContext } from "./types"; import { NetworkInterceptor } from "./types"; +const hydraPubDomain = process.env.HYDRA_PUB ?? "http://localhost:4444"; // This will be used as a domain for paths starting with hydraPubPathPrefix +const hydraAdmDomain = process.env.HYDRA_ADM ?? "http://localhost:4445"; // This will be used as a domain for paths starting with hydraAdmPathPrefix +const hydraPubPathPrefix = "/recipe/oauth2/pub"; // Replaced with "/oauth2" when sending the request (/recipe/oauth2/pub/token -> /oauth2/token) +const hydraAdmPathPrefix = "/recipe/oauth2/admin"; // Replaced with "/admin" when sending the request (/recipe/oauth2/admin/clients -> /admin/clients) + export class Querier { private static initCalled = false; private static hosts: { domain: NormalisedURLDomain; basePath: NormalisedURLPath }[] | undefined = undefined; @@ -488,6 +493,18 @@ export class Querier { } let currentDomain: string = this.__hosts[Querier.lastTriedIndex].domain.getAsStringDangerous(); let currentBasePath: string = this.__hosts[Querier.lastTriedIndex].basePath.getAsStringDangerous(); + + let strPath = path.getAsStringDangerous(); + if (strPath.startsWith(hydraPubPathPrefix)) { + currentDomain = hydraPubDomain; + strPath.replace(hydraPubPathPrefix, "/oauth2"); + } + + if (strPath.startsWith(hydraAdmPathPrefix)) { + currentDomain = hydraAdmDomain; + strPath.replace(hydraAdmPathPrefix, "/admin"); + } + const url = currentDomain + currentBasePath + path.getAsStringDangerous(); const maxRetries = 5; From 7a29a7a2870eaa1c5ae497168bfdb9b99eecf0fa Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Thu, 13 Jun 2024 21:57:51 +0200 Subject: [PATCH 03/35] fix: fix temp solution for hydra calls --- lib/build/querier.js | 6 +++--- lib/ts/querier.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/build/querier.js b/lib/build/querier.js index aa53a5266..121687dcb 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -381,13 +381,13 @@ class Querier { let strPath = path.getAsStringDangerous(); if (strPath.startsWith(hydraPubPathPrefix)) { currentDomain = hydraPubDomain; - strPath.replace(hydraPubPathPrefix, "/oauth2"); + strPath = strPath.replace(hydraPubPathPrefix, "/oauth2"); } if (strPath.startsWith(hydraAdmPathPrefix)) { currentDomain = hydraAdmDomain; - strPath.replace(hydraAdmPathPrefix, "/admin"); + strPath = strPath.replace(hydraAdmPathPrefix, "/admin"); } - const url = currentDomain + currentBasePath + path.getAsStringDangerous(); + const url = currentDomain + currentBasePath + strPath; const maxRetries = 5; if (retryInfoMap === undefined) { retryInfoMap = {}; diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index 7d02f2377..26daf1951 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -497,15 +497,15 @@ export class Querier { let strPath = path.getAsStringDangerous(); if (strPath.startsWith(hydraPubPathPrefix)) { currentDomain = hydraPubDomain; - strPath.replace(hydraPubPathPrefix, "/oauth2"); + strPath = strPath.replace(hydraPubPathPrefix, "/oauth2"); } if (strPath.startsWith(hydraAdmPathPrefix)) { currentDomain = hydraAdmDomain; - strPath.replace(hydraAdmPathPrefix, "/admin"); + strPath = strPath.replace(hydraAdmPathPrefix, "/admin"); } - const url = currentDomain + currentBasePath + path.getAsStringDangerous(); + const url = currentDomain + currentBasePath + strPath; const maxRetries = 5; if (retryInfoMap === undefined) { From 16e9631523694568961331179a30a0c16f1f44a4 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Mon, 17 Jun 2024 14:12:22 +0530 Subject: [PATCH 04/35] feat: Add a recipe function to create OAuth2Client (#859) --- lib/build/querier.js | 8 + lib/build/recipe/oauth2/OAuth2Client.d.ts | 304 +++++++++++++ lib/build/recipe/oauth2/OAuth2Client.js | 122 ++++++ lib/build/recipe/oauth2/index.d.ts | 18 +- lib/build/recipe/oauth2/index.js | 11 +- lib/build/recipe/oauth2/recipe.js | 2 +- .../recipe/oauth2/recipeImplementation.d.ts | 2 +- .../recipe/oauth2/recipeImplementation.js | 38 +- lib/build/recipe/oauth2/types.d.ts | 69 ++- lib/build/utils.d.ts | 1 + lib/build/utils.js | 8 +- lib/ts/querier.ts | 12 + lib/ts/recipe/oauth2/OAuth2Client.ts | 414 ++++++++++++++++++ lib/ts/recipe/oauth2/index.ts | 9 +- lib/ts/recipe/oauth2/recipe.ts | 2 +- lib/ts/recipe/oauth2/recipeImplementation.ts | 36 +- lib/ts/recipe/oauth2/types.ts | 76 +++- lib/ts/utils.ts | 6 + test/oauth2/oauth2client.test.js | 82 ++++ test/utils.js | 2 + 20 files changed, 1208 insertions(+), 14 deletions(-) create mode 100644 lib/build/recipe/oauth2/OAuth2Client.d.ts create mode 100644 lib/build/recipe/oauth2/OAuth2Client.js create mode 100644 lib/ts/recipe/oauth2/OAuth2Client.ts create mode 100644 test/oauth2/oauth2client.test.js diff --git a/lib/build/querier.js b/lib/build/querier.js index 121687dcb..663603253 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -379,6 +379,7 @@ class Querier { let currentDomain = this.__hosts[Querier.lastTriedIndex].domain.getAsStringDangerous(); let currentBasePath = this.__hosts[Querier.lastTriedIndex].basePath.getAsStringDangerous(); let strPath = path.getAsStringDangerous(); + const isHydraAPICall = strPath.startsWith(hydraAdmPathPrefix) || strPath.startsWith(hydraPubPathPrefix); if (strPath.startsWith(hydraPubPathPrefix)) { currentDomain = hydraPubDomain; strPath = strPath.replace(hydraPubPathPrefix, "/oauth2"); @@ -406,6 +407,10 @@ class Querier { if (process.env.TEST_MODE === "testing") { Querier.hostsAliveForTesting.add(currentDomain + currentBasePath); } + // TODO: Temporary solution for handling Hydra API calls. Remove when Hydra is no longer called directly. + if (isHydraAPICall) { + return handleHydraAPICall(response); + } if (response.status !== 200) { throw response; } @@ -502,3 +507,6 @@ Querier.hostsAliveForTesting = new Set(); Querier.networkInterceptor = undefined; Querier.globalCacheTag = Date.now(); Querier.disableCache = false; +async function handleHydraAPICall(response) { + return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().json() } }; +} diff --git a/lib/build/recipe/oauth2/OAuth2Client.d.ts b/lib/build/recipe/oauth2/OAuth2Client.d.ts new file mode 100644 index 000000000..be174bde5 --- /dev/null +++ b/lib/build/recipe/oauth2/OAuth2Client.d.ts @@ -0,0 +1,304 @@ +// @ts-nocheck +import { OAuth2ClientOptions } from "./types"; +export declare class OAuth2Client { + /** + * OAuth 2.0 Client ID + * The ID is immutable. If no ID is provided, a UUID4 will be generated. + */ + clientId: string; + /** + * OAuth 2.0 Client Secret + * The secret will be included in the create request as cleartext, and then + * never again. The secret is kept in hashed format and is not recoverable once lost. + */ + clientSecret: string; + /** + * OAuth 2.0 Client Name + * The human-readable name of the client to be presented to the end-user during authorization. + */ + clientName: string; + /** + * OAuth 2.0 Client Scope + * Scope is a string containing a space-separated list of scope values that the client + * can use when requesting access tokens. + */ + scope: string; + /** + * Array of redirect URIs + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + redirectUris: string[] | null; + /** + * Authorization Code Grant Access Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + authorizationCodeGrantAccessTokenLifespan: string | null; + /** + * Authorization Code Grant ID Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + authorizationCodeGrantIdTokenLifespan: string | null; + /** + * Authorization Code Grant Refresh Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + authorizationCodeGrantRefreshTokenLifespan: string | null; + /** + * Client Credentials Grant Access Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + clientCredentialsGrantAccessTokenLifespan: string | null; + /** + * Implicit Grant Access Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + implicitGrantAccessTokenLifespan: string | null; + /** + * Implicit Grant ID Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + implicitGrantIdTokenLifespan: string | null; + /** + * JWT Bearer Grant Access Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + jwtBearerGrantAccessTokenLifespan: string | null; + /** + * Refresh Token Grant Access Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + refreshTokenGrantAccessTokenLifespan: string | null; + /** + * Refresh Token Grant ID Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + refreshTokenGrantIdTokenLifespan: string | null; + /** + * Refresh Token Grant Refresh Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + refreshTokenGrantRefreshTokenLifespan: string | null; + /** + * OAuth 2.0 Token Endpoint Authentication Method + * Requested Client Authentication method for the Token Endpoint. + */ + tokenEndpointAuthMethod: string; + /** + * OAuth 2.0 Token Endpoint Signing Algorithm + * Requested Client Authentication signing algorithm for the Token Endpoint. + */ + tokenEndpointAuthSigningAlg: string | null; + /** + * OAuth 2.0 Access Token Strategy + * AccessTokenStrategy is the strategy used to generate access tokens. + * Valid options are jwt and opaque. + */ + accessTokenStrategy: "jwt" | "opaque" | null; + /** + * OpenID Connect Back-Channel Logout Session Required + * Boolean value specifying whether the RP requires that a sid (session ID) Claim be included in the Logout + * Token to identify the RP session with the OP when the backchannel_logout_uri is used. + * If omitted, the default value is false. + */ + backchannelLogoutSessionRequired: boolean; + /** + * OpenID Connect Back-Channel Logout URI + * RP URL that will cause the RP to log itself out when sent a Logout Token by the OP. + */ + backchannelLogoutUri: string | null; + /** + * OpenID Connect Front-Channel Logout Session Required + * Boolean value specifying whether the RP requires that iss (issuer) and sid (session ID) query parameters be + * included to identify the RP session with the OP when the frontchannel_logout_uri is used. + * If omitted, the default value is false. + */ + frontchannelLogoutSessionRequired: boolean; + /** + * OpenID Connect Front-Channel Logout URI + * RP URL that will cause the RP to log itself out when rendered in an iframe by the OP. + */ + frontchannelLogoutUri: string | null; + /** + * OpenID Connect Request Object Signing Algorithm + * JWS alg algorithm that MUST be used for signing Request Objects sent to the OP. All Request Objects + * from this Client MUST be rejected, if not signed with this algorithm. + */ + requestObjectSigningAlg: string | null; + /** + * OpenID Connect Sector Identifier URI + * URL using the https scheme to be used in calculating Pseudonymous Identifiers by the OP. The URL references a + * file with a single JSON array of redirect_uri values. + */ + sectorIdentifierUri: string | null; + /** + * OpenID Connect Request Userinfo Signed Response Algorithm + * JWS alg algorithm REQUIRED for signing UserInfo Responses. If this is specified, the response will be JWT + * serialized, and signed using JWS. + */ + userinfoSignedResponseAlg: string | null; + /** + * OAuth 2.0 Client JSON Web Key Set + * Client's JSON Web Key Set [JWK] document, passed by value. + */ + jwks: Record; + /** + * OAuth 2.0 Client JSON Web Key Set URL + * URL for the Client's JSON Web Key Set [JWK] document. + */ + jwksUri: string | null; + /** + * OAuth 2.0 Client Owner + * Owner is a string identifying the owner of the OAuth 2.0 Client. + */ + owner: string; + /** + * OAuth 2.0 Client URI + * ClientURI is a URL string of a web page providing information about the client. + */ + clientUri: string; + /** + * Array of allowed CORS origins + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + allowedCorsOrigins: string[]; + /** + * Array of audiences + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + audience: string[]; + /** + * Array of grant types + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + grantTypes: string[] | null; + /** + * Array of post-logout redirect URIs + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + postLogoutRedirectUris: string[] | null; + /** + * Array of request URIs + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + requestUris: string[] | null; + /** + * Array of response types + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + responseTypes: string[] | null; + /** + * Array of contacts + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + contacts: string[] | null; + /** + * OAuth 2.0 Client Logo URI + * A URL string referencing the client's logo. + */ + logoUri: string; + /** + * OAuth 2.0 Client Policy URI + * PolicyURI is a URL string that points to a human-readable privacy policy document + * that describes how the deployment organization collects, uses, + * retains, and discloses personal data. + */ + policyUri: string; + /** + * OAuth 2.0 Client Terms of Service URI + * A URL string pointing to a human-readable terms of service + * document for the client that describes a contractual relationship + * between the end-user and the client that the end-user accepts when + * authorizing the client. + */ + tosUri: string; + /** + * SkipConsent skips the consent screen for this client. This field can only + * be set from the admin API. + */ + skipConsent: boolean; + /** + * SkipLogoutConsent skips the logout consent screen for this client. This field can only + * be set from the admin API. + */ + skipLogoutConsent: boolean | null; + /** + * OpenID Connect Subject Type + * Valid types include pairwise and public. + */ + subjectType: string; + /** + * OAuth 2.0 Client Creation Date + * CreatedAt returns the timestamp of the client's creation. + */ + createdAt: string; + /** + * OAuth 2.0 Client Last Update Date + * UpdatedAt returns the timestamp of the last update. + */ + updatedAt: string; + /** + * OpenID Connect Dynamic Client Registration Access Token + * RegistrationAccessToken can be used to update, get, or delete the OAuth2 Client. It is sent when creating a client + * using Dynamic Client Registration. + */ + registrationAccessToken: string; + /** + * OpenID Connect Dynamic Client Registration URL + * RegistrationClientURI is the URL used to update, get, or delete the OAuth2 Client. + */ + registrationClientUri: string; + /** + * Metadata - JSON object + * JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger. + */ + metadata: Record; + constructor({ + clientId, + clientSecret, + clientName, + scope, + redirectUris, + authorizationCodeGrantAccessTokenLifespan, + authorizationCodeGrantIdTokenLifespan, + authorizationCodeGrantRefreshTokenLifespan, + clientCredentialsGrantAccessTokenLifespan, + implicitGrantAccessTokenLifespan, + implicitGrantIdTokenLifespan, + jwtBearerGrantAccessTokenLifespan, + refreshTokenGrantAccessTokenLifespan, + refreshTokenGrantIdTokenLifespan, + refreshTokenGrantRefreshTokenLifespan, + tokenEndpointAuthMethod, + tokenEndpointAuthSigningAlg, + accessTokenStrategy, + backchannelLogoutSessionRequired, + backchannelLogoutUri, + frontchannelLogoutSessionRequired, + frontchannelLogoutUri, + requestObjectSigningAlg, + sectorIdentifierUri, + userinfoSignedResponseAlg, + jwks, + jwksUri, + owner, + clientUri, + allowedCorsOrigins, + audience, + grantTypes, + postLogoutRedirectUris, + requestUris, + responseTypes, + contacts, + logoUri, + policyUri, + tosUri, + skipConsent, + skipLogoutConsent, + subjectType, + createdAt, + updatedAt, + registrationAccessToken, + registrationClientUri, + metadata, + }: OAuth2ClientOptions); +} diff --git a/lib/build/recipe/oauth2/OAuth2Client.js b/lib/build/recipe/oauth2/OAuth2Client.js new file mode 100644 index 000000000..78232a67b --- /dev/null +++ b/lib/build/recipe/oauth2/OAuth2Client.js @@ -0,0 +1,122 @@ +"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.OAuth2Client = void 0; +class OAuth2Client { + constructor({ + clientId, + clientSecret, + clientName, + scope, + redirectUris = null, + authorizationCodeGrantAccessTokenLifespan = null, + authorizationCodeGrantIdTokenLifespan = null, + authorizationCodeGrantRefreshTokenLifespan = null, + clientCredentialsGrantAccessTokenLifespan = null, + implicitGrantAccessTokenLifespan = null, + implicitGrantIdTokenLifespan = null, + jwtBearerGrantAccessTokenLifespan = null, + refreshTokenGrantAccessTokenLifespan = null, + refreshTokenGrantIdTokenLifespan = null, + refreshTokenGrantRefreshTokenLifespan = null, + tokenEndpointAuthMethod, + tokenEndpointAuthSigningAlg = null, + accessTokenStrategy = null, + backchannelLogoutSessionRequired = false, + backchannelLogoutUri = null, + frontchannelLogoutSessionRequired = false, + frontchannelLogoutUri = null, + requestObjectSigningAlg = null, + sectorIdentifierUri = null, + userinfoSignedResponseAlg = null, + jwks = {}, + jwksUri = null, + owner = "", + clientUri = "", + allowedCorsOrigins = [], + audience = [], + grantTypes = null, + postLogoutRedirectUris = null, + requestUris = null, + responseTypes = null, + contacts = null, + logoUri = "", + policyUri = "", + tosUri = "", + skipConsent = false, + skipLogoutConsent = null, + subjectType, + createdAt, + updatedAt, + registrationAccessToken, + registrationClientUri, + metadata = {}, + }) { + /** + * Metadata - JSON object + * JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger. + */ + this.metadata = {}; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.clientName = clientName; + this.scope = scope; + this.redirectUris = redirectUris; + this.authorizationCodeGrantAccessTokenLifespan = authorizationCodeGrantAccessTokenLifespan; + this.authorizationCodeGrantIdTokenLifespan = authorizationCodeGrantIdTokenLifespan; + this.authorizationCodeGrantRefreshTokenLifespan = authorizationCodeGrantRefreshTokenLifespan; + this.clientCredentialsGrantAccessTokenLifespan = clientCredentialsGrantAccessTokenLifespan; + this.implicitGrantAccessTokenLifespan = implicitGrantAccessTokenLifespan; + this.implicitGrantIdTokenLifespan = implicitGrantIdTokenLifespan; + this.jwtBearerGrantAccessTokenLifespan = jwtBearerGrantAccessTokenLifespan; + this.refreshTokenGrantAccessTokenLifespan = refreshTokenGrantAccessTokenLifespan; + this.refreshTokenGrantIdTokenLifespan = refreshTokenGrantIdTokenLifespan; + this.refreshTokenGrantRefreshTokenLifespan = refreshTokenGrantRefreshTokenLifespan; + this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; + this.tokenEndpointAuthSigningAlg = tokenEndpointAuthSigningAlg; + this.accessTokenStrategy = accessTokenStrategy; + this.backchannelLogoutSessionRequired = backchannelLogoutSessionRequired; + this.backchannelLogoutUri = backchannelLogoutUri; + this.frontchannelLogoutSessionRequired = frontchannelLogoutSessionRequired; + this.frontchannelLogoutUri = frontchannelLogoutUri; + this.requestObjectSigningAlg = requestObjectSigningAlg; + this.sectorIdentifierUri = sectorIdentifierUri; + this.userinfoSignedResponseAlg = userinfoSignedResponseAlg; + this.jwks = jwks; + this.jwksUri = jwksUri; + this.owner = owner; + this.clientUri = clientUri; + this.allowedCorsOrigins = allowedCorsOrigins; + this.audience = audience; + this.grantTypes = grantTypes; + this.postLogoutRedirectUris = postLogoutRedirectUris; + this.requestUris = requestUris; + this.responseTypes = responseTypes; + this.contacts = contacts; + this.logoUri = logoUri; + this.policyUri = policyUri; + this.tosUri = tosUri; + this.skipConsent = skipConsent; + this.skipLogoutConsent = skipLogoutConsent; + this.subjectType = subjectType; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.registrationAccessToken = registrationAccessToken; + this.registrationClientUri = registrationClientUri; + this.metadata = metadata; + } +} +exports.OAuth2Client = OAuth2Client; diff --git a/lib/build/recipe/oauth2/index.d.ts b/lib/build/recipe/oauth2/index.d.ts index b3b1542de..040f56692 100644 --- a/lib/build/recipe/oauth2/index.d.ts +++ b/lib/build/recipe/oauth2/index.d.ts @@ -1,8 +1,24 @@ // @ts-nocheck +import { UserContext } from "../../types"; import Recipe from "./recipe"; -import { APIInterface, RecipeInterface, APIOptions } from "./types"; +import { APIInterface, RecipeInterface, APIOptions, OAuth2ClientOptions } from "./types"; export default class Wrapper { static init: typeof Recipe.init; + static createOAuth2Client( + input: OAuth2ClientOptions, + userContext: UserContext + ): Promise< + | { + status: "OK"; + client: import("./OAuth2Client").OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; } export declare let init: typeof Recipe.init; +export declare let createOAuth2Client: typeof Wrapper.createOAuth2Client; export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/build/recipe/oauth2/index.js b/lib/build/recipe/oauth2/index.js index fb52eae5a..d005203ca 100644 --- a/lib/build/recipe/oauth2/index.js +++ b/lib/build/recipe/oauth2/index.js @@ -19,9 +19,16 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.init = void 0; +exports.createOAuth2Client = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); -class Wrapper {} +class Wrapper { + static async createOAuth2Client(input, userContext) { + return await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.createOAuth2Client(input, userContext); + } +} exports.default = Wrapper; Wrapper.init = recipe_1.default.init; exports.init = Wrapper.init; +exports.createOAuth2Client = Wrapper.createOAuth2Client; diff --git a/lib/build/recipe/oauth2/recipe.js b/lib/build/recipe/oauth2/recipe.js index 44be8d270..6b2556b55 100644 --- a/lib/build/recipe/oauth2/recipe.js +++ b/lib/build/recipe/oauth2/recipe.js @@ -70,7 +70,7 @@ class Recipe extends recipeModule_1.default { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; } else { - throw new Error("JWT recipe has already been initialised. Please check your code for bugs."); + throw new Error("OAuth2 recipe has already been initialised. Please check your code for bugs."); } }; } diff --git a/lib/build/recipe/oauth2/recipeImplementation.d.ts b/lib/build/recipe/oauth2/recipeImplementation.d.ts index 513f5df92..d2a1542a2 100644 --- a/lib/build/recipe/oauth2/recipeImplementation.d.ts +++ b/lib/build/recipe/oauth2/recipeImplementation.d.ts @@ -3,7 +3,7 @@ import { Querier } from "../../querier"; import { NormalisedAppinfo } from "../../types"; import { RecipeInterface, TypeNormalisedInput } from "./types"; export default function getRecipeInterface( - _querier: Querier, + querier: Querier, _config: TypeNormalisedInput, _appInfo: NormalisedAppinfo ): RecipeInterface; diff --git a/lib/build/recipe/oauth2/recipeImplementation.js b/lib/build/recipe/oauth2/recipeImplementation.js index 67c4636f5..093b58c33 100644 --- a/lib/build/recipe/oauth2/recipeImplementation.js +++ b/lib/build/recipe/oauth2/recipeImplementation.js @@ -13,8 +13,42 @@ * 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 }); -function getRecipeInterface(_querier, _config, _appInfo) { - return {}; +const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); +const utils_1 = require("../../utils"); +const OAuth2Client_1 = require("./OAuth2Client"); +function getRecipeInterface(querier, _config, _appInfo) { + return { + createOAuth2Client: async function (input, userContext) { + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients`), + input, + userContext + ); + if (response.status === "OK") { + const oAuth2ClientInput = Object.keys(response.data).reduce((result, key) => { + const camelCaseKey = utils_1.toCamelCase(key); + result[camelCaseKey] = response.data[key]; + return result; + }, {}); + const client = new OAuth2Client_1.OAuth2Client(oAuth2ClientInput); + return { + status: "OK", + client, + }; + } else { + return { + status: "ERROR", + error: response.data.error, + errorHint: response.data.errorHint, + }; + } + }, + }; } exports.default = getRecipeInterface; diff --git a/lib/build/recipe/oauth2/types.d.ts b/lib/build/recipe/oauth2/types.d.ts index f2e26d16d..cefbd0b40 100644 --- a/lib/build/recipe/oauth2/types.d.ts +++ b/lib/build/recipe/oauth2/types.d.ts @@ -1,6 +1,8 @@ // @ts-nocheck import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; +import { UserContext } from "../../types"; +import { OAuth2Client } from "./OAuth2Client"; export declare type TypeInput = { override?: { functions?: ( @@ -27,5 +29,70 @@ export declare type APIOptions = { req: BaseRequest; res: BaseResponse; }; -export declare type RecipeInterface = {}; +export declare type RecipeInterface = { + createOAuth2Client( + input: OAuth2ClientOptions, + userContext: UserContext + ): Promise< + | { + status: "OK"; + client: OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; +}; export declare type APIInterface = {}; +export declare type OAuth2ClientOptions = { + clientId: string; + clientSecret: string; + clientName: string; + scope: string; + redirectUris?: string[] | null; + authorizationCodeGrantAccessTokenLifespan?: string | null; + authorizationCodeGrantIdTokenLifespan?: string | null; + authorizationCodeGrantRefreshTokenLifespan?: string | null; + clientCredentialsGrantAccessTokenLifespan?: string | null; + implicitGrantAccessTokenLifespan?: string | null; + implicitGrantIdTokenLifespan?: string | null; + jwtBearerGrantAccessTokenLifespan?: string | null; + refreshTokenGrantAccessTokenLifespan?: string | null; + refreshTokenGrantIdTokenLifespan?: string | null; + refreshTokenGrantRefreshTokenLifespan?: string | null; + tokenEndpointAuthMethod: string; + tokenEndpointAuthSigningAlg?: string | null; + accessTokenStrategy?: "jwt" | "opaque" | null; + backchannelLogoutSessionRequired?: boolean; + backchannelLogoutUri?: string | null; + frontchannelLogoutSessionRequired?: boolean; + frontchannelLogoutUri?: string | null; + requestObjectSigningAlg?: string | null; + sectorIdentifierUri?: string | null; + userinfoSignedResponseAlg?: string | null; + jwks?: Record; + jwksUri?: string | null; + owner?: string; + clientUri?: string; + allowedCorsOrigins?: string[]; + audience?: string[]; + grantTypes?: string[] | null; + postLogoutRedirectUris?: string[] | null; + requestUris?: string[] | null; + responseTypes?: string[] | null; + contacts?: string[] | null; + logoUri?: string; + policyUri?: string; + tosUri?: string; + skipConsent?: boolean; + skipLogoutConsent?: boolean | null; + subjectType: string; + createdAt: string; + updatedAt: string; + registrationAccessToken: string; + registrationClientUri: string; + metadata?: Record; +}; +export declare type CreateOAuth2ClientInput = Partial>; diff --git a/lib/build/utils.d.ts b/lib/build/utils.d.ts index 0659b1aae..dea78977c 100644 --- a/lib/build/utils.d.ts +++ b/lib/build/utils.d.ts @@ -57,3 +57,4 @@ export declare function postWithFetch( } >; export declare function normaliseEmail(email: string): string; +export declare function toCamelCase(str: string): string; diff --git a/lib/build/utils.js b/lib/build/utils.js index 73a678826..451dd5391 100644 --- a/lib/build/utils.js +++ b/lib/build/utils.js @@ -41,7 +41,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.normaliseEmail = exports.postWithFetch = exports.getFromObjectCaseInsensitive = exports.getTopLevelDomainForSameSiteResolution = exports.setRequestInUserContextIfNotDefined = exports.getUserContext = exports.makeDefaultUserContextFromAPI = exports.humaniseMilliseconds = exports.frontendHasInterceptor = exports.getRidFromHeader = exports.hasGreaterThanEqualToFDI = exports.getLatestFDIVersionFromFDIList = exports.getBackwardsCompatibleUserInfo = exports.isAnIpAddress = exports.send200Response = exports.sendNon200Response = exports.sendNon200ResponseWithMessage = exports.normaliseHttpMethod = exports.normaliseInputAppInfoOrThrowError = exports.maxVersion = exports.getLargestVersionFromIntersection = exports.doFetch = void 0; +exports.toCamelCase = exports.normaliseEmail = exports.postWithFetch = exports.getFromObjectCaseInsensitive = exports.getTopLevelDomainForSameSiteResolution = exports.setRequestInUserContextIfNotDefined = exports.getUserContext = exports.makeDefaultUserContextFromAPI = exports.humaniseMilliseconds = exports.frontendHasInterceptor = exports.getRidFromHeader = exports.hasGreaterThanEqualToFDI = exports.getLatestFDIVersionFromFDIList = exports.getBackwardsCompatibleUserInfo = exports.isAnIpAddress = exports.send200Response = exports.sendNon200Response = exports.sendNon200ResponseWithMessage = exports.normaliseHttpMethod = exports.normaliseInputAppInfoOrThrowError = exports.maxVersion = exports.getLargestVersionFromIntersection = exports.doFetch = void 0; const psl = __importStar(require("psl")); const normalisedURLDomain_1 = __importDefault(require("./normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); @@ -425,3 +425,9 @@ function normaliseEmail(email) { return email; } exports.normaliseEmail = normaliseEmail; +function toCamelCase(str) { + return str.replace(/([-_][a-z])/gi, (match) => { + return match.toUpperCase().replace("-", "").replace("_", ""); + }); +} +exports.toCamelCase = toCamelCase; diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index 26daf1951..33f6fa884 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -495,6 +495,8 @@ export class Querier { let currentBasePath: string = this.__hosts[Querier.lastTriedIndex].basePath.getAsStringDangerous(); let strPath = path.getAsStringDangerous(); + const isHydraAPICall = strPath.startsWith(hydraAdmPathPrefix) || strPath.startsWith(hydraPubPathPrefix); + if (strPath.startsWith(hydraPubPathPrefix)) { currentDomain = hydraPubDomain; strPath = strPath.replace(hydraPubPathPrefix, "/oauth2"); @@ -525,6 +527,12 @@ export class Querier { if (process.env.TEST_MODE === "testing") { Querier.hostsAliveForTesting.add(currentDomain + currentBasePath); } + + // TODO: Temporary solution for handling Hydra API calls. Remove when Hydra is no longer called directly. + if (isHydraAPICall) { + return handleHydraAPICall(response); + } + if (response.status !== 200) { throw response; } @@ -575,3 +583,7 @@ export class Querier { } }; } + +async function handleHydraAPICall(response: Response) { + return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().json() } }; +} diff --git a/lib/ts/recipe/oauth2/OAuth2Client.ts b/lib/ts/recipe/oauth2/OAuth2Client.ts new file mode 100644 index 000000000..c7fde8c73 --- /dev/null +++ b/lib/ts/recipe/oauth2/OAuth2Client.ts @@ -0,0 +1,414 @@ +/* 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. + */ + +import { OAuth2ClientOptions } from "./types"; + +export class OAuth2Client { + /** + * OAuth 2.0 Client ID + * The ID is immutable. If no ID is provided, a UUID4 will be generated. + */ + clientId: string; + + /** + * OAuth 2.0 Client Secret + * The secret will be included in the create request as cleartext, and then + * never again. The secret is kept in hashed format and is not recoverable once lost. + */ + clientSecret: string; + + /** + * OAuth 2.0 Client Name + * The human-readable name of the client to be presented to the end-user during authorization. + */ + clientName: string; + + /** + * OAuth 2.0 Client Scope + * Scope is a string containing a space-separated list of scope values that the client + * can use when requesting access tokens. + */ + scope: string; + + /** + * Array of redirect URIs + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + redirectUris: string[] | null; + + /** + * Authorization Code Grant Access Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + authorizationCodeGrantAccessTokenLifespan: string | null; + + /** + * Authorization Code Grant ID Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + authorizationCodeGrantIdTokenLifespan: string | null; + + /** + * Authorization Code Grant Refresh Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + authorizationCodeGrantRefreshTokenLifespan: string | null; + + /** + * Client Credentials Grant Access Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + clientCredentialsGrantAccessTokenLifespan: string | null; + + /** + * Implicit Grant Access Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + implicitGrantAccessTokenLifespan: string | null; + + /** + * Implicit Grant ID Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + implicitGrantIdTokenLifespan: string | null; + + /** + * JWT Bearer Grant Access Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + jwtBearerGrantAccessTokenLifespan: string | null; + + /** + * Refresh Token Grant Access Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + refreshTokenGrantAccessTokenLifespan: string | null; + + /** + * Refresh Token Grant ID Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + refreshTokenGrantIdTokenLifespan: string | null; + + /** + * Refresh Token Grant Refresh Token Lifespan + * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ + */ + refreshTokenGrantRefreshTokenLifespan: string | null; + + /** + * OAuth 2.0 Token Endpoint Authentication Method + * Requested Client Authentication method for the Token Endpoint. + */ + tokenEndpointAuthMethod: string; + + /** + * OAuth 2.0 Token Endpoint Signing Algorithm + * Requested Client Authentication signing algorithm for the Token Endpoint. + */ + tokenEndpointAuthSigningAlg: string | null; + + /** + * OAuth 2.0 Access Token Strategy + * AccessTokenStrategy is the strategy used to generate access tokens. + * Valid options are jwt and opaque. + */ + accessTokenStrategy: "jwt" | "opaque" | null; + + /** + * OpenID Connect Back-Channel Logout Session Required + * Boolean value specifying whether the RP requires that a sid (session ID) Claim be included in the Logout + * Token to identify the RP session with the OP when the backchannel_logout_uri is used. + * If omitted, the default value is false. + */ + backchannelLogoutSessionRequired: boolean; + + /** + * OpenID Connect Back-Channel Logout URI + * RP URL that will cause the RP to log itself out when sent a Logout Token by the OP. + */ + backchannelLogoutUri: string | null; + + /** + * OpenID Connect Front-Channel Logout Session Required + * Boolean value specifying whether the RP requires that iss (issuer) and sid (session ID) query parameters be + * included to identify the RP session with the OP when the frontchannel_logout_uri is used. + * If omitted, the default value is false. + */ + frontchannelLogoutSessionRequired: boolean; + + /** + * OpenID Connect Front-Channel Logout URI + * RP URL that will cause the RP to log itself out when rendered in an iframe by the OP. + */ + frontchannelLogoutUri: string | null; + + /** + * OpenID Connect Request Object Signing Algorithm + * JWS alg algorithm that MUST be used for signing Request Objects sent to the OP. All Request Objects + * from this Client MUST be rejected, if not signed with this algorithm. + */ + requestObjectSigningAlg: string | null; + + /** + * OpenID Connect Sector Identifier URI + * URL using the https scheme to be used in calculating Pseudonymous Identifiers by the OP. The URL references a + * file with a single JSON array of redirect_uri values. + */ + sectorIdentifierUri: string | null; + + /** + * OpenID Connect Request Userinfo Signed Response Algorithm + * JWS alg algorithm REQUIRED for signing UserInfo Responses. If this is specified, the response will be JWT + * serialized, and signed using JWS. + */ + userinfoSignedResponseAlg: string | null; + + /** + * OAuth 2.0 Client JSON Web Key Set + * Client's JSON Web Key Set [JWK] document, passed by value. + */ + jwks: Record; + + /** + * OAuth 2.0 Client JSON Web Key Set URL + * URL for the Client's JSON Web Key Set [JWK] document. + */ + jwksUri: string | null; + + /** + * OAuth 2.0 Client Owner + * Owner is a string identifying the owner of the OAuth 2.0 Client. + */ + owner: string; + + /** + * OAuth 2.0 Client URI + * ClientURI is a URL string of a web page providing information about the client. + */ + clientUri: string; + + /** + * Array of allowed CORS origins + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + allowedCorsOrigins: string[]; + + /** + * Array of audiences + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + audience: string[]; + + /** + * Array of grant types + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + grantTypes: string[] | null; + + /** + * Array of post-logout redirect URIs + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + postLogoutRedirectUris: string[] | null; + + /** + * Array of request URIs + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + requestUris: string[] | null; + + /** + * Array of response types + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + responseTypes: string[] | null; + + /** + * Array of contacts + * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. + */ + contacts: string[] | null; + + /** + * OAuth 2.0 Client Logo URI + * A URL string referencing the client's logo. + */ + logoUri: string; + + /** + * OAuth 2.0 Client Policy URI + * PolicyURI is a URL string that points to a human-readable privacy policy document + * that describes how the deployment organization collects, uses, + * retains, and discloses personal data. + */ + policyUri: string; + + /** + * OAuth 2.0 Client Terms of Service URI + * A URL string pointing to a human-readable terms of service + * document for the client that describes a contractual relationship + * between the end-user and the client that the end-user accepts when + * authorizing the client. + */ + tosUri: string; + + /** + * SkipConsent skips the consent screen for this client. This field can only + * be set from the admin API. + */ + skipConsent: boolean; + + /** + * SkipLogoutConsent skips the logout consent screen for this client. This field can only + * be set from the admin API. + */ + skipLogoutConsent: boolean | null; + + /** + * OpenID Connect Subject Type + * Valid types include pairwise and public. + */ + subjectType: string; + + /** + * OAuth 2.0 Client Creation Date + * CreatedAt returns the timestamp of the client's creation. + */ + createdAt: string; + + /** + * OAuth 2.0 Client Last Update Date + * UpdatedAt returns the timestamp of the last update. + */ + updatedAt: string; + + /** + * OpenID Connect Dynamic Client Registration Access Token + * RegistrationAccessToken can be used to update, get, or delete the OAuth2 Client. It is sent when creating a client + * using Dynamic Client Registration. + */ + registrationAccessToken: string; + + /** + * OpenID Connect Dynamic Client Registration URL + * RegistrationClientURI is the URL used to update, get, or delete the OAuth2 Client. + */ + registrationClientUri: string; + + /** + * Metadata - JSON object + * JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger. + */ + metadata: Record = {}; + + constructor({ + clientId, + clientSecret, + clientName, + scope, + redirectUris = null, + authorizationCodeGrantAccessTokenLifespan = null, + authorizationCodeGrantIdTokenLifespan = null, + authorizationCodeGrantRefreshTokenLifespan = null, + clientCredentialsGrantAccessTokenLifespan = null, + implicitGrantAccessTokenLifespan = null, + implicitGrantIdTokenLifespan = null, + jwtBearerGrantAccessTokenLifespan = null, + refreshTokenGrantAccessTokenLifespan = null, + refreshTokenGrantIdTokenLifespan = null, + refreshTokenGrantRefreshTokenLifespan = null, + tokenEndpointAuthMethod, + tokenEndpointAuthSigningAlg = null, + accessTokenStrategy = null, + backchannelLogoutSessionRequired = false, + backchannelLogoutUri = null, + frontchannelLogoutSessionRequired = false, + frontchannelLogoutUri = null, + requestObjectSigningAlg = null, + sectorIdentifierUri = null, + userinfoSignedResponseAlg = null, + jwks = {}, + jwksUri = null, + owner = "", + clientUri = "", + allowedCorsOrigins = [], + audience = [], + grantTypes = null, + postLogoutRedirectUris = null, + requestUris = null, + responseTypes = null, + contacts = null, + logoUri = "", + policyUri = "", + tosUri = "", + skipConsent = false, + skipLogoutConsent = null, + subjectType, + createdAt, + updatedAt, + registrationAccessToken, + registrationClientUri, + metadata = {}, + }: OAuth2ClientOptions) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.clientName = clientName; + this.scope = scope; + this.redirectUris = redirectUris; + this.authorizationCodeGrantAccessTokenLifespan = authorizationCodeGrantAccessTokenLifespan; + this.authorizationCodeGrantIdTokenLifespan = authorizationCodeGrantIdTokenLifespan; + this.authorizationCodeGrantRefreshTokenLifespan = authorizationCodeGrantRefreshTokenLifespan; + this.clientCredentialsGrantAccessTokenLifespan = clientCredentialsGrantAccessTokenLifespan; + this.implicitGrantAccessTokenLifespan = implicitGrantAccessTokenLifespan; + this.implicitGrantIdTokenLifespan = implicitGrantIdTokenLifespan; + this.jwtBearerGrantAccessTokenLifespan = jwtBearerGrantAccessTokenLifespan; + this.refreshTokenGrantAccessTokenLifespan = refreshTokenGrantAccessTokenLifespan; + this.refreshTokenGrantIdTokenLifespan = refreshTokenGrantIdTokenLifespan; + this.refreshTokenGrantRefreshTokenLifespan = refreshTokenGrantRefreshTokenLifespan; + this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; + this.tokenEndpointAuthSigningAlg = tokenEndpointAuthSigningAlg; + this.accessTokenStrategy = accessTokenStrategy; + this.backchannelLogoutSessionRequired = backchannelLogoutSessionRequired; + this.backchannelLogoutUri = backchannelLogoutUri; + this.frontchannelLogoutSessionRequired = frontchannelLogoutSessionRequired; + this.frontchannelLogoutUri = frontchannelLogoutUri; + this.requestObjectSigningAlg = requestObjectSigningAlg; + this.sectorIdentifierUri = sectorIdentifierUri; + this.userinfoSignedResponseAlg = userinfoSignedResponseAlg; + this.jwks = jwks; + this.jwksUri = jwksUri; + this.owner = owner; + this.clientUri = clientUri; + this.allowedCorsOrigins = allowedCorsOrigins; + this.audience = audience; + this.grantTypes = grantTypes; + this.postLogoutRedirectUris = postLogoutRedirectUris; + this.requestUris = requestUris; + this.responseTypes = responseTypes; + this.contacts = contacts; + this.logoUri = logoUri; + this.policyUri = policyUri; + this.tosUri = tosUri; + this.skipConsent = skipConsent; + this.skipLogoutConsent = skipLogoutConsent; + this.subjectType = subjectType; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.registrationAccessToken = registrationAccessToken; + this.registrationClientUri = registrationClientUri; + this.metadata = metadata; + } +} diff --git a/lib/ts/recipe/oauth2/index.ts b/lib/ts/recipe/oauth2/index.ts index d0c7eb45e..a8fe9e352 100644 --- a/lib/ts/recipe/oauth2/index.ts +++ b/lib/ts/recipe/oauth2/index.ts @@ -13,13 +13,20 @@ * under the License. */ +import { UserContext } from "../../types"; import Recipe from "./recipe"; -import { APIInterface, RecipeInterface, APIOptions } from "./types"; +import { APIInterface, RecipeInterface, APIOptions, OAuth2ClientOptions } from "./types"; export default class Wrapper { static init = Recipe.init; + + static async createOAuth2Client(input: OAuth2ClientOptions, userContext: UserContext) { + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createOAuth2Client(input, userContext); + } } export let init = Wrapper.init; +export let createOAuth2Client = Wrapper.createOAuth2Client; + export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/ts/recipe/oauth2/recipe.ts b/lib/ts/recipe/oauth2/recipe.ts index 72aa025ae..1b0c5aa5d 100644 --- a/lib/ts/recipe/oauth2/recipe.ts +++ b/lib/ts/recipe/oauth2/recipe.ts @@ -67,7 +67,7 @@ export default class Recipe extends RecipeModule { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; } else { - throw new Error("JWT recipe has already been initialised. Please check your code for bugs."); + throw new Error("OAuth2 recipe has already been initialised. Please check your code for bugs."); } }; } diff --git a/lib/ts/recipe/oauth2/recipeImplementation.ts b/lib/ts/recipe/oauth2/recipeImplementation.ts index 1bd119bb2..c78ed3306 100644 --- a/lib/ts/recipe/oauth2/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2/recipeImplementation.ts @@ -13,14 +13,46 @@ * under the License. */ +import NormalisedURLPath from "../../normalisedURLPath"; import { Querier } from "../../querier"; import { NormalisedAppinfo } from "../../types"; +import { toCamelCase } from "../../utils"; +import { OAuth2Client } from "./OAuth2Client"; import { RecipeInterface, TypeNormalisedInput } from "./types"; export default function getRecipeInterface( - _querier: Querier, + querier: Querier, _config: TypeNormalisedInput, _appInfo: NormalisedAppinfo ): RecipeInterface { - return {}; + return { + createOAuth2Client: async function (input, userContext) { + let response = await querier.sendPostRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/clients`), + input, + userContext + ); + + if (response.status === "OK") { + const oAuth2ClientInput = Object.keys(response.data).reduce((result, key) => { + const camelCaseKey = toCamelCase(key); + result[camelCaseKey] = response.data[key]; + return result; + }, {} as any); + + const client = new OAuth2Client(oAuth2ClientInput); + + return { + status: "OK", + client, + }; + } else { + return { + status: "ERROR", + error: response.data.error, + errorHint: response.data.errorHint, + }; + } + }, + }; } diff --git a/lib/ts/recipe/oauth2/types.ts b/lib/ts/recipe/oauth2/types.ts index 31bfdbf94..12040f03a 100644 --- a/lib/ts/recipe/oauth2/types.ts +++ b/lib/ts/recipe/oauth2/types.ts @@ -15,6 +15,8 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; +import { UserContext } from "../../types"; +import { OAuth2Client } from "./OAuth2Client"; export type TypeInput = { override?: { @@ -45,6 +47,78 @@ export type APIOptions = { res: BaseResponse; }; -export type RecipeInterface = {}; +export type RecipeInterface = { + createOAuth2Client( + input: OAuth2ClientOptions, + userContext: UserContext + ): Promise< + | { + status: "OK"; + client: OAuth2Client; + } + // TODO: Define specific error types once requirements are clearer + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; +}; export type APIInterface = {}; + +export type OAuth2ClientOptions = { + clientId: string; + clientSecret: string; + clientName: string; + scope: string; + redirectUris?: string[] | null; + + authorizationCodeGrantAccessTokenLifespan?: string | null; + authorizationCodeGrantIdTokenLifespan?: string | null; + authorizationCodeGrantRefreshTokenLifespan?: string | null; + clientCredentialsGrantAccessTokenLifespan?: string | null; + implicitGrantAccessTokenLifespan?: string | null; + implicitGrantIdTokenLifespan?: string | null; + jwtBearerGrantAccessTokenLifespan?: string | null; + refreshTokenGrantAccessTokenLifespan?: string | null; + refreshTokenGrantIdTokenLifespan?: string | null; + refreshTokenGrantRefreshTokenLifespan?: string | null; + + tokenEndpointAuthMethod: string; + tokenEndpointAuthSigningAlg?: string | null; + accessTokenStrategy?: "jwt" | "opaque" | null; + + backchannelLogoutSessionRequired?: boolean; + backchannelLogoutUri?: string | null; + frontchannelLogoutSessionRequired?: boolean; + frontchannelLogoutUri?: string | null; + requestObjectSigningAlg?: string | null; + sectorIdentifierUri?: string | null; + userinfoSignedResponseAlg?: string | null; + + jwks?: Record; + jwksUri?: string | null; + owner?: string; + clientUri?: string; + allowedCorsOrigins?: string[]; + audience?: string[]; + grantTypes?: string[] | null; + postLogoutRedirectUris?: string[] | null; + requestUris?: string[] | null; + responseTypes?: string[] | null; + contacts?: string[] | null; + logoUri?: string; + policyUri?: string; + tosUri?: string; + skipConsent?: boolean; + skipLogoutConsent?: boolean | null; + subjectType: string; + createdAt: string; + updatedAt: string; + registrationAccessToken: string; + registrationClientUri: string; + metadata?: Record; +}; + +export type CreateOAuth2ClientInput = Partial>; diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts index 82aabe181..5370cc812 100644 --- a/lib/ts/utils.ts +++ b/lib/ts/utils.ts @@ -422,3 +422,9 @@ export function normaliseEmail(email: string): string { return email; } + +export function toCamelCase(str: string): string { + return str.replace(/([-_][a-z])/gi, (match) => { + return match.toUpperCase().replace("-", "").replace("_", ""); + }); +} diff --git a/test/oauth2/oauth2client.test.js b/test/oauth2/oauth2client.test.js new file mode 100644 index 000000000..c66ac9f25 --- /dev/null +++ b/test/oauth2/oauth2client.test.js @@ -0,0 +1,82 @@ +let assert = require("assert"); + +const { printPath, setupST, startST, killAllST, cleanST } = require("../utils"); +let { ProcessState } = require("../../lib/build/processState"); +let STExpress = require("../../"); +let OAuth2Recipe = require("../../recipe/oauth2"); +let { Querier } = require("../../lib/build/querier"); +const { maxVersion } = require("../../lib/build/utils"); + +describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}`, function () { + beforeEach(async function () { + await killAllST(); + await setupST(); + ProcessState.getInstance().reset(); + }); + + after(async function () { + await killAllST(); + await cleanST(); + }); + + it("should create an OAuth2Client instance with empty input", async function () { + const connectionURI = await startST(); + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [OAuth2Recipe.init()], + }); + + // Only run for version >= 2.9 + let querier = Querier.getNewInstanceOrThrowError(undefined); + let apiVersion = await querier.getAPIVersion(); + if (maxVersion(apiVersion, "2.8") === "2.8") { + return; + } + + const { client } = await OAuth2Recipe.createOAuth2Client({}, {}); + + assert(client.clientId !== undefined); + assert(client.clientSecret !== undefined); + assert.strictEqual(client.scope, "offline_access offline openid"); + }); + + it("should create an OAuth2Client instance with custom input", async function () { + const connectionURI = await startST(); + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [OAuth2Recipe.init()], + }); + + // Only run for version >= 2.9 + let querier = Querier.getNewInstanceOrThrowError(undefined); + let apiVersion = await querier.getAPIVersion(); + if (maxVersion(apiVersion, "2.8") === "2.8") { + return; + } + + const { client } = await OAuth2Recipe.createOAuth2Client( + { + client_id: "client_id", + client_secret: "client_secret", + }, + {} + ); + + assert(client.clientId === "client_id"); + assert(client.clientSecret === "client_secret"); + }); +}); diff --git a/test/utils.js b/test/utils.js index 75fd04980..6cfa420d9 100644 --- a/test/utils.js +++ b/test/utils.js @@ -31,6 +31,7 @@ let PasswordlessRecipe = require("..//lib/build/recipe/passwordless/recipe").def let MultitenancyRecipe = require("../lib/build/recipe/multitenancy/recipe").default; let MultiFactorAuthRecipe = require("../lib/build/recipe/multifactorauth/recipe").default; const UserRolesRecipe = require("../lib/build/recipe/userroles/recipe").default; +const OAuth2Recipe = require("../lib/build/recipe/oauth2/recipe").default; let { ProcessState } = require("../lib/build/processState"); let { Querier } = require("../lib/build/querier"); let { maxVersion } = require("../lib/build/utils"); @@ -266,6 +267,7 @@ module.exports.resetAll = function (disableLogging = true) { MultitenancyRecipe.reset(); TotpRecipe.reset(); MultiFactorAuthRecipe.reset(); + OAuth2Recipe.reset(); if (disableLogging) { debug.disable(); } From fb51f362dad2034e15114915648eb799841b95ed Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 21 Jun 2024 15:30:33 +0530 Subject: [PATCH 05/35] feat: Add recipe functions to update/delete OAuth2Client (#863) * feat: Add recipe functions to update/delete OAuth2Client * fix: PR changes --- lib/build/querier.d.ts | 1 + lib/build/querier.js | 50 ++++++++- lib/build/recipe/oauth2/OAuth2Client.d.ts | 25 +++-- lib/build/recipe/oauth2/OAuth2Client.js | 24 ++-- lib/build/recipe/oauth2/index.d.ts | 40 ++++++- lib/build/recipe/oauth2/index.js | 14 ++- .../recipe/oauth2/recipeImplementation.js | 56 ++++++++-- lib/build/recipe/oauth2/types.d.ts | 78 +++++++++++-- lib/build/types.d.ts | 5 +- lib/build/utils.d.ts | 7 ++ lib/build/utils.js | 16 ++- lib/ts/querier.ts | 60 +++++++++- lib/ts/recipe/oauth2/OAuth2Client.ts | 49 +++++---- lib/ts/recipe/oauth2/index.ts | 21 +++- lib/ts/recipe/oauth2/recipeImplementation.ts | 61 ++++++++-- lib/ts/recipe/oauth2/types.ts | 82 ++++++++++++-- lib/ts/types.ts | 7 +- lib/ts/utils.ts | 15 +++ test/oauth2/oauth2client.test.js | 104 +++++++++++++++--- 19 files changed, 603 insertions(+), 112 deletions(-) diff --git a/lib/build/querier.d.ts b/lib/build/querier.d.ts index 0e0ad52bd..5bbcd3fec 100644 --- a/lib/build/querier.d.ts +++ b/lib/build/querier.d.ts @@ -50,6 +50,7 @@ export declare class Querier { headers: Headers; }>; sendPutRequest: (path: NormalisedURLPath, body: any, userContext: UserContext) => Promise; + sendPatchRequest: (path: NormalisedURLPath, body: any, userContext: UserContext) => Promise; invalidateCoreCallCache: (userContext: UserContext, updGlobalCacheTagIfNecessary?: boolean) => void; getAllCoreUrlsForPath(path: string): string[]; private sendRequestHelper; diff --git a/lib/build/querier.js b/lib/build/querier.js index 663603253..84eb5efb9 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -355,6 +355,48 @@ class Querier { ); return respBody; }; + // path should start with "/" + this.sendPatchRequest = async (path, body, userContext) => { + var _a; + this.invalidateCoreCallCache(userContext); + const { body: respBody } = await this.sendRequestHelper( + path, + "PATCH", + async (url) => { + let apiVersion = await this.getAPIVersion(); + let headers = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; + if (Querier.apiKey !== undefined) { + headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); + } + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = Object.assign(Object.assign({}, headers), { rid: this.rIdToCore }); + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor( + { + url: url, + method: "patch", + headers: headers, + body: body, + }, + userContext + ); + url = request.url; + headers = request.headers; + if (request.body !== undefined) { + body = request.body; + } + } + return utils_1.doFetch(url, { + method: "PATCH", + body: body !== undefined ? JSON.stringify(body) : undefined, + headers, + }); + }, + ((_a = this.__hosts) === null || _a === void 0 ? void 0 : _a.length) || 0 + ); + return respBody; + }; this.invalidateCoreCallCache = (userContext, updGlobalCacheTagIfNecessary = true) => { var _a; if ( @@ -508,5 +550,11 @@ Querier.networkInterceptor = undefined; Querier.globalCacheTag = Date.now(); Querier.disableCache = false; async function handleHydraAPICall(response) { - return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().json() } }; + const contentType = response.headers.get("Content-Type"); + if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("application/json")) { + return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().json() } }; + } else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("text/plain")) { + return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().text() } }; + } + return { body: { status: response.ok ? "OK" : "ERROR" } }; } diff --git a/lib/build/recipe/oauth2/OAuth2Client.d.ts b/lib/build/recipe/oauth2/OAuth2Client.d.ts index be174bde5..3a074bb04 100644 --- a/lib/build/recipe/oauth2/OAuth2Client.d.ts +++ b/lib/build/recipe/oauth2/OAuth2Client.d.ts @@ -87,55 +87,55 @@ export declare class OAuth2Client { * OAuth 2.0 Token Endpoint Signing Algorithm * Requested Client Authentication signing algorithm for the Token Endpoint. */ - tokenEndpointAuthSigningAlg: string | null; + tokenEndpointAuthSigningAlg?: string; /** * OAuth 2.0 Access Token Strategy * AccessTokenStrategy is the strategy used to generate access tokens. * Valid options are jwt and opaque. */ - accessTokenStrategy: "jwt" | "opaque" | null; + accessTokenStrategy?: "jwt" | "opaque"; /** * OpenID Connect Back-Channel Logout Session Required * Boolean value specifying whether the RP requires that a sid (session ID) Claim be included in the Logout * Token to identify the RP session with the OP when the backchannel_logout_uri is used. * If omitted, the default value is false. */ - backchannelLogoutSessionRequired: boolean; + backchannelLogoutSessionRequired?: boolean; /** * OpenID Connect Back-Channel Logout URI * RP URL that will cause the RP to log itself out when sent a Logout Token by the OP. */ - backchannelLogoutUri: string | null; + backchannelLogoutUri?: string; /** * OpenID Connect Front-Channel Logout Session Required * Boolean value specifying whether the RP requires that iss (issuer) and sid (session ID) query parameters be * included to identify the RP session with the OP when the frontchannel_logout_uri is used. * If omitted, the default value is false. */ - frontchannelLogoutSessionRequired: boolean; + frontchannelLogoutSessionRequired?: boolean; /** * OpenID Connect Front-Channel Logout URI * RP URL that will cause the RP to log itself out when rendered in an iframe by the OP. */ - frontchannelLogoutUri: string | null; + frontchannelLogoutUri?: string; /** * OpenID Connect Request Object Signing Algorithm * JWS alg algorithm that MUST be used for signing Request Objects sent to the OP. All Request Objects * from this Client MUST be rejected, if not signed with this algorithm. */ - requestObjectSigningAlg: string | null; + requestObjectSigningAlg?: string; /** * OpenID Connect Sector Identifier URI * URL using the https scheme to be used in calculating Pseudonymous Identifiers by the OP. The URL references a * file with a single JSON array of redirect_uri values. */ - sectorIdentifierUri: string | null; + sectorIdentifierUri?: string; /** * OpenID Connect Request Userinfo Signed Response Algorithm * JWS alg algorithm REQUIRED for signing UserInfo Responses. If this is specified, the response will be JWT * serialized, and signed using JWS. */ - userinfoSignedResponseAlg: string | null; + userinfoSignedResponseAlg: string; /** * OAuth 2.0 Client JSON Web Key Set * Client's JSON Web Key Set [JWK] document, passed by value. @@ -145,7 +145,7 @@ export declare class OAuth2Client { * OAuth 2.0 Client JSON Web Key Set URL * URL for the Client's JSON Web Key Set [JWK] document. */ - jwksUri: string | null; + jwksUri?: string; /** * OAuth 2.0 Client Owner * Owner is a string identifying the owner of the OAuth 2.0 Client. @@ -175,12 +175,12 @@ export declare class OAuth2Client { * Array of post-logout redirect URIs * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. */ - postLogoutRedirectUris: string[] | null; + postLogoutRedirectUris?: string[]; /** * Array of request URIs * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. */ - requestUris: string[] | null; + requestUris?: string[]; /** * Array of response types * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. @@ -301,4 +301,5 @@ export declare class OAuth2Client { registrationClientUri, metadata, }: OAuth2ClientOptions); + static fromAPIResponse(response: any): OAuth2Client; } diff --git a/lib/build/recipe/oauth2/OAuth2Client.js b/lib/build/recipe/oauth2/OAuth2Client.js index 78232a67b..af937addc 100644 --- a/lib/build/recipe/oauth2/OAuth2Client.js +++ b/lib/build/recipe/oauth2/OAuth2Client.js @@ -15,6 +15,7 @@ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.OAuth2Client = void 0; +const utils_1 = require("../../utils"); class OAuth2Client { constructor({ clientId, @@ -33,24 +34,24 @@ class OAuth2Client { refreshTokenGrantIdTokenLifespan = null, refreshTokenGrantRefreshTokenLifespan = null, tokenEndpointAuthMethod, - tokenEndpointAuthSigningAlg = null, - accessTokenStrategy = null, + tokenEndpointAuthSigningAlg, + accessTokenStrategy, backchannelLogoutSessionRequired = false, - backchannelLogoutUri = null, + backchannelLogoutUri, frontchannelLogoutSessionRequired = false, - frontchannelLogoutUri = null, - requestObjectSigningAlg = null, - sectorIdentifierUri = null, - userinfoSignedResponseAlg = null, + frontchannelLogoutUri, + requestObjectSigningAlg, + sectorIdentifierUri, + userinfoSignedResponseAlg, jwks = {}, - jwksUri = null, + jwksUri, owner = "", clientUri = "", allowedCorsOrigins = [], audience = [], grantTypes = null, - postLogoutRedirectUris = null, - requestUris = null, + postLogoutRedirectUris, + requestUris, responseTypes = null, contacts = null, logoUri = "", @@ -118,5 +119,8 @@ class OAuth2Client { this.registrationClientUri = registrationClientUri; this.metadata = metadata; } + static fromAPIResponse(response) { + return new OAuth2Client(utils_1.transformObjectKeys(response, "camelCase")); + } } exports.OAuth2Client = OAuth2Client; diff --git a/lib/build/recipe/oauth2/index.d.ts b/lib/build/recipe/oauth2/index.d.ts index 040f56692..4a3e09e08 100644 --- a/lib/build/recipe/oauth2/index.d.ts +++ b/lib/build/recipe/oauth2/index.d.ts @@ -1,11 +1,18 @@ // @ts-nocheck import { UserContext } from "../../types"; import Recipe from "./recipe"; -import { APIInterface, RecipeInterface, APIOptions, OAuth2ClientOptions } from "./types"; +import { + APIInterface, + RecipeInterface, + APIOptions, + CreateOAuth2ClientInput, + UpdateOAuth2ClientInput, + DeleteOAuth2ClientInput, +} from "./types"; export default class Wrapper { static init: typeof Recipe.init; static createOAuth2Client( - input: OAuth2ClientOptions, + input: CreateOAuth2ClientInput, userContext: UserContext ): Promise< | { @@ -18,7 +25,36 @@ export default class Wrapper { errorHint: string; } >; + static updateOAuth2Client( + input: UpdateOAuth2ClientInput, + userContext: UserContext + ): Promise< + | { + status: "OK"; + client: import("./OAuth2Client").OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; + static deleteOAuth2Client( + input: DeleteOAuth2ClientInput, + userContext: UserContext + ): Promise< + | { + status: "OK"; + } + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; } export declare let init: typeof Recipe.init; export declare let createOAuth2Client: typeof Wrapper.createOAuth2Client; +export declare let updateOAuth2Client: typeof Wrapper.updateOAuth2Client; +export declare let deleteOAuth2Client: typeof Wrapper.deleteOAuth2Client; export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/build/recipe/oauth2/index.js b/lib/build/recipe/oauth2/index.js index d005203ca..2e668ec64 100644 --- a/lib/build/recipe/oauth2/index.js +++ b/lib/build/recipe/oauth2/index.js @@ -19,7 +19,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.createOAuth2Client = exports.init = void 0; +exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); class Wrapper { static async createOAuth2Client(input, userContext) { @@ -27,8 +27,20 @@ class Wrapper { .getInstanceOrThrowError() .recipeInterfaceImpl.createOAuth2Client(input, userContext); } + static async updateOAuth2Client(input, userContext) { + return await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.updateOAuth2Client(input, userContext); + } + static async deleteOAuth2Client(input, userContext) { + return await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.deleteOAuth2Client(input, userContext); + } } exports.default = Wrapper; Wrapper.init = recipe_1.default.init; exports.init = Wrapper.init; exports.createOAuth2Client = Wrapper.createOAuth2Client; +exports.updateOAuth2Client = Wrapper.updateOAuth2Client; +exports.deleteOAuth2Client = Wrapper.deleteOAuth2Client; diff --git a/lib/build/recipe/oauth2/recipeImplementation.js b/lib/build/recipe/oauth2/recipeImplementation.js index 093b58c33..3bc682b81 100644 --- a/lib/build/recipe/oauth2/recipeImplementation.js +++ b/lib/build/recipe/oauth2/recipeImplementation.js @@ -27,19 +27,13 @@ function getRecipeInterface(querier, _config, _appInfo) { createOAuth2Client: async function (input, userContext) { let response = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients`), - input, + utils_1.transformObjectKeys(input, "snake-case"), userContext ); if (response.status === "OK") { - const oAuth2ClientInput = Object.keys(response.data).reduce((result, key) => { - const camelCaseKey = utils_1.toCamelCase(key); - result[camelCaseKey] = response.data[key]; - return result; - }, {}); - const client = new OAuth2Client_1.OAuth2Client(oAuth2ClientInput); return { status: "OK", - client, + client: OAuth2Client_1.OAuth2Client.fromAPIResponse(response.data), }; } else { return { @@ -49,6 +43,52 @@ function getRecipeInterface(querier, _config, _appInfo) { }; } }, + updateOAuth2Client: async function (input, userContext) { + // We convert the input into an array of "replace" operations + const requestBody = Object.entries(input).reduce((result, [key, value]) => { + result.push({ + from: `/${utils_1.toSnakeCase(key)}`, + op: "replace", + path: `/${utils_1.toSnakeCase(key)}`, + value, + }); + return result; + }, []); + let response = await querier.sendPatchRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients/${input.clientId}`), + requestBody, + userContext + ); + if (response.status === "OK") { + return { + status: "OK", + client: OAuth2Client_1.OAuth2Client.fromAPIResponse(response.data), + }; + } else { + return { + status: "ERROR", + error: response.data.error, + errorHint: response.data.errorHint, + }; + } + }, + deleteOAuth2Client: async function (input, userContext) { + let response = await querier.sendDeleteRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients/${input.clientId}`), + undefined, + undefined, + userContext + ); + if (response.status === "OK") { + return { status: "OK" }; + } else { + return { + status: "ERROR", + error: response.data.error, + errorHint: response.data.errorHint, + }; + } + }, }; } exports.default = getRecipeInterface; diff --git a/lib/build/recipe/oauth2/types.d.ts b/lib/build/recipe/oauth2/types.d.ts index cefbd0b40..0ffa7dc7d 100644 --- a/lib/build/recipe/oauth2/types.d.ts +++ b/lib/build/recipe/oauth2/types.d.ts @@ -1,7 +1,7 @@ // @ts-nocheck import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; -import { UserContext } from "../../types"; +import { NonNullableProperties, UserContext } from "../../types"; import { OAuth2Client } from "./OAuth2Client"; export declare type TypeInput = { override?: { @@ -31,7 +31,7 @@ export declare type APIOptions = { }; export declare type RecipeInterface = { createOAuth2Client( - input: OAuth2ClientOptions, + input: CreateOAuth2ClientInput, userContext: UserContext ): Promise< | { @@ -44,6 +44,33 @@ export declare type RecipeInterface = { errorHint: string; } >; + updateOAuth2Client( + input: UpdateOAuth2ClientInput, + userContext: UserContext + ): Promise< + | { + status: "OK"; + client: OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; + deleteOAuth2Client( + input: DeleteOAuth2ClientInput, + userContext: UserContext + ): Promise< + | { + status: "OK"; + } + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; }; export declare type APIInterface = {}; export declare type OAuth2ClientOptions = { @@ -63,24 +90,24 @@ export declare type OAuth2ClientOptions = { refreshTokenGrantIdTokenLifespan?: string | null; refreshTokenGrantRefreshTokenLifespan?: string | null; tokenEndpointAuthMethod: string; - tokenEndpointAuthSigningAlg?: string | null; - accessTokenStrategy?: "jwt" | "opaque" | null; + tokenEndpointAuthSigningAlg?: string; + accessTokenStrategy?: "jwt" | "opaque"; backchannelLogoutSessionRequired?: boolean; - backchannelLogoutUri?: string | null; + backchannelLogoutUri?: string; frontchannelLogoutSessionRequired?: boolean; - frontchannelLogoutUri?: string | null; - requestObjectSigningAlg?: string | null; - sectorIdentifierUri?: string | null; - userinfoSignedResponseAlg?: string | null; + frontchannelLogoutUri?: string; + requestObjectSigningAlg?: string; + sectorIdentifierUri?: string; + userinfoSignedResponseAlg: string; jwks?: Record; - jwksUri?: string | null; + jwksUri?: string; owner?: string; clientUri?: string; allowedCorsOrigins?: string[]; audience?: string[]; grantTypes?: string[] | null; - postLogoutRedirectUris?: string[] | null; - requestUris?: string[] | null; + postLogoutRedirectUris?: string[]; + requestUris?: string[]; responseTypes?: string[] | null; contacts?: string[] | null; logoUri?: string; @@ -96,3 +123,30 @@ export declare type OAuth2ClientOptions = { metadata?: Record; }; export declare type CreateOAuth2ClientInput = Partial>; +export declare type UpdateOAuth2ClientInput = NonNullableProperties< + Omit< + CreateOAuth2ClientInput, + | "redirectUris" + | "grantTypes" + | "postLogoutRedirectUris" + | "requestUris" + | "responseTypes" + | "contacts" + | "registrationAccessToken" + | "registrationClientUri" + | "metadata" + > +> & { + redirectUris?: string[] | null; + grantTypes?: string[] | null; + postLogoutRedirectUris?: string[] | null; + requestUris?: string[] | null; + responseTypes?: string[] | null; + contacts?: string[] | null; + registrationAccessToken?: string | null; + registrationClientUri?: string | null; + metadata?: Record | null; +}; +export declare type DeleteOAuth2ClientInput = { + clientId: string; +}; diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 4ee670175..c45e6de40 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -10,6 +10,9 @@ declare type Brand = { [__brand]: B; }; declare type Branded = T & Brand; +export declare type NonNullableProperties = { + [P in keyof T]: NonNullable; +}; export declare type UserContext = Branded, "UserContext">; export declare type AppInfo = { appName: string; @@ -62,7 +65,7 @@ export declare type APIHandled = { id: string; disabled: boolean; }; -export declare type HTTPMethod = "post" | "get" | "delete" | "put" | "options" | "trace"; +export declare type HTTPMethod = "post" | "get" | "delete" | "put" | "patch" | "options" | "trace"; export declare type JSONPrimitive = string | number | boolean | null; export declare type JSONArray = Array; export declare type JSONValue = JSONPrimitive | JSONObject | JSONArray | undefined; diff --git a/lib/build/utils.d.ts b/lib/build/utils.d.ts index dea78977c..f583171fe 100644 --- a/lib/build/utils.d.ts +++ b/lib/build/utils.d.ts @@ -58,3 +58,10 @@ export declare function postWithFetch( >; export declare function normaliseEmail(email: string): string; export declare function toCamelCase(str: string): string; +export declare function toSnakeCase(str: string): string; +export declare function transformObjectKeys( + obj: { + [key: string]: any; + }, + caseType: "snake-case" | "camelCase" +): T; diff --git a/lib/build/utils.js b/lib/build/utils.js index 451dd5391..831173dd1 100644 --- a/lib/build/utils.js +++ b/lib/build/utils.js @@ -41,7 +41,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.toCamelCase = exports.normaliseEmail = exports.postWithFetch = exports.getFromObjectCaseInsensitive = exports.getTopLevelDomainForSameSiteResolution = exports.setRequestInUserContextIfNotDefined = exports.getUserContext = exports.makeDefaultUserContextFromAPI = exports.humaniseMilliseconds = exports.frontendHasInterceptor = exports.getRidFromHeader = exports.hasGreaterThanEqualToFDI = exports.getLatestFDIVersionFromFDIList = exports.getBackwardsCompatibleUserInfo = exports.isAnIpAddress = exports.send200Response = exports.sendNon200Response = exports.sendNon200ResponseWithMessage = exports.normaliseHttpMethod = exports.normaliseInputAppInfoOrThrowError = exports.maxVersion = exports.getLargestVersionFromIntersection = exports.doFetch = void 0; +exports.transformObjectKeys = exports.toSnakeCase = exports.toCamelCase = exports.normaliseEmail = exports.postWithFetch = exports.getFromObjectCaseInsensitive = exports.getTopLevelDomainForSameSiteResolution = exports.setRequestInUserContextIfNotDefined = exports.getUserContext = exports.makeDefaultUserContextFromAPI = exports.humaniseMilliseconds = exports.frontendHasInterceptor = exports.getRidFromHeader = exports.hasGreaterThanEqualToFDI = exports.getLatestFDIVersionFromFDIList = exports.getBackwardsCompatibleUserInfo = exports.isAnIpAddress = exports.send200Response = exports.sendNon200Response = exports.sendNon200ResponseWithMessage = exports.normaliseHttpMethod = exports.normaliseInputAppInfoOrThrowError = exports.maxVersion = exports.getLargestVersionFromIntersection = exports.doFetch = void 0; const psl = __importStar(require("psl")); const normalisedURLDomain_1 = __importDefault(require("./normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); @@ -431,3 +431,17 @@ function toCamelCase(str) { }); } exports.toCamelCase = toCamelCase; +function toSnakeCase(str) { + return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); +} +exports.toSnakeCase = toSnakeCase; +// Transforms the keys of an object from camelCase to snakeCase or vice versa. +function transformObjectKeys(obj, caseType) { + const transformKey = caseType === "camelCase" ? toCamelCase : toSnakeCase; + return Object.entries(obj).reduce((result, [key, value]) => { + const transformedKey = transformKey(key); + result[transformedKey] = value; + return result; + }, {}); +} +exports.transformObjectKeys = transformObjectKeys; diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index 33f6fa884..a8fee053f 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -449,6 +449,56 @@ export class Querier { return respBody; }; + // path should start with "/" + sendPatchRequest = async (path: NormalisedURLPath, body: any, userContext: UserContext): Promise => { + this.invalidateCoreCallCache(userContext); + + const { body: respBody } = await this.sendRequestHelper( + path, + "PATCH", + async (url: string) => { + let apiVersion = await this.getAPIVersion(); + let headers: any = { "cdi-version": apiVersion, "content-type": "application/json; charset=utf-8" }; + if (Querier.apiKey !== undefined) { + headers = { + ...headers, + "api-key": Querier.apiKey, + }; + } + if (path.isARecipePath() && this.rIdToCore !== undefined) { + headers = { + ...headers, + rid: this.rIdToCore, + }; + } + if (Querier.networkInterceptor !== undefined) { + let request = Querier.networkInterceptor( + { + url: url, + method: "patch", + headers: headers, + body: body, + }, + userContext + ); + url = request.url; + headers = request.headers; + if (request.body !== undefined) { + body = request.body; + } + } + + return doFetch(url, { + method: "PATCH", + body: body !== undefined ? JSON.stringify(body) : undefined, + headers, + }); + }, + this.__hosts?.length || 0 + ); + return respBody; + }; + invalidateCoreCallCache = (userContext: UserContext, updGlobalCacheTagIfNecessary = true) => { if (updGlobalCacheTagIfNecessary && userContext._default?.keepCacheAlive !== true) { Querier.globalCacheTag = Date.now(); @@ -585,5 +635,13 @@ export class Querier { } async function handleHydraAPICall(response: Response) { - return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().json() } }; + const contentType = response.headers.get("Content-Type"); + + if (contentType?.startsWith("application/json")) { + return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().json() } }; + } else if (contentType?.startsWith("text/plain")) { + return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().text() } }; + } + + return { body: { status: response.ok ? "OK" : "ERROR" } }; } diff --git a/lib/ts/recipe/oauth2/OAuth2Client.ts b/lib/ts/recipe/oauth2/OAuth2Client.ts index c7fde8c73..7126f3198 100644 --- a/lib/ts/recipe/oauth2/OAuth2Client.ts +++ b/lib/ts/recipe/oauth2/OAuth2Client.ts @@ -13,6 +13,7 @@ * under the License. */ +import { transformObjectKeys } from "../../utils"; import { OAuth2ClientOptions } from "./types"; export class OAuth2Client { @@ -118,14 +119,14 @@ export class OAuth2Client { * OAuth 2.0 Token Endpoint Signing Algorithm * Requested Client Authentication signing algorithm for the Token Endpoint. */ - tokenEndpointAuthSigningAlg: string | null; + tokenEndpointAuthSigningAlg?: string; /** * OAuth 2.0 Access Token Strategy * AccessTokenStrategy is the strategy used to generate access tokens. * Valid options are jwt and opaque. */ - accessTokenStrategy: "jwt" | "opaque" | null; + accessTokenStrategy?: "jwt" | "opaque"; /** * OpenID Connect Back-Channel Logout Session Required @@ -133,13 +134,13 @@ export class OAuth2Client { * Token to identify the RP session with the OP when the backchannel_logout_uri is used. * If omitted, the default value is false. */ - backchannelLogoutSessionRequired: boolean; + backchannelLogoutSessionRequired?: boolean; /** * OpenID Connect Back-Channel Logout URI * RP URL that will cause the RP to log itself out when sent a Logout Token by the OP. */ - backchannelLogoutUri: string | null; + backchannelLogoutUri?: string; /** * OpenID Connect Front-Channel Logout Session Required @@ -147,34 +148,34 @@ export class OAuth2Client { * included to identify the RP session with the OP when the frontchannel_logout_uri is used. * If omitted, the default value is false. */ - frontchannelLogoutSessionRequired: boolean; + frontchannelLogoutSessionRequired?: boolean; /** * OpenID Connect Front-Channel Logout URI * RP URL that will cause the RP to log itself out when rendered in an iframe by the OP. */ - frontchannelLogoutUri: string | null; + frontchannelLogoutUri?: string; /** * OpenID Connect Request Object Signing Algorithm * JWS alg algorithm that MUST be used for signing Request Objects sent to the OP. All Request Objects * from this Client MUST be rejected, if not signed with this algorithm. */ - requestObjectSigningAlg: string | null; + requestObjectSigningAlg?: string; /** * OpenID Connect Sector Identifier URI * URL using the https scheme to be used in calculating Pseudonymous Identifiers by the OP. The URL references a * file with a single JSON array of redirect_uri values. */ - sectorIdentifierUri: string | null; + sectorIdentifierUri?: string; /** * OpenID Connect Request Userinfo Signed Response Algorithm * JWS alg algorithm REQUIRED for signing UserInfo Responses. If this is specified, the response will be JWT * serialized, and signed using JWS. */ - userinfoSignedResponseAlg: string | null; + userinfoSignedResponseAlg: string; /** * OAuth 2.0 Client JSON Web Key Set @@ -186,7 +187,7 @@ export class OAuth2Client { * OAuth 2.0 Client JSON Web Key Set URL * URL for the Client's JSON Web Key Set [JWK] document. */ - jwksUri: string | null; + jwksUri?: string; /** * OAuth 2.0 Client Owner @@ -222,13 +223,13 @@ export class OAuth2Client { * Array of post-logout redirect URIs * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. */ - postLogoutRedirectUris: string[] | null; + postLogoutRedirectUris?: string[]; /** * Array of request URIs * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. */ - requestUris: string[] | null; + requestUris?: string[]; /** * Array of response types @@ -331,24 +332,24 @@ export class OAuth2Client { refreshTokenGrantIdTokenLifespan = null, refreshTokenGrantRefreshTokenLifespan = null, tokenEndpointAuthMethod, - tokenEndpointAuthSigningAlg = null, - accessTokenStrategy = null, + tokenEndpointAuthSigningAlg, + accessTokenStrategy, backchannelLogoutSessionRequired = false, - backchannelLogoutUri = null, + backchannelLogoutUri, frontchannelLogoutSessionRequired = false, - frontchannelLogoutUri = null, - requestObjectSigningAlg = null, - sectorIdentifierUri = null, - userinfoSignedResponseAlg = null, + frontchannelLogoutUri, + requestObjectSigningAlg, + sectorIdentifierUri, + userinfoSignedResponseAlg, jwks = {}, - jwksUri = null, + jwksUri, owner = "", clientUri = "", allowedCorsOrigins = [], audience = [], grantTypes = null, - postLogoutRedirectUris = null, - requestUris = null, + postLogoutRedirectUris, + requestUris, responseTypes = null, contacts = null, logoUri = "", @@ -411,4 +412,8 @@ export class OAuth2Client { this.registrationClientUri = registrationClientUri; this.metadata = metadata; } + + static fromAPIResponse(response: any): OAuth2Client { + return new OAuth2Client(transformObjectKeys(response, "camelCase")); + } } diff --git a/lib/ts/recipe/oauth2/index.ts b/lib/ts/recipe/oauth2/index.ts index a8fe9e352..bf9905eb6 100644 --- a/lib/ts/recipe/oauth2/index.ts +++ b/lib/ts/recipe/oauth2/index.ts @@ -15,18 +15,35 @@ import { UserContext } from "../../types"; import Recipe from "./recipe"; -import { APIInterface, RecipeInterface, APIOptions, OAuth2ClientOptions } from "./types"; +import { + APIInterface, + RecipeInterface, + APIOptions, + CreateOAuth2ClientInput, + UpdateOAuth2ClientInput, + DeleteOAuth2ClientInput, +} from "./types"; export default class Wrapper { static init = Recipe.init; - static async createOAuth2Client(input: OAuth2ClientOptions, userContext: UserContext) { + static async createOAuth2Client(input: CreateOAuth2ClientInput, userContext: UserContext) { return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createOAuth2Client(input, userContext); } + static async updateOAuth2Client(input: UpdateOAuth2ClientInput, userContext: UserContext) { + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.updateOAuth2Client(input, userContext); + } + static async deleteOAuth2Client(input: DeleteOAuth2ClientInput, userContext: UserContext) { + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.deleteOAuth2Client(input, userContext); + } } export let init = Wrapper.init; export let createOAuth2Client = Wrapper.createOAuth2Client; +export let updateOAuth2Client = Wrapper.updateOAuth2Client; + +export let deleteOAuth2Client = Wrapper.deleteOAuth2Client; + export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/ts/recipe/oauth2/recipeImplementation.ts b/lib/ts/recipe/oauth2/recipeImplementation.ts index c78ed3306..e44e733a1 100644 --- a/lib/ts/recipe/oauth2/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2/recipeImplementation.ts @@ -16,7 +16,7 @@ import NormalisedURLPath from "../../normalisedURLPath"; import { Querier } from "../../querier"; import { NormalisedAppinfo } from "../../types"; -import { toCamelCase } from "../../utils"; +import { toSnakeCase, transformObjectKeys } from "../../utils"; import { OAuth2Client } from "./OAuth2Client"; import { RecipeInterface, TypeNormalisedInput } from "./types"; @@ -29,23 +29,66 @@ export default function getRecipeInterface( createOAuth2Client: async function (input, userContext) { let response = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth2/admin/clients`), - input, + transformObjectKeys(input, "snake-case"), userContext ); if (response.status === "OK") { - const oAuth2ClientInput = Object.keys(response.data).reduce((result, key) => { - const camelCaseKey = toCamelCase(key); - result[camelCaseKey] = response.data[key]; - return result; - }, {} as any); + return { + status: "OK", + client: OAuth2Client.fromAPIResponse(response.data), + }; + } else { + return { + status: "ERROR", + error: response.data.error, + errorHint: response.data.errorHint, + }; + } + }, + updateOAuth2Client: async function (input, userContext) { + // We convert the input into an array of "replace" operations + const requestBody = Object.entries(input).reduce< + Array<{ from: string; op: "replace"; path: string; value: any }> + >((result, [key, value]) => { + result.push({ + from: `/${toSnakeCase(key)}`, + op: "replace", + path: `/${toSnakeCase(key)}`, + value, + }); + return result; + }, []); - const client = new OAuth2Client(oAuth2ClientInput); + let response = await querier.sendPatchRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/clients/${input.clientId}`), + requestBody, + userContext + ); + if (response.status === "OK") { return { status: "OK", - client, + client: OAuth2Client.fromAPIResponse(response.data), + }; + } else { + return { + status: "ERROR", + error: response.data.error, + errorHint: response.data.errorHint, }; + } + }, + deleteOAuth2Client: async function (input, userContext) { + let response = await querier.sendDeleteRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/clients/${input.clientId}`), + undefined, + undefined, + userContext + ); + + if (response.status === "OK") { + return { status: "OK" }; } else { return { status: "ERROR", diff --git a/lib/ts/recipe/oauth2/types.ts b/lib/ts/recipe/oauth2/types.ts index 12040f03a..d45298a47 100644 --- a/lib/ts/recipe/oauth2/types.ts +++ b/lib/ts/recipe/oauth2/types.ts @@ -15,7 +15,7 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; -import { UserContext } from "../../types"; +import { NonNullableProperties, UserContext } from "../../types"; import { OAuth2Client } from "./OAuth2Client"; export type TypeInput = { @@ -49,7 +49,7 @@ export type APIOptions = { export type RecipeInterface = { createOAuth2Client( - input: OAuth2ClientOptions, + input: CreateOAuth2ClientInput, userContext: UserContext ): Promise< | { @@ -63,6 +63,35 @@ export type RecipeInterface = { errorHint: string; } >; + updateOAuth2Client( + input: UpdateOAuth2ClientInput, + userContext: UserContext + ): Promise< + | { + status: "OK"; + client: OAuth2Client; + } + // TODO: Define specific error types once requirements are clearer + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; + deleteOAuth2Client( + input: DeleteOAuth2ClientInput, + userContext: UserContext + ): Promise< + | { + status: "OK"; + } + // TODO: Define specific error types once requirements are clearer + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; }; export type APIInterface = {}; @@ -86,26 +115,26 @@ export type OAuth2ClientOptions = { refreshTokenGrantRefreshTokenLifespan?: string | null; tokenEndpointAuthMethod: string; - tokenEndpointAuthSigningAlg?: string | null; - accessTokenStrategy?: "jwt" | "opaque" | null; + tokenEndpointAuthSigningAlg?: string; + accessTokenStrategy?: "jwt" | "opaque"; backchannelLogoutSessionRequired?: boolean; - backchannelLogoutUri?: string | null; + backchannelLogoutUri?: string; frontchannelLogoutSessionRequired?: boolean; - frontchannelLogoutUri?: string | null; - requestObjectSigningAlg?: string | null; - sectorIdentifierUri?: string | null; - userinfoSignedResponseAlg?: string | null; + frontchannelLogoutUri?: string; + requestObjectSigningAlg?: string; + sectorIdentifierUri?: string; + userinfoSignedResponseAlg: string; jwks?: Record; - jwksUri?: string | null; + jwksUri?: string; owner?: string; clientUri?: string; allowedCorsOrigins?: string[]; audience?: string[]; grantTypes?: string[] | null; - postLogoutRedirectUris?: string[] | null; - requestUris?: string[] | null; + postLogoutRedirectUris?: string[]; + requestUris?: string[]; responseTypes?: string[] | null; contacts?: string[] | null; logoUri?: string; @@ -122,3 +151,32 @@ export type OAuth2ClientOptions = { }; export type CreateOAuth2ClientInput = Partial>; + +export type UpdateOAuth2ClientInput = NonNullableProperties< + Omit< + CreateOAuth2ClientInput, + | "redirectUris" + | "grantTypes" + | "postLogoutRedirectUris" + | "requestUris" + | "responseTypes" + | "contacts" + | "registrationAccessToken" + | "registrationClientUri" + | "metadata" + > +> & { + redirectUris?: string[] | null; + grantTypes?: string[] | null; + postLogoutRedirectUris?: string[] | null; + requestUris?: string[] | null; + responseTypes?: string[] | null; + contacts?: string[] | null; + registrationAccessToken?: string | null; + registrationClientUri?: string | null; + metadata?: Record | null; +}; + +export type DeleteOAuth2ClientInput = { + clientId: string; +}; diff --git a/lib/ts/types.ts b/lib/ts/types.ts index a90d96f0f..e87e6b2a7 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -24,6 +24,11 @@ type Brand = { [__brand]: B }; type Branded = T & Brand; +// A utility type that makes all properties of a given type non-nullable. +export type NonNullableProperties = { + [P in keyof T]: NonNullable; +}; + // Record is still quite generic and we would like to ensure type safety for the userContext // so we use the concept of branded type, which enables catching of issues at compile time. // Detailed explanation about branded types is available here - https://egghead.io/blog/using-branded-types-in-typescript @@ -86,7 +91,7 @@ export type APIHandled = { disabled: boolean; }; -export type HTTPMethod = "post" | "get" | "delete" | "put" | "options" | "trace"; +export type HTTPMethod = "post" | "get" | "delete" | "put" | "patch" | "options" | "trace"; export type JSONPrimitive = string | number | boolean | null; export type JSONArray = Array; diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts index 5370cc812..c55687fb4 100644 --- a/lib/ts/utils.ts +++ b/lib/ts/utils.ts @@ -428,3 +428,18 @@ export function toCamelCase(str: string): string { return match.toUpperCase().replace("-", "").replace("_", ""); }); } + +export function toSnakeCase(str: string): string { + return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); +} + +// Transforms the keys of an object from camelCase to snakeCase or vice versa. +export function transformObjectKeys(obj: { [key: string]: any }, caseType: "snake-case" | "camelCase"): T { + const transformKey = caseType === "camelCase" ? toCamelCase : toSnakeCase; + + return Object.entries(obj).reduce((result, [key, value]) => { + const transformedKey = transformKey(key); + result[transformedKey] = value; + return result; + }, {} as any) as T; +} diff --git a/test/oauth2/oauth2client.test.js b/test/oauth2/oauth2client.test.js index c66ac9f25..563bb9370 100644 --- a/test/oauth2/oauth2client.test.js +++ b/test/oauth2/oauth2client.test.js @@ -4,8 +4,6 @@ const { printPath, setupST, startST, killAllST, cleanST } = require("../utils"); let { ProcessState } = require("../../lib/build/processState"); let STExpress = require("../../"); let OAuth2Recipe = require("../../recipe/oauth2"); -let { Querier } = require("../../lib/build/querier"); -const { maxVersion } = require("../../lib/build/utils"); describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}`, function () { beforeEach(async function () { @@ -33,13 +31,6 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` recipeList: [OAuth2Recipe.init()], }); - // Only run for version >= 2.9 - let querier = Querier.getNewInstanceOrThrowError(undefined); - let apiVersion = await querier.getAPIVersion(); - if (maxVersion(apiVersion, "2.8") === "2.8") { - return; - } - const { client } = await OAuth2Recipe.createOAuth2Client({}, {}); assert(client.clientId !== undefined); @@ -61,13 +52,82 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` recipeList: [OAuth2Recipe.init()], }); - // Only run for version >= 2.9 - let querier = Querier.getNewInstanceOrThrowError(undefined); - let apiVersion = await querier.getAPIVersion(); - if (maxVersion(apiVersion, "2.8") === "2.8") { - return; - } + const { client } = await OAuth2Recipe.createOAuth2Client( + { + client_id: "client_id", + client_secret: "client_secret", + }, + {} + ); + + assert.strictEqual(client.clientId, "client_id"); + assert.strictEqual(client.clientSecret, "client_secret"); + }); + + it("should update the OAuth2Client", async function () { + const connectionURI = await startST(); + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [OAuth2Recipe.init()], + }); + + // Create a client + const { client } = await OAuth2Recipe.createOAuth2Client( + { + client_id: "client_id", + client_secret: "client_secret", + scope: "offline_access offline", + redirectUris: ["http://localhost:3000"], + }, + {} + ); + + assert.strictEqual(client.clientId, "client_id"); + assert.strictEqual(client.clientSecret, "client_secret"); + assert.strictEqual(client.scope, "offline_access offline"); + assert.strictEqual(JSON.stringify(client.redirectUris), JSON.stringify(["http://localhost:3000"])); + assert.strictEqual(JSON.stringify(client.metadata), JSON.stringify({})); + + // Update the client + const { client: updatedClient } = await OAuth2Recipe.updateOAuth2Client( + { + clientId: client.clientId, + clientSecret: "new_client_secret", + scope: "offline_access", + redirectUris: null, + metadata: { a: 1, b: 2 }, + }, + {} + ); + assert.strictEqual(updatedClient.clientSecret, "new_client_secret"); + assert.strictEqual(updatedClient.scope, "offline_access"); + assert.strictEqual(updatedClient.redirectUris, null); + assert.strictEqual(JSON.stringify(updatedClient.metadata), JSON.stringify({ a: 1, b: 2 })); + }); + + it("should delete the OAuth2Client", async function () { + const connectionURI = await startST(); + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [OAuth2Recipe.init()], + }); + + // Create a client const { client } = await OAuth2Recipe.createOAuth2Client( { client_id: "client_id", @@ -76,7 +136,17 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` {} ); - assert(client.clientId === "client_id"); - assert(client.clientSecret === "client_secret"); + assert.strictEqual(client.clientId, "client_id"); + assert.strictEqual(client.clientSecret, "client_secret"); + + // Delete the client + const { status } = await OAuth2Recipe.deleteOAuth2Client( + { + clientId: client.clientId, + }, + {} + ); + + assert.strictEqual(status, "OK"); }); }); From 568a2f34b78d6fc44390282d792811ef27b3cf9f Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 21 Jun 2024 15:42:17 +0530 Subject: [PATCH 06/35] feat: Add recipe functions to get OAuth2Clients (#865) * feat: Add recipe functions to update/delete OAuth2Client * fix: PR changes * feat: Add recipe functions to get OAuth2Clients * fix: PR changes --------- Co-authored-by: Mihaly Lengyel --- lib/build/querier.js | 15 +++- lib/build/recipe/oauth2/index.d.ts | 17 ++++ lib/build/recipe/oauth2/index.js | 8 +- .../recipe/oauth2/recipeImplementation.js | 34 ++++++++ lib/build/recipe/oauth2/types.d.ts | 33 ++++++++ lib/ts/querier.ts | 15 +++- lib/ts/recipe/oauth2/index.ts | 6 ++ lib/ts/recipe/oauth2/recipeImplementation.ts | 39 ++++++++++ lib/ts/recipe/oauth2/types.ts | 38 +++++++++ test/oauth2/oauth2client.test.js | 77 +++++++++++++++++++ 10 files changed, 275 insertions(+), 7 deletions(-) diff --git a/lib/build/querier.js b/lib/build/querier.js index 84eb5efb9..d8b74b342 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -552,9 +552,18 @@ Querier.disableCache = false; async function handleHydraAPICall(response) { const contentType = response.headers.get("Content-Type"); if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("application/json")) { - return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().json() } }; + return { + body: { + status: response.ok ? "OK" : "ERROR", + data: await response.clone().json(), + }, + headers: response.headers, + }; } else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("text/plain")) { - return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().text() } }; + return { + body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().text() }, + headers: response.headers, + }; } - return { body: { status: response.ok ? "OK" : "ERROR" } }; + return { body: { status: response.ok ? "OK" : "ERROR", headers: response.headers } }; } diff --git a/lib/build/recipe/oauth2/index.d.ts b/lib/build/recipe/oauth2/index.d.ts index 4a3e09e08..6df42ad6c 100644 --- a/lib/build/recipe/oauth2/index.d.ts +++ b/lib/build/recipe/oauth2/index.d.ts @@ -8,9 +8,25 @@ import { CreateOAuth2ClientInput, UpdateOAuth2ClientInput, DeleteOAuth2ClientInput, + GetOAuth2ClientsInput, } from "./types"; export default class Wrapper { static init: typeof Recipe.init; + static getOAuth2Clients( + input: GetOAuth2ClientsInput, + userContext: UserContext + ): Promise< + | { + status: "OK"; + clients: import("./OAuth2Client").OAuth2Client[]; + nextPaginationToken?: string | undefined; + } + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; static createOAuth2Client( input: CreateOAuth2ClientInput, userContext: UserContext @@ -54,6 +70,7 @@ export default class Wrapper { >; } export declare let init: typeof Recipe.init; +export declare let getOAuth2Clients: typeof Wrapper.getOAuth2Clients; export declare let createOAuth2Client: typeof Wrapper.createOAuth2Client; export declare let updateOAuth2Client: typeof Wrapper.updateOAuth2Client; export declare let deleteOAuth2Client: typeof Wrapper.deleteOAuth2Client; diff --git a/lib/build/recipe/oauth2/index.js b/lib/build/recipe/oauth2/index.js index 2e668ec64..7769bc796 100644 --- a/lib/build/recipe/oauth2/index.js +++ b/lib/build/recipe/oauth2/index.js @@ -19,9 +19,14 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.init = void 0; +exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.init = void 0; const recipe_1 = __importDefault(require("./recipe")); class Wrapper { + static async getOAuth2Clients(input, userContext) { + return await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.getOAuth2Clients(input, userContext); + } static async createOAuth2Client(input, userContext) { return await recipe_1.default .getInstanceOrThrowError() @@ -41,6 +46,7 @@ class Wrapper { exports.default = Wrapper; Wrapper.init = recipe_1.default.init; exports.init = Wrapper.init; +exports.getOAuth2Clients = Wrapper.getOAuth2Clients; exports.createOAuth2Client = Wrapper.createOAuth2Client; exports.updateOAuth2Client = Wrapper.updateOAuth2Client; exports.deleteOAuth2Client = Wrapper.deleteOAuth2Client; diff --git a/lib/build/recipe/oauth2/recipeImplementation.js b/lib/build/recipe/oauth2/recipeImplementation.js index 3bc682b81..03852ae4b 100644 --- a/lib/build/recipe/oauth2/recipeImplementation.js +++ b/lib/build/recipe/oauth2/recipeImplementation.js @@ -24,6 +24,40 @@ const utils_1 = require("../../utils"); const OAuth2Client_1 = require("./OAuth2Client"); function getRecipeInterface(querier, _config, _appInfo) { return { + getOAuth2Clients: async function (input, userContext) { + let response = await querier.sendGetRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients`), + Object.assign(Object.assign({}, utils_1.transformObjectKeys(input, "snake-case")), { + page_token: input.paginationToken, + }), + userContext + ); + if (response.status === "OK") { + // Pagination info is in the Link header, containing comma-separated links: + // "first", "next" (if applicable). + // Example: Link: ; rel="first", ; rel="next" + // We parse the nextPaginationToken from the Link header using RegExp + let nextPaginationToken; + const linkHeader = response.headers.get("link"); + const nextLinkMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/); + if (nextLinkMatch) { + const url = nextLinkMatch[1]; + const urlParams = new URLSearchParams(url.split("?")[1]); + nextPaginationToken = urlParams.get("page_token"); + } + return { + status: "OK", + clients: response.data.map((client) => OAuth2Client_1.OAuth2Client.fromAPIResponse(client)), + nextPaginationToken, + }; + } else { + return { + status: "ERROR", + error: response.data.error, + errorHint: response.data.errorHint, + }; + } + }, createOAuth2Client: async function (input, userContext) { let response = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients`), diff --git a/lib/build/recipe/oauth2/types.d.ts b/lib/build/recipe/oauth2/types.d.ts index 0ffa7dc7d..cbbe7e76f 100644 --- a/lib/build/recipe/oauth2/types.d.ts +++ b/lib/build/recipe/oauth2/types.d.ts @@ -30,6 +30,21 @@ export declare type APIOptions = { res: BaseResponse; }; export declare type RecipeInterface = { + getOAuth2Clients( + input: GetOAuth2ClientsInput, + userContext: UserContext + ): Promise< + | { + status: "OK"; + clients: Array; + nextPaginationToken?: string; + } + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; createOAuth2Client( input: CreateOAuth2ClientInput, userContext: UserContext @@ -122,6 +137,24 @@ export declare type OAuth2ClientOptions = { registrationClientUri: string; metadata?: Record; }; +export declare type GetOAuth2ClientsInput = { + /** + * Items per Page. Defaults to 250. + */ + pageSize?: number; + /** + * Next Page Token. Defaults to "1". + */ + paginationToken?: string; + /** + * The name of the clients to filter by. + */ + clientName?: string; + /** + * The owner of the clients to filter by. + */ + owner?: string; +}; export declare type CreateOAuth2ClientInput = Partial>; export declare type UpdateOAuth2ClientInput = NonNullableProperties< Omit< diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index a8fee053f..7bcff5193 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -638,10 +638,19 @@ async function handleHydraAPICall(response: Response) { const contentType = response.headers.get("Content-Type"); if (contentType?.startsWith("application/json")) { - return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().json() } }; + return { + body: { + status: response.ok ? "OK" : "ERROR", + data: await response.clone().json(), + }, + headers: response.headers, + }; } else if (contentType?.startsWith("text/plain")) { - return { body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().text() } }; + return { + body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().text() }, + headers: response.headers, + }; } - return { body: { status: response.ok ? "OK" : "ERROR" } }; + return { body: { status: response.ok ? "OK" : "ERROR", headers: response.headers } }; } diff --git a/lib/ts/recipe/oauth2/index.ts b/lib/ts/recipe/oauth2/index.ts index bf9905eb6..6962a0e09 100644 --- a/lib/ts/recipe/oauth2/index.ts +++ b/lib/ts/recipe/oauth2/index.ts @@ -22,11 +22,15 @@ import { CreateOAuth2ClientInput, UpdateOAuth2ClientInput, DeleteOAuth2ClientInput, + GetOAuth2ClientsInput, } from "./types"; export default class Wrapper { static init = Recipe.init; + static async getOAuth2Clients(input: GetOAuth2ClientsInput, userContext: UserContext) { + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Clients(input, userContext); + } static async createOAuth2Client(input: CreateOAuth2ClientInput, userContext: UserContext) { return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createOAuth2Client(input, userContext); } @@ -40,6 +44,8 @@ export default class Wrapper { export let init = Wrapper.init; +export let getOAuth2Clients = Wrapper.getOAuth2Clients; + export let createOAuth2Client = Wrapper.createOAuth2Client; export let updateOAuth2Client = Wrapper.updateOAuth2Client; diff --git a/lib/ts/recipe/oauth2/recipeImplementation.ts b/lib/ts/recipe/oauth2/recipeImplementation.ts index e44e733a1..9a341d53c 100644 --- a/lib/ts/recipe/oauth2/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2/recipeImplementation.ts @@ -26,6 +26,45 @@ export default function getRecipeInterface( _appInfo: NormalisedAppinfo ): RecipeInterface { return { + getOAuth2Clients: async function (input, userContext) { + let response = await querier.sendGetRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/clients`), + { + ...transformObjectKeys(input, "snake-case"), + page_token: input.paginationToken, + }, + userContext + ); + + if (response.status === "OK") { + // Pagination info is in the Link header, containing comma-separated links: + // "first", "next" (if applicable). + // Example: Link: ; rel="first", ; rel="next" + + // We parse the nextPaginationToken from the Link header using RegExp + let nextPaginationToken: string | undefined; + const linkHeader = response.headers.get("link"); + + const nextLinkMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/); + if (nextLinkMatch) { + const url = nextLinkMatch[1]; + const urlParams = new URLSearchParams(url.split("?")[1]); + nextPaginationToken = urlParams.get("page_token") as string; + } + + return { + status: "OK", + clients: response.data.map((client: any) => OAuth2Client.fromAPIResponse(client)), + nextPaginationToken, + }; + } else { + return { + status: "ERROR", + error: response.data.error, + errorHint: response.data.errorHint, + }; + } + }, createOAuth2Client: async function (input, userContext) { let response = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth2/admin/clients`), diff --git a/lib/ts/recipe/oauth2/types.ts b/lib/ts/recipe/oauth2/types.ts index d45298a47..0558366c6 100644 --- a/lib/ts/recipe/oauth2/types.ts +++ b/lib/ts/recipe/oauth2/types.ts @@ -48,6 +48,22 @@ export type APIOptions = { }; export type RecipeInterface = { + getOAuth2Clients( + input: GetOAuth2ClientsInput, + userContext: UserContext + ): Promise< + | { + status: "OK"; + clients: Array; + nextPaginationToken?: string; + } + // TODO: Define specific error types once requirements are clearer + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; createOAuth2Client( input: CreateOAuth2ClientInput, userContext: UserContext @@ -150,6 +166,28 @@ export type OAuth2ClientOptions = { metadata?: Record; }; +export type GetOAuth2ClientsInput = { + /** + * Items per Page. Defaults to 250. + */ + pageSize?: number; + + /** + * Next Page Token. Defaults to "1". + */ + paginationToken?: string; + + /** + * The name of the clients to filter by. + */ + clientName?: string; + + /** + * The owner of the clients to filter by. + */ + owner?: string; +}; + export type CreateOAuth2ClientInput = Partial>; export type UpdateOAuth2ClientInput = NonNullableProperties< diff --git a/test/oauth2/oauth2client.test.js b/test/oauth2/oauth2client.test.js index 563bb9370..a2e806247 100644 --- a/test/oauth2/oauth2client.test.js +++ b/test/oauth2/oauth2client.test.js @@ -149,4 +149,81 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` assert.strictEqual(status, "OK"); }); + + it("should get OAuth2Clients with pagination", async function () { + const connectionURI = await startST(); + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [OAuth2Recipe.init()], + }); + + // Create 10 clients + for (let i = 0; i < 10; i++) { + await OAuth2Recipe.createOAuth2Client( + { + client_id: `client_id_${i}`, + }, + {} + ); + } + + let allClients = []; + let nextPaginationToken = undefined; + + // Fetch clients in pages of 3 + do { + const result = await OAuth2Recipe.getOAuth2Clients( + { pageSize: 3, paginationToken: nextPaginationToken }, + {} + ); + assert.strictEqual(result.status, "OK"); + nextPaginationToken = result.nextPaginationToken; + allClients.push(...result.clients); + } while (nextPaginationToken); + + // Check the client IDs + for (let i = 0; i < 10; i++) { + assert.strictEqual(allClients[i].clientId, `client_id_${i}`); + } + }); + + it("should get OAuth2Clients with filter", async function () { + const connectionURI = await startST(); + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [OAuth2Recipe.init()], + }); + + // Create 5 clients with clientName = "customClientName" + for (let i = 0; i < 5; i++) { + await OAuth2Recipe.createOAuth2Client({ clientName: "customClientName" }, {}); + } + + // Create 5 clients with owner = "test" + for (let i = 0; i < 5; i++) { + await OAuth2Recipe.createOAuth2Client({ owner: "test" }, {}); + } + + let result = await OAuth2Recipe.getOAuth2Clients({ clientName: "customClientName" }, {}); + assert.strictEqual(result.status, "OK"); + assert.strictEqual(result.clients.length, 5); + + result = await OAuth2Recipe.getOAuth2Clients({ owner: "test" }, {}); + assert.strictEqual(result.status, "OK"); + assert.strictEqual(result.clients.length, 5); + }); }); From 2fd8ef4ddab978938a9f892ee4a31d7f3036c25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mih=C3=A1ly=20Lengyel?= Date: Sun, 14 Jul 2024 11:11:45 +0200 Subject: [PATCH 07/35] feat: add initial oauth2 client apis (#866) * 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 * feat!: improve how we handle changing email addresses and users becoming unverified when account linking requires verification (#869) * feat: update email and pw change logic and add more security checks * feat: update error messages * refactor: improve debug logs and clarify conditions * chore: update changelog * chore: empty line from changelog * refactor: remove duplicated check and bypass mapping for already mapped errcodes * chore: update changelog * feat: call isEmailChangeAllowed in pwless updateUser (#875) * feat: call isEmailChangeAllowed in pwless updateUser * test: add updateUser to test-server * chore: remove unnecessary item from changelog * chore: extend changelog to mention exact function names * test: add logging to default overrides in test-server (#876) * fix: circular dependency * fix: fix types in oauth2 index exposed functions * feat: add token building callbacks * test: move the session object and claims to the BE sdk server (#879) * fixes issue of refresh not clearing tokens * adding dev-v18.0.2 tag to this commit to ensure building --------- Co-authored-by: Ankit Tiwari Co-authored-by: rishabhpoddar --- .gitignore | 3 +- CHANGELOG.md | 18 +- docs/classes/framework.BaseRequest.html | 2 +- docs/classes/framework.BaseResponse.html | 2 +- .../framework_custom.CollectingResponse.html | 2 +- .../framework_custom.PreParsedRequest.html | 2 +- docs/classes/index.RecipeUserId.html | 2 +- docs/classes/index.User.html | 2 +- docs/classes/index.default.html | 2 +- .../ingredients_emaildelivery.default.html | 2 +- .../ingredients_smsdelivery.default.html | 2 +- .../recipe_accountlinking.default.html | 6 +- docs/classes/recipe_dashboard.default.html | 2 +- .../classes/recipe_emailpassword.default.html | 4 +- .../recipe_emailverification.default.html | 2 +- docs/classes/recipe_jwt.default.html | 2 +- .../recipe_multifactorauth.default.html | 2 +- docs/classes/recipe_multitenancy.default.html | 2 +- docs/classes/recipe_openid.default.html | 2 +- docs/classes/recipe_passwordless.default.html | 6 +- docs/classes/recipe_session.default.html | 4 +- docs/classes/recipe_thirdparty.default.html | 2 +- docs/classes/recipe_totp.default.html | 2 +- docs/classes/recipe_usermetadata.default.html | 2 +- docs/classes/recipe_userroles.default.html | 2 +- .../framework_awsLambda.SessionEvent.html | 2 +- .../framework_awsLambda.SessionEventV2.html | 2 +- .../framework_express.SessionRequest.html | 2 +- .../framework_hapi.SessionRequest.html | 2 +- .../framework_koa.SessionContext.html | 2 +- .../framework_loopback.SessionContext.html | 2 +- .../recipe_session.SessionContainer.html | 2 +- .../recipe_session.VerifySessionOptions.html | 2 +- docs/modules/framework.html | 2 +- docs/modules/framework_awsLambda.html | 2 +- docs/modules/framework_custom.html | 2 +- docs/modules/framework_express.html | 2 +- docs/modules/framework_fastify.html | 2 +- docs/modules/framework_hapi.html | 2 +- docs/modules/framework_koa.html | 2 +- docs/modules/framework_loopback.html | 2 +- docs/modules/index.html | 2 +- docs/modules/recipe_accountlinking.html | 2 +- docs/modules/recipe_dashboard.html | 2 +- docs/modules/recipe_emailpassword.html | 4 +- docs/modules/recipe_emailverification.html | 2 +- docs/modules/recipe_jwt.html | 2 +- docs/modules/recipe_multifactorauth.html | 2 +- docs/modules/recipe_multitenancy.html | 2 +- docs/modules/recipe_openid.html | 2 +- docs/modules/recipe_passwordless.html | 2 +- docs/modules/recipe_session.html | 8 +- docs/modules/recipe_thirdparty.html | 2 +- docs/modules/recipe_totp.html | 2 +- docs/modules/recipe_usermetadata.html | 4 +- docs/modules/recipe_userroles.html | 2 +- lib/build/querier.d.ts | 10 +- lib/build/querier.js | 45 ++- lib/build/recipe/accountlinking/index.js | 3 +- lib/build/recipe/accountlinking/recipe.d.ts | 10 +- lib/build/recipe/accountlinking/recipe.js | 89 ++++- .../emailpassword/api/implementation.js | 74 ++-- .../emailpassword/recipeImplementation.js | 34 ++ lib/build/recipe/jwt/api/implementation.js | 9 + lib/build/recipe/jwt/recipeImplementation.js | 1 + .../multitenancy/recipeImplementation.js | 2 + lib/build/recipe/oauth2/api/auth.d.ts | 8 + lib/build/recipe/oauth2/api/auth.js | 70 ++++ lib/build/recipe/oauth2/api/consent.d.ts | 8 + lib/build/recipe/oauth2/api/consent.js | 63 ++++ lib/build/recipe/oauth2/api/implementation.js | 177 ++++++++- lib/build/recipe/oauth2/api/login.d.ts | 8 + lib/build/recipe/oauth2/api/login.js | 87 +++++ lib/build/recipe/oauth2/api/loginInfo.d.ts | 8 + lib/build/recipe/oauth2/api/loginInfo.js | 38 ++ lib/build/recipe/oauth2/api/logout.d.ts | 8 + lib/build/recipe/oauth2/api/logout.js | 57 +++ lib/build/recipe/oauth2/api/token.d.ts | 8 + lib/build/recipe/oauth2/api/token.js | 30 ++ lib/build/recipe/oauth2/constants.d.ts | 6 + lib/build/recipe/oauth2/constants.js | 8 +- lib/build/recipe/oauth2/index.d.ts | 9 +- lib/build/recipe/oauth2/index.js | 9 +- lib/build/recipe/oauth2/recipe.d.ts | 14 +- lib/build/recipe/oauth2/recipe.js | 131 ++++++- .../recipe/oauth2/recipeImplementation.d.ts | 5 +- .../recipe/oauth2/recipeImplementation.js | 288 ++++++++++++++- lib/build/recipe/oauth2/types.d.ts | 266 +++++++++++++- .../api/getOpenIdDiscoveryConfiguration.js | 5 + lib/build/recipe/openid/index.d.ts | 5 + lib/build/recipe/openid/recipe.js | 2 +- .../recipe/openid/recipeImplementation.d.ts | 4 +- .../recipe/openid/recipeImplementation.js | 9 +- lib/build/recipe/openid/types.d.ts | 10 + .../passwordless/recipeImplementation.js | 34 ++ lib/build/recipe/session/index.d.ts | 5 + lib/build/recipe/session/sessionFunctions.js | 2 + .../recipe/session/sessionRequestFunctions.js | 4 +- .../recipe/thirdparty/api/implementation.js | 82 +---- .../recipe/thirdparty/recipeImplementation.js | 30 +- lib/build/recipe/totp/recipeImplementation.js | 1 + .../usermetadata/recipeImplementation.js | 1 + .../recipe/userroles/recipeImplementation.js | 2 + lib/build/supertokens.js | 1 + lib/build/utils.js | 2 + lib/ts/querier.ts | 42 ++- lib/ts/recipe/accountlinking/index.ts | 3 +- lib/ts/recipe/accountlinking/recipe.ts | 92 +++-- .../emailpassword/api/implementation.ts | 80 ++-- .../emailpassword/recipeImplementation.ts | 37 ++ lib/ts/recipe/jwt/api/implementation.ts | 10 + lib/ts/recipe/jwt/recipeImplementation.ts | 1 + .../multitenancy/recipeImplementation.ts | 2 + lib/ts/recipe/oauth2/api/auth.ts | 71 ++++ lib/ts/recipe/oauth2/api/consent.ts | 66 ++++ lib/ts/recipe/oauth2/api/implementation.ts | 174 ++++++++- lib/ts/recipe/oauth2/api/login.ts | 76 ++++ lib/ts/recipe/oauth2/api/loginInfo.ts | 44 +++ lib/ts/recipe/oauth2/api/logout.ts | 63 ++++ lib/ts/recipe/oauth2/api/token.ts | 37 ++ lib/ts/recipe/oauth2/constants.ts | 7 + lib/ts/recipe/oauth2/index.ts | 30 +- lib/ts/recipe/oauth2/recipe.ts | 149 +++++++- lib/ts/recipe/oauth2/recipeImplementation.ts | 308 +++++++++++++++- lib/ts/recipe/oauth2/types.ts | 343 ++++++++++++++++- .../api/getOpenIdDiscoveryConfiguration.ts | 5 + lib/ts/recipe/openid/api/implementation.ts | 11 +- lib/ts/recipe/openid/recipe.ts | 4 +- lib/ts/recipe/openid/recipeImplementation.ts | 19 +- lib/ts/recipe/openid/types.ts | 10 + .../passwordless/recipeImplementation.ts | 37 ++ lib/ts/recipe/session/sessionFunctions.ts | 2 + .../recipe/session/sessionRequestFunctions.ts | 4 +- .../recipe/thirdparty/api/implementation.ts | 82 +---- .../recipe/thirdparty/recipeImplementation.ts | 33 +- lib/ts/recipe/totp/recipeImplementation.ts | 1 + .../usermetadata/recipeImplementation.ts | 1 + .../recipe/userroles/recipeImplementation.ts | 8 +- lib/ts/supertokens.ts | 1 + lib/ts/utils.ts | 2 + package-lock.json | 26 +- package.json | 2 + test/auth-modes.test.js | 37 +- test/emailpassword/updateEmailPass.test.js | 4 +- test/querier.test.js | 2 +- test/test-server/src/index.ts | 347 ++++++++++++------ test/test-server/src/oauth2.ts | 46 +++ test/test-server/src/overrideLogging.ts | 68 ++++ test/test-server/src/passwordless.ts | 19 + test/test-server/src/session.ts | 330 ++++++++++++++++- test/test-server/src/utils.ts | 149 +++++++- test/with-typescript/index.ts | 5 + 152 files changed, 4206 insertions(+), 617 deletions(-) create mode 100644 lib/build/recipe/oauth2/api/auth.d.ts create mode 100644 lib/build/recipe/oauth2/api/auth.js create mode 100644 lib/build/recipe/oauth2/api/consent.d.ts create mode 100644 lib/build/recipe/oauth2/api/consent.js create mode 100644 lib/build/recipe/oauth2/api/login.d.ts create mode 100644 lib/build/recipe/oauth2/api/login.js create mode 100644 lib/build/recipe/oauth2/api/loginInfo.d.ts create mode 100644 lib/build/recipe/oauth2/api/loginInfo.js create mode 100644 lib/build/recipe/oauth2/api/logout.d.ts create mode 100644 lib/build/recipe/oauth2/api/logout.js create mode 100644 lib/build/recipe/oauth2/api/token.d.ts create mode 100644 lib/build/recipe/oauth2/api/token.js create mode 100644 lib/ts/recipe/oauth2/api/auth.ts create mode 100644 lib/ts/recipe/oauth2/api/consent.ts create mode 100644 lib/ts/recipe/oauth2/api/login.ts create mode 100644 lib/ts/recipe/oauth2/api/loginInfo.ts create mode 100644 lib/ts/recipe/oauth2/api/logout.ts create mode 100644 lib/ts/recipe/oauth2/api/token.ts create mode 100644 test/test-server/src/oauth2.ts create mode 100644 test/test-server/src/overrideLogging.ts diff --git a/.gitignore b/.gitignore index a5e771d2c..b8649d610 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /node_modules +test/test-server/node_modules /examples/**/node_modules .DS_Store /.history @@ -12,4 +13,4 @@ releasePassword /test_report /temp_test_exports /temp_* -/.nyc_output \ No newline at end of file +/.nyc_output diff --git a/CHANGELOG.md b/CHANGELOG.md index c7b27634b..0fa6d6469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking changes - Defined the entry points of the library using the "exports" field in package.json to make ESM imports more comfortable. This can cause some issues for applications using directory imports from the `lib/build` directory. In those cases we recommend adding `index.js` to the import path. - +- `isEmailChangeAllowed` now returns false for unverified addresses if input user is a primary user and there exists another user with the same email address and linking requires verification +- Generating a password reset token is now denied if all of the following is true: + - a linked email password user exists + - the email address is not verified + - the user has another email address or phone number associated with it +- Account linking based on emails now require the email to be verified in both users if `shouldRequireVerification` is set to `true` instead of only requiring it for the recipe user. - The access token cookie expiry has been changed from 100 years to 1 year due to some browsers capping the maximum expiry at 400 days. No action is needed on your part. +- Recipe functions that update the email address of users now call `isEmailChangeAllowed` to check if the email update should be allowed or not. + - This only has an effect if account linking is turned on. + - This is aimed to help you avoid security issues. + - `isEmailChangeAllowed` is now called in functions: + - `updateUser` (Passwordless recipe) + - `updateEmailOrPassword` (EmailPassword recipe) + - `manuallyCreateOrUpdateUser` (ThirdParty recipe) ### Changes @@ -26,6 +38,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - now tries to link accounts based on account info if AccountLinking is enabled - Extracted some tests into a separate [backend-sdk-testing](https://github.com/supertokens/backend-sdk-testing/) repo, to reuse tests between our backend SDKs +## [18.0.2] - 2024-07-09 + +- `refreshPOST` and `refreshSession` now clears all user tokens upon CSRF failures and if no tokens are found. See the latest comment on https://github.com/supertokens/supertokens-node/issues/141 for more details. + ## [18.0.1] - 2024-06-19 ### Fixes diff --git a/docs/classes/framework.BaseRequest.html b/docs/classes/framework.BaseRequest.html index 45471aed9..0909c657f 100644 --- a/docs/classes/framework.BaseRequest.html +++ b/docs/classes/framework.BaseRequest.html @@ -1 +1 @@ -BaseRequest | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class BaseRequest Abstract

Hierarchy

Index

Constructors

Properties

getCookieValue: ((key_: string) => undefined | string)

Type declaration

    • (key_: string): undefined | string
    • Parameters

      • key_: string

      Returns undefined | string

getHeaderValue: ((key: string) => undefined | string)

Type declaration

    • (key: string): undefined | string
    • Parameters

      • key: string

      Returns undefined | string

getKeyValueFromQuery: ((key: string) => undefined | string)

Type declaration

    • (key: string): undefined | string
    • Parameters

      • key: string

      Returns undefined | string

getMethod: (() => HTTPMethod)

Type declaration

    • (): HTTPMethod
    • Returns HTTPMethod

getOriginalURL: (() => string)

Type declaration

    • (): string
    • Returns string

original: any
parsedJSONBody: any
parsedUrlEncodedFormData: any
wrapperUsed: boolean

Methods

  • getFormData(): Promise<any>
  • getFormDataFromRequestBody(): Promise<any>
  • getJSONBody(): Promise<any>
  • getJSONFromRequestBody(): Promise<any>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Property
  • Method
  • Interface
  • Protected method
  • Private property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +BaseRequest | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class BaseRequest Abstract

Hierarchy

Index

Constructors

Properties

getCookieValue: ((key_: string) => undefined | string)

Type declaration

    • (key_: string): undefined | string
    • Parameters

      • key_: string

      Returns undefined | string

getHeaderValue: ((key: string) => undefined | string)

Type declaration

    • (key: string): undefined | string
    • Parameters

      • key: string

      Returns undefined | string

getKeyValueFromQuery: ((key: string) => undefined | string)

Type declaration

    • (key: string): undefined | string
    • Parameters

      • key: string

      Returns undefined | string

getMethod: (() => HTTPMethod)

Type declaration

    • (): HTTPMethod
    • Returns HTTPMethod

getOriginalURL: (() => string)

Type declaration

    • (): string
    • Returns string

original: any
parsedJSONBody: any
parsedUrlEncodedFormData: any
wrapperUsed: boolean

Methods

  • getFormData(): Promise<any>
  • getFormDataFromRequestBody(): Promise<any>
  • getJSONBody(): Promise<any>
  • getJSONFromRequestBody(): Promise<any>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Property
  • Method
  • Interface
  • Protected method
  • Private property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/framework.BaseResponse.html b/docs/classes/framework.BaseResponse.html index 63d3a613b..c493c2617 100644 --- a/docs/classes/framework.BaseResponse.html +++ b/docs/classes/framework.BaseResponse.html @@ -1 +1 @@ -BaseResponse | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class BaseResponse Abstract

Hierarchy

Index

Constructors

Properties

original: any
removeHeader: ((key: string) => void)

Type declaration

    • (key: string): void
    • Parameters

      • key: string

      Returns void

sendHTMLResponse: ((html: string) => void)

Type declaration

    • (html: string): void
    • Parameters

      • html: string

      Returns void

sendJSONResponse: ((content: any) => void)

Type declaration

    • (content: any): void
    • Parameters

      • content: any

      Returns void

setCookie: ((key: string, value: string, domain: undefined | string, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void)

Type declaration

    • (key: string, value: string, domain: undefined | string, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none"): void
    • Parameters

      • key: string
      • value: string
      • domain: undefined | string
      • secure: boolean
      • httpOnly: boolean
      • expires: number
      • path: string
      • sameSite: "strict" | "lax" | "none"

      Returns void

setHeader: ((key: string, value: string, allowDuplicateKey: boolean) => void)

Type declaration

    • (key: string, value: string, allowDuplicateKey: boolean): void
    • Parameters

      • key: string
      • value: string
      • allowDuplicateKey: boolean

      Returns void

setStatusCode: ((statusCode: number) => void)

Type declaration

    • (statusCode: number): void
    • Parameters

      • statusCode: number

      Returns void

wrapperUsed: boolean

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Property
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +BaseResponse | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class BaseResponse Abstract

Hierarchy

Index

Constructors

Properties

original: any
removeHeader: ((key: string) => void)

Type declaration

    • (key: string): void
    • Parameters

      • key: string

      Returns void

sendHTMLResponse: ((html: string) => void)

Type declaration

    • (html: string): void
    • Parameters

      • html: string

      Returns void

sendJSONResponse: ((content: any) => void)

Type declaration

    • (content: any): void
    • Parameters

      • content: any

      Returns void

setCookie: ((key: string, value: string, domain: undefined | string, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void)

Type declaration

    • (key: string, value: string, domain: undefined | string, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none"): void
    • Parameters

      • key: string
      • value: string
      • domain: undefined | string
      • secure: boolean
      • httpOnly: boolean
      • expires: number
      • path: string
      • sameSite: "strict" | "lax" | "none"

      Returns void

setHeader: ((key: string, value: string, allowDuplicateKey: boolean) => void)

Type declaration

    • (key: string, value: string, allowDuplicateKey: boolean): void
    • Parameters

      • key: string
      • value: string
      • allowDuplicateKey: boolean

      Returns void

setStatusCode: ((statusCode: number) => void)

Type declaration

    • (statusCode: number): void
    • Parameters

      • statusCode: number

      Returns void

wrapperUsed: boolean

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Property
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/framework_custom.CollectingResponse.html b/docs/classes/framework_custom.CollectingResponse.html index 4e0698cea..81010d1d6 100644 --- a/docs/classes/framework_custom.CollectingResponse.html +++ b/docs/classes/framework_custom.CollectingResponse.html @@ -1,2 +1,2 @@ -CollectingResponse | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

Index

Constructors

Properties

body?: string
cookies: CookieInfo[]
headers: Headers
original: any
statusCode: number
wrapperUsed: boolean

Methods

  • removeHeader(key: string): void
  • sendHTMLResponse(html: string): void
  • sendJSONResponse(content: any): void
  • setCookie(key: string, value: string, domain: undefined | string, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none"): void
  • Parameters

    • key: string
    • value: string
    • domain: undefined | string
    • secure: boolean
    • httpOnly: boolean
    • expires: number
    • path: string
    • sameSite: "strict" | "lax" | "none"

    Returns void

  • setHeader(key: string, value: string, allowDuplicateKey: boolean): void
  • setStatusCode(statusCode: number): void
  • resetPasswordUsingToken(tenantId: string, token: string, newPassword: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
  • Parameters

    • tenantId: string
    • token: string
    • newPassword: string
    • Optional userContext: Record<string, any>

    Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

  • sendEmail(input: TypeEmailPasswordPasswordResetEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
  • sendResetPasswordEmail(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>
  • Parameters

    • tenantId: string
    • userId: string
    • email: string
    • Optional userContext: Record<string, any>

    Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>

  • signIn(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>
  • signIn(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
  • Parameters

    • tenantId: string
    • email: string
    • password: string
    • Optional session: undefined
    • Optional userContext: Record<string, any>

    Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>

  • Parameters

    • tenantId: string
    • email: string
    • password: string
    • session: SessionContainer
    • Optional userContext: Record<string, any>

    Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

  • signUp(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>
  • signUp(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
  • Parameters

    • tenantId: string
    • email: string
    • password: string
    • Optional session: undefined
    • Optional userContext: Record<string, any>

    Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>

  • Parameters

    • tenantId: string
    • email: string
    • password: string
    • session: SessionContainer
    • Optional userContext: Record<string, any>

    Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

  • updateEmailOrPassword(input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
  • Parameters

    • input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }
      • Optional applyPasswordPolicy?: boolean
      • Optional email?: string
      • Optional password?: string
      • recipeUserId: RecipeUserId
      • Optional tenantIdForPasswordPolicy?: string
      • Optional userContext?: Record<string, any>

    Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

  • verifyCredentials(tenantId: string, email: string, password: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>
  • Parameters

    • tenantId: string
    • email: string
    • password: string
    • Optional userContext: Record<string, any>

    Returns Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/recipe_emailverification.default.html b/docs/classes/recipe_emailverification.default.html index 948dcd52f..c03361803 100644 --- a/docs/classes/recipe_emailverification.default.html +++ b/docs/classes/recipe_emailverification.default.html @@ -1 +1 @@ -default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

EmailVerificationClaim: EmailVerificationClaimClass = EmailVerificationClaim
Error: typeof default = SuperTokensError
init: ((config: TypeInput) => RecipeListFunction) = Recipe.init

Type declaration

    • (config: TypeInput): RecipeListFunction
    • Parameters

      • config: TypeInput

      Returns RecipeListFunction

Methods

  • createEmailVerificationLink(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
  • createEmailVerificationToken(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
  • isEmailVerified(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<boolean>
  • revokeEmailVerificationTokens(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
  • sendEmail(input: TypeEmailVerificationEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
  • sendEmailVerificationEmail(tenantId: string, userId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
  • unverifyEmail(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
  • verifyEmailUsingToken(tenantId: string, token: string, attemptAccountLinking?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

EmailVerificationClaim: EmailVerificationClaimClass = EmailVerificationClaim
Error: typeof default = SuperTokensError
init: ((config: TypeInput) => RecipeListFunction) = Recipe.init

Type declaration

    • (config: TypeInput): RecipeListFunction
    • Parameters

      • config: TypeInput

      Returns RecipeListFunction

Methods

  • createEmailVerificationLink(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
  • createEmailVerificationToken(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
  • isEmailVerified(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<boolean>
  • revokeEmailVerificationTokens(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
  • sendEmail(input: TypeEmailVerificationEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
  • sendEmailVerificationEmail(tenantId: string, userId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
  • unverifyEmail(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
  • verifyEmailUsingToken(tenantId: string, token: string, attemptAccountLinking?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/recipe_jwt.default.html b/docs/classes/recipe_jwt.default.html index ff9a0ce45..d1b7e548f 100644 --- a/docs/classes/recipe_jwt.default.html +++ b/docs/classes/recipe_jwt.default.html @@ -1 +1 @@ -default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

Methods

Constructors

Properties

init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

Type declaration

    • (config?: TypeInput): RecipeListFunction
    • Parameters

      • Optional config: TypeInput

      Returns RecipeListFunction

Methods

  • createJWT(payload: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
  • Parameters

    • payload: any
    • Optional validitySeconds: number
    • Optional useStaticSigningKey: boolean
    • Optional userContext: Record<string, any>

    Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

  • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

Methods

Constructors

Properties

init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

Type declaration

    • (config?: TypeInput): RecipeListFunction
    • Parameters

      • Optional config: TypeInput

      Returns RecipeListFunction

Methods

  • createJWT(payload: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
  • Parameters

    • payload: any
    • Optional validitySeconds: number
    • Optional useStaticSigningKey: boolean
    • Optional userContext: Record<string, any>

    Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

  • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/recipe_multifactorauth.default.html b/docs/classes/recipe_multifactorauth.default.html index d02257e2a..29a9adcd0 100644 --- a/docs/classes/recipe_multifactorauth.default.html +++ b/docs/classes/recipe_multifactorauth.default.html @@ -1 +1 @@ -default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

FactorIds: { EMAILPASSWORD: string; LINK_EMAIL: string; LINK_PHONE: string; OTP_EMAIL: string; OTP_PHONE: string; THIRDPARTY: string; TOTP: string } = FactorIds

Type declaration

  • EMAILPASSWORD: string
  • LINK_EMAIL: string
  • LINK_PHONE: string
  • OTP_EMAIL: string
  • OTP_PHONE: string
  • THIRDPARTY: string
  • TOTP: string
MultiFactorAuthClaim: MultiFactorAuthClaimClass = MultiFactorAuthClaim
init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

Type declaration

    • (config?: TypeInput): RecipeListFunction
    • Parameters

      • Optional config: TypeInput

      Returns RecipeListFunction

Methods

  • addToRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>
  • assertAllowedToSetupFactorElseThrowInvalidClaimError(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
  • getFactorsSetupForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
  • getMFARequirementsForAuth(session: SessionContainer, userContext?: Record<string, any>): Promise<MFARequirementList>
  • getRequiredSecondaryFactorsForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
  • markFactorAsCompleteInSession(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
  • removeFromRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

FactorIds: { EMAILPASSWORD: string; LINK_EMAIL: string; LINK_PHONE: string; OTP_EMAIL: string; OTP_PHONE: string; THIRDPARTY: string; TOTP: string } = FactorIds

Type declaration

  • EMAILPASSWORD: string
  • LINK_EMAIL: string
  • LINK_PHONE: string
  • OTP_EMAIL: string
  • OTP_PHONE: string
  • THIRDPARTY: string
  • TOTP: string
MultiFactorAuthClaim: MultiFactorAuthClaimClass = MultiFactorAuthClaim
init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

Type declaration

    • (config?: TypeInput): RecipeListFunction
    • Parameters

      • Optional config: TypeInput

      Returns RecipeListFunction

Methods

  • addToRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>
  • assertAllowedToSetupFactorElseThrowInvalidClaimError(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
  • getFactorsSetupForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
  • getMFARequirementsForAuth(session: SessionContainer, userContext?: Record<string, any>): Promise<MFARequirementList>
  • getRequiredSecondaryFactorsForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
  • markFactorAsCompleteInSession(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
  • removeFromRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/recipe_multitenancy.default.html b/docs/classes/recipe_multitenancy.default.html index e15676cd8..bd863959a 100644 --- a/docs/classes/recipe_multitenancy.default.html +++ b/docs/classes/recipe_multitenancy.default.html @@ -1 +1 @@ -default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

Type declaration

    • (config?: TypeInput): RecipeListFunction
    • Parameters

      • Optional config: TypeInput

      Returns RecipeListFunction

Methods

  • associateUserToTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
  • Parameters

    • tenantId: string
    • recipeUserId: RecipeUserId
    • Optional userContext: Record<string, any>

    Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

  • createOrUpdateTenant(tenantId: string, config?: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
  • Parameters

    • tenantId: string
    • Optional config: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }
      • Optional coreConfig?: {}
        • [key: string]: any
      • Optional emailPasswordEnabled?: boolean
      • Optional firstFactors?: string[]
      • Optional passwordlessEnabled?: boolean
      • Optional requiredSecondaryFactors?: string[]
      • Optional thirdPartyEnabled?: boolean
    • Optional userContext: Record<string, any>

    Returns Promise<{ createdNew: boolean; status: "OK" }>

  • createOrUpdateThirdPartyConfig(tenantId: string, config: ProviderConfig, skipValidation?: boolean, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
  • Parameters

    • tenantId: string
    • config: ProviderConfig
    • Optional skipValidation: boolean
    • Optional userContext: Record<string, any>

    Returns Promise<{ createdNew: boolean; status: "OK" }>

  • deleteTenant(tenantId: string, userContext?: Record<string, any>): Promise<{ didExist: boolean; status: "OK" }>
  • deleteThirdPartyConfig(tenantId: string, thirdPartyId: string, userContext?: Record<string, any>): Promise<{ didConfigExist: boolean; status: "OK" }>
  • Parameters

    • tenantId: string
    • thirdPartyId: string
    • Optional userContext: Record<string, any>

    Returns Promise<{ didConfigExist: boolean; status: "OK" }>

  • disassociateUserFromTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAssociated: boolean }>
  • getTenant(tenantId: string, userContext?: Record<string, any>): Promise<undefined | { status: "OK" } & TenantConfig>
  • listAllTenants(userContext?: Record<string, any>): Promise<{ status: "OK"; tenants: ({ tenantId: string } & TenantConfig)[] }>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

Type declaration

    • (config?: TypeInput): RecipeListFunction
    • Parameters

      • Optional config: TypeInput

      Returns RecipeListFunction

Methods

  • associateUserToTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
  • Parameters

    • tenantId: string
    • recipeUserId: RecipeUserId
    • Optional userContext: Record<string, any>

    Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

  • createOrUpdateTenant(tenantId: string, config?: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
  • Parameters

    • tenantId: string
    • Optional config: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }
      • Optional coreConfig?: {}
        • [key: string]: any
      • Optional emailPasswordEnabled?: boolean
      • Optional firstFactors?: string[]
      • Optional passwordlessEnabled?: boolean
      • Optional requiredSecondaryFactors?: string[]
      • Optional thirdPartyEnabled?: boolean
    • Optional userContext: Record<string, any>

    Returns Promise<{ createdNew: boolean; status: "OK" }>

  • createOrUpdateThirdPartyConfig(tenantId: string, config: ProviderConfig, skipValidation?: boolean, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
  • Parameters

    • tenantId: string
    • config: ProviderConfig
    • Optional skipValidation: boolean
    • Optional userContext: Record<string, any>

    Returns Promise<{ createdNew: boolean; status: "OK" }>

  • deleteTenant(tenantId: string, userContext?: Record<string, any>): Promise<{ didExist: boolean; status: "OK" }>
  • deleteThirdPartyConfig(tenantId: string, thirdPartyId: string, userContext?: Record<string, any>): Promise<{ didConfigExist: boolean; status: "OK" }>
  • Parameters

    • tenantId: string
    • thirdPartyId: string
    • Optional userContext: Record<string, any>

    Returns Promise<{ didConfigExist: boolean; status: "OK" }>

  • disassociateUserFromTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAssociated: boolean }>
  • getTenant(tenantId: string, userContext?: Record<string, any>): Promise<undefined | { status: "OK" } & TenantConfig>
  • listAllTenants(userContext?: Record<string, any>): Promise<{ status: "OK"; tenants: ({ tenantId: string } & TenantConfig)[] }>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/recipe_openid.default.html b/docs/classes/recipe_openid.default.html index 52eba9a25..43eba9587 100644 --- a/docs/classes/recipe_openid.default.html +++ b/docs/classes/recipe_openid.default.html @@ -1 +1 @@ -default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

init: ((config?: TypeInput) => RecipeListFunction) = OpenIdRecipe.init

Type declaration

    • (config?: TypeInput): RecipeListFunction
    • Parameters

      • Optional config: TypeInput

      Returns RecipeListFunction

Methods

  • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
  • Parameters

    • Optional payload: any
    • Optional validitySeconds: number
    • Optional useStaticSigningKey: boolean
    • Optional userContext: Record<string, any>

    Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

  • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
  • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
  • Parameters

    • Optional userContext: Record<string, any>

    Returns Promise<{ issuer: string; jwks_uri: string; status: "OK" }>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

init: ((config?: TypeInput) => RecipeListFunction) = OpenIdRecipe.init

Type declaration

    • (config?: TypeInput): RecipeListFunction
    • Parameters

      • Optional config: TypeInput

      Returns RecipeListFunction

Methods

  • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
  • Parameters

    • Optional payload: any
    • Optional validitySeconds: number
    • Optional useStaticSigningKey: boolean
    • Optional userContext: Record<string, any>

    Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

  • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
  • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
  • Parameters

    • Optional userContext: Record<string, any>

    Returns Promise<{ issuer: string; jwks_uri: string; status: "OK" }>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/recipe_passwordless.default.html b/docs/classes/recipe_passwordless.default.html index c7f4dcbdf..9fa20c95f 100644 --- a/docs/classes/recipe_passwordless.default.html +++ b/docs/classes/recipe_passwordless.default.html @@ -1,14 +1,14 @@ -default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

Error: typeof default = SuperTokensError
init: ((config: TypeInput) => RecipeListFunction) = Recipe.init

Type declaration

    • (config: TypeInput): RecipeListFunction
    • Parameters

      • config: TypeInput

      Returns RecipeListFunction

Methods

  • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
  • +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    Error: typeof default = SuperTokensError
    init: ((config: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config: TypeInput): RecipeListFunction
      • Parameters

        • config: TypeInput

        Returns RecipeListFunction

    Methods

    • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • This function will only verify the code (not consume it), and: NOT create a new user if it doesn't exist NOT verify the user email if it exists NOT do any linking NOT delete the code unless it returned RESTART_FLOW_ERROR

      -

      Parameters

      • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • +

      Parameters

      • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      1. verifies the code
      2. creates the user if it doesn't exist
      3. tries to link it
      4. marks the email as verified
      -

      Parameters

      • input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • createCode(input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
    • Parameters

      • input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

    • createMagicLink(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<string>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<string>

    • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>
        • Optional userInputCode?: string

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

    • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • listCodesByEmail(input: { email: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> }
        • email: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }
        • phoneNumber: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }
        • preAuthSessionId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • revokeAllCodes(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • revokeCode(input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • sendEmail(input: TypePasswordlessEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendSms(input: TypePasswordlessSmsDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • signInUp(input: { email: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: string; user: User }>
    • updateUser(input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
    • Parameters

      • input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }
        • Optional email?: null | string
        • Optional phoneNumber?: null | string
        • recipeUserId: RecipeUserId
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +

    Parameters

    • input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }

    Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

  • Parameters

    • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }

    Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

  • createCode(input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
  • Parameters

    • input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }

    Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

  • createMagicLink(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<string>
  • Parameters

    • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

    Returns Promise<string>

  • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
  • Parameters

    • input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }
      • deviceId: string
      • tenantId: string
      • Optional userContext?: Record<string, any>
      • Optional userInputCode?: string

    Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

  • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
  • Parameters

    • input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }
      • deviceId: string
      • tenantId: string
      • Optional userContext?: Record<string, any>

    Returns Promise<undefined | DeviceType>

  • listCodesByEmail(input: { email: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
  • Parameters

    • input: { email: string; tenantId: string; userContext?: Record<string, any> }
      • email: string
      • tenantId: string
      • Optional userContext?: Record<string, any>

    Returns Promise<DeviceType[]>

  • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
  • Parameters

    • input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }
      • phoneNumber: string
      • tenantId: string
      • Optional userContext?: Record<string, any>

    Returns Promise<DeviceType[]>

  • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
  • Parameters

    • input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }
      • preAuthSessionId: string
      • tenantId: string
      • Optional userContext?: Record<string, any>

    Returns Promise<undefined | DeviceType>

  • revokeAllCodes(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
  • Parameters

    • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

    Returns Promise<{ status: "OK" }>

  • revokeCode(input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
  • Parameters

    • input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

    Returns Promise<{ status: "OK" }>

  • sendEmail(input: TypePasswordlessEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
  • sendSms(input: TypePasswordlessSmsDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
  • signInUp(input: { email: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: string; user: User }>
  • updateUser(input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
  • Parameters

    • input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }
      • Optional email?: null | string
      • Optional phoneNumber?: null | string
      • recipeUserId: RecipeUserId
      • Optional userContext?: Record<string, any>

    Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/recipe_session.default.html b/docs/classes/recipe_session.default.html index af38bef04..1f84390f3 100644 --- a/docs/classes/recipe_session.default.html +++ b/docs/classes/recipe_session.default.html @@ -1,4 +1,4 @@ -default | supertokens-node
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • default

Index

Constructors

Properties

Error: typeof default = SuperTokensError
init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

Type declaration

    • (config?: TypeInput): RecipeListFunction
    • Parameters

      • Optional config: TypeInput

      Returns RecipeListFunction

Methods

  • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
  • Parameters

    • Optional payload: any
    • Optional validitySeconds: number
    • Optional useStaticSigningKey: boolean
    • Optional userContext: Record<string, any>

    Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

  • createNewSession(req: any, res: any, tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, userContext?: Record<string, any>): Promise<SessionContainer>
  • createNewSessionWithoutRequestResponse(tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, disableAntiCsrf?: boolean, userContext?: Record<string, any>): Promise<SessionContainer>
  • fetchAndSetClaim(sessionHandle: string, claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<boolean>
  • getAllSessionHandlesForUser(userId: string, fetchSessionsForAllLinkedAccounts?: boolean, tenantId?: string, userContext?: Record<string, any>): Promise<string[]>
  • Parameters

    • userId: string
    • fetchSessionsForAllLinkedAccounts: boolean = true
    • Optional tenantId: string
    • Optional userContext: Record<string, any>

    Returns Promise<string[]>

  • getClaimValue<T>(sessionHandle: string, claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<{ status: "SESSION_DOES_NOT_EXIST_ERROR" } | { status: "OK"; value: undefined | T }>
  • Type Parameters

    • T

    Parameters

    • sessionHandle: string
    • claim: SessionClaim<T>
    • Optional userContext: Record<string, any>

    Returns Promise<{ status: "SESSION_DOES_NOT_EXIST_ERROR" } | { status: "OK"; value: undefined | T }>

  • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[] }>
  • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
  • getSessionInformation(sessionHandle: string, userContext?: Record<string, any>): Promise<undefined | SessionInformation>
  • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string): Promise<SessionContainer>
  • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { sessionRequired?: true }, userContext?: Record<string, any>): Promise<SessionContainer>
  • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { sessionRequired: false }, userContext?: Record<string, any>): Promise<undefined | SessionContainer>
  • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions, userContext?: Record<string, any>): Promise<undefined | SessionContainer>
  • mergeIntoAccessTokenPayload(sessionHandle: string, accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<boolean>
  • refreshSession(req: any, res: any, userContext?: Record<string, any>): Promise<SessionContainer>
  • refreshSessionWithoutRequestResponse(refreshToken: string, disableAntiCsrf?: boolean, antiCsrfToken?: string, userContext?: Record<string, any>): Promise<SessionContainer>
  • removeClaim(sessionHandle: string, claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<boolean>
  • revokeAllSessionsForUser(userId: string, revokeSessionsForLinkedAccounts?: boolean, tenantId?: string, userContext?: Record<string, any>): Promise<string[]>
  • Parameters

    • userId: string
    • revokeSessionsForLinkedAccounts: boolean = true
    • Optional tenantId: string
    • Optional userContext: Record<string, any>

    Returns Promise<string[]>

  • revokeMultipleSessions(sessionHandles: string[], userContext?: Record<string, any>): Promise<string[]>
  • revokeSession(sessionHandle: string, userContext?: Record<string, any>): Promise<boolean>
  • setClaimValue<T>(sessionHandle: string, claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<boolean>
  • Type Parameters

    • T

    Parameters

    • sessionHandle: string
    • claim: SessionClaim<T>
    • value: T
    • Optional userContext: Record<string, any>

    Returns Promise<boolean>

  • updateSessionDataInDatabase(sessionHandle: string, newSessionData: any, userContext?: Record<string, any>): Promise<boolean>

Legend

  • Variable
  • Function
  • Function with type parameter
  • Type alias
  • Type alias with type parameter
  • Class
  • Class with type parameter
  • Constructor
  • Static property
  • Static method
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Returns Promise<SessionContainer>

  • Parameters

    • accessToken: string
    • Optional antiCsrfToken: string
    • Optional options: VerifySessionOptions & { sessionRequired?: true }
    • Optional userContext: Record<string, any>

    Returns Promise<SessionContainer>

  • Parameters

    • accessToken: string
    • Optional antiCsrfToken: string
    • Optional options: VerifySessionOptions & { sessionRequired: false }
    • Optional userContext: Record<string, any>

    Returns Promise<undefined | SessionContainer>

  • Parameters

    • accessToken: string
    • Optional antiCsrfToken: string
    • Optional options: VerifySessionOptions
    • Optional userContext: Record<string, any>

    Returns Promise<undefined | SessionContainer>

    • mergeIntoAccessTokenPayload(sessionHandle: string, accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<boolean>
    • refreshSession(req: any, res: any, userContext?: Record<string, any>): Promise<SessionContainer>
    • refreshSessionWithoutRequestResponse(refreshToken: string, disableAntiCsrf?: boolean, antiCsrfToken?: string, userContext?: Record<string, any>): Promise<SessionContainer>
    • removeClaim(sessionHandle: string, claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<boolean>
    • revokeAllSessionsForUser(userId: string, revokeSessionsForLinkedAccounts?: boolean, tenantId?: string, userContext?: Record<string, any>): Promise<string[]>
    • Parameters

      • userId: string
      • revokeSessionsForLinkedAccounts: boolean = true
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<string[]>

    • revokeMultipleSessions(sessionHandles: string[], userContext?: Record<string, any>): Promise<string[]>
    • revokeSession(sessionHandle: string, userContext?: Record<string, any>): Promise<boolean>
    • setClaimValue<T>(sessionHandle: string, claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<boolean>
    • Type Parameters

      • T

      Parameters

      • sessionHandle: string
      • claim: SessionClaim<T>
      • value: T
      • Optional userContext: Record<string, any>

      Returns Promise<boolean>

    • updateSessionDataInDatabase(sessionHandle: string, newSessionData: any, userContext?: Record<string, any>): Promise<boolean>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_thirdparty.default.html b/docs/classes/recipe_thirdparty.default.html index c8b489b0c..24cb78832 100644 --- a/docs/classes/recipe_thirdparty.default.html +++ b/docs/classes/recipe_thirdparty.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    Error: typeof default = SuperTokensError
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • getProvider(tenantId: string, thirdPartyId: string, clientType: undefined | string, userContext?: Record<string, any>): Promise<undefined | TypeProvider>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session?: undefined, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session: SessionContainer, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    Error: typeof default = SuperTokensError
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • getProvider(tenantId: string, thirdPartyId: string, clientType: undefined | string, userContext?: Record<string, any>): Promise<undefined | TypeProvider>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session?: undefined, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session: SessionContainer, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_totp.default.html b/docs/classes/recipe_totp.default.html index f3a25113f..e703f5a49 100644 --- a/docs/classes/recipe_totp.default.html +++ b/docs/classes/recipe_totp.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • createDevice(userId: string, userIdentifierInfo?: string, deviceName?: string, skew?: number, period?: number, userContext?: Record<string, any>): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • userId: string
      • Optional userIdentifierInfo: string
      • Optional deviceName: string
      • Optional skew: number
      • Optional period: number
      • Optional userContext: Record<string, any>

      Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • listDevices(userId: string, userContext?: Record<string, any>): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice(userId: string, deviceName: string, userContext?: Record<string, any>): Promise<{ didDeviceExist: boolean; status: "OK" }>
    • Parameters

      • userId: string
      • deviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice(userId: string, existingDeviceName: string, newDeviceName: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>
    • Parameters

      • userId: string
      • existingDeviceName: string
      • newDeviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>

    • verifyDevice(tenantId: string, userId: string, deviceName: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • deviceName: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP(tenantId: string, userId: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • createDevice(userId: string, userIdentifierInfo?: string, deviceName?: string, skew?: number, period?: number, userContext?: Record<string, any>): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • userId: string
      • Optional userIdentifierInfo: string
      • Optional deviceName: string
      • Optional skew: number
      • Optional period: number
      • Optional userContext: Record<string, any>

      Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • listDevices(userId: string, userContext?: Record<string, any>): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice(userId: string, deviceName: string, userContext?: Record<string, any>): Promise<{ didDeviceExist: boolean; status: "OK" }>
    • Parameters

      • userId: string
      • deviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice(userId: string, existingDeviceName: string, newDeviceName: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>
    • Parameters

      • userId: string
      • existingDeviceName: string
      • newDeviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>

    • verifyDevice(tenantId: string, userId: string, deviceName: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • deviceName: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP(tenantId: string, userId: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_usermetadata.default.html b/docs/classes/recipe_usermetadata.default.html index d0238478b..63b097c7f 100644 --- a/docs/classes/recipe_usermetadata.default.html +++ b/docs/classes/recipe_usermetadata.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • clearUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • getUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ metadata: any; status: "OK" }>
    • updateUserMetadata(userId: string, metadataUpdate: JSONObject, userContext?: Record<string, any>): Promise<{ metadata: JSONObject; status: "OK" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • clearUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • getUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ metadata: any; status: "OK" }>
    • updateUserMetadata(userId: string, metadataUpdate: JSONObject, userContext?: Record<string, any>): Promise<{ metadata: JSONObject; status: "OK" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_userroles.default.html b/docs/classes/recipe_userroles.default.html index 09ab46ff1..b77d05901 100644 --- a/docs/classes/recipe_userroles.default.html +++ b/docs/classes/recipe_userroles.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    PermissionClaim: PermissionClaimClass = PermissionClaim
    UserRoleClaim: UserRoleClaimClass = UserRoleClaim
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • addRoleToUser(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ createdNewRole: boolean; status: "OK" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole(role: string, userContext?: Record<string, any>): Promise<{ didRoleExist: boolean; status: "OK" }>
    • getAllRoles(userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole(role: string, userContext?: Record<string, any>): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser(tenantId: string, userId: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • Parameters

      • tenantId: string
      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ roles: string[]; status: "OK" }>

    • getRolesThatHavePermission(permission: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getUsersThatHaveRole(tenantId: string, role: string, userContext?: Record<string, any>): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • removePermissionsFromRole(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    PermissionClaim: PermissionClaimClass = PermissionClaim
    UserRoleClaim: UserRoleClaimClass = UserRoleClaim
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • addRoleToUser(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ createdNewRole: boolean; status: "OK" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole(role: string, userContext?: Record<string, any>): Promise<{ didRoleExist: boolean; status: "OK" }>
    • getAllRoles(userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole(role: string, userContext?: Record<string, any>): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser(tenantId: string, userId: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • Parameters

      • tenantId: string
      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ roles: string[]; status: "OK" }>

    • getRolesThatHavePermission(permission: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getUsersThatHaveRole(tenantId: string, role: string, userContext?: Record<string, any>): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • removePermissionsFromRole(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/interfaces/framework_awsLambda.SessionEvent.html b/docs/interfaces/framework_awsLambda.SessionEvent.html index 717f87471..d5723e13b 100644 --- a/docs/interfaces/framework_awsLambda.SessionEvent.html +++ b/docs/interfaces/framework_awsLambda.SessionEvent.html @@ -1 +1 @@ -SessionEvent | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SupertokensLambdaEvent
      • SessionEvent

    Index

    Properties

    body: null | string
    headers: APIGatewayProxyEventHeaders
    httpMethod: string
    isBase64Encoded: boolean
    multiValueHeaders: APIGatewayProxyEventMultiValueHeaders
    multiValueQueryStringParameters: null | APIGatewayProxyEventMultiValueQueryStringParameters
    path: string
    pathParameters: null | APIGatewayProxyEventPathParameters
    queryStringParameters: null | APIGatewayProxyEventQueryStringParameters
    requestContext: APIGatewayEventRequestContextWithAuthorizer<APIGatewayEventDefaultAuthorizerContext>
    resource: string
    stageVariables: null | APIGatewayProxyEventStageVariables
    supertokens: { response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] } }

    Type declaration

    • response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] }
      • cookies: string[]
      • headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[]

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +SessionEvent | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SupertokensLambdaEvent
      • SessionEvent

    Index

    Properties

    body: null | string
    headers: APIGatewayProxyEventHeaders
    httpMethod: string
    isBase64Encoded: boolean
    multiValueHeaders: APIGatewayProxyEventMultiValueHeaders
    multiValueQueryStringParameters: null | APIGatewayProxyEventMultiValueQueryStringParameters
    path: string
    pathParameters: null | APIGatewayProxyEventPathParameters
    queryStringParameters: null | APIGatewayProxyEventQueryStringParameters
    requestContext: APIGatewayEventRequestContextWithAuthorizer<APIGatewayEventDefaultAuthorizerContext>
    resource: string
    stageVariables: null | APIGatewayProxyEventStageVariables
    supertokens: { response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] } }

    Type declaration

    • response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] }
      • cookies: string[]
      • headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[]

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/interfaces/framework_awsLambda.SessionEventV2.html b/docs/interfaces/framework_awsLambda.SessionEventV2.html index fd65728df..60337b237 100644 --- a/docs/interfaces/framework_awsLambda.SessionEventV2.html +++ b/docs/interfaces/framework_awsLambda.SessionEventV2.html @@ -1 +1 @@ -SessionEventV2 | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SupertokensLambdaEventV2
      • SessionEventV2

    Index

    Properties

    body?: string
    cookies?: string[]
    headers: APIGatewayProxyEventHeaders
    isBase64Encoded: boolean
    pathParameters?: APIGatewayProxyEventPathParameters
    queryStringParameters?: APIGatewayProxyEventQueryStringParameters
    rawPath: string
    rawQueryString: string
    requestContext: { accountId: string; apiId: string; authorizer?: { jwt: { claims: {}; scopes: string[] } }; domainName: string; domainPrefix: string; http: { method: string; path: string; protocol: string; sourceIp: string; userAgent: string }; requestId: string; routeKey: string; stage: string; time: string; timeEpoch: number }

    Type declaration

    • accountId: string
    • apiId: string
    • Optional authorizer?: { jwt: { claims: {}; scopes: string[] } }
      • jwt: { claims: {}; scopes: string[] }
        • claims: {}
          • [name: string]: string | number | boolean | string[]
        • scopes: string[]
    • domainName: string
    • domainPrefix: string
    • http: { method: string; path: string; protocol: string; sourceIp: string; userAgent: string }
      • method: string
      • path: string
      • protocol: string
      • sourceIp: string
      • userAgent: string
    • requestId: string
    • routeKey: string
    • stage: string
    • time: string
    • timeEpoch: number
    routeKey: string
    stageVariables?: APIGatewayProxyEventStageVariables
    supertokens: { response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] } }

    Type declaration

    • response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] }
      • cookies: string[]
      • headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[]
    version: string

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +SessionEventV2 | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SupertokensLambdaEventV2
      • SessionEventV2

    Index

    Properties

    body?: string
    cookies?: string[]
    headers: APIGatewayProxyEventHeaders
    isBase64Encoded: boolean
    pathParameters?: APIGatewayProxyEventPathParameters
    queryStringParameters?: APIGatewayProxyEventQueryStringParameters
    rawPath: string
    rawQueryString: string
    requestContext: { accountId: string; apiId: string; authorizer?: { jwt: { claims: {}; scopes: string[] } }; domainName: string; domainPrefix: string; http: { method: string; path: string; protocol: string; sourceIp: string; userAgent: string }; requestId: string; routeKey: string; stage: string; time: string; timeEpoch: number }

    Type declaration

    • accountId: string
    • apiId: string
    • Optional authorizer?: { jwt: { claims: {}; scopes: string[] } }
      • jwt: { claims: {}; scopes: string[] }
        • claims: {}
          • [name: string]: string | number | boolean | string[]
        • scopes: string[]
    • domainName: string
    • domainPrefix: string
    • http: { method: string; path: string; protocol: string; sourceIp: string; userAgent: string }
      • method: string
      • path: string
      • protocol: string
      • sourceIp: string
      • userAgent: string
    • requestId: string
    • routeKey: string
    • stage: string
    • time: string
    • timeEpoch: number
    routeKey: string
    stageVariables?: APIGatewayProxyEventStageVariables
    supertokens: { response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] } }

    Type declaration

    • response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] }
      • cookies: string[]
      • headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[]
    version: string

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/interfaces/framework_express.SessionRequest.html b/docs/interfaces/framework_express.SessionRequest.html index d2749a739..a86db681c 100644 --- a/docs/interfaces/framework_express.SessionRequest.html +++ b/docs/interfaces/framework_express.SessionRequest.html @@ -135,7 +135,7 @@
    route: any
    secure: boolean

    Short-hand for:

    req.protocol == 'https'

    -
    signedCookies: any
    socket: Socket
    +
    signedCookies: any
    socket: Socket

    The net.Socket object associated with the connection.

    With HTTPS support, use request.socket.getPeerCertificate() to obtain the client's authentication details.

    diff --git a/docs/interfaces/framework_hapi.SessionRequest.html b/docs/interfaces/framework_hapi.SessionRequest.html index fcb315eb0..76ddb08f1 100644 --- a/docs/interfaces/framework_hapi.SessionRequest.html +++ b/docs/interfaces/framework_hapi.SessionRequest.html @@ -89,7 +89,7 @@
    server: Server

    Access: read only and the public server interface. The server object.

    -
    state: Dictionary<any>
    +
    state: Dictionary<any>

    An object containing parsed HTTP state information (cookies) where each key is the cookie name and value is the matching cookie content after processing using any registered cookie definition.

    url: URL

    The parsed request URI.

    diff --git a/docs/interfaces/framework_koa.SessionContext.html b/docs/interfaces/framework_koa.SessionContext.html index 5a7cf7ee3..fbda99c36 100644 --- a/docs/interfaces/framework_koa.SessionContext.html +++ b/docs/interfaces/framework_koa.SessionContext.html @@ -81,7 +81,7 @@
    secure: boolean

    Short-hand for:

    this.protocol == 'https'

    -
    socket: Socket
    +
    socket: Socket

    Return the request socket.

    stale: boolean

    Check if the request is stale, aka diff --git a/docs/interfaces/framework_loopback.SessionContext.html b/docs/interfaces/framework_loopback.SessionContext.html index 8238ed078..74e57d185 100644 --- a/docs/interfaces/framework_loopback.SessionContext.html +++ b/docs/interfaces/framework_loopback.SessionContext.html @@ -14,7 +14,7 @@

    A flag to tell if the response is finished.

    scope: BindingScope

    Scope for binding resolution

    -
    subscriptionManager: ContextSubscriptionManager
    +
    subscriptionManager: ContextSubscriptionManager

    Manager for observer subscriptions

    tagIndexer: ContextTagIndexer

    Indexer for bindings by tag

    diff --git a/docs/interfaces/recipe_session.SessionContainer.html b/docs/interfaces/recipe_session.SessionContainer.html index 24f200395..88e6f6986 100644 --- a/docs/interfaces/recipe_session.SessionContainer.html +++ b/docs/interfaces/recipe_session.SessionContainer.html @@ -1 +1 @@ -SessionContainer | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SessionContainer

    Index

    Methods

    • attachToRequestResponse(reqResInfo: ReqResInfo, userContext?: Record<string, any>): void | Promise<void>
    • fetchAndSetClaim<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<void>
    • getAccessToken(userContext?: Record<string, any>): string
    • getAccessTokenPayload(userContext?: Record<string, any>): any
    • getAllSessionTokensDangerously(): { accessAndFrontTokenUpdated: boolean; accessToken: string; antiCsrfToken: undefined | string; frontToken: string; refreshToken: undefined | string }
    • Returns { accessAndFrontTokenUpdated: boolean; accessToken: string; antiCsrfToken: undefined | string; frontToken: string; refreshToken: undefined | string }

      • accessAndFrontTokenUpdated: boolean
      • accessToken: string
      • antiCsrfToken: undefined | string
      • frontToken: string
      • refreshToken: undefined | string
    • getClaimValue<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<undefined | T>
    • getExpiry(userContext?: Record<string, any>): Promise<number>
    • getHandle(userContext?: Record<string, any>): string
    • getRecipeUserId(userContext?: Record<string, any>): RecipeUserId
    • getSessionDataFromDatabase(userContext?: Record<string, any>): Promise<any>
    • getTenantId(userContext?: Record<string, any>): string
    • getTimeCreated(userContext?: Record<string, any>): Promise<number>
    • getUserId(userContext?: Record<string, any>): string
    • mergeIntoAccessTokenPayload(accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<void>
    • removeClaim(claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<void>
    • revokeSession(userContext?: Record<string, any>): Promise<void>
    • setClaimValue<T>(claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<void>
    • updateSessionDataInDatabase(newSessionData: any, userContext?: Record<string, any>): Promise<any>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Method
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +SessionContainer | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SessionContainer

    Index

    Methods

    • attachToRequestResponse(reqResInfo: ReqResInfo, userContext?: Record<string, any>): void | Promise<void>
    • fetchAndSetClaim<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<void>
    • getAccessToken(userContext?: Record<string, any>): string
    • getAccessTokenPayload(userContext?: Record<string, any>): any
    • getAllSessionTokensDangerously(): { accessAndFrontTokenUpdated: boolean; accessToken: string; antiCsrfToken: undefined | string; frontToken: string; refreshToken: undefined | string }
    • Returns { accessAndFrontTokenUpdated: boolean; accessToken: string; antiCsrfToken: undefined | string; frontToken: string; refreshToken: undefined | string }

      • accessAndFrontTokenUpdated: boolean
      • accessToken: string
      • antiCsrfToken: undefined | string
      • frontToken: string
      • refreshToken: undefined | string
    • getClaimValue<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<undefined | T>
    • getExpiry(userContext?: Record<string, any>): Promise<number>
    • getHandle(userContext?: Record<string, any>): string
    • getRecipeUserId(userContext?: Record<string, any>): RecipeUserId
    • getSessionDataFromDatabase(userContext?: Record<string, any>): Promise<any>
    • getTenantId(userContext?: Record<string, any>): string
    • getTimeCreated(userContext?: Record<string, any>): Promise<number>
    • getUserId(userContext?: Record<string, any>): string
    • mergeIntoAccessTokenPayload(accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<void>
    • removeClaim(claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<void>
    • revokeSession(userContext?: Record<string, any>): Promise<void>
    • setClaimValue<T>(claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<void>
    • updateSessionDataInDatabase(newSessionData: any, userContext?: Record<string, any>): Promise<any>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Method
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/interfaces/recipe_session.VerifySessionOptions.html b/docs/interfaces/recipe_session.VerifySessionOptions.html index c9bbae358..ffb134e64 100644 --- a/docs/interfaces/recipe_session.VerifySessionOptions.html +++ b/docs/interfaces/recipe_session.VerifySessionOptions.html @@ -1 +1 @@ -VerifySessionOptions | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Method
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +VerifySessionOptions | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Method
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework.html b/docs/modules/framework.html index 7e0bf4c43..25ac792e8 100644 --- a/docs/modules/framework.html +++ b/docs/modules/framework.html @@ -1 +1 @@ -framework | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework

    Index

    Variables

    awsLambda: framework/awsLambda = awsLambdaFramework
    default: { awsLambda: framework/awsLambda; express: framework/express; fastify: framework/fastify; hapi: framework/hapi; koa: framework/koa; loopback: framework/loopback }

    Type declaration

    express: framework/express = expressFramework
    fastify: framework/fastify = fastifyFramework
    hapi: framework/hapi = hapiFramework
    koa: framework/koa = koaFramework
    loopback: framework/loopback = loopbackFramework

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework

    Index

    Variables

    awsLambda: framework/awsLambda = awsLambdaFramework
    default: { awsLambda: framework/awsLambda; express: framework/express; fastify: framework/fastify; hapi: framework/hapi; koa: framework/koa; loopback: framework/loopback }

    Type declaration

    express: framework/express = expressFramework
    fastify: framework/fastify = fastifyFramework
    hapi: framework/hapi = hapiFramework
    koa: framework/koa = koaFramework
    loopback: framework/loopback = loopbackFramework

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_awsLambda.html b/docs/modules/framework_awsLambda.html index 39477d9a1..e1cd760bb 100644 --- a/docs/modules/framework_awsLambda.html +++ b/docs/modules/framework_awsLambda.html @@ -1 +1 @@ -framework/awsLambda | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/awsLambda

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/awsLambda | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/awsLambda

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_custom.html b/docs/modules/framework_custom.html index 046fb726b..d4330d3b8 100644 --- a/docs/modules/framework_custom.html +++ b/docs/modules/framework_custom.html @@ -1 +1 @@ -framework/custom | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/custom

    Index

    Functions

    • middleware<OrigReqType, OrigRespType>(wrapRequest?: ((req: OrigReqType) => BaseRequest), wrapResponse?: ((req: OrigRespType) => BaseResponse)): ((request: OrigReqType, response: OrigRespType, next?: NextFunction) => Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>)
    • Type Parameters

      Parameters

      Returns ((request: OrigReqType, response: OrigRespType, next?: NextFunction) => Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>)

        • (request: OrigReqType, response: OrigRespType, next?: NextFunction): Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>
        • Parameters

          • request: OrigReqType
          • response: OrigRespType
          • Optional next: NextFunction

          Returns Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/custom | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/custom

    Index

    Functions

    • middleware<OrigReqType, OrigRespType>(wrapRequest?: ((req: OrigReqType) => BaseRequest), wrapResponse?: ((req: OrigRespType) => BaseResponse)): ((request: OrigReqType, response: OrigRespType, next?: NextFunction) => Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>)
    • Type Parameters

      Parameters

      Returns ((request: OrigReqType, response: OrigRespType, next?: NextFunction) => Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>)

        • (request: OrigReqType, response: OrigRespType, next?: NextFunction): Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>
        • Parameters

          • request: OrigReqType
          • response: OrigRespType
          • Optional next: NextFunction

          Returns Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_express.html b/docs/modules/framework_express.html index d8a8e9daa..487e2fca4 100644 --- a/docs/modules/framework_express.html +++ b/docs/modules/framework_express.html @@ -1 +1 @@ -framework/express | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/express

    Index

    Functions

    • errorHandler(): ((err: any, req: Request, res: Response, next: NextFunction) => Promise<void>)
    • Returns ((err: any, req: Request, res: Response, next: NextFunction) => Promise<void>)

        • (err: any, req: Request, res: Response, next: NextFunction): Promise<void>
        • Parameters

          • err: any
          • req: Request
          • res: Response
          • next: NextFunction

          Returns Promise<void>

    • middleware(): ((req: Request, res: Response, next: NextFunction) => Promise<void>)
    • Returns ((req: Request, res: Response, next: NextFunction) => Promise<void>)

        • (req: Request, res: Response, next: NextFunction): Promise<void>
        • Parameters

          • req: Request
          • res: Response
          • next: NextFunction

          Returns Promise<void>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/express | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/express

    Index

    Functions

    • errorHandler(): ((err: any, req: Request, res: Response, next: NextFunction) => Promise<void>)
    • Returns ((err: any, req: Request, res: Response, next: NextFunction) => Promise<void>)

        • (err: any, req: Request, res: Response, next: NextFunction): Promise<void>
        • Parameters

          • err: any
          • req: Request
          • res: Response
          • next: NextFunction

          Returns Promise<void>

    • middleware(): ((req: Request, res: Response, next: NextFunction) => Promise<void>)
    • Returns ((req: Request, res: Response, next: NextFunction) => Promise<void>)

        • (req: Request, res: Response, next: NextFunction): Promise<void>
        • Parameters

          • req: Request
          • res: Response
          • next: NextFunction

          Returns Promise<void>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_fastify.html b/docs/modules/framework_fastify.html index a1c2086ac..7b028bd73 100644 --- a/docs/modules/framework_fastify.html +++ b/docs/modules/framework_fastify.html @@ -1 +1 @@ -framework/fastify | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/fastify

    Index

    Type Aliases

    SessionRequest<TRequest>: TRequest & { session?: SessionContainer }

    Type Parameters

    • TRequest extends OriginalFastifyRequest = OriginalFastifyRequest

    Functions

    • errorHandler(): ((err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>) => Promise<void>)
    • Returns ((err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>) => Promise<void>)

        • (err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>): Promise<void>
        • Parameters

          • err: any
          • req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>
          • res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>

          Returns Promise<void>

    • plugin(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyLoggerInstance>, opts: Record<never, never>, done: ((err?: Error) => void)): void
    • Parameters

      • instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyLoggerInstance>
      • opts: Record<never, never>
      • done: ((err?: Error) => void)
          • (err?: Error): void
          • Parameters

            • Optional err: Error

            Returns void

      Returns void

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/fastify | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/fastify

    Index

    Type Aliases

    SessionRequest<TRequest>: TRequest & { session?: SessionContainer }

    Type Parameters

    • TRequest extends OriginalFastifyRequest = OriginalFastifyRequest

    Functions

    • errorHandler(): ((err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>) => Promise<void>)
    • Returns ((err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>) => Promise<void>)

        • (err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>): Promise<void>
        • Parameters

          • err: any
          • req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>
          • res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>

          Returns Promise<void>

    • plugin(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyLoggerInstance>, opts: Record<never, never>, done: ((err?: Error) => void)): void
    • Parameters

      • instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyLoggerInstance>
      • opts: Record<never, never>
      • done: ((err?: Error) => void)
          • (err?: Error): void
          • Parameters

            • Optional err: Error

            Returns void

      Returns void

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_hapi.html b/docs/modules/framework_hapi.html index 51fec0510..fde73f1b6 100644 --- a/docs/modules/framework_hapi.html +++ b/docs/modules/framework_hapi.html @@ -1 +1 @@ -framework/hapi | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/hapi

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/hapi | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/hapi

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_koa.html b/docs/modules/framework_koa.html index 115e282ca..6d7197485 100644 --- a/docs/modules/framework_koa.html +++ b/docs/modules/framework_koa.html @@ -1 +1 @@ -framework/koa | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/koa

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/koa | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/koa

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_loopback.html b/docs/modules/framework_loopback.html index ed9518d76..38da80f79 100644 --- a/docs/modules/framework_loopback.html +++ b/docs/modules/framework_loopback.html @@ -1 +1 @@ -framework/loopback | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/loopback

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/loopback | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/loopback

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/index.html b/docs/modules/index.html index 739f94dcb..5a10bf0f9 100644 --- a/docs/modules/index.html +++ b/docs/modules/index.html @@ -1 +1 @@ -index | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Index

    Variables

    Error: typeof default = SuperTokensWrapper.Error

    Functions

    • createUserIdMapping(input: { externalUserId: string; externalUserIdInfo?: string; force?: boolean; superTokensUserId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR" } | { doesExternalUserIdExist: boolean; doesSuperTokensUserIdExist: boolean; status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR" }>
    • Parameters

      • input: { externalUserId: string; externalUserIdInfo?: string; force?: boolean; superTokensUserId: string; userContext?: Record<string, any> }
        • externalUserId: string
        • Optional externalUserIdInfo?: string
        • Optional force?: boolean
        • superTokensUserId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR" } | { doesExternalUserIdExist: boolean; doesSuperTokensUserIdExist: boolean; status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR" }>

    • deleteUser(userId: string, removeAllLinkedAccounts?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • Parameters

      • userId: string
      • removeAllLinkedAccounts: boolean = true
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" }>

    • deleteUserIdMapping(input: { force?: boolean; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ didMappingExist: boolean; status: "OK" }>
    • Parameters

      • input: { force?: boolean; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional force?: boolean
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ didMappingExist: boolean; status: "OK" }>

    • getAllCORSHeaders(): string[]
    • getRequestFromUserContext(userContext: undefined | UserContext): undefined | BaseRequest
    • getUser(userId: string, userContext?: Record<string, any>): Promise<undefined | User>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<undefined | User>

    • getUserCount(includeRecipeIds?: string[], tenantId?: string, userContext?: Record<string, any>): Promise<number>
    • Parameters

      • Optional includeRecipeIds: string[]
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<number>

    • getUserIdMapping(input: { userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ externalUserId: string; externalUserIdInfo: undefined | string; status: "OK"; superTokensUserId: string } | { status: "UNKNOWN_MAPPING_ERROR" }>
    • Parameters

      • input: { userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ externalUserId: string; externalUserIdInfo: undefined | string; status: "OK"; superTokensUserId: string } | { status: "UNKNOWN_MAPPING_ERROR" }>

    • getUsersNewestFirst(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }): Promise<{ nextPaginationToken?: string; users: User[] }>
    • Parameters

      • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }
        • Optional includeRecipeIds?: string[]
        • Optional limit?: number
        • Optional paginationToken?: string
        • Optional query?: {}
          • [key: string]: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • getUsersOldestFirst(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }): Promise<{ nextPaginationToken?: string; users: User[] }>
    • Parameters

      • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }
        • Optional includeRecipeIds?: string[]
        • Optional limit?: number
        • Optional paginationToken?: string
        • Optional query?: {}
          • [key: string]: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • init(config: TypeInput): void
    • listUsersByAccountInfo(tenantId: string, accountInfo: AccountInfo, doUnionOfAccountInfo?: boolean, userContext?: Record<string, any>): Promise<User[]>
    • Parameters

      • tenantId: string
      • accountInfo: AccountInfo
      • doUnionOfAccountInfo: boolean = false
      • Optional userContext: Record<string, any>

      Returns Promise<User[]>

    • updateOrDeleteUserIdMappingInfo(input: { externalUserIdInfo?: string; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ status: "OK" | "UNKNOWN_MAPPING_ERROR" }>
    • Parameters

      • input: { externalUserIdInfo?: string; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional externalUserIdInfo?: string
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ status: "OK" | "UNKNOWN_MAPPING_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +index | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Index

    Variables

    Error: typeof default = SuperTokensWrapper.Error

    Functions

    • createUserIdMapping(input: { externalUserId: string; externalUserIdInfo?: string; force?: boolean; superTokensUserId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR" } | { doesExternalUserIdExist: boolean; doesSuperTokensUserIdExist: boolean; status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR" }>
    • Parameters

      • input: { externalUserId: string; externalUserIdInfo?: string; force?: boolean; superTokensUserId: string; userContext?: Record<string, any> }
        • externalUserId: string
        • Optional externalUserIdInfo?: string
        • Optional force?: boolean
        • superTokensUserId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR" } | { doesExternalUserIdExist: boolean; doesSuperTokensUserIdExist: boolean; status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR" }>

    • deleteUser(userId: string, removeAllLinkedAccounts?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • Parameters

      • userId: string
      • removeAllLinkedAccounts: boolean = true
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" }>

    • deleteUserIdMapping(input: { force?: boolean; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ didMappingExist: boolean; status: "OK" }>
    • Parameters

      • input: { force?: boolean; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional force?: boolean
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ didMappingExist: boolean; status: "OK" }>

    • getAllCORSHeaders(): string[]
    • getRequestFromUserContext(userContext: undefined | UserContext): undefined | BaseRequest
    • getUser(userId: string, userContext?: Record<string, any>): Promise<undefined | User>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<undefined | User>

    • getUserCount(includeRecipeIds?: string[], tenantId?: string, userContext?: Record<string, any>): Promise<number>
    • Parameters

      • Optional includeRecipeIds: string[]
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<number>

    • getUserIdMapping(input: { userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ externalUserId: string; externalUserIdInfo: undefined | string; status: "OK"; superTokensUserId: string } | { status: "UNKNOWN_MAPPING_ERROR" }>
    • Parameters

      • input: { userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ externalUserId: string; externalUserIdInfo: undefined | string; status: "OK"; superTokensUserId: string } | { status: "UNKNOWN_MAPPING_ERROR" }>

    • getUsersNewestFirst(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }): Promise<{ nextPaginationToken?: string; users: User[] }>
    • Parameters

      • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }
        • Optional includeRecipeIds?: string[]
        • Optional limit?: number
        • Optional paginationToken?: string
        • Optional query?: {}
          • [key: string]: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • getUsersOldestFirst(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }): Promise<{ nextPaginationToken?: string; users: User[] }>
    • Parameters

      • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }
        • Optional includeRecipeIds?: string[]
        • Optional limit?: number
        • Optional paginationToken?: string
        • Optional query?: {}
          • [key: string]: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • init(config: TypeInput): void
    • listUsersByAccountInfo(tenantId: string, accountInfo: AccountInfo, doUnionOfAccountInfo?: boolean, userContext?: Record<string, any>): Promise<User[]>
    • Parameters

      • tenantId: string
      • accountInfo: AccountInfo
      • doUnionOfAccountInfo: boolean = false
      • Optional userContext: Record<string, any>

      Returns Promise<User[]>

    • updateOrDeleteUserIdMappingInfo(input: { externalUserIdInfo?: string; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ status: "OK" | "UNKNOWN_MAPPING_ERROR" }>
    • Parameters

      • input: { externalUserIdInfo?: string; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional externalUserIdInfo?: string
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ status: "OK" | "UNKNOWN_MAPPING_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_accountlinking.html b/docs/modules/recipe_accountlinking.html index a80aa29ff..065f8a3ec 100644 --- a/docs/modules/recipe_accountlinking.html +++ b/docs/modules/recipe_accountlinking.html @@ -1 +1 @@ -recipe/accountlinking | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/accountlinking

    Index

    Type Aliases

    RecipeInterface: { canCreatePrimaryUser: any; canLinkAccounts: any; createPrimaryUser: any; deleteUser: any; getUser: any; getUsers: any; linkAccounts: any; listUsersByAccountInfo: any; unlinkAccount: any }

    Type declaration

    • canCreatePrimaryUser:function
      • canCreatePrimaryUser(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
      • Parameters

        Returns Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • canLinkAccounts:function
      • canLinkAccounts(input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
      • Parameters

        • input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }
          • primaryUserId: string
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • createPrimaryUser:function
      • createPrimaryUser(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
      • Parameters

        Returns Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • deleteUser:function
      • deleteUser(input: { removeAllLinkedAccounts: boolean; userContext: UserContext; userId: string }): Promise<{ status: "OK" }>
      • Parameters

        • input: { removeAllLinkedAccounts: boolean; userContext: UserContext; userId: string }
          • removeAllLinkedAccounts: boolean
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" }>

    • getUser:function
      • getUser(input: { userContext: UserContext; userId: string }): Promise<undefined | User>
    • getUsers:function
      • getUsers(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; timeJoinedOrder: "ASC" | "DESC"; userContext: UserContext }): Promise<{ nextPaginationToken?: string; users: User[] }>
      • Parameters

        • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; timeJoinedOrder: "ASC" | "DESC"; userContext: UserContext }
          • Optional includeRecipeIds?: string[]
          • Optional limit?: number
          • Optional paginationToken?: string
          • Optional query?: {}
            • [key: string]: string
          • tenantId: string
          • timeJoinedOrder: "ASC" | "DESC"
          • userContext: UserContext

        Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • linkAccounts:function
      • linkAccounts(input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
      • Parameters

        • input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }
          • primaryUserId: string
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • listUsersByAccountInfo:function
      • listUsersByAccountInfo(input: { accountInfo: AccountInfo; doUnionOfAccountInfo: boolean; tenantId: string; userContext: UserContext }): Promise<User[]>
      • Parameters

        • input: { accountInfo: AccountInfo; doUnionOfAccountInfo: boolean; tenantId: string; userContext: UserContext }
          • accountInfo: AccountInfo
          • doUnionOfAccountInfo: boolean
          • tenantId: string
          • userContext: UserContext

        Returns Promise<User[]>

    • unlinkAccount:function
      • unlinkAccount(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; wasLinked: boolean; wasRecipeUserDeleted: boolean }>

    Functions

    • canCreatePrimaryUser(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • canLinkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record<string, any>): Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • primaryUserId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • createPrimaryUser(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • createPrimaryUserIdOrLinkAccounts(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainer, userContext?: Record<string, any>): Promise<User>
    • getPrimaryUserThatCanBeLinkedToRecipeUserId(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<undefined | User>
    • init(config?: TypeInput): RecipeListFunction
    • isEmailChangeAllowed(recipeUserId: RecipeUserId, newEmail: string, isVerified: boolean, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • isSignInAllowed(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • isSignUpAllowed(tenantId: string, newUser: AccountInfoWithRecipeId, isVerified: boolean, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • linkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record<string, any>): Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • primaryUserId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • unlinkAccount(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasLinked: boolean; wasRecipeUserDeleted: boolean }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/accountlinking | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/accountlinking

    Index

    Type Aliases

    RecipeInterface: { canCreatePrimaryUser: any; canLinkAccounts: any; createPrimaryUser: any; deleteUser: any; getUser: any; getUsers: any; linkAccounts: any; listUsersByAccountInfo: any; unlinkAccount: any }

    Type declaration

    • canCreatePrimaryUser:function
      • canCreatePrimaryUser(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
      • Parameters

        Returns Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • canLinkAccounts:function
      • canLinkAccounts(input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
      • Parameters

        • input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }
          • primaryUserId: string
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • createPrimaryUser:function
      • createPrimaryUser(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
      • Parameters

        Returns Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • deleteUser:function
      • deleteUser(input: { removeAllLinkedAccounts: boolean; userContext: UserContext; userId: string }): Promise<{ status: "OK" }>
      • Parameters

        • input: { removeAllLinkedAccounts: boolean; userContext: UserContext; userId: string }
          • removeAllLinkedAccounts: boolean
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" }>

    • getUser:function
      • getUser(input: { userContext: UserContext; userId: string }): Promise<undefined | User>
    • getUsers:function
      • getUsers(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; timeJoinedOrder: "ASC" | "DESC"; userContext: UserContext }): Promise<{ nextPaginationToken?: string; users: User[] }>
      • Parameters

        • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; timeJoinedOrder: "ASC" | "DESC"; userContext: UserContext }
          • Optional includeRecipeIds?: string[]
          • Optional limit?: number
          • Optional paginationToken?: string
          • Optional query?: {}
            • [key: string]: string
          • tenantId: string
          • timeJoinedOrder: "ASC" | "DESC"
          • userContext: UserContext

        Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • linkAccounts:function
      • linkAccounts(input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
      • Parameters

        • input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }
          • primaryUserId: string
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • listUsersByAccountInfo:function
      • listUsersByAccountInfo(input: { accountInfo: AccountInfo; doUnionOfAccountInfo: boolean; tenantId: string; userContext: UserContext }): Promise<User[]>
      • Parameters

        • input: { accountInfo: AccountInfo; doUnionOfAccountInfo: boolean; tenantId: string; userContext: UserContext }
          • accountInfo: AccountInfo
          • doUnionOfAccountInfo: boolean
          • tenantId: string
          • userContext: UserContext

        Returns Promise<User[]>

    • unlinkAccount:function
      • unlinkAccount(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; wasLinked: boolean; wasRecipeUserDeleted: boolean }>

    Functions

    • canCreatePrimaryUser(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • canLinkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record<string, any>): Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • primaryUserId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • createPrimaryUser(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • createPrimaryUserIdOrLinkAccounts(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainer, userContext?: Record<string, any>): Promise<User>
    • getPrimaryUserThatCanBeLinkedToRecipeUserId(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<undefined | User>
    • init(config?: TypeInput): RecipeListFunction
    • isEmailChangeAllowed(recipeUserId: RecipeUserId, newEmail: string, isVerified: boolean, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • isSignInAllowed(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • isSignUpAllowed(tenantId: string, newUser: AccountInfoWithRecipeId, isVerified: boolean, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • linkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record<string, any>): Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • primaryUserId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • unlinkAccount(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasLinked: boolean; wasRecipeUserDeleted: boolean }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_dashboard.html b/docs/modules/recipe_dashboard.html index 02a730af8..115a5dd33 100644 --- a/docs/modules/recipe_dashboard.html +++ b/docs/modules/recipe_dashboard.html @@ -1 +1 @@ -recipe/dashboard | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/dashboard

    Index

    Type Aliases

    APIInterface: { dashboardGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<string>) }

    Type declaration

    • dashboardGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<string>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { getDashboardBundleLocation: any; shouldAllowAccess: any }

    Type declaration

    • getDashboardBundleLocation:function
      • getDashboardBundleLocation(input: { userContext: UserContext }): Promise<string>
    • shouldAllowAccess:function
      • shouldAllowAccess(input: { config: TypeNormalisedInput; req: BaseRequest; userContext: UserContext }): Promise<boolean>

    Functions

    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/dashboard | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/dashboard

    Index

    Type Aliases

    APIInterface: { dashboardGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<string>) }

    Type declaration

    • dashboardGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<string>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { getDashboardBundleLocation: any; shouldAllowAccess: any }

    Type declaration

    • getDashboardBundleLocation:function
      • getDashboardBundleLocation(input: { userContext: UserContext }): Promise<string>
    • shouldAllowAccess:function
      • shouldAllowAccess(input: { config: TypeNormalisedInput; req: BaseRequest; userContext: UserContext }): Promise<boolean>

    Functions

    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_emailpassword.html b/docs/modules/recipe_emailpassword.html index cf69c66dd..f2070586d 100644 --- a/docs/modules/recipe_emailpassword.html +++ b/docs/modules/recipe_emailpassword.html @@ -1,5 +1,5 @@ -recipe/emailpassword | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/emailpassword

    Index

    Type Aliases

    APIInterface: { emailExistsGET: undefined | ((input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ exists: boolean; status: "OK" } | GeneralErrorResponse>); generatePasswordResetTokenPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ status: "OK" } | { reason: string; status: "PASSWORD_RESET_NOT_ALLOWED" } | GeneralErrorResponse>); passwordResetPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; token: string; userContext: UserContext }) => Promise<{ email: string; status: "OK"; user: User } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" } | GeneralErrorResponse>); signInPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_NOT_ALLOWED" } | { status: "WRONG_CREDENTIALS_ERROR" } | GeneralErrorResponse>); signUpPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_UP_NOT_ALLOWED" } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>) }

    Type declaration

    • emailExistsGET: undefined | ((input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ exists: boolean; status: "OK" } | GeneralErrorResponse>)
    • generatePasswordResetTokenPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ status: "OK" } | { reason: string; status: "PASSWORD_RESET_NOT_ALLOWED" } | GeneralErrorResponse>)
    • passwordResetPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; token: string; userContext: UserContext }) => Promise<{ email: string; status: "OK"; user: User } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" } | GeneralErrorResponse>)
    • signInPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_NOT_ALLOWED" } | { status: "WRONG_CREDENTIALS_ERROR" } | GeneralErrorResponse>)
    • signUpPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_UP_NOT_ALLOWED" } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypeEmailPasswordEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { consumePasswordResetToken: any; createNewRecipeUser: any; createResetPasswordToken: any; signIn: any; signUp: any; updateEmailOrPassword: any; verifyCredentials: any }

    Type declaration

    • consumePasswordResetToken:function
      • consumePasswordResetToken(input: { tenantId: string; token: string; userContext: UserContext }): Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>
      • Parameters

        • input: { tenantId: string; token: string; userContext: UserContext }
          • tenantId: string
          • token: string
          • userContext: UserContext

        Returns Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>

    • createNewRecipeUser:function
      • createNewRecipeUser(input: { email: string; password: string; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>
      • Parameters

        • input: { email: string; password: string; tenantId: string; userContext: UserContext }
          • email: string
          • password: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>

    • createResetPasswordToken:function
      • createResetPasswordToken(input: { email: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>
      • +recipe/emailpassword | supertokens-node
        Options
        All
        • Public
        • Public/Protected
        • All
        Menu

        Module recipe/emailpassword

        Index

        Type Aliases

        APIInterface: { emailExistsGET: undefined | ((input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ exists: boolean; status: "OK" } | GeneralErrorResponse>); generatePasswordResetTokenPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ status: "OK" } | { reason: string; status: "PASSWORD_RESET_NOT_ALLOWED" } | GeneralErrorResponse>); passwordResetPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; token: string; userContext: UserContext }) => Promise<{ email: string; status: "OK"; user: User } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" } | GeneralErrorResponse>); signInPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_NOT_ALLOWED" } | { status: "WRONG_CREDENTIALS_ERROR" } | GeneralErrorResponse>); signUpPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_UP_NOT_ALLOWED" } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>) }

        Type declaration

        • emailExistsGET: undefined | ((input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ exists: boolean; status: "OK" } | GeneralErrorResponse>)
        • generatePasswordResetTokenPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ status: "OK" } | { reason: string; status: "PASSWORD_RESET_NOT_ALLOWED" } | GeneralErrorResponse>)
        • passwordResetPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; token: string; userContext: UserContext }) => Promise<{ email: string; status: "OK"; user: User } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" } | GeneralErrorResponse>)
        • signInPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_NOT_ALLOWED" } | { status: "WRONG_CREDENTIALS_ERROR" } | GeneralErrorResponse>)
        • signUpPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_UP_NOT_ALLOWED" } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>)
        APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypeEmailPasswordEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

        Type declaration

        RecipeInterface: { consumePasswordResetToken: any; createNewRecipeUser: any; createResetPasswordToken: any; signIn: any; signUp: any; updateEmailOrPassword: any; verifyCredentials: any }

        Type declaration

        • consumePasswordResetToken:function
          • consumePasswordResetToken(input: { tenantId: string; token: string; userContext: UserContext }): Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>
          • Parameters

            • input: { tenantId: string; token: string; userContext: UserContext }
              • tenantId: string
              • token: string
              • userContext: UserContext

            Returns Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>

        • createNewRecipeUser:function
          • createNewRecipeUser(input: { email: string; password: string; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>
          • Parameters

            • input: { email: string; password: string; tenantId: string; userContext: UserContext }
              • email: string
              • password: string
              • tenantId: string
              • userContext: UserContext

            Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>

        • createResetPasswordToken:function
          • createResetPasswordToken(input: { email: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>
          • We pass in the email as well to this function cause the input userId may not be associated with an emailpassword account. In this case, we need to know which email to use to create an emailpassword account later on.

            -

            Parameters

            • input: { email: string; tenantId: string; userContext: UserContext; userId: string }
              • email: string
              • tenantId: string
              • userContext: UserContext
              • userId: string

            Returns Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>

        • signIn:function
          • signIn(input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
          • Parameters

            • input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }
              • email: string
              • password: string
              • session: SessionContainer | undefined
              • tenantId: string
              • userContext: UserContext

            Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

        • signUp:function
          • signUp(input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
          • Parameters

            • input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }
              • email: string
              • password: string
              • session: SessionContainer | undefined
              • tenantId: string
              • userContext: UserContext

            Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

        • updateEmailOrPassword:function
          • updateEmailOrPassword(input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy: string; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
          • Parameters

            • input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy: string; userContext: UserContext }
              • Optional applyPasswordPolicy?: boolean
              • Optional email?: string
              • Optional password?: string
              • recipeUserId: RecipeUserId
              • tenantIdForPasswordPolicy: string
              • userContext: UserContext

            Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

        • verifyCredentials:function
          • verifyCredentials(input: { email: string; password: string; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>
          • Parameters

            • input: { email: string; password: string; tenantId: string; userContext: UserContext }
              • email: string
              • password: string
              • tenantId: string
              • userContext: UserContext

            Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>

        Variables

        Error: typeof default = Wrapper.Error

        Functions

        • consumePasswordResetToken(tenantId: string, token: string, userContext?: Record<string, any>): Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>
        • Parameters

          • tenantId: string
          • token: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>

        • createResetPasswordLink(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" }>
        • Parameters

          • tenantId: string
          • userId: string
          • email: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ link: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" }>

        • createResetPasswordToken(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>
        • Parameters

          • tenantId: string
          • userId: string
          • email: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>

        • init(config?: TypeInput): RecipeListFunction
        • resetPasswordUsingToken(tenantId: string, token: string, newPassword: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
        • Parameters

          • tenantId: string
          • token: string
          • newPassword: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

        • sendEmail(input: TypeEmailPasswordPasswordResetEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
        • sendResetPasswordEmail(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>
        • Parameters

          • tenantId: string
          • userId: string
          • email: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>

        • signIn(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>
        • signIn(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
        • Parameters

          • tenantId: string
          • email: string
          • password: string
          • Optional session: undefined
          • Optional userContext: Record<string, any>

          Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>

        • Parameters

          • tenantId: string
          • email: string
          • password: string
          • session: SessionContainer
          • Optional userContext: Record<string, any>

          Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

        • signUp(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>
        • signUp(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
        • Parameters

          • tenantId: string
          • email: string
          • password: string
          • Optional session: undefined
          • Optional userContext: Record<string, any>

          Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>

        • Parameters

          • tenantId: string
          • email: string
          • password: string
          • session: SessionContainer
          • Optional userContext: Record<string, any>

          Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

        • updateEmailOrPassword(input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
        • Parameters

          • input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }
            • Optional applyPasswordPolicy?: boolean
            • Optional email?: string
            • Optional password?: string
            • recipeUserId: RecipeUserId
            • Optional tenantIdForPasswordPolicy?: string
            • Optional userContext?: Record<string, any>

          Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

        • verifyCredentials(tenantId: string, email: string, password: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>
        • Parameters

          • tenantId: string
          • email: string
          • password: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>

        Legend

        • Variable
        • Function
        • Function with type parameter
        • Type alias
        • Type alias with type parameter
        • Class
        • Class with type parameter
        • Interface

        Settings

        Theme

        Generated using TypeDoc

        \ No newline at end of file +

        Parameters

        • input: { email: string; tenantId: string; userContext: UserContext; userId: string }
          • email: string
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • signIn:function
      • signIn(input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }
          • email: string
          • password: string
          • session: SessionContainer | undefined
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • signUp:function
      • signUp(input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }
          • email: string
          • password: string
          • session: SessionContainer | undefined
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • updateEmailOrPassword:function
      • updateEmailOrPassword(input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy: string; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
      • Parameters

        • input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy: string; userContext: UserContext }
          • Optional applyPasswordPolicy?: boolean
          • Optional email?: string
          • Optional password?: string
          • recipeUserId: RecipeUserId
          • tenantIdForPasswordPolicy: string
          • userContext: UserContext

        Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

    • verifyCredentials:function
      • verifyCredentials(input: { email: string; password: string; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>
      • Parameters

        • input: { email: string; password: string; tenantId: string; userContext: UserContext }
          • email: string
          • password: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>

    Variables

    Error: typeof default = Wrapper.Error

    Functions

    • consumePasswordResetToken(tenantId: string, token: string, userContext?: Record<string, any>): Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>
    • Parameters

      • tenantId: string
      • token: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>

    • createResetPasswordLink(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • email: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ link: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • createResetPasswordToken(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • email: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • init(config?: TypeInput): RecipeListFunction
    • resetPasswordUsingToken(tenantId: string, token: string, newPassword: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
    • Parameters

      • tenantId: string
      • token: string
      • newPassword: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

    • sendEmail(input: TypeEmailPasswordPasswordResetEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendResetPasswordEmail(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • email: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>

    • signIn(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>
    • signIn(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>

    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • signUp(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>
    • signUp(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>

    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • updateEmailOrPassword(input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
    • Parameters

      • input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }
        • Optional applyPasswordPolicy?: boolean
        • Optional email?: string
        • Optional password?: string
        • recipeUserId: RecipeUserId
        • Optional tenantIdForPasswordPolicy?: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

    • verifyCredentials(tenantId: string, email: string, password: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>
    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_emailverification.html b/docs/modules/recipe_emailverification.html index f112fa41d..0c51980c0 100644 --- a/docs/modules/recipe_emailverification.html +++ b/docs/modules/recipe_emailverification.html @@ -1 +1 @@ -recipe/emailverification | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/emailverification

    Index

    Type Aliases

    APIInterface: { generateEmailVerifyTokenPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ status: "OK" } | { newSession?: SessionContainer; status: "EMAIL_ALREADY_VERIFIED_ERROR" } | GeneralErrorResponse>); isEmailVerifiedGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ isVerified: boolean; newSession?: SessionContainer; status: "OK" } | GeneralErrorResponse>); verifyEmailPOST: undefined | ((input: { options: APIOptions; session: SessionContainer | undefined; tenantId: string; token: string; userContext: UserContext }) => Promise<{ newSession?: SessionContainer; status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" } | GeneralErrorResponse>) }

    Type declaration

    • generateEmailVerifyTokenPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ status: "OK" } | { newSession?: SessionContainer; status: "EMAIL_ALREADY_VERIFIED_ERROR" } | GeneralErrorResponse>)
    • isEmailVerifiedGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ isVerified: boolean; newSession?: SessionContainer; status: "OK" } | GeneralErrorResponse>)
    • verifyEmailPOST: undefined | ((input: { options: APIOptions; session: SessionContainer | undefined; tenantId: string; token: string; userContext: UserContext }) => Promise<{ newSession?: SessionContainer; status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" } | GeneralErrorResponse>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypeEmailVerificationEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • emailDelivery: default<TypeEmailVerificationEmailDeliveryInput>
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    RecipeInterface: { createEmailVerificationToken: any; isEmailVerified: any; revokeEmailVerificationTokens: any; unverifyEmail: any; verifyEmailUsingToken: any }

    Type declaration

    • createEmailVerificationToken:function
      • createEmailVerificationToken(input: { email: string; recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • isEmailVerified:function
      • isEmailVerified(input: { email: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<boolean>
    • revokeEmailVerificationTokens:function
      • revokeEmailVerificationTokens(input: { email: string; recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
    • unverifyEmail:function
      • unverifyEmail(input: { email: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK" }>
    • verifyEmailUsingToken:function
      • verifyEmailUsingToken(input: { attemptAccountLinking: boolean; tenantId: string; token: string; userContext: UserContext }): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>
      • Parameters

        • input: { attemptAccountLinking: boolean; tenantId: string; token: string; userContext: UserContext }
          • attemptAccountLinking: boolean
          • tenantId: string
          • token: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

    UserEmailInfo: { email: string; recipeUserId: RecipeUserId }

    Type declaration

    Variables

    EmailVerificationClaim: EmailVerificationClaimClass = ...
    Error: typeof default = Wrapper.Error

    Functions

    • createEmailVerificationLink(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • createEmailVerificationToken(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • init(config: TypeInput): RecipeListFunction
    • isEmailVerified(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<boolean>
    • revokeEmailVerificationTokens(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • sendEmail(input: TypeEmailVerificationEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendEmailVerificationEmail(tenantId: string, userId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • unverifyEmail(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • verifyEmailUsingToken(tenantId: string, token: string, attemptAccountLinking?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/emailverification | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/emailverification

    Index

    Type Aliases

    APIInterface: { generateEmailVerifyTokenPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ status: "OK" } | { newSession?: SessionContainer; status: "EMAIL_ALREADY_VERIFIED_ERROR" } | GeneralErrorResponse>); isEmailVerifiedGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ isVerified: boolean; newSession?: SessionContainer; status: "OK" } | GeneralErrorResponse>); verifyEmailPOST: undefined | ((input: { options: APIOptions; session: SessionContainer | undefined; tenantId: string; token: string; userContext: UserContext }) => Promise<{ newSession?: SessionContainer; status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" } | GeneralErrorResponse>) }

    Type declaration

    • generateEmailVerifyTokenPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ status: "OK" } | { newSession?: SessionContainer; status: "EMAIL_ALREADY_VERIFIED_ERROR" } | GeneralErrorResponse>)
    • isEmailVerifiedGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ isVerified: boolean; newSession?: SessionContainer; status: "OK" } | GeneralErrorResponse>)
    • verifyEmailPOST: undefined | ((input: { options: APIOptions; session: SessionContainer | undefined; tenantId: string; token: string; userContext: UserContext }) => Promise<{ newSession?: SessionContainer; status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" } | GeneralErrorResponse>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypeEmailVerificationEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • emailDelivery: default<TypeEmailVerificationEmailDeliveryInput>
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    RecipeInterface: { createEmailVerificationToken: any; isEmailVerified: any; revokeEmailVerificationTokens: any; unverifyEmail: any; verifyEmailUsingToken: any }

    Type declaration

    • createEmailVerificationToken:function
      • createEmailVerificationToken(input: { email: string; recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • isEmailVerified:function
      • isEmailVerified(input: { email: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<boolean>
    • revokeEmailVerificationTokens:function
      • revokeEmailVerificationTokens(input: { email: string; recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
    • unverifyEmail:function
      • unverifyEmail(input: { email: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK" }>
    • verifyEmailUsingToken:function
      • verifyEmailUsingToken(input: { attemptAccountLinking: boolean; tenantId: string; token: string; userContext: UserContext }): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>
      • Parameters

        • input: { attemptAccountLinking: boolean; tenantId: string; token: string; userContext: UserContext }
          • attemptAccountLinking: boolean
          • tenantId: string
          • token: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

    UserEmailInfo: { email: string; recipeUserId: RecipeUserId }

    Type declaration

    Variables

    EmailVerificationClaim: EmailVerificationClaimClass = ...
    Error: typeof default = Wrapper.Error

    Functions

    • createEmailVerificationLink(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • createEmailVerificationToken(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • init(config: TypeInput): RecipeListFunction
    • isEmailVerified(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<boolean>
    • revokeEmailVerificationTokens(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • sendEmail(input: TypeEmailVerificationEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendEmailVerificationEmail(tenantId: string, userId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • unverifyEmail(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • verifyEmailUsingToken(tenantId: string, token: string, attemptAccountLinking?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_jwt.html b/docs/modules/recipe_jwt.html index 5717a952f..20b4d5e92 100644 --- a/docs/modules/recipe_jwt.html +++ b/docs/modules/recipe_jwt.html @@ -1 +1 @@ -recipe/jwt | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/jwt

    Index

    Type Aliases

    APIInterface: { getJWKSGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<{ keys: JsonWebKey[] } | GeneralErrorResponse>) }

    Type declaration

    • getJWKSGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<{ keys: JsonWebKey[] } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    JsonWebKey: { alg: string; e: string; kid: string; kty: string; n: string; use: string }

    Type declaration

    • alg: string
    • e: string
    • kid: string
    • kty: string
    • n: string
    • use: string
    RecipeInterface: { createJWT: any; getJWKS: any }

    Type declaration

    • createJWT:function
      • createJWT(input: { payload?: any; useStaticSigningKey?: boolean; userContext: UserContext; validitySeconds?: number }): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
      • Parameters

        • input: { payload?: any; useStaticSigningKey?: boolean; userContext: UserContext; validitySeconds?: number }
          • Optional payload?: any
          • Optional useStaticSigningKey?: boolean
          • userContext: UserContext
          • Optional validitySeconds?: number

        Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS:function
      • getJWKS(input: { userContext: UserContext }): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>

    Functions

    • createJWT(payload: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/jwt | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/jwt

    Index

    Type Aliases

    APIInterface: { getJWKSGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<{ keys: JsonWebKey[] } | GeneralErrorResponse>) }

    Type declaration

    • getJWKSGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<{ keys: JsonWebKey[] } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    JsonWebKey: { alg: string; e: string; kid: string; kty: string; n: string; use: string }

    Type declaration

    • alg: string
    • e: string
    • kid: string
    • kty: string
    • n: string
    • use: string
    RecipeInterface: { createJWT: any; getJWKS: any }

    Type declaration

    • createJWT:function
      • createJWT(input: { payload?: any; useStaticSigningKey?: boolean; userContext: UserContext; validitySeconds?: number }): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
      • Parameters

        • input: { payload?: any; useStaticSigningKey?: boolean; userContext: UserContext; validitySeconds?: number }
          • Optional payload?: any
          • Optional useStaticSigningKey?: boolean
          • userContext: UserContext
          • Optional validitySeconds?: number

        Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS:function
      • getJWKS(input: { userContext: UserContext }): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>

    Functions

    • createJWT(payload: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_multifactorauth.html b/docs/modules/recipe_multifactorauth.html index 18b84158e..b59d78664 100644 --- a/docs/modules/recipe_multifactorauth.html +++ b/docs/modules/recipe_multifactorauth.html @@ -1 +1 @@ -recipe/multifactorauth | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/multifactorauth

    Index

    Type Aliases

    APIInterface: { resyncSessionAndFetchMFAInfoPUT: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ emails: Record<string, string[] | undefined>; factors: { allowedToSetup: string[]; alreadySetup: string[]; next: string[] }; phoneNumbers: Record<string, string[] | undefined>; status: "OK" } | GeneralErrorResponse>) }

    Type declaration

    • resyncSessionAndFetchMFAInfoPUT: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ emails: Record<string, string[] | undefined>; factors: { allowedToSetup: string[]; alreadySetup: string[]; next: string[] }; phoneNumbers: Record<string, string[] | undefined>; status: "OK" } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; recipeInstance: Recipe; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { addToRequiredSecondaryFactorsForUser: any; assertAllowedToSetupFactorElseThrowInvalidClaimError: any; getFactorsSetupForUser: any; getMFARequirementsForAuth: any; getRequiredSecondaryFactorsForUser: any; markFactorAsCompleteInSession: any; removeFromRequiredSecondaryFactorsForUser: any }

    Type declaration

    • addToRequiredSecondaryFactorsForUser:function
      • addToRequiredSecondaryFactorsForUser(input: { factorId: string; userContext: UserContext; userId: string }): Promise<void>
      • Parameters

        • input: { factorId: string; userContext: UserContext; userId: string }
          • factorId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<void>

    • assertAllowedToSetupFactorElseThrowInvalidClaimError:function
      • assertAllowedToSetupFactorElseThrowInvalidClaimError(input: { factorId: string; factorsSetUpForUser: Promise<string[]>; mfaRequirementsForAuth: Promise<MFARequirementList>; session: SessionContainer; userContext: UserContext }): Promise<void>
      • Parameters

        • input: { factorId: string; factorsSetUpForUser: Promise<string[]>; mfaRequirementsForAuth: Promise<MFARequirementList>; session: SessionContainer; userContext: UserContext }
          • factorId: string
          • factorsSetUpForUser: Promise<string[]>
          • mfaRequirementsForAuth: Promise<MFARequirementList>
          • session: SessionContainer
          • userContext: UserContext

        Returns Promise<void>

    • getFactorsSetupForUser:function
      • getFactorsSetupForUser(input: { user: User; userContext: UserContext }): Promise<string[]>
    • getMFARequirementsForAuth:function
      • getMFARequirementsForAuth(input: { accessTokenPayload: JSONObject; completedFactors: MFAClaimValue["c"]; factorsSetUpForUser: Promise<string[]>; requiredSecondaryFactorsForTenant: Promise<string[]>; requiredSecondaryFactorsForUser: Promise<string[]>; tenantId: string; user: Promise<User>; userContext: UserContext }): MFARequirementList | Promise<MFARequirementList>
      • Parameters

        • input: { accessTokenPayload: JSONObject; completedFactors: MFAClaimValue["c"]; factorsSetUpForUser: Promise<string[]>; requiredSecondaryFactorsForTenant: Promise<string[]>; requiredSecondaryFactorsForUser: Promise<string[]>; tenantId: string; user: Promise<User>; userContext: UserContext }
          • accessTokenPayload: JSONObject
          • completedFactors: MFAClaimValue["c"]
          • factorsSetUpForUser: Promise<string[]>
          • requiredSecondaryFactorsForTenant: Promise<string[]>
          • requiredSecondaryFactorsForUser: Promise<string[]>
          • tenantId: string
          • user: Promise<User>
          • userContext: UserContext

        Returns MFARequirementList | Promise<MFARequirementList>

    • getRequiredSecondaryFactorsForUser:function
      • getRequiredSecondaryFactorsForUser(input: { userContext: UserContext; userId: string }): Promise<string[]>
    • markFactorAsCompleteInSession:function
      • markFactorAsCompleteInSession(input: { factorId: string; session: SessionContainer; userContext: UserContext }): Promise<void>
    • removeFromRequiredSecondaryFactorsForUser:function
      • removeFromRequiredSecondaryFactorsForUser(input: { factorId: string; userContext: UserContext; userId: string }): Promise<void>

    Variables

    FactorIds: { EMAILPASSWORD: string; LINK_EMAIL: string; LINK_PHONE: string; OTP_EMAIL: string; OTP_PHONE: string; THIRDPARTY: string; TOTP: string } = ...

    Type declaration

    • EMAILPASSWORD: string
    • LINK_EMAIL: string
    • LINK_PHONE: string
    • OTP_EMAIL: string
    • OTP_PHONE: string
    • THIRDPARTY: string
    • TOTP: string
    MultiFactorAuthClaim: MultiFactorAuthClaimClass = ...

    Functions

    • addToRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>
    • assertAllowedToSetupFactorElseThrowInvalidClaimError(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • getFactorsSetupForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • getRequiredSecondaryFactorsForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • init(config?: TypeInput): RecipeListFunction
    • markFactorAsCompleteInSession(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • removeFromRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/multifactorauth | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/multifactorauth

    Index

    Type Aliases

    APIInterface: { resyncSessionAndFetchMFAInfoPUT: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ emails: Record<string, string[] | undefined>; factors: { allowedToSetup: string[]; alreadySetup: string[]; next: string[] }; phoneNumbers: Record<string, string[] | undefined>; status: "OK" } | GeneralErrorResponse>) }

    Type declaration

    • resyncSessionAndFetchMFAInfoPUT: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ emails: Record<string, string[] | undefined>; factors: { allowedToSetup: string[]; alreadySetup: string[]; next: string[] }; phoneNumbers: Record<string, string[] | undefined>; status: "OK" } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; recipeInstance: Recipe; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { addToRequiredSecondaryFactorsForUser: any; assertAllowedToSetupFactorElseThrowInvalidClaimError: any; getFactorsSetupForUser: any; getMFARequirementsForAuth: any; getRequiredSecondaryFactorsForUser: any; markFactorAsCompleteInSession: any; removeFromRequiredSecondaryFactorsForUser: any }

    Type declaration

    • addToRequiredSecondaryFactorsForUser:function
      • addToRequiredSecondaryFactorsForUser(input: { factorId: string; userContext: UserContext; userId: string }): Promise<void>
      • Parameters

        • input: { factorId: string; userContext: UserContext; userId: string }
          • factorId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<void>

    • assertAllowedToSetupFactorElseThrowInvalidClaimError:function
      • assertAllowedToSetupFactorElseThrowInvalidClaimError(input: { factorId: string; factorsSetUpForUser: Promise<string[]>; mfaRequirementsForAuth: Promise<MFARequirementList>; session: SessionContainer; userContext: UserContext }): Promise<void>
      • Parameters

        • input: { factorId: string; factorsSetUpForUser: Promise<string[]>; mfaRequirementsForAuth: Promise<MFARequirementList>; session: SessionContainer; userContext: UserContext }
          • factorId: string
          • factorsSetUpForUser: Promise<string[]>
          • mfaRequirementsForAuth: Promise<MFARequirementList>
          • session: SessionContainer
          • userContext: UserContext

        Returns Promise<void>

    • getFactorsSetupForUser:function
      • getFactorsSetupForUser(input: { user: User; userContext: UserContext }): Promise<string[]>
    • getMFARequirementsForAuth:function
      • getMFARequirementsForAuth(input: { accessTokenPayload: JSONObject; completedFactors: MFAClaimValue["c"]; factorsSetUpForUser: Promise<string[]>; requiredSecondaryFactorsForTenant: Promise<string[]>; requiredSecondaryFactorsForUser: Promise<string[]>; tenantId: string; user: Promise<User>; userContext: UserContext }): MFARequirementList | Promise<MFARequirementList>
      • Parameters

        • input: { accessTokenPayload: JSONObject; completedFactors: MFAClaimValue["c"]; factorsSetUpForUser: Promise<string[]>; requiredSecondaryFactorsForTenant: Promise<string[]>; requiredSecondaryFactorsForUser: Promise<string[]>; tenantId: string; user: Promise<User>; userContext: UserContext }
          • accessTokenPayload: JSONObject
          • completedFactors: MFAClaimValue["c"]
          • factorsSetUpForUser: Promise<string[]>
          • requiredSecondaryFactorsForTenant: Promise<string[]>
          • requiredSecondaryFactorsForUser: Promise<string[]>
          • tenantId: string
          • user: Promise<User>
          • userContext: UserContext

        Returns MFARequirementList | Promise<MFARequirementList>

    • getRequiredSecondaryFactorsForUser:function
      • getRequiredSecondaryFactorsForUser(input: { userContext: UserContext; userId: string }): Promise<string[]>
    • markFactorAsCompleteInSession:function
      • markFactorAsCompleteInSession(input: { factorId: string; session: SessionContainer; userContext: UserContext }): Promise<void>
    • removeFromRequiredSecondaryFactorsForUser:function
      • removeFromRequiredSecondaryFactorsForUser(input: { factorId: string; userContext: UserContext; userId: string }): Promise<void>

    Variables

    FactorIds: { EMAILPASSWORD: string; LINK_EMAIL: string; LINK_PHONE: string; OTP_EMAIL: string; OTP_PHONE: string; THIRDPARTY: string; TOTP: string } = ...

    Type declaration

    • EMAILPASSWORD: string
    • LINK_EMAIL: string
    • LINK_PHONE: string
    • OTP_EMAIL: string
    • OTP_PHONE: string
    • THIRDPARTY: string
    • TOTP: string
    MultiFactorAuthClaim: MultiFactorAuthClaimClass = ...

    Functions

    • addToRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>
    • assertAllowedToSetupFactorElseThrowInvalidClaimError(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • getFactorsSetupForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • getRequiredSecondaryFactorsForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • init(config?: TypeInput): RecipeListFunction
    • markFactorAsCompleteInSession(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • removeFromRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_multitenancy.html b/docs/modules/recipe_multitenancy.html index 3b61cf73c..bc199eb52 100644 --- a/docs/modules/recipe_multitenancy.html +++ b/docs/modules/recipe_multitenancy.html @@ -1 +1 @@ -recipe/multitenancy | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/multitenancy

    Index

    Type Aliases

    APIInterface: { loginMethodsGET: any }

    Type declaration

    • loginMethodsGET:function
      • loginMethodsGET(input: { clientType?: string; options: APIOptions; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { emailPassword: { enabled: boolean }; firstFactors: string[]; passwordless: { enabled: boolean }; status: "OK"; thirdParty: { enabled: boolean; providers: { id: string; name?: string }[] } }>
      • Parameters

        • input: { clientType?: string; options: APIOptions; tenantId: string; userContext: UserContext }
          • Optional clientType?: string
          • options: APIOptions
          • tenantId: string
          • userContext: UserContext

        Returns Promise<GeneralErrorResponse | { emailPassword: { enabled: boolean }; firstFactors: string[]; passwordless: { enabled: boolean }; status: "OK"; thirdParty: { enabled: boolean; providers: { id: string; name?: string }[] } }>

    APIOptions: { allAvailableFirstFactors: string[]; config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse; staticFirstFactors: string[] | undefined; staticThirdPartyProviders: ProviderInput[] }

    Type declaration

    • allAvailableFirstFactors: string[]
    • config: TypeNormalisedInput
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    • staticFirstFactors: string[] | undefined
    • staticThirdPartyProviders: ProviderInput[]
    RecipeInterface: { associateUserToTenant: any; createOrUpdateTenant: any; createOrUpdateThirdPartyConfig: any; deleteTenant: any; deleteThirdPartyConfig: any; disassociateUserFromTenant: any; getTenant: any; getTenantId: any; listAllTenants: any }

    Type declaration

    • associateUserToTenant:function
      • associateUserToTenant(input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
      • Parameters

        • input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }
          • recipeUserId: RecipeUserId
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

    • createOrUpdateTenant:function
      • createOrUpdateTenant(input: { config?: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }; tenantId: string; userContext: UserContext }): Promise<{ createdNew: boolean; status: "OK" }>
      • Parameters

        • input: { config?: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }; tenantId: string; userContext: UserContext }
          • Optional config?: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }
            • Optional coreConfig?: {}
              • [key: string]: any
            • Optional emailPasswordEnabled?: boolean
            • Optional firstFactors?: string[]
            • Optional passwordlessEnabled?: boolean
            • Optional requiredSecondaryFactors?: string[]
            • Optional thirdPartyEnabled?: boolean
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ createdNew: boolean; status: "OK" }>

    • createOrUpdateThirdPartyConfig:function
      • createOrUpdateThirdPartyConfig(input: { config: ProviderConfig; skipValidation?: boolean; tenantId: string; userContext: UserContext }): Promise<{ createdNew: boolean; status: "OK" }>
      • Parameters

        • input: { config: ProviderConfig; skipValidation?: boolean; tenantId: string; userContext: UserContext }
          • config: ProviderConfig
          • Optional skipValidation?: boolean
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ createdNew: boolean; status: "OK" }>

    • deleteTenant:function
      • deleteTenant(input: { tenantId: string; userContext: UserContext }): Promise<{ didExist: boolean; status: "OK" }>
      • Parameters

        • input: { tenantId: string; userContext: UserContext }
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ didExist: boolean; status: "OK" }>

    • deleteThirdPartyConfig:function
      • deleteThirdPartyConfig(input: { tenantId: string; thirdPartyId: string; userContext: UserContext }): Promise<{ didConfigExist: boolean; status: "OK" }>
      • Parameters

        • input: { tenantId: string; thirdPartyId: string; userContext: UserContext }
          • tenantId: string
          • thirdPartyId: string
          • userContext: UserContext

        Returns Promise<{ didConfigExist: boolean; status: "OK" }>

    • disassociateUserFromTenant:function
      • disassociateUserFromTenant(input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; wasAssociated: boolean }>
    • getTenant:function
      • getTenant(input: { tenantId: string; userContext: UserContext }): Promise<undefined | { status: "OK" } & TenantConfig>
      • Parameters

        • input: { tenantId: string; userContext: UserContext }
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | { status: "OK" } & TenantConfig>

    • getTenantId:function
      • getTenantId(input: { tenantIdFromFrontend: string; userContext: UserContext }): Promise<string>
      • Parameters

        • input: { tenantIdFromFrontend: string; userContext: UserContext }
          • tenantIdFromFrontend: string
          • userContext: UserContext

        Returns Promise<string>

    • listAllTenants:function
      • listAllTenants(input: { userContext: UserContext }): Promise<{ status: "OK"; tenants: (TenantConfig & { tenantId: string })[] }>
      • Parameters

        • input: { userContext: UserContext }
          • userContext: UserContext

        Returns Promise<{ status: "OK"; tenants: (TenantConfig & { tenantId: string })[] }>

    Variables

    AllowedDomainsClaim: AllowedDomainsClaimClass = ...

    Functions

    • associateUserToTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
    • Parameters

      • tenantId: string
      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

    • createOrUpdateTenant(tenantId: string, config?: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • Optional config: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }
        • Optional coreConfig?: {}
          • [key: string]: any
        • Optional emailPasswordEnabled?: boolean
        • Optional firstFactors?: string[]
        • Optional passwordlessEnabled?: boolean
        • Optional requiredSecondaryFactors?: string[]
        • Optional thirdPartyEnabled?: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • createOrUpdateThirdPartyConfig(tenantId: string, config: ProviderConfig, skipValidation?: boolean, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • config: ProviderConfig
      • Optional skipValidation: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • deleteTenant(tenantId: string, userContext?: Record<string, any>): Promise<{ didExist: boolean; status: "OK" }>
    • deleteThirdPartyConfig(tenantId: string, thirdPartyId: string, userContext?: Record<string, any>): Promise<{ didConfigExist: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didConfigExist: boolean; status: "OK" }>

    • disassociateUserFromTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAssociated: boolean }>
    • getTenant(tenantId: string, userContext?: Record<string, any>): Promise<undefined | { status: "OK" } & TenantConfig>
    • init(config?: TypeInput): RecipeListFunction
    • listAllTenants(userContext?: Record<string, any>): Promise<{ status: "OK"; tenants: ({ tenantId: string } & TenantConfig)[] }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/multitenancy | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/multitenancy

    Index

    Type Aliases

    APIInterface: { loginMethodsGET: any }

    Type declaration

    • loginMethodsGET:function
      • loginMethodsGET(input: { clientType?: string; options: APIOptions; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { emailPassword: { enabled: boolean }; firstFactors: string[]; passwordless: { enabled: boolean }; status: "OK"; thirdParty: { enabled: boolean; providers: { id: string; name?: string }[] } }>
      • Parameters

        • input: { clientType?: string; options: APIOptions; tenantId: string; userContext: UserContext }
          • Optional clientType?: string
          • options: APIOptions
          • tenantId: string
          • userContext: UserContext

        Returns Promise<GeneralErrorResponse | { emailPassword: { enabled: boolean }; firstFactors: string[]; passwordless: { enabled: boolean }; status: "OK"; thirdParty: { enabled: boolean; providers: { id: string; name?: string }[] } }>

    APIOptions: { allAvailableFirstFactors: string[]; config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse; staticFirstFactors: string[] | undefined; staticThirdPartyProviders: ProviderInput[] }

    Type declaration

    • allAvailableFirstFactors: string[]
    • config: TypeNormalisedInput
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    • staticFirstFactors: string[] | undefined
    • staticThirdPartyProviders: ProviderInput[]
    RecipeInterface: { associateUserToTenant: any; createOrUpdateTenant: any; createOrUpdateThirdPartyConfig: any; deleteTenant: any; deleteThirdPartyConfig: any; disassociateUserFromTenant: any; getTenant: any; getTenantId: any; listAllTenants: any }

    Type declaration

    • associateUserToTenant:function
      • associateUserToTenant(input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
      • Parameters

        • input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }
          • recipeUserId: RecipeUserId
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

    • createOrUpdateTenant:function
      • createOrUpdateTenant(input: { config?: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }; tenantId: string; userContext: UserContext }): Promise<{ createdNew: boolean; status: "OK" }>
      • Parameters

        • input: { config?: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }; tenantId: string; userContext: UserContext }
          • Optional config?: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }
            • Optional coreConfig?: {}
              • [key: string]: any
            • Optional emailPasswordEnabled?: boolean
            • Optional firstFactors?: string[]
            • Optional passwordlessEnabled?: boolean
            • Optional requiredSecondaryFactors?: string[]
            • Optional thirdPartyEnabled?: boolean
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ createdNew: boolean; status: "OK" }>

    • createOrUpdateThirdPartyConfig:function
      • createOrUpdateThirdPartyConfig(input: { config: ProviderConfig; skipValidation?: boolean; tenantId: string; userContext: UserContext }): Promise<{ createdNew: boolean; status: "OK" }>
      • Parameters

        • input: { config: ProviderConfig; skipValidation?: boolean; tenantId: string; userContext: UserContext }
          • config: ProviderConfig
          • Optional skipValidation?: boolean
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ createdNew: boolean; status: "OK" }>

    • deleteTenant:function
      • deleteTenant(input: { tenantId: string; userContext: UserContext }): Promise<{ didExist: boolean; status: "OK" }>
      • Parameters

        • input: { tenantId: string; userContext: UserContext }
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ didExist: boolean; status: "OK" }>

    • deleteThirdPartyConfig:function
      • deleteThirdPartyConfig(input: { tenantId: string; thirdPartyId: string; userContext: UserContext }): Promise<{ didConfigExist: boolean; status: "OK" }>
      • Parameters

        • input: { tenantId: string; thirdPartyId: string; userContext: UserContext }
          • tenantId: string
          • thirdPartyId: string
          • userContext: UserContext

        Returns Promise<{ didConfigExist: boolean; status: "OK" }>

    • disassociateUserFromTenant:function
      • disassociateUserFromTenant(input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; wasAssociated: boolean }>
    • getTenant:function
      • getTenant(input: { tenantId: string; userContext: UserContext }): Promise<undefined | { status: "OK" } & TenantConfig>
      • Parameters

        • input: { tenantId: string; userContext: UserContext }
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | { status: "OK" } & TenantConfig>

    • getTenantId:function
      • getTenantId(input: { tenantIdFromFrontend: string; userContext: UserContext }): Promise<string>
      • Parameters

        • input: { tenantIdFromFrontend: string; userContext: UserContext }
          • tenantIdFromFrontend: string
          • userContext: UserContext

        Returns Promise<string>

    • listAllTenants:function
      • listAllTenants(input: { userContext: UserContext }): Promise<{ status: "OK"; tenants: (TenantConfig & { tenantId: string })[] }>
      • Parameters

        • input: { userContext: UserContext }
          • userContext: UserContext

        Returns Promise<{ status: "OK"; tenants: (TenantConfig & { tenantId: string })[] }>

    Variables

    AllowedDomainsClaim: AllowedDomainsClaimClass = ...

    Functions

    • associateUserToTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
    • Parameters

      • tenantId: string
      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

    • createOrUpdateTenant(tenantId: string, config?: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • Optional config: { coreConfig?: {}; emailPasswordEnabled?: boolean; firstFactors?: string[]; passwordlessEnabled?: boolean; requiredSecondaryFactors?: string[]; thirdPartyEnabled?: boolean }
        • Optional coreConfig?: {}
          • [key: string]: any
        • Optional emailPasswordEnabled?: boolean
        • Optional firstFactors?: string[]
        • Optional passwordlessEnabled?: boolean
        • Optional requiredSecondaryFactors?: string[]
        • Optional thirdPartyEnabled?: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • createOrUpdateThirdPartyConfig(tenantId: string, config: ProviderConfig, skipValidation?: boolean, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • config: ProviderConfig
      • Optional skipValidation: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • deleteTenant(tenantId: string, userContext?: Record<string, any>): Promise<{ didExist: boolean; status: "OK" }>
    • deleteThirdPartyConfig(tenantId: string, thirdPartyId: string, userContext?: Record<string, any>): Promise<{ didConfigExist: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didConfigExist: boolean; status: "OK" }>

    • disassociateUserFromTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAssociated: boolean }>
    • getTenant(tenantId: string, userContext?: Record<string, any>): Promise<undefined | { status: "OK" } & TenantConfig>
    • init(config?: TypeInput): RecipeListFunction
    • listAllTenants(userContext?: Record<string, any>): Promise<{ status: "OK"; tenants: ({ tenantId: string } & TenantConfig)[] }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_openid.html b/docs/modules/recipe_openid.html index 51d77d9be..10eb335f0 100644 --- a/docs/modules/recipe_openid.html +++ b/docs/modules/recipe_openid.html @@ -1 +1 @@ -recipe/openid | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/openid

    Index

    Functions

    • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • Optional payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
    • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
    • Parameters

      • Optional userContext: Record<string, any>

      Returns Promise<{ issuer: string; jwks_uri: string; status: "OK" }>

    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/openid | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/openid

    Index

    Functions

    • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • Optional payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
    • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
    • Parameters

      • Optional userContext: Record<string, any>

      Returns Promise<{ issuer: string; jwks_uri: string; status: "OK" }>

    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_passwordless.html b/docs/modules/recipe_passwordless.html index a1e685692..68ae1cab6 100644 --- a/docs/modules/recipe_passwordless.html +++ b/docs/modules/recipe_passwordless.html @@ -1 +1 @@ -recipe/passwordless | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/passwordless

    Index

    Type Aliases

    APIInterface: { consumeCodePOST?: any; createCodePOST?: any; emailExistsGET?: any; phoneNumberExistsGET?: any; resendCodePOST?: any }

    Type declaration

    • consumeCodePOST?:function
      • consumeCodePOST(input: { deviceId: string; preAuthSessionId: string; userInputCode: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { linkCode: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { createdNewRecipeUser: boolean; session: SessionContainer; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; userInputCode: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { linkCode: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<GeneralErrorResponse | { createdNewRecipeUser: boolean; session: SessionContainer; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • createCodePOST?:function
      • createCodePOST(input: { email: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { phoneNumber: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { deviceId: string; flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; preAuthSessionId: string; status: "OK" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
      • Parameters

        • input: { email: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { phoneNumber: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<GeneralErrorResponse | { deviceId: string; flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; preAuthSessionId: string; status: "OK" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • emailExistsGET?:function
      • emailExistsGET(input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>
    • phoneNumberExistsGET?:function
      • phoneNumberExistsGET(input: { options: APIOptions; phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>
      • Parameters

        • input: { options: APIOptions; phoneNumber: string; tenantId: string; userContext: UserContext }
          • options: APIOptions
          • phoneNumber: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>

    • resendCodePOST?:function
      • resendCodePOST(input: { deviceId: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { status: "RESTART_FLOW_ERROR" | "OK" }>
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypePasswordlessEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse; smsDelivery: default<TypePasswordlessSmsDeliveryInput> }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • emailDelivery: default<TypePasswordlessEmailDeliveryInput>
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    • smsDelivery: default<TypePasswordlessSmsDeliveryInput>
    RecipeInterface: { checkCode: any; consumeCode: any; createCode: any; createNewCodeForDevice: any; listCodesByDeviceId: any; listCodesByEmail: any; listCodesByPhoneNumber: any; listCodesByPreAuthSessionId: any; revokeAllCodes: any; revokeCode: any; updateUser: any }

    Type declaration

    • checkCode:function
      • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • consumeCode:function
      • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • createCode:function
      • createCode(input: { email: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string } & { phoneNumber: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
      • Parameters

        • input: { email: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string } & { phoneNumber: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string }

        Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

    • createNewCodeForDevice:function
      • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext: UserContext; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
      • Parameters

        • input: { deviceId: string; tenantId: string; userContext: UserContext; userInputCode?: string }
          • deviceId: string
          • tenantId: string
          • userContext: UserContext
          • Optional userInputCode?: string

        Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

    • listCodesByDeviceId:function
      • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext: UserContext }): Promise<undefined | DeviceType>
      • Parameters

        • input: { deviceId: string; tenantId: string; userContext: UserContext }
          • deviceId: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | DeviceType>

    • listCodesByEmail:function
      • listCodesByEmail(input: { email: string; tenantId: string; userContext: UserContext }): Promise<DeviceType[]>
      • Parameters

        • input: { email: string; tenantId: string; userContext: UserContext }
          • email: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<DeviceType[]>

    • listCodesByPhoneNumber:function
      • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<DeviceType[]>
      • Parameters

        • input: { phoneNumber: string; tenantId: string; userContext: UserContext }
          • phoneNumber: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<DeviceType[]>

    • listCodesByPreAuthSessionId:function
      • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<undefined | DeviceType>
      • Parameters

        • input: { preAuthSessionId: string; tenantId: string; userContext: UserContext }
          • preAuthSessionId: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | DeviceType>

    • revokeAllCodes:function
      • revokeAllCodes(input: { email: string; tenantId: string; userContext: UserContext } | { phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
      • Parameters

        • input: { email: string; tenantId: string; userContext: UserContext } | { phoneNumber: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ status: "OK" }>

    • revokeCode:function
      • revokeCode(input: { codeId: string; tenantId: string; userContext: UserContext } | { preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
      • Parameters

        • input: { codeId: string; tenantId: string; userContext: UserContext } | { preAuthSessionId: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ status: "OK" }>

    • updateUser:function
      • updateUser(input: { email?: string | null; phoneNumber?: string | null; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
      • Parameters

        • input: { email?: string | null; phoneNumber?: string | null; recipeUserId: RecipeUserId; userContext: UserContext }
          • Optional email?: string | null
          • Optional phoneNumber?: string | null
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

    Variables

    Error: typeof default = Wrapper.Error

    Functions

    • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • createCode(input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
    • Parameters

      • input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

    • createMagicLink(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<string>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<string>

    • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>
        • Optional userInputCode?: string

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

    • init(config: TypeInput): RecipeListFunction
    • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • listCodesByEmail(input: { email: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> }
        • email: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }
        • phoneNumber: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }
        • preAuthSessionId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • revokeAllCodes(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • revokeCode(input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • sendEmail(input: TypePasswordlessEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendSms(input: TypePasswordlessSmsDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • signInUp(input: { email: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: string; user: User }>
    • updateUser(input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
    • Parameters

      • input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }
        • Optional email?: null | string
        • Optional phoneNumber?: null | string
        • recipeUserId: RecipeUserId
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/passwordless | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/passwordless

    Index

    Type Aliases

    APIInterface: { consumeCodePOST?: any; createCodePOST?: any; emailExistsGET?: any; phoneNumberExistsGET?: any; resendCodePOST?: any }

    Type declaration

    • consumeCodePOST?:function
      • consumeCodePOST(input: { deviceId: string; preAuthSessionId: string; userInputCode: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { linkCode: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { createdNewRecipeUser: boolean; session: SessionContainer; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; userInputCode: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { linkCode: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<GeneralErrorResponse | { createdNewRecipeUser: boolean; session: SessionContainer; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • createCodePOST?:function
      • createCodePOST(input: { email: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { phoneNumber: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { deviceId: string; flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; preAuthSessionId: string; status: "OK" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
      • Parameters

        • input: { email: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { phoneNumber: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<GeneralErrorResponse | { deviceId: string; flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; preAuthSessionId: string; status: "OK" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • emailExistsGET?:function
      • emailExistsGET(input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>
    • phoneNumberExistsGET?:function
      • phoneNumberExistsGET(input: { options: APIOptions; phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>
      • Parameters

        • input: { options: APIOptions; phoneNumber: string; tenantId: string; userContext: UserContext }
          • options: APIOptions
          • phoneNumber: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>

    • resendCodePOST?:function
      • resendCodePOST(input: { deviceId: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { status: "RESTART_FLOW_ERROR" | "OK" }>
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypePasswordlessEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse; smsDelivery: default<TypePasswordlessSmsDeliveryInput> }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • emailDelivery: default<TypePasswordlessEmailDeliveryInput>
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    • smsDelivery: default<TypePasswordlessSmsDeliveryInput>
    RecipeInterface: { checkCode: any; consumeCode: any; createCode: any; createNewCodeForDevice: any; listCodesByDeviceId: any; listCodesByEmail: any; listCodesByPhoneNumber: any; listCodesByPreAuthSessionId: any; revokeAllCodes: any; revokeCode: any; updateUser: any }

    Type declaration

    • checkCode:function
      • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • consumeCode:function
      • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • createCode:function
      • createCode(input: { email: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string } & { phoneNumber: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
      • Parameters

        • input: { email: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string } & { phoneNumber: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string }

        Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

    • createNewCodeForDevice:function
      • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext: UserContext; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
      • Parameters

        • input: { deviceId: string; tenantId: string; userContext: UserContext; userInputCode?: string }
          • deviceId: string
          • tenantId: string
          • userContext: UserContext
          • Optional userInputCode?: string

        Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

    • listCodesByDeviceId:function
      • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext: UserContext }): Promise<undefined | DeviceType>
      • Parameters

        • input: { deviceId: string; tenantId: string; userContext: UserContext }
          • deviceId: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | DeviceType>

    • listCodesByEmail:function
      • listCodesByEmail(input: { email: string; tenantId: string; userContext: UserContext }): Promise<DeviceType[]>
      • Parameters

        • input: { email: string; tenantId: string; userContext: UserContext }
          • email: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<DeviceType[]>

    • listCodesByPhoneNumber:function
      • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<DeviceType[]>
      • Parameters

        • input: { phoneNumber: string; tenantId: string; userContext: UserContext }
          • phoneNumber: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<DeviceType[]>

    • listCodesByPreAuthSessionId:function
      • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<undefined | DeviceType>
      • Parameters

        • input: { preAuthSessionId: string; tenantId: string; userContext: UserContext }
          • preAuthSessionId: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | DeviceType>

    • revokeAllCodes:function
      • revokeAllCodes(input: { email: string; tenantId: string; userContext: UserContext } | { phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
      • Parameters

        • input: { email: string; tenantId: string; userContext: UserContext } | { phoneNumber: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ status: "OK" }>

    • revokeCode:function
      • revokeCode(input: { codeId: string; tenantId: string; userContext: UserContext } | { preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
      • Parameters

        • input: { codeId: string; tenantId: string; userContext: UserContext } | { preAuthSessionId: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ status: "OK" }>

    • updateUser:function
      • updateUser(input: { email?: string | null; phoneNumber?: string | null; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
      • Parameters

        • input: { email?: string | null; phoneNumber?: string | null; recipeUserId: RecipeUserId; userContext: UserContext }
          • Optional email?: string | null
          • Optional phoneNumber?: string | null
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

    Variables

    Error: typeof default = Wrapper.Error

    Functions

    • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • createCode(input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
    • Parameters

      • input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

    • createMagicLink(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<string>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<string>

    • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>
        • Optional userInputCode?: string

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

    • init(config: TypeInput): RecipeListFunction
    • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • listCodesByEmail(input: { email: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> }
        • email: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }
        • phoneNumber: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }
        • preAuthSessionId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • revokeAllCodes(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • revokeCode(input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • sendEmail(input: TypePasswordlessEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendSms(input: TypePasswordlessSmsDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • signInUp(input: { email: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: string; user: User }>
    • updateUser(input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
    • Parameters

      • input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }
        • Optional email?: null | string
        • Optional phoneNumber?: null | string
        • recipeUserId: RecipeUserId
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_session.html b/docs/modules/recipe_session.html index c661a3139..dc35823bd 100644 --- a/docs/modules/recipe_session.html +++ b/docs/modules/recipe_session.html @@ -1,13 +1,13 @@ -recipe/session | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/session

    Index

    Type Aliases

    APIInterface: { refreshPOST: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<SessionContainer>); signOutPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ status: "OK" } | GeneralErrorResponse>); verifySession: any }

    Type declaration

  • mergeIntoAccessTokenPayload:function
    • mergeIntoAccessTokenPayload(input: { accessTokenPayloadUpdate: JSONObject; sessionHandle: string; userContext: UserContext }): Promise<boolean>
  • refreshSession:function
    • refreshSession(input: { antiCsrfToken?: string; disableAntiCsrf: boolean; refreshToken: string; userContext: UserContext }): Promise<SessionContainer>
    • Parameters

      • input: { antiCsrfToken?: string; disableAntiCsrf: boolean; refreshToken: string; userContext: UserContext }
        • Optional antiCsrfToken?: string
        • disableAntiCsrf: boolean
        • refreshToken: string
        • userContext: UserContext

      Returns Promise<SessionContainer>

  • regenerateAccessToken:function
    • regenerateAccessToken(input: { accessToken: string; newAccessTokenPayload?: any; userContext: UserContext }): Promise<undefined | { accessToken?: { createdTime: number; expiry: number; token: string }; session: { handle: string; recipeUserId: RecipeUserId; tenantId: string; userDataInJWT: any; userId: string }; status: "OK" }>
    • Parameters

      • input: { accessToken: string; newAccessTokenPayload?: any; userContext: UserContext }
        • accessToken: string
        • Optional newAccessTokenPayload?: any
        • userContext: UserContext

      Returns Promise<undefined | { accessToken?: { createdTime: number; expiry: number; token: string }; session: { handle: string; recipeUserId: RecipeUserId; tenantId: string; userDataInJWT: any; userId: string }; status: "OK" }>

      Returns false if the sessionHandle does not exist

      +
  • removeClaim:function
    • removeClaim(input: { claim: SessionClaim<any>; sessionHandle: string; userContext: UserContext }): Promise<boolean>
    • Parameters

      • input: { claim: SessionClaim<any>; sessionHandle: string; userContext: UserContext }
        • claim: SessionClaim<any>
        • sessionHandle: string
        • userContext: UserContext

      Returns Promise<boolean>

  • revokeAllSessionsForUser:function
    • revokeAllSessionsForUser(input: { revokeAcrossAllTenants?: boolean; revokeSessionsForLinkedAccounts: boolean; tenantId: string; userContext: UserContext; userId: string }): Promise<string[]>
    • Parameters

      • input: { revokeAcrossAllTenants?: boolean; revokeSessionsForLinkedAccounts: boolean; tenantId: string; userContext: UserContext; userId: string }
        • Optional revokeAcrossAllTenants?: boolean
        • revokeSessionsForLinkedAccounts: boolean
        • tenantId: string
        • userContext: UserContext
        • userId: string

      Returns Promise<string[]>

  • revokeMultipleSessions:function
    • revokeMultipleSessions(input: { sessionHandles: string[]; userContext: UserContext }): Promise<string[]>
    • Parameters

      • input: { sessionHandles: string[]; userContext: UserContext }
        • sessionHandles: string[]
        • userContext: UserContext

      Returns Promise<string[]>

  • revokeSession:function
    • revokeSession(input: { sessionHandle: string; userContext: UserContext }): Promise<boolean>
    • Parameters

      • input: { sessionHandle: string; userContext: UserContext }
        • sessionHandle: string
        • userContext: UserContext

      Returns Promise<boolean>

  • setClaimValue:function
    • setClaimValue<T>(input: { claim: SessionClaim<T>; sessionHandle: string; userContext: UserContext; value: T }): Promise<boolean>
    • Type Parameters

      • T

      Parameters

      • input: { claim: SessionClaim<T>; sessionHandle: string; userContext: UserContext; value: T }
        • claim: SessionClaim<T>
        • sessionHandle: string
        • userContext: UserContext
        • value: T

      Returns Promise<boolean>

  • updateSessionDataInDatabase:function
    • updateSessionDataInDatabase(input: { newSessionData: any; sessionHandle: string; userContext: UserContext }): Promise<boolean>
    • Parameters

      • input: { newSessionData: any; sessionHandle: string; userContext: UserContext }
        • newSessionData: any
        • sessionHandle: string
        • userContext: UserContext

      Returns Promise<boolean>

  • validateClaims:function
    • validateClaims(input: { accessTokenPayload: any; claimValidators: SessionClaimValidator[]; recipeUserId: RecipeUserId; userContext: UserContext; userId: string }): Promise<{ accessTokenPayloadUpdate?: any; invalidClaims: ClaimValidationError[] }>
  • SessionClaimValidator: ({ claim: SessionClaim<any>; shouldRefetch: any } | {}) & { id: string; validate: any }
    SessionInformation: { customClaimsInAccessTokenPayload: any; expiry: number; recipeUserId: RecipeUserId; sessionDataInDatabase: any; sessionHandle: string; tenantId: string; timeCreated: number; userId: string }

    Type declaration

    • customClaimsInAccessTokenPayload: any
    • expiry: number
    • recipeUserId: RecipeUserId
    • sessionDataInDatabase: any
    • sessionHandle: string
    • tenantId: string
    • timeCreated: number
    • userId: string

    Variables

    Error: typeof default = SessionWrapper.Error

    Functions

    • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • Optional payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • createNewSession(req: any, res: any, tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, userContext?: Record<string, any>): Promise<SessionContainer>
    • createNewSessionWithoutRequestResponse(tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, disableAntiCsrf?: boolean, userContext?: Record<string, any>): Promise<SessionContainer>
    • fetchAndSetClaim(sessionHandle: string, claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<boolean>
    • getAllSessionHandlesForUser(userId: string, fetchSessionsForAllLinkedAccounts?: boolean, tenantId?: string, userContext?: Record<string, any>): Promise<string[]>
    • Parameters

      • userId: string
      • fetchSessionsForAllLinkedAccounts: boolean = true
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<string[]>

    • getClaimValue<T>(sessionHandle: string, claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<{ status: "SESSION_DOES_NOT_EXIST_ERROR" } | { status: "OK"; value: undefined | T }>
    • Type Parameters

      • T

      Parameters

      • sessionHandle: string
      • claim: SessionClaim<T>
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "SESSION_DOES_NOT_EXIST_ERROR" } | { status: "OK"; value: undefined | T }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[] }>
    • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
    • getSessionInformation(sessionHandle: string, userContext?: Record<string, any>): Promise<undefined | SessionInformation>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string): Promise<SessionContainer>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { sessionRequired?: true }, userContext?: Record<string, any>): Promise<SessionContainer>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { sessionRequired: false }, userContext?: Record<string, any>): Promise<undefined | SessionContainer>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions, userContext?: Record<string, any>): Promise<undefined | SessionContainer>
    • init(config?: TypeInput): RecipeListFunction
    • mergeIntoAccessTokenPayload(sessionHandle: string, accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<boolean>
    • refreshSession(req: any, res: any, userContext?: Record<string, any>): Promise<SessionContainer>
    • refreshSessionWithoutRequestResponse(refreshToken: string, disableAntiCsrf?: boolean, antiCsrfToken?: string, userContext?: Record<string, any>): Promise<SessionContainer>
    • removeClaim(sessionHandle: string, claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<boolean>
    • revokeAllSessionsForUser(userId: string, revokeSessionsForLinkedAccounts?: boolean, tenantId?: string, userContext?: Record<string, any>): Promise<string[]>
    • Parameters

      • userId: string
      • revokeSessionsForLinkedAccounts: boolean = true
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<string[]>

    • revokeMultipleSessions(sessionHandles: string[], userContext?: Record<string, any>): Promise<string[]>
    • revokeSession(sessionHandle: string, userContext?: Record<string, any>): Promise<boolean>
    • setClaimValue<T>(sessionHandle: string, claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<boolean>
    • Type Parameters

      • T

      Parameters

      • sessionHandle: string
      • claim: SessionClaim<T>
      • value: T
      • Optional userContext: Record<string, any>

      Returns Promise<boolean>

    • updateSessionDataInDatabase(sessionHandle: string, newSessionData: any, userContext?: Record<string, any>): Promise<boolean>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_thirdparty.html b/docs/modules/recipe_thirdparty.html index 3e88c9b3d..6c0437075 100644 --- a/docs/modules/recipe_thirdparty.html +++ b/docs/modules/recipe_thirdparty.html @@ -1 +1 @@ -recipe/thirdparty | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/thirdparty

    Index

    Type Aliases

    APIInterface: { appleRedirectHandlerPOST: undefined | ((input: { formPostInfoFromProvider: {}; options: APIOptions; userContext: UserContext }) => Promise<void>); authorisationUrlGET: undefined | ((input: { options: APIOptions; provider: TypeProvider; redirectURIOnProviderDashboard: string; tenantId: string; userContext: UserContext }) => Promise<{ pkceCodeVerifier?: string; status: "OK"; urlWithQueryParams: string } | GeneralErrorResponse>); signInUpPOST: undefined | ((input: { options: APIOptions; provider: TypeProvider; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & ({ redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any } } | { oAuthTokens: {} })) => Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer; status: "OK"; user: User } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | GeneralErrorResponse>) }

    Type declaration

    • appleRedirectHandlerPOST: undefined | ((input: { formPostInfoFromProvider: {}; options: APIOptions; userContext: UserContext }) => Promise<void>)
    • authorisationUrlGET: undefined | ((input: { options: APIOptions; provider: TypeProvider; redirectURIOnProviderDashboard: string; tenantId: string; userContext: UserContext }) => Promise<{ pkceCodeVerifier?: string; status: "OK"; urlWithQueryParams: string } | GeneralErrorResponse>)
    • signInUpPOST: undefined | ((input: { options: APIOptions; provider: TypeProvider; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & ({ redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any } } | { oAuthTokens: {} })) => Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer; status: "OK"; user: User } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | GeneralErrorResponse>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; isInServerlessEnv: boolean; providers: ProviderInput[]; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • isInServerlessEnv: boolean
    • providers: ProviderInput[]
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    RecipeInterface: { getProvider: any; manuallyCreateOrUpdateUser: any; signInUp: any }

    Type declaration

    • getProvider:function
      • getProvider(input: { clientType?: string; tenantId: string; thirdPartyId: string; userContext: UserContext }): Promise<undefined | TypeProvider>
      • Parameters

        • input: { clientType?: string; tenantId: string; thirdPartyId: string; userContext: UserContext }
          • Optional clientType?: string
          • tenantId: string
          • thirdPartyId: string
          • userContext: UserContext

        Returns Promise<undefined | TypeProvider>

    • manuallyCreateOrUpdateUser:function
      • manuallyCreateOrUpdateUser(input: { email: string; isVerified: boolean; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; isVerified: boolean; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }
          • email: string
          • isVerified: boolean
          • session: SessionContainer | undefined
          • tenantId: string
          • thirdPartyId: string
          • thirdPartyUserId: string
          • userContext: UserContext

        Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • signInUp:function
      • signInUp(input: { email: string; isVerified: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }): Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; isVerified: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }
          • email: string
          • isVerified: boolean
          • oAuthTokens: {}
            • [key: string]: any
          • rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }
            • Optional fromIdTokenPayload?: {}
              • [key: string]: any
            • Optional fromUserInfoAPI?: {}
              • [key: string]: any
          • session: SessionContainer | undefined
          • tenantId: string
          • thirdPartyId: string
          • thirdPartyUserId: string
          • userContext: UserContext

        Returns Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    TypeProvider: { config: ProviderConfigForClientType; id: string; exchangeAuthCodeForOAuthTokens: any; getAuthorisationRedirectURL: any; getConfigForClientType: any; getUserInfo: any }

    Type declaration

    • config: ProviderConfigForClientType
    • id: string
    • exchangeAuthCodeForOAuthTokens:function
      • exchangeAuthCodeForOAuthTokens(input: { redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }; userContext: UserContext }): Promise<any>
      • Parameters

        • input: { redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }; userContext: UserContext }
          • redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }
            • Optional pkceCodeVerifier?: string
            • redirectURIOnProviderDashboard: string
            • redirectURIQueryParams: any
          • userContext: UserContext

        Returns Promise<any>

    • getAuthorisationRedirectURL:function
      • getAuthorisationRedirectURL(input: { redirectURIOnProviderDashboard: string; userContext: UserContext }): Promise<{ pkceCodeVerifier?: string; urlWithQueryParams: string }>
      • Parameters

        • input: { redirectURIOnProviderDashboard: string; userContext: UserContext }
          • redirectURIOnProviderDashboard: string
          • userContext: UserContext

        Returns Promise<{ pkceCodeVerifier?: string; urlWithQueryParams: string }>

    • getConfigForClientType:function
      • getConfigForClientType(input: { clientType?: string; userContext: UserContext }): Promise<ProviderConfigForClientType>
      • Parameters

        • input: { clientType?: string; userContext: UserContext }
          • Optional clientType?: string
          • userContext: UserContext

        Returns Promise<ProviderConfigForClientType>

    • getUserInfo:function
      • getUserInfo(input: { oAuthTokens: any; userContext: UserContext }): Promise<UserInfo>

    Variables

    Error: typeof default = Wrapper.Error

    Functions

    • getProvider(tenantId: string, thirdPartyId: string, clientType: undefined | string, userContext?: Record<string, any>): Promise<undefined | TypeProvider>
    • init(config?: TypeInput): RecipeListFunction
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session?: undefined, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session: SessionContainer, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/thirdparty | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/thirdparty

    Index

    Type Aliases

    APIInterface: { appleRedirectHandlerPOST: undefined | ((input: { formPostInfoFromProvider: {}; options: APIOptions; userContext: UserContext }) => Promise<void>); authorisationUrlGET: undefined | ((input: { options: APIOptions; provider: TypeProvider; redirectURIOnProviderDashboard: string; tenantId: string; userContext: UserContext }) => Promise<{ pkceCodeVerifier?: string; status: "OK"; urlWithQueryParams: string } | GeneralErrorResponse>); signInUpPOST: undefined | ((input: { options: APIOptions; provider: TypeProvider; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & ({ redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any } } | { oAuthTokens: {} })) => Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer; status: "OK"; user: User } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | GeneralErrorResponse>) }

    Type declaration

    • appleRedirectHandlerPOST: undefined | ((input: { formPostInfoFromProvider: {}; options: APIOptions; userContext: UserContext }) => Promise<void>)
    • authorisationUrlGET: undefined | ((input: { options: APIOptions; provider: TypeProvider; redirectURIOnProviderDashboard: string; tenantId: string; userContext: UserContext }) => Promise<{ pkceCodeVerifier?: string; status: "OK"; urlWithQueryParams: string } | GeneralErrorResponse>)
    • signInUpPOST: undefined | ((input: { options: APIOptions; provider: TypeProvider; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & ({ redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any } } | { oAuthTokens: {} })) => Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer; status: "OK"; user: User } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | GeneralErrorResponse>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; isInServerlessEnv: boolean; providers: ProviderInput[]; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • isInServerlessEnv: boolean
    • providers: ProviderInput[]
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    RecipeInterface: { getProvider: any; manuallyCreateOrUpdateUser: any; signInUp: any }

    Type declaration

    • getProvider:function
      • getProvider(input: { clientType?: string; tenantId: string; thirdPartyId: string; userContext: UserContext }): Promise<undefined | TypeProvider>
      • Parameters

        • input: { clientType?: string; tenantId: string; thirdPartyId: string; userContext: UserContext }
          • Optional clientType?: string
          • tenantId: string
          • thirdPartyId: string
          • userContext: UserContext

        Returns Promise<undefined | TypeProvider>

    • manuallyCreateOrUpdateUser:function
      • manuallyCreateOrUpdateUser(input: { email: string; isVerified: boolean; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; isVerified: boolean; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }
          • email: string
          • isVerified: boolean
          • session: SessionContainer | undefined
          • tenantId: string
          • thirdPartyId: string
          • thirdPartyUserId: string
          • userContext: UserContext

        Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • signInUp:function
      • signInUp(input: { email: string; isVerified: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }): Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; isVerified: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }
          • email: string
          • isVerified: boolean
          • oAuthTokens: {}
            • [key: string]: any
          • rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }
            • Optional fromIdTokenPayload?: {}
              • [key: string]: any
            • Optional fromUserInfoAPI?: {}
              • [key: string]: any
          • session: SessionContainer | undefined
          • tenantId: string
          • thirdPartyId: string
          • thirdPartyUserId: string
          • userContext: UserContext

        Returns Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    TypeProvider: { config: ProviderConfigForClientType; id: string; exchangeAuthCodeForOAuthTokens: any; getAuthorisationRedirectURL: any; getConfigForClientType: any; getUserInfo: any }

    Type declaration

    • config: ProviderConfigForClientType
    • id: string
    • exchangeAuthCodeForOAuthTokens:function
      • exchangeAuthCodeForOAuthTokens(input: { redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }; userContext: UserContext }): Promise<any>
      • Parameters

        • input: { redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }; userContext: UserContext }
          • redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }
            • Optional pkceCodeVerifier?: string
            • redirectURIOnProviderDashboard: string
            • redirectURIQueryParams: any
          • userContext: UserContext

        Returns Promise<any>

    • getAuthorisationRedirectURL:function
      • getAuthorisationRedirectURL(input: { redirectURIOnProviderDashboard: string; userContext: UserContext }): Promise<{ pkceCodeVerifier?: string; urlWithQueryParams: string }>
      • Parameters

        • input: { redirectURIOnProviderDashboard: string; userContext: UserContext }
          • redirectURIOnProviderDashboard: string
          • userContext: UserContext

        Returns Promise<{ pkceCodeVerifier?: string; urlWithQueryParams: string }>

    • getConfigForClientType:function
      • getConfigForClientType(input: { clientType?: string; userContext: UserContext }): Promise<ProviderConfigForClientType>
      • Parameters

        • input: { clientType?: string; userContext: UserContext }
          • Optional clientType?: string
          • userContext: UserContext

        Returns Promise<ProviderConfigForClientType>

    • getUserInfo:function
      • getUserInfo(input: { oAuthTokens: any; userContext: UserContext }): Promise<UserInfo>

    Variables

    Error: typeof default = Wrapper.Error

    Functions

    • getProvider(tenantId: string, thirdPartyId: string, clientType: undefined | string, userContext?: Record<string, any>): Promise<undefined | TypeProvider>
    • init(config?: TypeInput): RecipeListFunction
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session?: undefined, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session: SessionContainer, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_totp.html b/docs/modules/recipe_totp.html index 3e2c21b89..82339ee25 100644 --- a/docs/modules/recipe_totp.html +++ b/docs/modules/recipe_totp.html @@ -1 +1 @@ -recipe/totp | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/totp

    Index

    Type Aliases

    APIInterface: { createDevicePOST: undefined | ((input: { deviceName?: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>); listDevicesGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" } | GeneralErrorResponse>); removeDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ didDeviceExist: boolean; status: "OK" } | GeneralErrorResponse>); verifyDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>); verifyTOTPPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>) }

    Type declaration

    • createDevicePOST: undefined | ((input: { deviceName?: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>)
    • listDevicesGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" } | GeneralErrorResponse>)
    • removeDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ didDeviceExist: boolean; status: "OK" } | GeneralErrorResponse>)
    • verifyDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>)
    • verifyTOTPPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { createDevice: any; getUserIdentifierInfoForUserId: any; listDevices: any; removeDevice: any; updateDevice: any; verifyDevice: any; verifyTOTP: any }

    Type declaration

    • createDevice:function
      • createDevice(input: { deviceName?: string; period?: number; skew?: number; userContext: UserContext; userId: string; userIdentifierInfo?: string }): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
      • Parameters

        • input: { deviceName?: string; period?: number; skew?: number; userContext: UserContext; userId: string; userIdentifierInfo?: string }
          • Optional deviceName?: string
          • Optional period?: number
          • Optional skew?: number
          • userContext: UserContext
          • userId: string
          • Optional userIdentifierInfo?: string

        Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • getUserIdentifierInfoForUserId:function
      • getUserIdentifierInfoForUserId(input: { userContext: UserContext; userId: string }): Promise<{ info: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR" }>
      • Parameters

        • input: { userContext: UserContext; userId: string }
          • userContext: UserContext
          • userId: string

        Returns Promise<{ info: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR" }>

    • listDevices:function
      • listDevices(input: { userContext: UserContext; userId: string }): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
      • Parameters

        • input: { userContext: UserContext; userId: string }
          • userContext: UserContext
          • userId: string

        Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice:function
      • removeDevice(input: { deviceName: string; userContext: UserContext; userId: string }): Promise<{ didDeviceExist: boolean; status: "OK" }>
      • Parameters

        • input: { deviceName: string; userContext: UserContext; userId: string }
          • deviceName: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice:function
      • updateDevice(input: { existingDeviceName: string; newDeviceName: string; userContext: UserContext; userId: string }): Promise<{ status: "OK" | "UNKNOWN_DEVICE_ERROR" | "DEVICE_ALREADY_EXISTS_ERROR" }>
      • Parameters

        • input: { existingDeviceName: string; newDeviceName: string; userContext: UserContext; userId: string }
          • existingDeviceName: string
          • newDeviceName: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" | "UNKNOWN_DEVICE_ERROR" | "DEVICE_ALREADY_EXISTS_ERROR" }>

    • verifyDevice:function
      • verifyDevice(input: { deviceName: string; tenantId: string; totp: string; userContext: UserContext; userId: string }): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
      • Parameters

        • input: { deviceName: string; tenantId: string; totp: string; userContext: UserContext; userId: string }
          • deviceName: string
          • tenantId: string
          • totp: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP:function
      • verifyTOTP(input: { tenantId: string; totp: string; userContext: UserContext; userId: string }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
      • Parameters

        • input: { tenantId: string; totp: string; userContext: UserContext; userId: string }
          • tenantId: string
          • totp: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Functions

    • createDevice(userId: string, userIdentifierInfo?: string, deviceName?: string, skew?: number, period?: number, userContext?: Record<string, any>): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • userId: string
      • Optional userIdentifierInfo: string
      • Optional deviceName: string
      • Optional skew: number
      • Optional period: number
      • Optional userContext: Record<string, any>

      Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • init(config?: TypeInput): RecipeListFunction
    • listDevices(userId: string, userContext?: Record<string, any>): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice(userId: string, deviceName: string, userContext?: Record<string, any>): Promise<{ didDeviceExist: boolean; status: "OK" }>
    • Parameters

      • userId: string
      • deviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice(userId: string, existingDeviceName: string, newDeviceName: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>
    • Parameters

      • userId: string
      • existingDeviceName: string
      • newDeviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>

    • verifyDevice(tenantId: string, userId: string, deviceName: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • deviceName: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP(tenantId: string, userId: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/totp | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/totp

    Index

    Type Aliases

    APIInterface: { createDevicePOST: undefined | ((input: { deviceName?: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>); listDevicesGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" } | GeneralErrorResponse>); removeDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ didDeviceExist: boolean; status: "OK" } | GeneralErrorResponse>); verifyDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>); verifyTOTPPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>) }

    Type declaration

    • createDevicePOST: undefined | ((input: { deviceName?: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>)
    • listDevicesGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" } | GeneralErrorResponse>)
    • removeDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ didDeviceExist: boolean; status: "OK" } | GeneralErrorResponse>)
    • verifyDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>)
    • verifyTOTPPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { createDevice: any; getUserIdentifierInfoForUserId: any; listDevices: any; removeDevice: any; updateDevice: any; verifyDevice: any; verifyTOTP: any }

    Type declaration

    • createDevice:function
      • createDevice(input: { deviceName?: string; period?: number; skew?: number; userContext: UserContext; userId: string; userIdentifierInfo?: string }): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
      • Parameters

        • input: { deviceName?: string; period?: number; skew?: number; userContext: UserContext; userId: string; userIdentifierInfo?: string }
          • Optional deviceName?: string
          • Optional period?: number
          • Optional skew?: number
          • userContext: UserContext
          • userId: string
          • Optional userIdentifierInfo?: string

        Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • getUserIdentifierInfoForUserId:function
      • getUserIdentifierInfoForUserId(input: { userContext: UserContext; userId: string }): Promise<{ info: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR" }>
      • Parameters

        • input: { userContext: UserContext; userId: string }
          • userContext: UserContext
          • userId: string

        Returns Promise<{ info: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR" }>

    • listDevices:function
      • listDevices(input: { userContext: UserContext; userId: string }): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
      • Parameters

        • input: { userContext: UserContext; userId: string }
          • userContext: UserContext
          • userId: string

        Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice:function
      • removeDevice(input: { deviceName: string; userContext: UserContext; userId: string }): Promise<{ didDeviceExist: boolean; status: "OK" }>
      • Parameters

        • input: { deviceName: string; userContext: UserContext; userId: string }
          • deviceName: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice:function
      • updateDevice(input: { existingDeviceName: string; newDeviceName: string; userContext: UserContext; userId: string }): Promise<{ status: "OK" | "UNKNOWN_DEVICE_ERROR" | "DEVICE_ALREADY_EXISTS_ERROR" }>
      • Parameters

        • input: { existingDeviceName: string; newDeviceName: string; userContext: UserContext; userId: string }
          • existingDeviceName: string
          • newDeviceName: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" | "UNKNOWN_DEVICE_ERROR" | "DEVICE_ALREADY_EXISTS_ERROR" }>

    • verifyDevice:function
      • verifyDevice(input: { deviceName: string; tenantId: string; totp: string; userContext: UserContext; userId: string }): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
      • Parameters

        • input: { deviceName: string; tenantId: string; totp: string; userContext: UserContext; userId: string }
          • deviceName: string
          • tenantId: string
          • totp: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP:function
      • verifyTOTP(input: { tenantId: string; totp: string; userContext: UserContext; userId: string }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
      • Parameters

        • input: { tenantId: string; totp: string; userContext: UserContext; userId: string }
          • tenantId: string
          • totp: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Functions

    • createDevice(userId: string, userIdentifierInfo?: string, deviceName?: string, skew?: number, period?: number, userContext?: Record<string, any>): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • userId: string
      • Optional userIdentifierInfo: string
      • Optional deviceName: string
      • Optional skew: number
      • Optional period: number
      • Optional userContext: Record<string, any>

      Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • init(config?: TypeInput): RecipeListFunction
    • listDevices(userId: string, userContext?: Record<string, any>): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice(userId: string, deviceName: string, userContext?: Record<string, any>): Promise<{ didDeviceExist: boolean; status: "OK" }>
    • Parameters

      • userId: string
      • deviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice(userId: string, existingDeviceName: string, newDeviceName: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>
    • Parameters

      • userId: string
      • existingDeviceName: string
      • newDeviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>

    • verifyDevice(tenantId: string, userId: string, deviceName: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • deviceName: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP(tenantId: string, userId: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_usermetadata.html b/docs/modules/recipe_usermetadata.html index ddc173c38..2881b37e9 100644 --- a/docs/modules/recipe_usermetadata.html +++ b/docs/modules/recipe_usermetadata.html @@ -1,4 +1,4 @@ -recipe/usermetadata | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/usermetadata

    Index

    Type Aliases

    RecipeInterface: { clearUserMetadata: any; getUserMetadata: any; updateUserMetadata: any }

    Type declaration

    • clearUserMetadata:function
      • clearUserMetadata(input: { userContext: UserContext; userId: string }): Promise<{ status: "OK" }>
    • getUserMetadata:function
      • getUserMetadata(input: { userContext: UserContext; userId: string }): Promise<{ metadata: any; status: "OK" }>
      • Parameters

        • input: { userContext: UserContext; userId: string }
          • userContext: UserContext
          • userId: string

        Returns Promise<{ metadata: any; status: "OK" }>

    • updateUserMetadata:function
      • updateUserMetadata(input: { metadataUpdate: JSONObject; userContext: UserContext; userId: string }): Promise<{ metadata: JSONObject; status: "OK" }>
      • +recipe/usermetadata | supertokens-node
        Options
        All
        • Public
        • Public/Protected
        • All
        Menu

        Module recipe/usermetadata

        Index

        Type Aliases

        RecipeInterface: { clearUserMetadata: any; getUserMetadata: any; updateUserMetadata: any }

        Type declaration

        • clearUserMetadata:function
          • clearUserMetadata(input: { userContext: UserContext; userId: string }): Promise<{ status: "OK" }>
        • getUserMetadata:function
          • getUserMetadata(input: { userContext: UserContext; userId: string }): Promise<{ metadata: any; status: "OK" }>
          • Parameters

            • input: { userContext: UserContext; userId: string }
              • userContext: UserContext
              • userId: string

            Returns Promise<{ metadata: any; status: "OK" }>

        • updateUserMetadata:function
          • updateUserMetadata(input: { metadataUpdate: JSONObject; userContext: UserContext; userId: string }): Promise<{ metadata: JSONObject; status: "OK" }>
          • Updates the metadata object of the user by doing a shallow merge of the stored and the update JSONs and removing properties set to null on the root level of the update object. e.g.:

            @@ -7,4 +7,4 @@
          • update: { "notifications": { "sms": true }, "todos": null }
          • result: { "preferences": { "theme":"dark" }, "notifications": { "sms": true } }
          -

        Parameters

        • input: { metadataUpdate: JSONObject; userContext: UserContext; userId: string }
          • metadataUpdate: JSONObject
          • userContext: UserContext
          • userId: string

        Returns Promise<{ metadata: JSONObject; status: "OK" }>

    Functions

    • clearUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • getUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ metadata: any; status: "OK" }>
    • init(config?: TypeInput): RecipeListFunction
    • updateUserMetadata(userId: string, metadataUpdate: JSONObject, userContext?: Record<string, any>): Promise<{ metadata: JSONObject; status: "OK" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +

    Parameters

    • input: { metadataUpdate: JSONObject; userContext: UserContext; userId: string }
      • metadataUpdate: JSONObject
      • userContext: UserContext
      • userId: string

    Returns Promise<{ metadata: JSONObject; status: "OK" }>

    Functions

    • clearUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • getUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ metadata: any; status: "OK" }>
    • init(config?: TypeInput): RecipeListFunction
    • updateUserMetadata(userId: string, metadataUpdate: JSONObject, userContext?: Record<string, any>): Promise<{ metadata: JSONObject; status: "OK" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_userroles.html b/docs/modules/recipe_userroles.html index 320b060fc..d46177c65 100644 --- a/docs/modules/recipe_userroles.html +++ b/docs/modules/recipe_userroles.html @@ -1 +1 @@ -recipe/userroles | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/userroles

    Index

    Type Aliases

    RecipeInterface: { addRoleToUser: any; createNewRoleOrAddPermissions: any; deleteRole: any; getAllRoles: any; getPermissionsForRole: any; getRolesForUser: any; getRolesThatHavePermission: any; getUsersThatHaveRole: any; removePermissionsFromRole: any; removeUserRole: any }

    Type declaration

    • addRoleToUser:function
      • addRoleToUser(input: { role: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext; userId: string }
          • role: string
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions:function
      • createNewRoleOrAddPermissions(input: { permissions: string[]; role: string; userContext: UserContext }): Promise<{ createdNewRole: boolean; status: "OK" }>
      • Parameters

        • input: { permissions: string[]; role: string; userContext: UserContext }
          • permissions: string[]
          • role: string
          • userContext: UserContext

        Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole:function
      • deleteRole(input: { role: string; userContext: UserContext }): Promise<{ didRoleExist: boolean; status: "OK" }>
      • Parameters

        • input: { role: string; userContext: UserContext }
          • role: string
          • userContext: UserContext

        Returns Promise<{ didRoleExist: boolean; status: "OK" }>

    • getAllRoles:function
      • getAllRoles(input: { userContext: UserContext }): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole:function
      • getPermissionsForRole(input: { role: string; userContext: UserContext }): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; userContext: UserContext }
          • role: string
          • userContext: UserContext

        Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser:function
      • getRolesForUser(input: { tenantId: string; userContext: UserContext; userId: string }): Promise<{ roles: string[]; status: "OK" }>
      • Parameters

        • input: { tenantId: string; userContext: UserContext; userId: string }
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ roles: string[]; status: "OK" }>

    • getRolesThatHavePermission:function
      • getRolesThatHavePermission(input: { permission: string; userContext: UserContext }): Promise<{ roles: string[]; status: "OK" }>
      • Parameters

        • input: { permission: string; userContext: UserContext }
          • permission: string
          • userContext: UserContext

        Returns Promise<{ roles: string[]; status: "OK" }>

    • getUsersThatHaveRole:function
      • getUsersThatHaveRole(input: { role: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext }
          • role: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • removePermissionsFromRole:function
      • removePermissionsFromRole(input: { permissions: string[]; role: string; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { permissions: string[]; role: string; userContext: UserContext }
          • permissions: string[]
          • role: string
          • userContext: UserContext

        Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole:function
      • removeUserRole(input: { role: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext; userId: string }
          • role: string
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Variables

    PermissionClaim: PermissionClaimClass = ...
    UserRoleClaim: UserRoleClaimClass = ...

    Functions

    • addRoleToUser(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ createdNewRole: boolean; status: "OK" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole(role: string, userContext?: Record<string, any>): Promise<{ didRoleExist: boolean; status: "OK" }>
    • getAllRoles(userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole(role: string, userContext?: Record<string, any>): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser(tenantId: string, userId: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getRolesThatHavePermission(permission: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getUsersThatHaveRole(tenantId: string, role: string, userContext?: Record<string, any>): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • init(config?: TypeInput): RecipeListFunction
    • removePermissionsFromRole(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/userroles | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/userroles

    Index

    Type Aliases

    RecipeInterface: { addRoleToUser: any; createNewRoleOrAddPermissions: any; deleteRole: any; getAllRoles: any; getPermissionsForRole: any; getRolesForUser: any; getRolesThatHavePermission: any; getUsersThatHaveRole: any; removePermissionsFromRole: any; removeUserRole: any }

    Type declaration

    • addRoleToUser:function
      • addRoleToUser(input: { role: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext; userId: string }
          • role: string
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions:function
      • createNewRoleOrAddPermissions(input: { permissions: string[]; role: string; userContext: UserContext }): Promise<{ createdNewRole: boolean; status: "OK" }>
      • Parameters

        • input: { permissions: string[]; role: string; userContext: UserContext }
          • permissions: string[]
          • role: string
          • userContext: UserContext

        Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole:function
      • deleteRole(input: { role: string; userContext: UserContext }): Promise<{ didRoleExist: boolean; status: "OK" }>
      • Parameters

        • input: { role: string; userContext: UserContext }
          • role: string
          • userContext: UserContext

        Returns Promise<{ didRoleExist: boolean; status: "OK" }>

    • getAllRoles:function
      • getAllRoles(input: { userContext: UserContext }): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole:function
      • getPermissionsForRole(input: { role: string; userContext: UserContext }): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; userContext: UserContext }
          • role: string
          • userContext: UserContext

        Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser:function
      • getRolesForUser(input: { tenantId: string; userContext: UserContext; userId: string }): Promise<{ roles: string[]; status: "OK" }>
      • Parameters

        • input: { tenantId: string; userContext: UserContext; userId: string }
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ roles: string[]; status: "OK" }>

    • getRolesThatHavePermission:function
      • getRolesThatHavePermission(input: { permission: string; userContext: UserContext }): Promise<{ roles: string[]; status: "OK" }>
      • Parameters

        • input: { permission: string; userContext: UserContext }
          • permission: string
          • userContext: UserContext

        Returns Promise<{ roles: string[]; status: "OK" }>

    • getUsersThatHaveRole:function
      • getUsersThatHaveRole(input: { role: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext }
          • role: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • removePermissionsFromRole:function
      • removePermissionsFromRole(input: { permissions: string[]; role: string; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { permissions: string[]; role: string; userContext: UserContext }
          • permissions: string[]
          • role: string
          • userContext: UserContext

        Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole:function
      • removeUserRole(input: { role: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext; userId: string }
          • role: string
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Variables

    PermissionClaim: PermissionClaimClass = ...
    UserRoleClaim: UserRoleClaimClass = ...

    Functions

    • addRoleToUser(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ createdNewRole: boolean; status: "OK" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole(role: string, userContext?: Record<string, any>): Promise<{ didRoleExist: boolean; status: "OK" }>
    • getAllRoles(userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole(role: string, userContext?: Record<string, any>): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser(tenantId: string, userId: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getRolesThatHavePermission(permission: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getUsersThatHaveRole(tenantId: string, role: string, userContext?: Record<string, any>): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • init(config?: TypeInput): RecipeListFunction
    • removePermissionsFromRole(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/lib/build/querier.d.ts b/lib/build/querier.d.ts index 5bbcd3fec..6d7607b4b 100644 --- a/lib/build/querier.d.ts +++ b/lib/build/querier.d.ts @@ -3,6 +3,8 @@ import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; import { UserContext } from "./types"; import { NetworkInterceptor } from "./types"; +export declare const hydraPubDomain: string; +export declare const hydraPubPathPrefix = "/recipe/oauth2/pub"; export declare class Querier { private static initCalled; private static hosts; @@ -44,12 +46,18 @@ export declare class Querier { sendGetRequestWithResponseHeaders: ( path: NormalisedURLPath, params: Record, + inpHeaders: Record | undefined, userContext: UserContext ) => Promise<{ body: any; headers: Headers; }>; - sendPutRequest: (path: NormalisedURLPath, body: any, userContext: UserContext) => Promise; + sendPutRequest: ( + path: NormalisedURLPath, + body: any, + params: Record, + userContext: UserContext + ) => Promise; sendPatchRequest: (path: NormalisedURLPath, body: any, userContext: UserContext) => Promise; invalidateCoreCallCache: (userContext: UserContext, updGlobalCacheTagIfNecessary?: boolean) => void; getAllCoreUrlsForPath(path: string): string[]; diff --git a/lib/build/querier.js b/lib/build/querier.js index d8b74b342..dc289267f 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -6,7 +6,7 @@ var __importDefault = }; var _a, _b; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Querier = void 0; +exports.Querier = exports.hydraPubPathPrefix = exports.hydraPubDomain = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the @@ -27,9 +27,9 @@ const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); const processState_1 = require("./processState"); const constants_1 = require("./constants"); const logger_1 = require("./logger"); -const hydraPubDomain = (_a = process.env.HYDRA_PUB) !== null && _a !== void 0 ? _a : "http://localhost:4444"; // This will be used as a domain for paths starting with hydraPubPathPrefix +exports.hydraPubDomain = (_a = process.env.HYDRA_PUB) !== null && _a !== void 0 ? _a : "http://localhost:4444"; // This will be used as a domain for paths starting with hydraPubPathPrefix const hydraAdmDomain = (_b = process.env.HYDRA_ADM) !== null && _b !== void 0 ? _b : "http://localhost:4445"; // This will be used as a domain for paths starting with hydraAdmPathPrefix -const hydraPubPathPrefix = "/recipe/oauth2/pub"; // Replaced with "/oauth2" when sending the request (/recipe/oauth2/pub/token -> /oauth2/token) +exports.hydraPubPathPrefix = "/recipe/oauth2/pub"; // Replaced with "/oauth2" when sending the request (/recipe/oauth2/pub/token -> /oauth2/token) const hydraAdmPathPrefix = "/recipe/oauth2/admin"; // Replaced with "/admin" when sending the request (/recipe/oauth2/admin/clients -> /admin/clients) class Querier { // we have rIdToCore so that recipes can force change the rId sent to core. This is a hack until the core is able @@ -84,6 +84,8 @@ class Querier { this.sendPostRequest = async (path, body, userContext) => { var _a; this.invalidateCoreCallCache(userContext); + // TODO: remove FormData + const isForm = body !== undefined && body instanceof FormData; const { body: respBody } = await this.sendRequestHelper( path, "POST", @@ -91,8 +93,10 @@ class Querier { let apiVersion = await this.getAPIVersion(); let headers = { "cdi-version": apiVersion, - "content-type": "application/json; charset=utf-8", }; + if (!isForm) { + headers["content-type"] = "application/json; charset=utf-8"; + } if (Querier.apiKey !== undefined) { headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); } @@ -117,7 +121,7 @@ class Querier { } return utils_1.doFetch(url, { method: "POST", - body: body !== undefined ? JSON.stringify(body) : undefined, + body: isForm ? body : body !== undefined ? JSON.stringify(body) : undefined, headers, }); }, @@ -250,6 +254,9 @@ class Querier { method: "GET", headers, }); + if (response.status === 302) { + return response; + } if (response.status === 200 && !Querier.disableCache) { // If the request was successful, we save the result into the cache // plus we update the cache tag @@ -270,14 +277,15 @@ class Querier { ); return respBody; }; - this.sendGetRequestWithResponseHeaders = async (path, params, userContext) => { + this.sendGetRequestWithResponseHeaders = async (path, params, inpHeaders, userContext) => { var _a; return await this.sendRequestHelper( path, "GET", async (url) => { let apiVersion = await this.getAPIVersion(); - let headers = { "cdi-version": apiVersion }; + let headers = inpHeaders !== null && inpHeaders !== void 0 ? inpHeaders : {}; + headers["cdi-version"] = apiVersion; if (Querier.apiKey !== undefined) { headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); } @@ -314,7 +322,7 @@ class Querier { ); }; // path should start with "/" - this.sendPutRequest = async (path, body, userContext) => { + this.sendPutRequest = async (path, body, params, userContext) => { var _a; this.invalidateCoreCallCache(userContext); const { body: respBody } = await this.sendRequestHelper( @@ -336,6 +344,7 @@ class Querier { method: "put", headers: headers, body: body, + params: params, }, userContext ); @@ -345,7 +354,12 @@ class Querier { body = request.body; } } - return utils_1.doFetch(url, { + const finalURL = new URL(url); + const searchParams = new URLSearchParams( + Object.entries(params).filter(([_, value]) => value !== undefined) + ); + finalURL.search = searchParams.toString(); + return utils_1.doFetch(finalURL.toString(), { method: "PUT", body: body !== undefined ? JSON.stringify(body) : undefined, headers, @@ -421,13 +435,16 @@ class Querier { let currentDomain = this.__hosts[Querier.lastTriedIndex].domain.getAsStringDangerous(); let currentBasePath = this.__hosts[Querier.lastTriedIndex].basePath.getAsStringDangerous(); let strPath = path.getAsStringDangerous(); - const isHydraAPICall = strPath.startsWith(hydraAdmPathPrefix) || strPath.startsWith(hydraPubPathPrefix); - if (strPath.startsWith(hydraPubPathPrefix)) { - currentDomain = hydraPubDomain; - strPath = strPath.replace(hydraPubPathPrefix, "/oauth2"); + const isHydraAPICall = + strPath.startsWith(hydraAdmPathPrefix) || strPath.startsWith(exports.hydraPubPathPrefix); + if (strPath.startsWith(exports.hydraPubPathPrefix)) { + currentDomain = exports.hydraPubDomain; + currentBasePath = ""; + strPath = strPath.replace(exports.hydraPubPathPrefix, "/oauth2"); } if (strPath.startsWith(hydraAdmPathPrefix)) { currentDomain = hydraAdmDomain; + currentBasePath = ""; strPath = strPath.replace(hydraAdmPathPrefix, "/admin"); } const url = currentDomain + currentBasePath + strPath; @@ -565,5 +582,5 @@ async function handleHydraAPICall(response) { headers: response.headers, }; } - return { body: { status: response.ok ? "OK" : "ERROR", headers: response.headers } }; + return { body: { status: response.ok ? "OK" : "ERROR" }, headers: response.headers }; } diff --git a/lib/build/recipe/accountlinking/index.js b/lib/build/recipe/accountlinking/index.js index ad00a94fb..68e7fbbb5 100644 --- a/lib/build/recipe/accountlinking/index.js +++ b/lib/build/recipe/accountlinking/index.js @@ -129,13 +129,14 @@ class Wrapper { } static async isEmailChangeAllowed(recipeUserId, newEmail, isVerified, session, userContext) { const user = await __1.getUser(recipeUserId.getAsString(), userContext); - return await recipe_1.default.getInstance().isEmailChangeAllowed({ + const res = await recipe_1.default.getInstance().isEmailChangeAllowed({ user, newEmail, isVerified, session, userContext: utils_1.getUserContext(userContext), }); + return res.allowed; } } exports.default = Wrapper; diff --git a/lib/build/recipe/accountlinking/recipe.d.ts b/lib/build/recipe/accountlinking/recipe.d.ts index 48970b36d..f6050bce1 100644 --- a/lib/build/recipe/accountlinking/recipe.d.ts +++ b/lib/build/recipe/accountlinking/recipe.d.ts @@ -104,7 +104,15 @@ export default class Recipe extends RecipeModule { isVerified: boolean; session: SessionContainerInterface | undefined; userContext: UserContext; - }) => Promise; + }) => Promise< + | { + allowed: true; + } + | { + allowed: false; + reason: "PRIMARY_USER_CONFLICT" | "ACCOUNT_TAKEOVER_RISK"; + } + >; verifyEmailForRecipeUserIfLinkedAccountsAreVerified: (input: { user: User; recipeUserId: RecipeUserId; diff --git a/lib/build/recipe/accountlinking/recipe.js b/lib/build/recipe/accountlinking/recipe.js index ac9e83d6e..656686bc6 100644 --- a/lib/build/recipe/accountlinking/recipe.js +++ b/lib/build/recipe/accountlinking/recipe.js @@ -384,35 +384,73 @@ class Recipe extends recipeModule_1.default { * on the link by mistake, causing account linking to happen which can result * in account take over if this recipe user is malicious. */ - let user = input.user; - if (user === undefined) { + let inputUser = input.user; + if (inputUser === undefined) { throw new Error("Passed in recipe user id does not exist"); } - for (const tenantId of user.tenantIds) { + for (const tenantId of inputUser.tenantIds) { let existingUsersWithNewEmail = await this.recipeInterfaceImpl.listUsersByAccountInfo({ - tenantId: user.tenantIds[0], + tenantId: inputUser.tenantIds[0], accountInfo: { email: input.newEmail, }, doUnionOfAccountInfo: false, userContext: input.userContext, }); - let primaryUserForNewEmail = existingUsersWithNewEmail.filter((u) => u.isPrimaryUser); - if (primaryUserForNewEmail.length > 1) { + let otherUsersWithNewEmail = existingUsersWithNewEmail.filter((u) => u.id !== inputUser.id); + let otherPrimaryUserForNewEmail = otherUsersWithNewEmail.filter((u) => u.isPrimaryUser); + if (otherPrimaryUserForNewEmail.length > 1) { throw new Error("You found a bug. Please report it on github.com/supertokens/supertokens-node"); } - if (user.isPrimaryUser) { + if (inputUser.isPrimaryUser) { // this is condition one from the above comment. - if (primaryUserForNewEmail.length === 1 && primaryUserForNewEmail[0].id !== user.id) { + if (otherPrimaryUserForNewEmail.length !== 0) { logger_1.logDebugMessage( - "isEmailChangeAllowed: returning false cause email change will lead to two primary users having same email" + `isEmailChangeAllowed: returning false cause email change will lead to two primary users having same email on ${tenantId}` ); - return false; + return { allowed: false, reason: "PRIMARY_USER_CONFLICT" }; + } + if (input.isVerified) { + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified and doesn't belong to any other primary user` + ); + continue; + } + if (inputUser.loginMethods.some((lm) => lm.hasSameEmailAs(input.newEmail) && lm.verified)) { + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified in another login method and doesn't belong to any other primary user` + ); + continue; + } + if (otherUsersWithNewEmail.length === 0) { + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary and the new email doesn't belong to any other user (primary or non-primary)` + ); + continue; + } + const shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( + otherUsersWithNewEmail[0].loginMethods[0], + inputUser, + input.session, + tenantId, + input.userContext + ); + if (!shouldDoAccountLinking.shouldAutomaticallyLink) { + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause linking is disabled` + ); + continue; + } + if (!shouldDoAccountLinking.shouldRequireVerification) { + logger_1.logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause linking is doesn't require email verification` + ); + continue; } logger_1.logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary and new email doesn't belong to any other primary user` + `isEmailChangeAllowed: returning false because the user hasn't verified the new email address and there exists another user with it on ${tenantId} and linking requires verification` ); - continue; + return { allowed: false, reason: "ACCOUNT_TAKEOVER_RISK" }; } else { if (input.isVerified) { logger_1.logDebugMessage( @@ -420,16 +458,16 @@ class Recipe extends recipeModule_1.default { ); continue; } - if (user.loginMethods[0].hasSameEmailAs(input.newEmail)) { + if (inputUser.loginMethods[0].hasSameEmailAs(input.newEmail)) { logger_1.logDebugMessage( `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary and new email is same as the older one` ); continue; } - if (primaryUserForNewEmail.length === 1) { + if (otherPrimaryUserForNewEmail.length === 1) { let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( - user.loginMethods[0], - primaryUserForNewEmail[0], + inputUser.loginMethods[0], + otherPrimaryUserForNewEmail[0], input.session, tenantId, input.userContext @@ -449,7 +487,7 @@ class Recipe extends recipeModule_1.default { logger_1.logDebugMessage( "isEmailChangeAllowed: returning false cause input user is not a primary there exists a primary user exists with the new email." ); - return false; + return { allowed: false, reason: "ACCOUNT_TAKEOVER_RISK" }; } logger_1.logDebugMessage( `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary no primary user exists with the new email` @@ -460,7 +498,7 @@ class Recipe extends recipeModule_1.default { logger_1.logDebugMessage( "isEmailChangeAllowed: returning true cause email change can happen on all tenants the user is part of" ); - return true; + return { allowed: true }; }; this.verifyEmailForRecipeUserIfLinkedAccountsAreVerified = async (input) => { try { @@ -651,9 +689,20 @@ class Recipe extends recipeModule_1.default { ); return { status: "NO_LINK" }; } - if (shouldDoAccountLinking.shouldRequireVerification && !inputUser.loginMethods[0].verified) { + const accountInfoVerifiedInPrimUser = primaryUserThatCanBeLinkedToTheInputUser.loginMethods.some( + (lm) => + (inputUser.loginMethods[0].email !== undefined && + lm.hasSameEmailAs(inputUser.loginMethods[0].email)) || + (inputUser.loginMethods[0].phoneNumber !== undefined && + lm.hasSamePhoneNumberAs(inputUser.loginMethods[0].phoneNumber) && + lm.verified) + ); + if ( + shouldDoAccountLinking.shouldRequireVerification && + (!inputUser.loginMethods[0].verified || !accountInfoVerifiedInPrimUser) + ) { logger_1.logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified" + "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified in the new or the primary user" ); return { status: "NO_LINK" }; } diff --git a/lib/build/recipe/emailpassword/api/implementation.js b/lib/build/recipe/emailpassword/api/implementation.js index 4e8045c98..6ed81771b 100644 --- a/lib/build/recipe/emailpassword/api/implementation.js +++ b/lib/build/recipe/emailpassword/api/implementation.js @@ -122,6 +122,31 @@ function getAPIImplementation() { emailPasswordAccount.recipeUserId ); } + // Next we check if there is any login method in which the input email is verified. + // If that is the case, then it's proven that the user owns the email and we can + // trust linking of the email password account. + let emailVerified = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + return lm.hasSameEmailAs(email) && lm.verified; + }) !== undefined; + // finally, we check if the primary user has any other email / phone number + // associated with this account - and if it does, then it means that + // there is a risk of account takeover, so we do not allow the token to be generated + let hasOtherEmailOrPhone = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + // we do the extra undefined check below cause + // hasSameEmailAs returns false if the lm.email is undefined, and + // we want to check that the email is different as opposed to email + // not existing in lm. + return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; + }) !== undefined; + if (!emailVerified && hasOtherEmailOrPhone) { + return { + status: "PASSWORD_RESET_NOT_ALLOWED", + reason: + "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + }; + } let shouldDoAccountLinkingResponse = await recipe_1.default .getInstance() .config.shouldDoAutomaticAccountLinking( @@ -228,51 +253,10 @@ function getAPIImplementation() { emailPasswordAccount.recipeUserId ); } - // Now we start the required security checks. First we check if the primary user - // it has just one linked account. And if that's true, then we continue - // cause then there is no scope for account takeover - if (primaryUserAssociatedWithEmail.loginMethods.length === 1) { - return await generateAndSendPasswordResetToken( - primaryUserAssociatedWithEmail.id, - emailPasswordAccount.recipeUserId - ); - } - // Next we check if there is any login method in which the input email is verified. - // If that is the case, then it's proven that the user owns the email and we can - // trust linking of the email password account. - let emailVerified = - primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - return lm.hasSameEmailAs(email) && lm.verified; - }) !== undefined; - if (emailVerified) { - return await generateAndSendPasswordResetToken( - primaryUserAssociatedWithEmail.id, - emailPasswordAccount.recipeUserId - ); - } - // finally, we check if the primary user has any other email / phone number - // associated with this account - and if it does, then it means that - // there is a risk of account takeover, so we do not allow the token to be generated - let hasOtherEmailOrPhone = - primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - // we do the extra undefined check below cause - // hasSameEmailAs returns false if the lm.email is undefined, and - // we want to check that the email is different as opposed to email - // not existing in lm. - return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; - }) !== undefined; - if (hasOtherEmailOrPhone) { - return { - status: "PASSWORD_RESET_NOT_ALLOWED", - reason: - "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", - }; - } else { - return await generateAndSendPasswordResetToken( - primaryUserAssociatedWithEmail.id, - emailPasswordAccount.recipeUserId - ); - } + return await generateAndSendPasswordResetToken( + primaryUserAssociatedWithEmail.id, + emailPasswordAccount.recipeUserId + ); }, passwordResetPOST: async function ({ formFields, token, tenantId, options, userContext }) { async function markEmailAsVerified(recipeUserId, email) { diff --git a/lib/build/recipe/emailpassword/recipeImplementation.js b/lib/build/recipe/emailpassword/recipeImplementation.js index 937dca20e..d5734d770 100644 --- a/lib/build/recipe/emailpassword/recipeImplementation.js +++ b/lib/build/recipe/emailpassword/recipeImplementation.js @@ -6,6 +6,7 @@ var __importDefault = }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../accountlinking/recipe")); +const recipe_2 = __importDefault(require("../emailverification/recipe")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const __1 = require("../.."); const constants_1 = require("./constants"); @@ -160,6 +161,38 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { ); }, updateEmailOrPassword: async function (input) { + const accountLinking = recipe_1.default.getInstance(); + if (input.email) { + const user = await __1.getUser(input.recipeUserId.getAsString(), input.userContext); + if (user === undefined) { + return { status: "UNKNOWN_USER_ID_ERROR" }; + } + const evInstance = recipe_2.default.getInstance(); + let isEmailVerified = false; + if (evInstance) { + isEmailVerified = await evInstance.recipeInterfaceImpl.isEmailVerified({ + recipeUserId: input.recipeUserId, + email: input.email, + userContext: input.userContext, + }); + } + const isEmailChangeAllowed = await accountLinking.isEmailChangeAllowed({ + user, + isVerified: isEmailVerified, + newEmail: input.email, + session: undefined, + userContext: input.userContext, + }); + if (!isEmailChangeAllowed.allowed) { + return { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", + reason: + isEmailChangeAllowed.reason === "ACCOUNT_TAKEOVER_RISK" + ? "New email cannot be applied to existing account because of account takeover risks." + : "New email cannot be applied to existing account because of there is another primary user with the same email address.", + }; + } + } if (input.applyPasswordPolicy || input.applyPasswordPolicy === undefined) { let formFields = getEmailPasswordConfig().signUpFeature.formFields; if (input.password !== undefined) { @@ -190,6 +223,7 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { email: input.email, password: input.password, }, + {}, input.userContext ); if (response.status === "OK") { diff --git a/lib/build/recipe/jwt/api/implementation.js b/lib/build/recipe/jwt/api/implementation.js index e52174f38..4e8bc342f 100644 --- a/lib/build/recipe/jwt/api/implementation.js +++ b/lib/build/recipe/jwt/api/implementation.js @@ -21,6 +21,15 @@ function getAPIImplementation() { if (resp.validityInSeconds !== undefined) { options.res.setHeader("Cache-Control", `max-age=${resp.validityInSeconds}, must-revalidate`, false); } + const oauth2 = require("../../oauth2").getInstance(); + // TODO: dirty hack until we get core support + if (oauth2 !== undefined) { + const oauth2JWKSRes = await fetch("http://localhost:4444/.well-known/jwks.json"); + if (oauth2JWKSRes.ok) { + const oauth2RespBody = await oauth2JWKSRes.json(); + resp.keys = resp.keys.concat(oauth2RespBody.keys); + } + } return { keys: resp.keys, }; diff --git a/lib/build/recipe/jwt/recipeImplementation.js b/lib/build/recipe/jwt/recipeImplementation.js index 073f14b88..0ac8ddf6f 100644 --- a/lib/build/recipe/jwt/recipeImplementation.js +++ b/lib/build/recipe/jwt/recipeImplementation.js @@ -54,6 +54,7 @@ function getRecipeInterface(querier, config, appInfo) { const { body, headers } = await querier.sendGetRequestWithResponseHeaders( new normalisedURLPath_1.default("/.well-known/jwks.json"), {}, + undefined, userContext ); let validityInSeconds = defaultJWKSMaxAge; diff --git a/lib/build/recipe/multitenancy/recipeImplementation.js b/lib/build/recipe/multitenancy/recipeImplementation.js index cee1af3c8..31d8c09a4 100644 --- a/lib/build/recipe/multitenancy/recipeImplementation.js +++ b/lib/build/recipe/multitenancy/recipeImplementation.js @@ -16,6 +16,7 @@ function getRecipeInterface(querier) { let response = await querier.sendPutRequest( new normalisedURLPath_1.default(`/recipe/multitenancy/tenant`), Object.assign({ tenantId }, config), + {}, userContext ); return response; @@ -62,6 +63,7 @@ function getRecipeInterface(querier) { config, skipValidation, }, + {}, userContext ); return response; diff --git a/lib/build/recipe/oauth2/api/auth.d.ts b/lib/build/recipe/oauth2/api/auth.d.ts new file mode 100644 index 000000000..059876918 --- /dev/null +++ b/lib/build/recipe/oauth2/api/auth.d.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function authGET( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2/api/auth.js b/lib/build/recipe/oauth2/api/auth.js new file mode 100644 index 000000000..30135c712 --- /dev/null +++ b/lib/build/recipe/oauth2/api/auth.js @@ -0,0 +1,70 @@ +"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 set_cookie_parser_1 = __importDefault(require("set-cookie-parser")); +const session_1 = __importDefault(require("../../session")); +async function authGET(apiImplementation, options, userContext) { + if (apiImplementation.authGET === undefined) { + return false; + } + const origURL = options.req.getOriginalURL(); + const splitURL = origURL.split("?"); + const params = new URLSearchParams(splitURL[1]); + let session; + try { + session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); + } catch (_a) { + // TODO: explain + // ignore + } + let response = await apiImplementation.authGET({ + options, + params: Object.fromEntries(params.entries()), + cookie: options.req.getHeaderValue("cookie"), + session, + userContext, + }); + if ("redirectTo" in response) { + // TODO: + 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); + } else { + utils_1.send200Response(options.res, response); + } + return true; +} +exports.default = authGET; diff --git a/lib/build/recipe/oauth2/api/consent.d.ts b/lib/build/recipe/oauth2/api/consent.d.ts new file mode 100644 index 000000000..b38df7de8 --- /dev/null +++ b/lib/build/recipe/oauth2/api/consent.d.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function consent( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2/api/consent.js b/lib/build/recipe/oauth2/api/consent.js new file mode 100644 index 000000000..b95113d71 --- /dev/null +++ b/lib/build/recipe/oauth2/api/consent.js @@ -0,0 +1,63 @@ +"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 }); +const utils_1 = require("../../../utils"); +// TODO: separate post and get? +async function consent(apiImplementation, options, userContext) { + var _a; + if (utils_1.normaliseHttpMethod(options.req.getMethod()) === "post") { + if (apiImplementation.consentPOST === undefined) { + return false; + } + const reqBody = await options.req.getJSONBody(); + let response = await apiImplementation.consentPOST({ + options, + accept: reqBody.accept, + consentChallenge: reqBody.consentChallenge, + grantScope: reqBody.grantScope, + remember: reqBody.remember, + userContext, + }); + if ("status" in response) { + utils_1.send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } else { + if (apiImplementation.consentGET === undefined) { + return false; + } + const consentChallenge = + (_a = options.req.getKeyValueFromQuery("consentChallenge")) !== null && _a !== void 0 + ? _a + : options.req.getKeyValueFromQuery("consent_challenge"); + if (consentChallenge === undefined) { + throw new Error("TODO"); + } + let response = await apiImplementation.consentGET({ + options, + consentChallenge, + userContext, + }); + if ("status" in response) { + utils_1.send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } + return true; +} +exports.default = consent; diff --git a/lib/build/recipe/oauth2/api/implementation.js b/lib/build/recipe/oauth2/api/implementation.js index 4a0dd5c82..b4bdc6cad 100644 --- a/lib/build/recipe/oauth2/api/implementation.js +++ b/lib/build/recipe/oauth2/api/implementation.js @@ -13,8 +13,183 @@ * 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 supertokens_1 = __importDefault(require("../../../supertokens")); function getAPIImplementation() { - return {}; + return { + loginGET: async ({ loginChallenge, options, session, userContext }) => { + var _a, _b; + const request = await options.recipeImplementation.getLoginRequest({ + challenge: loginChallenge, + userContext, + }); + if (request.skip) { + const accept = await options.recipeImplementation.acceptLoginRequest({ + challenge: loginChallenge, + subject: request.subject, + userContext, + }); + return { redirectTo: accept.redirectTo }; + } else if (session) { + if (session.getUserId() !== request.subject) { + // TODO? + } + const accept = await options.recipeImplementation.acceptLoginRequest({ + challenge: loginChallenge, + subject: session.getUserId(), + identityProviderSessionId: session.getHandle(), + userContext, + }); + return { redirectTo: accept.redirectTo }; + } + const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; + const websiteDomain = appInfo + .getOrigin({ + request: options.req, + userContext: userContext, + }) + .getAsStringDangerous(); + const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); + // TODO: + return { + redirectTo: + websiteDomain + + websiteBasePath + + `?hint=${ + (_b = (_a = request.oidcContext) === null || _a === void 0 ? void 0 : _a.login_hint) !== null && + _b !== void 0 + ? _b + : "" + }&loginChallenge=${loginChallenge}`, + }; + }, + loginPOST: async ({ loginChallenge, accept, options, session, userContext }) => { + const res = accept + ? await options.recipeImplementation.acceptLoginRequest({ + challenge: loginChallenge, + subject: session.getUserId(), + userContext, + }) + : await options.recipeImplementation.rejectLoginRequest({ + challenge: loginChallenge, + error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, + userContext, + }); + return { redirectTo: res.redirectTo }; + }, + logoutGET: async ({ logoutChallenge, options, userContext }) => { + const request = await options.recipeImplementation.getLogoutRequest({ + challenge: logoutChallenge, + userContext, + }); + const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; + return { + redirectTo: + appInfo + .getOrigin({ + request: options.req, + userContext: userContext, + }) + .getAsStringDangerous() + + appInfo.websiteBasePath.getAsStringDangerous() + + `/logout?challenge=${request.challenge}`, + }; + }, + logoutPOST: async ({ logoutChallenge, accept, options, userContext }) => { + if (accept) { + const res = await options.recipeImplementation.acceptLogoutRequest({ + challenge: logoutChallenge, + userContext, + }); + return { redirectTo: res.redirectTo }; + } + await options.recipeImplementation.rejectLogoutRequest({ + challenge: logoutChallenge, + // error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, + userContext, + }); + const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; + return { + redirectTo: + appInfo + .getOrigin({ + request: options.req, + userContext: userContext, + }) + .getAsStringDangerous() + appInfo.websiteBasePath.getAsStringDangerous(), + }; + }, + consentGET: async ({ consentChallenge, options, userContext }) => { + const request = await options.recipeImplementation.getConsentRequest({ + challenge: consentChallenge, + userContext, + }); + const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; + return { + redirectTo: + appInfo + .getOrigin({ + request: options.req, + userContext: userContext, + }) + .getAsStringDangerous() + + appInfo.websiteBasePath.getAsStringDangerous() + + `/consent?challenge=${request.challenge}&scopes=${request.requestedScope}&client=${request.client}&`, + }; + }, + consentPOST: async ({ consentChallenge, accept, remember, grantScope, options, userContext }) => { + const request = await options.recipeImplementation.getConsentRequest({ + challenge: consentChallenge, + userContext, + }); + const res = accept + ? await options.recipeImplementation.acceptConsentRequest({ + challenge: consentChallenge, + grantAccessTokenAudience: request.requestedAccessTokenAudience, + remember, + grantScope, + userContext, + }) + : await options.recipeImplementation.rejectConsentRequest({ + challenge: consentChallenge, + error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, + userContext, + }); + return { redirectTo: res.redirectTo }; + }, + authGET: async (input) => { + const res = await input.options.recipeImplementation.authorization({ + params: input.params, + cookies: input.cookie, + session: input.session, + userContext: input.userContext, + }); + return res; + }, + tokenPOST: async (input) => { + return input.options.recipeImplementation.token({ body: input.body, userContext: input.userContext }); + }, + loginInfoGET: async ({ loginChallenge, options, userContext }) => { + const { client } = await options.recipeImplementation.getLoginRequest({ + challenge: loginChallenge, + userContext, + }); + return { + status: "OK", + info: { + clientName: client.clientName, + tosUri: client.tosUri, + policyUri: client.policyUri, + logoUri: client.logoUri, + metadata: client.metadata, + }, + }; + }, + }; } exports.default = getAPIImplementation; diff --git a/lib/build/recipe/oauth2/api/login.d.ts b/lib/build/recipe/oauth2/api/login.d.ts new file mode 100644 index 000000000..6f4253cef --- /dev/null +++ b/lib/build/recipe/oauth2/api/login.d.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function login( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2/api/login.js b/lib/build/recipe/oauth2/api/login.js new file mode 100644 index 000000000..878d1a6a8 --- /dev/null +++ b/lib/build/recipe/oauth2/api/login.js @@ -0,0 +1,87 @@ +"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 session_1 = __importDefault(require("../../session")); +// TODO: separate post and get? +async function login(apiImplementation, options, userContext) { + var _a; + if (utils_1.normaliseHttpMethod(options.req.getMethod()) === "post") { + if (apiImplementation.loginPOST === undefined) { + return false; + } + const session = await session_1.default.getSession( + options.req, + options.res, + { sessionRequired: true }, + userContext + ); + const reqBody = await options.req.getJSONBody(); + let response = await apiImplementation.loginPOST({ + options, + accept: reqBody.accept, + loginChallenge: reqBody.loginChallenge, + session, + userContext, + }); + if ("status" in response) { + utils_1.send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } else { + if (apiImplementation.loginGET === undefined) { + return false; + } + let session; + try { + session = await session_1.default.getSession( + options.req, + options.res, + { sessionRequired: false }, + userContext + ); + } catch (_b) { + // TODO: Claim validation failure + } + // TODO: take only one + const loginChallenge = + (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 + ? _a + : options.req.getKeyValueFromQuery("loginChallenge"); + if (loginChallenge === undefined) { + throw new Error("TODO"); + } + let response = await apiImplementation.loginGET({ + options, + loginChallenge, + session, + userContext, + }); + if ("status" in response) { + utils_1.send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } + return true; +} +exports.default = login; diff --git a/lib/build/recipe/oauth2/api/loginInfo.d.ts b/lib/build/recipe/oauth2/api/loginInfo.d.ts new file mode 100644 index 000000000..536858263 --- /dev/null +++ b/lib/build/recipe/oauth2/api/loginInfo.d.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function loginInfoGET( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2/api/loginInfo.js b/lib/build/recipe/oauth2/api/loginInfo.js new file mode 100644 index 000000000..b1d06cd64 --- /dev/null +++ b/lib/build/recipe/oauth2/api/loginInfo.js @@ -0,0 +1,38 @@ +"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 }); +const utils_1 = require("../../../utils"); +async function loginInfoGET(apiImplementation, options, userContext) { + var _a; + if (apiImplementation.loginInfoGET === undefined) { + return false; + } + const loginChallenge = + (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 + ? _a + : options.req.getKeyValueFromQuery("loginChallenge"); + if (loginChallenge === undefined) { + throw new Error("TODO"); + } + let response = await apiImplementation.loginInfoGET({ + options, + loginChallenge, + userContext, + }); + utils_1.send200Response(options.res, response); + return true; +} +exports.default = loginInfoGET; diff --git a/lib/build/recipe/oauth2/api/logout.d.ts b/lib/build/recipe/oauth2/api/logout.d.ts new file mode 100644 index 000000000..d9242afff --- /dev/null +++ b/lib/build/recipe/oauth2/api/logout.d.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function logout( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2/api/logout.js b/lib/build/recipe/oauth2/api/logout.js new file mode 100644 index 000000000..ed9d07e8d --- /dev/null +++ b/lib/build/recipe/oauth2/api/logout.js @@ -0,0 +1,57 @@ +"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 }); +const utils_1 = require("../../../utils"); +// TODO: separate post and get? +async function logout(apiImplementation, options, userContext) { + if (utils_1.normaliseHttpMethod(options.req.getMethod()) === "post") { + if (apiImplementation.logoutPOST === undefined) { + return false; + } + const reqBody = await options.req.getJSONBody(); + let response = await apiImplementation.logoutPOST({ + options, + accept: reqBody.accept, + logoutChallenge: reqBody.logoutChallenge, + userContext, + }); + if ("status" in response) { + utils_1.send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } else { + if (apiImplementation.logoutGET === undefined) { + return false; + } + const logoutChallenge = options.req.getKeyValueFromQuery("logoutChallenge"); + if (logoutChallenge === undefined) { + throw new Error("TODO"); + } + let response = await apiImplementation.logoutGET({ + options, + logoutChallenge, + userContext, + }); + if ("status" in response) { + utils_1.send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } + return true; +} +exports.default = logout; diff --git a/lib/build/recipe/oauth2/api/token.d.ts b/lib/build/recipe/oauth2/api/token.d.ts new file mode 100644 index 000000000..c697b7744 --- /dev/null +++ b/lib/build/recipe/oauth2/api/token.d.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function tokenPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2/api/token.js b/lib/build/recipe/oauth2/api/token.js new file mode 100644 index 000000000..f8d076426 --- /dev/null +++ b/lib/build/recipe/oauth2/api/token.js @@ -0,0 +1,30 @@ +"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 }); +const utils_1 = require("../../../utils"); +async function tokenPOST(apiImplementation, options, userContext) { + if (apiImplementation.tokenPOST === undefined) { + return false; + } + let response = await apiImplementation.tokenPOST({ + options, + body: await options.req.getFormData(), + userContext, + }); + utils_1.send200Response(options.res, response); + return true; +} +exports.default = tokenPOST; diff --git a/lib/build/recipe/oauth2/constants.d.ts b/lib/build/recipe/oauth2/constants.d.ts index 0d55262a7..e5fea5b40 100644 --- a/lib/build/recipe/oauth2/constants.d.ts +++ b/lib/build/recipe/oauth2/constants.d.ts @@ -1,2 +1,8 @@ // @ts-nocheck export declare const OAUTH2_BASE_PATH = "/oauth2/"; +export declare const LOGIN_PATH = "/oauth2/login"; +export declare const LOGOUT_PATH = "/oauth2/logout"; +export declare const CONSENT_PATH = "/oauth2/consent"; +export declare const AUTH_PATH = "/oauth2/auth"; +export declare const TOKEN_PATH = "/oauth2/token"; +export declare const LOGIN_INFO_PATH = "/oauth2/login/info"; diff --git a/lib/build/recipe/oauth2/constants.js b/lib/build/recipe/oauth2/constants.js index e8dc43a0d..f249f20d4 100644 --- a/lib/build/recipe/oauth2/constants.js +++ b/lib/build/recipe/oauth2/constants.js @@ -14,5 +14,11 @@ * under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.OAUTH2_BASE_PATH = void 0; +exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.CONSENT_PATH = exports.LOGOUT_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; exports.OAUTH2_BASE_PATH = "/oauth2/"; +exports.LOGIN_PATH = "/oauth2/login"; +exports.LOGOUT_PATH = "/oauth2/logout"; +exports.CONSENT_PATH = "/oauth2/consent"; +exports.AUTH_PATH = "/oauth2/auth"; +exports.TOKEN_PATH = "/oauth2/token"; +exports.LOGIN_INFO_PATH = "/oauth2/login/info"; diff --git a/lib/build/recipe/oauth2/index.d.ts b/lib/build/recipe/oauth2/index.d.ts index 6df42ad6c..f0e29f04c 100644 --- a/lib/build/recipe/oauth2/index.d.ts +++ b/lib/build/recipe/oauth2/index.d.ts @@ -1,5 +1,4 @@ // @ts-nocheck -import { UserContext } from "../../types"; import Recipe from "./recipe"; import { APIInterface, @@ -14,7 +13,7 @@ export default class Wrapper { static init: typeof Recipe.init; static getOAuth2Clients( input: GetOAuth2ClientsInput, - userContext: UserContext + userContext?: Record ): Promise< | { status: "OK"; @@ -29,7 +28,7 @@ export default class Wrapper { >; static createOAuth2Client( input: CreateOAuth2ClientInput, - userContext: UserContext + userContext?: Record ): Promise< | { status: "OK"; @@ -43,7 +42,7 @@ export default class Wrapper { >; static updateOAuth2Client( input: UpdateOAuth2ClientInput, - userContext: UserContext + userContext?: Record ): Promise< | { status: "OK"; @@ -57,7 +56,7 @@ export default class Wrapper { >; static deleteOAuth2Client( input: DeleteOAuth2ClientInput, - userContext: UserContext + userContext?: Record ): Promise< | { status: "OK"; diff --git a/lib/build/recipe/oauth2/index.js b/lib/build/recipe/oauth2/index.js index 7769bc796..3a0f72db4 100644 --- a/lib/build/recipe/oauth2/index.js +++ b/lib/build/recipe/oauth2/index.js @@ -20,27 +20,28 @@ var __importDefault = }; Object.defineProperty(exports, "__esModule", { value: true }); exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.init = void 0; +const utils_1 = require("../../utils"); const recipe_1 = __importDefault(require("./recipe")); class Wrapper { static async getOAuth2Clients(input, userContext) { return await recipe_1.default .getInstanceOrThrowError() - .recipeInterfaceImpl.getOAuth2Clients(input, userContext); + .recipeInterfaceImpl.getOAuth2Clients(input, utils_1.getUserContext(userContext)); } static async createOAuth2Client(input, userContext) { return await recipe_1.default .getInstanceOrThrowError() - .recipeInterfaceImpl.createOAuth2Client(input, userContext); + .recipeInterfaceImpl.createOAuth2Client(input, utils_1.getUserContext(userContext)); } static async updateOAuth2Client(input, userContext) { return await recipe_1.default .getInstanceOrThrowError() - .recipeInterfaceImpl.updateOAuth2Client(input, userContext); + .recipeInterfaceImpl.updateOAuth2Client(input, utils_1.getUserContext(userContext)); } static async deleteOAuth2Client(input, userContext) { return await recipe_1.default .getInstanceOrThrowError() - .recipeInterfaceImpl.deleteOAuth2Client(input, userContext); + .recipeInterfaceImpl.deleteOAuth2Client(input, utils_1.getUserContext(userContext)); } } exports.default = Wrapper; diff --git a/lib/build/recipe/oauth2/recipe.d.ts b/lib/build/recipe/oauth2/recipe.d.ts index c979f3d0c..8efd16f20 100644 --- a/lib/build/recipe/oauth2/recipe.d.ts +++ b/lib/build/recipe/oauth2/recipe.d.ts @@ -3,30 +3,34 @@ import error from "../../error"; import type { BaseRequest, BaseResponse } from "../../framework"; import NormalisedURLPath from "../../normalisedURLPath"; import RecipeModule from "../../recipeModule"; -import { APIHandled, HTTPMethod, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; +import { APIHandled, HTTPMethod, JSONObject, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; import { APIInterface, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; +import { User } from "../../user"; export default class Recipe extends RecipeModule { static RECIPE_ID: string; private static instance; + private idTokenBuilders; config: TypeNormalisedInput; recipeInterfaceImpl: RecipeInterface; apiImpl: APIInterface; isInServerlessEnv: boolean; constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config?: TypeInput); + static getInstance(): Recipe | undefined; static getInstanceOrThrowError(): Recipe; static init(config?: TypeInput): RecipeListFunction; static reset(): void; getAPIsHandled(): APIHandled[]; handleAPIRequest: ( - _id: string, + id: string, _tenantId: string | undefined, - _req: BaseRequest, - _res: BaseResponse, + req: BaseRequest, + res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, - _userContext: UserContext + userContext: UserContext ) => Promise; handleError(error: error, _: BaseRequest, __: BaseResponse, _userContext: UserContext): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; + getDefaultIdTokenPayload(user: User, scopes: string[], userContext: UserContext): Promise; } diff --git a/lib/build/recipe/oauth2/recipe.js b/lib/build/recipe/oauth2/recipe.js index 6b2556b55..9a7a01277 100644 --- a/lib/build/recipe/oauth2/recipe.js +++ b/lib/build/recipe/oauth2/recipe.js @@ -20,25 +20,52 @@ var __importDefault = }; Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../../error")); +const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const querier_1 = require("../../querier"); const recipeModule_1 = __importDefault(require("../../recipeModule")); +const auth_1 = __importDefault(require("./api/auth")); +const consent_1 = __importDefault(require("./api/consent")); const implementation_1 = __importDefault(require("./api/implementation")); +const login_1 = __importDefault(require("./api/login")); +const logout_1 = __importDefault(require("./api/logout")); +const token_1 = __importDefault(require("./api/token")); +const loginInfo_1 = __importDefault(require("./api/loginInfo")); +const constants_1 = require("./constants"); const recipeImplementation_1 = __importDefault(require("./recipeImplementation")); const utils_1 = require("./utils"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config) { super(recipeId, appInfo); - this.handleAPIRequest = async (_id, _tenantId, _req, _res, _path, _method, _userContext) => { - // let options = { - // config: this.config, - // recipeId: this.getRecipeId(), - // isInServerlessEnv: this.isInServerlessEnv, - // recipeImplementation: this.recipeInterfaceImpl, - // req, - // res, - // }; - throw new Error("Not implemented"); + this.idTokenBuilders = []; + this.handleAPIRequest = async (id, _tenantId, req, res, _path, _method, userContext) => { + let options = { + config: this.config, + recipeId: this.getRecipeId(), + isInServerlessEnv: this.isInServerlessEnv, + recipeImplementation: this.recipeInterfaceImpl, + req, + res, + }; + if (id === constants_1.LOGIN_PATH) { + return login_1.default(this.apiImpl, options, userContext); + } + if (id === constants_1.LOGOUT_PATH) { + return logout_1.default(this.apiImpl, options, userContext); + } + if (id === constants_1.CONSENT_PATH) { + return consent_1.default(this.apiImpl, options, userContext); + } + if (id === constants_1.TOKEN_PATH) { + return token_1.default(this.apiImpl, options, userContext); + } + if (id === constants_1.AUTH_PATH) { + return auth_1.default(this.apiImpl, options, userContext); + } + if (id === constants_1.LOGIN_INFO_PATH) { + return loginInfo_1.default(this.apiImpl, options, userContext); + } + throw new Error("Should never come here: handleAPIRequest called with unknown id"); }; this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); this.isInServerlessEnv = isInServerlessEnv; @@ -47,7 +74,8 @@ class Recipe extends recipeModule_1.default { recipeImplementation_1.default( querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, - appInfo + appInfo, + this.getDefaultIdTokenPayload ) ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); @@ -58,6 +86,9 @@ class Recipe extends recipeModule_1.default { } } /* Init functions */ + static getInstance() { + return Recipe.instance; + } static getInstanceOrThrowError() { if (Recipe.instance !== undefined) { return Recipe.instance; @@ -82,7 +113,62 @@ class Recipe extends recipeModule_1.default { } /* RecipeModule functions */ getAPIsHandled() { - return []; + return [ + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.LOGIN_PATH), + id: constants_1.LOGIN_PATH, + disabled: this.apiImpl.loginPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.LOGIN_PATH), + id: constants_1.LOGIN_PATH, + disabled: this.apiImpl.loginGET === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.LOGOUT_PATH), + id: constants_1.LOGOUT_PATH, + disabled: this.apiImpl.logoutPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.LOGOUT_PATH), + id: constants_1.LOGOUT_PATH, + disabled: this.apiImpl.logoutGET === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.CONSENT_PATH), + id: constants_1.CONSENT_PATH, + disabled: this.apiImpl.consentPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.CONSENT_PATH), + id: constants_1.CONSENT_PATH, + disabled: this.apiImpl.consentGET === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.TOKEN_PATH), + id: constants_1.TOKEN_PATH, + disabled: this.apiImpl.tokenPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.AUTH_PATH), + id: constants_1.AUTH_PATH, + disabled: this.apiImpl.authGET === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.LOGIN_INFO_PATH), + id: constants_1.LOGIN_INFO_PATH, + disabled: this.apiImpl.loginInfoGET === undefined, + }, + ]; } handleError(error, _, __, _userContext) { throw error; @@ -93,6 +179,27 @@ class Recipe extends recipeModule_1.default { isErrorFromThisRecipe(err) { return error_1.default.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; } + async getDefaultIdTokenPayload(user, scopes, userContext) { + let payload = {}; + if (scopes.includes("email")) { + payload.email = user === null || user === void 0 ? void 0 : user.emails[0]; + payload.email_verified = user.loginMethods.some( + (lm) => lm.hasSameEmailAs(user === null || user === void 0 ? void 0 : user.emails[0]) && lm.verified + ); + } + if (scopes.includes("phoneNumber")) { + payload.phoneNumber = user === null || user === void 0 ? void 0 : user.phoneNumbers[0]; + payload.phoneNumber_verified = user.loginMethods.some( + (lm) => + lm.hasSamePhoneNumberAs(user === null || user === void 0 ? void 0 : user.phoneNumbers[0]) && + lm.verified + ); + } + for (const fn of this.idTokenBuilders) { + payload = Object.assign(Object.assign({}, payload), await fn(user, scopes, userContext)); + } + return payload; + } } exports.default = Recipe; Recipe.RECIPE_ID = "oauth2"; diff --git a/lib/build/recipe/oauth2/recipeImplementation.d.ts b/lib/build/recipe/oauth2/recipeImplementation.d.ts index d2a1542a2..0029a9016 100644 --- a/lib/build/recipe/oauth2/recipeImplementation.d.ts +++ b/lib/build/recipe/oauth2/recipeImplementation.d.ts @@ -1,9 +1,10 @@ // @ts-nocheck import { Querier } from "../../querier"; import { NormalisedAppinfo } from "../../types"; -import { RecipeInterface, TypeNormalisedInput } from "./types"; +import { RecipeInterface, TypeNormalisedInput, PayloadBuilderFunction } from "./types"; export default function getRecipeInterface( querier: Querier, _config: TypeNormalisedInput, - _appInfo: NormalisedAppinfo + _appInfo: NormalisedAppinfo, + getDefaultIdTokenPayload: PayloadBuilderFunction ): RecipeInterface; diff --git a/lib/build/recipe/oauth2/recipeImplementation.js b/lib/build/recipe/oauth2/recipeImplementation.js index 03852ae4b..ac5e17e13 100644 --- a/lib/build/recipe/oauth2/recipeImplementation.js +++ b/lib/build/recipe/oauth2/recipeImplementation.js @@ -20,25 +20,290 @@ var __importDefault = }; Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); +const querier_1 = require("../../querier"); const utils_1 = require("../../utils"); const OAuth2Client_1 = require("./OAuth2Client"); -function getRecipeInterface(querier, _config, _appInfo) { +const __1 = require("../.."); +function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload) { return { + getLoginRequest: async function (input) { + const resp = await querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/oauth2/admin/oauth2/auth/requests/login"), + { login_challenge: input.challenge }, + input.userContext + ); + return { + challenge: resp.data.challenge, + client: OAuth2Client_1.OAuth2Client.fromAPIResponse(resp.data.client), + oidcContext: resp.data.oidc_context, + requestUrl: resp.data.request_url, + requestedAccessTokenAudience: resp.data.requested_access_token_audience, + requestedScope: resp.data.requested_scope, + sessionId: resp.data.session_id, + skip: resp.data.skip, + subject: resp.data.subject, + }; + }, + acceptLoginRequest: async function (input) { + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/login/accept`), + { + acr: input.acr, + amr: input.amr, + context: input.context, + extend_session_lifespan: input.extendSessionLifespan, + force_subject_identifier: input.forceSubjectIdentifier, + identity_provider_session_id: input.identityProviderSessionId, + remember: input.remember, + remember_for: input.rememberFor, + subject: input.subject, + }, + { + login_challenge: input.challenge, + }, + input.userContext + ); + return { + // TODO: FIXME!!! + redirectTo: resp.data.redirect_to.replace( + querier_1.hydraPubDomain, + _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + ), + }; + }, + rejectLoginRequest: async function (input) { + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/login/reject`), + { + error: input.error.error, + error_debug: input.error.errorDebug, + error_description: input.error.errorDescription, + error_hint: input.error.errorHint, + status_code: input.error.statusCode, + }, + { + login_challenge: input.challenge, + }, + input.userContext + ); + return { + // TODO: FIXME!!! + redirectTo: resp.data.redirect_to.replace( + querier_1.hydraPubDomain, + _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + ), + }; + }, + getConsentRequest: async function (input) { + const resp = await querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/oauth2/admin/oauth2/auth/requests/consent"), + { consent_challenge: input.challenge }, + input.userContext + ); + return { + acr: resp.data.acr, + amr: resp.data.amr, + challenge: resp.data.challenge, + client: OAuth2Client_1.OAuth2Client.fromAPIResponse(resp.data.client), + context: resp.data.context, + loginChallenge: resp.data.login_challenge, + loginSessionId: resp.data.login_session_id, + oidcContext: resp.data.oidc_context, + requestUrl: resp.data.request_url, + requestedAccessTokenAudience: resp.data.requested_access_token_audience, + requestedScope: resp.data.requested_scope, + skip: resp.data.skip, + subject: resp.data.subject, + }; + }, + acceptConsentRequest: async function (input) { + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/consent/accept`), + { + context: input.context, + grant_access_token_audience: input.grantAccessTokenAudience, + grant_scope: input.grantScope, + handled_at: input.handledAt, + remember: input.remember, + remember_for: input.rememberFor, + session: input.session, + }, + { + consent_challenge: input.challenge, + }, + input.userContext + ); + return { + // TODO: FIXME!!! + redirectTo: resp.data.redirect_to.replace( + querier_1.hydraPubDomain, + _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + ), + }; + }, + rejectConsentRequest: async function (input) { + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/consent/reject`), + { + error: input.error.error, + error_debug: input.error.errorDebug, + error_description: input.error.errorDescription, + error_hint: input.error.errorHint, + status_code: input.error.statusCode, + }, + { + consent_challenge: input.challenge, + }, + input.userContext + ); + return { + // TODO: FIXME!!! + redirectTo: resp.data.redirect_to.replace( + querier_1.hydraPubDomain, + _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + ), + }; + }, + getLogoutRequest: async function (input) { + const resp = await querier.sendGetRequest( + new normalisedURLPath_1.default("/recipe/oauth2/admin/oauth2/auth/requests/logout"), + { logout_challenge: input.challenge }, + input.userContext + ); + return { + challenge: resp.data.challenge, + client: OAuth2Client_1.OAuth2Client.fromAPIResponse(resp.data.client), + requestUrl: resp.data.request_url, + rpInitiated: resp.data.rp_initiated, + sid: resp.data.sid, + subject: resp.data.subject, + }; + }, + acceptLogoutRequest: async function (input) { + const resp = await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/consent/logout/accept`), + {}, + { + logout_challenge: input.challenge, + }, + input.userContext + ); + return { + // TODO: FIXME!!! + redirectTo: resp.data.redirect_to.replace( + querier_1.hydraPubDomain, + _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + ), + }; + }, + rejectLogoutRequest: async function (input) { + await querier.sendPutRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/consent/logout/reject`), + {}, + { + logout_challenge: input.challenge, + }, + input.userContext + ); + }, + authorization: async function (input) { + var _a, _b, _c, _d; + const resp = await querier.sendGetRequestWithResponseHeaders( + new normalisedURLPath_1.default(`/recipe/oauth2/pub/auth`), + input.params, + { + Cookie: `${input.cookies}`, + }, + input.userContext + ); + const redirectTo = resp.headers.get("Location"); + if (redirectTo === undefined) { + throw new Error(resp.body); + } + const redirectToURL = new URL(redirectTo); + const consentChallenge = redirectToURL.searchParams.get("consent_challenge"); + if (consentChallenge !== null && input.session !== undefined) { + const consentRequest = await this.getConsentRequest({ + challenge: consentChallenge, + userContext: input.userContext, + }); + const user = await __1.getUser(input.session.getUserId()); + if (!user) { + throw new Error("Should not happen"); + } + if ( + consentRequest.skip || + ((_a = consentRequest.client) === null || _a === void 0 ? void 0 : _a.skipConsent) + ) { + const idToken = this.buildIdTokenPayload({ + user, + session: input.session, + defaultPayload: await getDefaultIdTokenPayload( + user, + (_b = consentRequest.requestedScope) !== null && _b !== void 0 ? _b : [], + input.userContext + ), + scopes: consentRequest.requestedScope || [], + userContext: input.userContext, + }); + const accessTokenPayload = this.buildAccessTokenPayload({ + user, + session: input.session, + defaultPayload: input.session.getAccessTokenPayload(input.userContext), + userContext: input.userContext, + scopes: consentRequest.requestedScope || [], + }); + const consentRes = await this.acceptConsentRequest( + Object.assign(Object.assign({}, input), { + challenge: consentRequest.challenge, + grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, + grantScope: consentRequest.requestedScope, + session: { + id_token: idToken, + access_token: accessTokenPayload, + }, + }) + ); + return { + redirectTo: consentRes.redirectTo, + setCookie: (_c = resp.headers.get("set-cookie")) !== null && _c !== void 0 ? _c : undefined, + }; + } + } + return { + redirectTo, + setCookie: (_d = resp.headers.get("set-cookie")) !== null && _d !== void 0 ? _d : undefined, + }; + }, + token: async function (input) { + const body = new FormData(); // TODO: we ideally want to avoid using formdata, the core can do the translation + for (const key in input.body) { + body.append(key, input.body[key]); + } + const res = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/pub/token`), + body, + input.userContext + ); + return res.data; + }, getOAuth2Clients: async function (input, userContext) { - let response = await querier.sendGetRequest( + var _a; + let response = await querier.sendGetRequestWithResponseHeaders( new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients`), Object.assign(Object.assign({}, utils_1.transformObjectKeys(input, "snake-case")), { page_token: input.paginationToken, }), + {}, userContext ); - if (response.status === "OK") { + if (response.body.status === "OK") { // Pagination info is in the Link header, containing comma-separated links: // "first", "next" (if applicable). // Example: Link: ; rel="first", ; rel="next" // We parse the nextPaginationToken from the Link header using RegExp let nextPaginationToken; - const linkHeader = response.headers.get("link"); + const linkHeader = (_a = response.headers.get("link")) !== null && _a !== void 0 ? _a : ""; const nextLinkMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/); if (nextLinkMatch) { const url = nextLinkMatch[1]; @@ -47,14 +312,14 @@ function getRecipeInterface(querier, _config, _appInfo) { } return { status: "OK", - clients: response.data.map((client) => OAuth2Client_1.OAuth2Client.fromAPIResponse(client)), + clients: response.body.data.map((client) => OAuth2Client_1.OAuth2Client.fromAPIResponse(client)), nextPaginationToken, }; } else { return { status: "ERROR", - error: response.data.error, - errorHint: response.data.errorHint, + error: response.body.data.error, + errorHint: response.body.data.errorHint, }; } }, @@ -123,6 +388,15 @@ function getRecipeInterface(querier, _config, _appInfo) { }; } }, + buildAccessTokenPayload: async function (input) { + return input.defaultPayload; + }, + buildIdTokenPayload: async function (input) { + return input.defaultPayload; + }, + buildUserInfo: async function (input) { + return input.user.toJson(); // Proper impl + }, }; } exports.default = getRecipeInterface; diff --git a/lib/build/recipe/oauth2/types.d.ts b/lib/build/recipe/oauth2/types.d.ts index cbbe7e76f..b95d8f50e 100644 --- a/lib/build/recipe/oauth2/types.d.ts +++ b/lib/build/recipe/oauth2/types.d.ts @@ -1,8 +1,10 @@ // @ts-nocheck import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; -import { NonNullableProperties, UserContext } from "../../types"; +import { GeneralErrorResponse, JSONObject, NonNullableProperties, UserContext } from "../../types"; +import { SessionContainerInterface } from "../session/types"; import { OAuth2Client } from "./OAuth2Client"; +import { User } from "../../user"; export declare type TypeInput = { override?: { functions?: ( @@ -29,7 +31,125 @@ export declare type APIOptions = { req: BaseRequest; res: BaseResponse; }; +export declare type ErrorOAuth2 = { + error: string; + errorDescription: string; + errorDebug?: string; + errorHint?: string; + statusCode?: number; +}; +export declare type ConsentRequest = { + acr?: string; + amr?: string[]; + challenge: string; + client?: OAuth2Client; + context?: JSONObject; + loginChallenge?: string; + loginSessionId?: string; + oidcContext?: any; + requestUrl?: string; + requestedAccessTokenAudience?: string[]; + requestedScope?: string[]; + skip?: boolean; + subject?: string; +}; +export declare type LoginRequest = { + challenge: string; + client: OAuth2Client; + oidcContext?: any; + requestUrl: string; + requestedAccessTokenAudience?: string[]; + requestedScope?: string[]; + sessionId?: string; + skip: boolean; + subject: string; +}; +export declare type LogoutRequest = { + challenge: string; + client: OAuth2Client; + requestUrl: string; + rpInitiated: boolean; + sid: string; + subject: string; +}; +export declare type TokenInfo = { + access_token: string; + expires_in: number; + id_token: string; + refresh_token: string; + scope: string; + token_type: string; +}; +export declare type LoginInfo = { + clientName: string; + tosUri: string; + policyUri: string; + logoUri: string; + metadata?: Record | null; +}; export declare type RecipeInterface = { + authorization(input: { + params: any; + cookies: string | undefined; + session: SessionContainerInterface | undefined; + userContext: UserContext; + }): Promise<{ + redirectTo: string; + setCookie: string | undefined; + }>; + token(input: { body: any; userContext: UserContext }): Promise; + getConsentRequest(input: { challenge: string; userContext: UserContext }): Promise; + acceptConsentRequest(input: { + challenge: string; + context?: any; + grantAccessTokenAudience?: string[]; + grantScope?: string[]; + handledAt?: string[]; + remember?: boolean; + rememberFor?: number; + session?: any; + userContext: UserContext; + }): Promise<{ + redirectTo: string; + }>; + rejectConsentRequest(input: { + challenge: string; + error: ErrorOAuth2; + userContext: UserContext; + }): Promise<{ + redirectTo: string; + }>; + getLoginRequest(input: { challenge: string; userContext: UserContext }): Promise; + acceptLoginRequest(input: { + challenge: string; + acr?: string; + amr?: string[]; + context?: any; + extendSessionLifespan?: boolean; + forceSubjectIdentifier?: string; + identityProviderSessionId?: string; + remember?: boolean; + rememberFor?: number; + subject: string; + userContext: UserContext; + }): Promise<{ + redirectTo: string; + }>; + rejectLoginRequest(input: { + challenge: string; + error: ErrorOAuth2; + userContext: UserContext; + }): Promise<{ + redirectTo: string; + }>; + getLogoutRequest(input: { challenge: string; userContext: UserContext }): Promise; + acceptLogoutRequest(input: { + challenge: string; + userContext: UserContext; + }): Promise<{ + redirectTo: string; + }>; + rejectLogoutRequest(input: { challenge: string; userContext: UserContext }): Promise; getOAuth2Clients( input: GetOAuth2ClientsInput, userContext: UserContext @@ -86,8 +206,145 @@ export declare type RecipeInterface = { errorHint: string; } >; + buildAccessTokenPayload(input: { + user: User; + session: SessionContainerInterface; + scopes: string[]; + defaultPayload: JSONObject; + userContext: UserContext; + }): Promise; + buildIdTokenPayload(input: { + user: User; + session: SessionContainerInterface; + scopes: string[]; + defaultPayload: JSONObject; + userContext: UserContext; + }): Promise; + buildUserInfo(input: { + user: User; + accessTokenPayload: JSONObject; + scopes: string[]; + defaultInfo: JSONObject; + userContext: UserContext; + }): Promise; +}; +export declare type APIInterface = { + loginGET: + | undefined + | ((input: { + loginChallenge: string; + options: APIOptions; + session?: SessionContainerInterface; + userContext: UserContext; + }) => Promise< + | { + redirectTo: string; + } + | GeneralErrorResponse + >); + loginPOST: + | undefined + | ((input: { + loginChallenge: string; + accept: boolean; + session: SessionContainerInterface; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + redirectTo: string; + } + | GeneralErrorResponse + >); + logoutGET: + | undefined + | ((input: { + logoutChallenge: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + redirectTo: string; + } + | GeneralErrorResponse + >); + logoutPOST: + | undefined + | ((input: { + logoutChallenge: string; + accept: boolean; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + redirectTo: string; + } + | GeneralErrorResponse + >); + consentGET: + | undefined + | ((input: { + consentChallenge: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + redirectTo: string; + } + | GeneralErrorResponse + >); + consentPOST: + | undefined + | ((input: { + consentChallenge: string; + accept: boolean; + grantScope: string[]; + remember: boolean; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + redirectTo: string; + } + | GeneralErrorResponse + >); + authGET: + | undefined + | ((input: { + params: any; + cookie: string | undefined; + session: SessionContainerInterface | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + redirectTo: string; + setCookie: string | undefined; + } + | ErrorOAuth2 + | GeneralErrorResponse + >); + tokenPOST: + | undefined + | ((input: { + body: any; + options: APIOptions; + userContext: UserContext; + }) => Promise); + loginInfoGET: + | undefined + | ((input: { + loginChallenge: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + info: LoginInfo; + } + | GeneralErrorResponse + >); }; -export declare type APIInterface = {}; export declare type OAuth2ClientOptions = { clientId: string; clientSecret: string; @@ -183,3 +440,8 @@ export declare type UpdateOAuth2ClientInput = NonNullableProperties< export declare type DeleteOAuth2ClientInput = { clientId: string; }; +export declare type PayloadBuilderFunction = ( + user: User, + scopes: string[], + userContext: UserContext +) => Promise; diff --git a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js index b308bfffb..7e2fdb593 100644 --- a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js +++ b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js @@ -14,6 +14,11 @@ async function getOpenIdDiscoveryConfiguration(apiImplementation, options, userC utils_1.send200Response(options.res, { issuer: result.issuer, jwks_uri: result.jwks_uri, + authorization_endpoint: result.authorization_endpoint, + token_endpoint: result.token_endpoint, + subject_types_supported: result.subject_types_supported, + id_token_signing_alg_values_supported: result.id_token_signing_alg_values_supported, + response_types_supported: result.response_types_supported, }); } else { utils_1.send200Response(options.res, result); diff --git a/lib/build/recipe/openid/index.d.ts b/lib/build/recipe/openid/index.d.ts index e94fd0092..a7f961b19 100644 --- a/lib/build/recipe/openid/index.d.ts +++ b/lib/build/recipe/openid/index.d.ts @@ -8,6 +8,11 @@ export default class OpenIdRecipeWrapper { status: "OK"; issuer: string; jwks_uri: string; + authorization_endpoint: string; + token_endpoint: string; + subject_types_supported: string[]; + id_token_signing_alg_values_supported: string[]; + response_types_supported: string[]; }>; static createJWT( payload?: any, diff --git a/lib/build/recipe/openid/recipe.js b/lib/build/recipe/openid/recipe.js index fd7a67398..4e0eb29e1 100644 --- a/lib/build/recipe/openid/recipe.js +++ b/lib/build/recipe/openid/recipe.js @@ -79,7 +79,7 @@ class OpenIdRecipe extends recipeModule_1.default { override: this.config.override.jwtFeature, }); let builder = new supertokens_js_override_1.default( - recipeImplementation_1.default(this.config, this.jwtRecipe.recipeInterfaceImpl) + recipeImplementation_1.default(this.config, this.jwtRecipe.recipeInterfaceImpl, appInfo) ); this.recipeImplementation = builder.override(this.config.override.functions).build(); let apiBuilder = new supertokens_js_override_1.default(implementation_1.default()); diff --git a/lib/build/recipe/openid/recipeImplementation.d.ts b/lib/build/recipe/openid/recipeImplementation.d.ts index d4698099c..be9ecbb29 100644 --- a/lib/build/recipe/openid/recipeImplementation.d.ts +++ b/lib/build/recipe/openid/recipeImplementation.d.ts @@ -1,7 +1,9 @@ // @ts-nocheck import { RecipeInterface, TypeNormalisedInput } from "./types"; import { RecipeInterface as JWTRecipeInterface } from "../jwt/types"; +import { NormalisedAppinfo } from "../../types"; export default function getRecipeInterface( config: TypeNormalisedInput, - jwtRecipeImplementation: JWTRecipeInterface + jwtRecipeImplementation: JWTRecipeInterface, + appInfo: NormalisedAppinfo ): RecipeInterface; diff --git a/lib/build/recipe/openid/recipeImplementation.js b/lib/build/recipe/openid/recipeImplementation.js index 0edb22162..d3b582d2e 100644 --- a/lib/build/recipe/openid/recipeImplementation.js +++ b/lib/build/recipe/openid/recipeImplementation.js @@ -7,7 +7,8 @@ var __importDefault = Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const constants_1 = require("../jwt/constants"); -function getRecipeInterface(config, jwtRecipeImplementation) { +const constants_2 = require("../oauth2/constants"); +function getRecipeInterface(config, jwtRecipeImplementation, appInfo) { return { getOpenIdDiscoveryConfiguration: async function () { let issuer = config.issuerDomain.getAsStringDangerous() + config.issuerPath.getAsStringDangerous(); @@ -16,10 +17,16 @@ function getRecipeInterface(config, jwtRecipeImplementation) { config.issuerPath .appendPath(new normalisedURLPath_1.default(constants_1.GET_JWKS_API)) .getAsStringDangerous(); + const apiBasePath = appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); return { status: "OK", issuer, jwks_uri, + authorization_endpoint: apiBasePath + constants_2.AUTH_PATH, + token_endpoint: apiBasePath + constants_2.TOKEN_PATH, + subject_types_supported: ["public"], + id_token_signing_alg_values_supported: ["RS256"], + response_types_supported: ["code", "id_token", "id_token token"], }; }, createJWT: async function ({ payload, validitySeconds, useStaticSigningKey, userContext }) { diff --git a/lib/build/recipe/openid/types.d.ts b/lib/build/recipe/openid/types.d.ts index 5b907a8d5..d99d7d529 100644 --- a/lib/build/recipe/openid/types.d.ts +++ b/lib/build/recipe/openid/types.d.ts @@ -66,6 +66,11 @@ export declare type APIInterface = { status: "OK"; issuer: string; jwks_uri: string; + authorization_endpoint: string; + token_endpoint: string; + subject_types_supported: string[]; + id_token_signing_alg_values_supported: string[]; + response_types_supported: string[]; } | GeneralErrorResponse >); @@ -77,6 +82,11 @@ export declare type RecipeInterface = { status: "OK"; issuer: string; jwks_uri: string; + authorization_endpoint: string; + token_endpoint: string; + subject_types_supported: string[]; + id_token_signing_alg_values_supported: string[]; + response_types_supported: string[]; }>; createJWT(input: { payload?: any; diff --git a/lib/build/recipe/passwordless/recipeImplementation.js b/lib/build/recipe/passwordless/recipeImplementation.js index 8a3782d64..f75801f1f 100644 --- a/lib/build/recipe/passwordless/recipeImplementation.js +++ b/lib/build/recipe/passwordless/recipeImplementation.js @@ -6,6 +6,7 @@ var __importDefault = }; Object.defineProperty(exports, "__esModule", { value: true }); const recipe_1 = __importDefault(require("../accountlinking/recipe")); +const recipe_2 = __importDefault(require("../emailverification/recipe")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const logger_1 = require("../../logger"); const user_1 = require("../../user"); @@ -147,9 +148,42 @@ function getRecipeInterface(querier) { return { status: "OK" }; }, updateUser: async function (input) { + const accountLinking = recipe_1.default.getInstance(); + if (input.email) { + const user = await __1.getUser(input.recipeUserId.getAsString(), input.userContext); + if (user === undefined) { + return { status: "UNKNOWN_USER_ID_ERROR" }; + } + const evInstance = recipe_2.default.getInstance(); + let isEmailVerified = false; + if (evInstance) { + isEmailVerified = await evInstance.recipeInterfaceImpl.isEmailVerified({ + recipeUserId: input.recipeUserId, + email: input.email, + userContext: input.userContext, + }); + } + const isEmailChangeAllowed = await accountLinking.isEmailChangeAllowed({ + user, + isVerified: isEmailVerified, + newEmail: input.email, + session: undefined, + userContext: input.userContext, + }); + if (!isEmailChangeAllowed.allowed) { + return { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", + reason: + isEmailChangeAllowed.reason === "ACCOUNT_TAKEOVER_RISK" + ? "New email cannot be applied to existing account because of account takeover risks." + : "New email cannot be applied to existing account because of there is another primary user with the same email address.", + }; + } + } let response = await querier.sendPutRequest( new normalisedURLPath_1.default(`/recipe/user`), copyAndRemoveUserContextAndTenantId(input), + {}, input.userContext ); if (response.status !== "OK") { diff --git a/lib/build/recipe/session/index.d.ts b/lib/build/recipe/session/index.d.ts index 55fbe988b..b96971a45 100644 --- a/lib/build/recipe/session/index.d.ts +++ b/lib/build/recipe/session/index.d.ts @@ -177,6 +177,11 @@ export default class SessionWrapper { status: "OK"; issuer: string; jwks_uri: string; + authorization_endpoint: string; + token_endpoint: string; + subject_types_supported: string[]; + id_token_signing_alg_values_supported: string[]; + response_types_supported: string[]; }>; static fetchAndSetClaim( sessionHandle: string, diff --git a/lib/build/recipe/session/sessionFunctions.js b/lib/build/recipe/session/sessionFunctions.js index 1f3d91722..90c828ce8 100644 --- a/lib/build/recipe/session/sessionFunctions.js +++ b/lib/build/recipe/session/sessionFunctions.js @@ -453,6 +453,7 @@ async function updateSessionDataInDatabase(helpers, sessionHandle, newSessionDat sessionHandle, userDataInDatabase: newSessionData, }, + {}, userContext ); if (response.status === "UNAUTHORISED") { @@ -470,6 +471,7 @@ async function updateAccessTokenPayload(helpers, sessionHandle, newAccessTokenPa sessionHandle, userDataInJWT: newAccessTokenPayload, }, + {}, userContext ); if (response.status === "UNAUTHORISED") { diff --git a/lib/build/recipe/session/sessionRequestFunctions.js b/lib/build/recipe/session/sessionRequestFunctions.js index dac80fb8e..1d33897a7 100644 --- a/lib/build/recipe/session/sessionRequestFunctions.js +++ b/lib/build/recipe/session/sessionRequestFunctions.js @@ -257,7 +257,7 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn throw new error_1.default({ message: "Refresh token not found. Are you sending the refresh token in the request?", payload: { - clearTokens: false, + clearTokens: true, }, type: error_1.default.UNAUTHORISED, }); @@ -280,7 +280,7 @@ async function refreshSessionInRequest({ res, req, userContext, config, recipeIn message: "anti-csrf check failed. Please pass 'rid: \"session\"' header in the request.", type: error_1.default.UNAUTHORISED, payload: { - clearTokens: false, // see https://github.com/supertokens/supertokens-node/issues/141 + clearTokens: true, // see https://github.com/supertokens/supertokens-node/issues/141 }, }); } diff --git a/lib/build/recipe/thirdparty/api/implementation.js b/lib/build/recipe/thirdparty/api/implementation.js index 860c226d2..e688b95ae 100644 --- a/lib/build/recipe/thirdparty/api/implementation.js +++ b/lib/build/recipe/thirdparty/api/implementation.js @@ -5,9 +5,8 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -const recipe_1 = __importDefault(require("../../accountlinking/recipe")); const emailverification_1 = __importDefault(require("../../emailverification")); -const recipe_2 = __importDefault(require("../../emailverification/recipe")); +const recipe_1 = __importDefault(require("../../emailverification/recipe")); const authUtils_1 = require("../../../authUtils"); const logger_1 = require("../../../logger"); function getAPIInterface() { @@ -36,7 +35,7 @@ function getAPIInterface() { "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_023)", }, }; - const { provider, tenantId, options, session, userContext } = input; + const { provider, tenantId, options, userContext } = input; let oAuthTokensToUse = {}; if ("redirectURIInfo" in input && input.redirectURIInfo !== undefined) { oAuthTokensToUse = await provider.exchangeAuthCodeForOAuthTokens({ @@ -92,84 +91,19 @@ function getAPIInterface() { // We do this check here and not in the recipe function cause we want to keep the // recipe function checks to a minimum so that the dev has complete control of // what they can do. - // The isEmailChangeAllowed function takes in a isVerified boolean. Now, even though - // we already have that from the input, that's just what the provider says. If the - // provider says that the email is NOT verified, it could have been that the email + // The isEmailChangeAllowed and preAuthChecks functions take an isVerified boolean. + // Now, even though we already have that from the input, that's just what the provider says. + // If the provider says that the email is NOT verified, it could have been that the email // is verified on the user's account via supertokens on a previous sign in / up. // So we just check that as well before calling isEmailChangeAllowed const recipeUserId = authenticatingUser.loginMethod.recipeUserId; - if (!emailInfo.isVerified && recipe_2.default.getInstance() !== undefined) { + if (!emailInfo.isVerified && recipe_1.default.getInstance() !== undefined) { emailInfo.isVerified = await emailverification_1.default.isEmailVerified( recipeUserId, emailInfo.id, userContext ); } - /** - * In this API, during only a sign in, we check for isEmailChangeAllowed first, then - * change the email by calling the recipe function, then check if is sign in allowed. - * This may result in a few states where email change is allowed, but still, sign in - * is not allowed: - * - * Various outcomes of isSignInAllowed vs isEmailChangeAllowed - * isSignInAllowed result: - * - is primary user -> TRUE - * - is recipe user - * - other recipe user exists - * - no -> TRUE - * - yes - * - email verified -> TRUE - * - email unverified -> FALSE - * - other primary user exists - * - no -> TRUE - * - yes - * - email verification status - * - this && primary -> TRUE - * - !this && !primary -> FALSE - * - this && !primary -> FALSE - * - !this && primary -> FALSE - * - * isEmailChangeAllowed result: - * - is primary user -> TRUE - * - is recipe user - * - other recipe user exists - * - no -> TRUE - * - yes - * - email verified -> TRUE - * - email unverified -> TRUE - * - other primary user exists - * - no -> TRUE - * - yes - * - email verification status - * - this && primary -> TRUE - * - !this && !primary -> FALSE - * - this && !primary -> TRUE - * - !this && primary -> FALSE - * - * Based on the above, isEmailChangeAllowed can return true, but isSignInAllowed will return false - * in the following situations: - * - If a recipe user is signing in with a new email, other recipe users with the same email exist, - * and one of them is unverfied. In this case, the email change will happen in the social login - * recipe, but the user will not be able to login anyway. - * - * - If the recipe user is signing in with a new email, there exists a primary user with the same - * email, but this new email is verified for the recipe user already, but the primary user's email - * is not verified. - */ - let isEmailChangeAllowed = await recipe_1.default.getInstance().isEmailChangeAllowed({ - user: authenticatingUser.user, - isVerified: emailInfo.isVerified, - newEmail: emailInfo.id, - session, - userContext, - }); - if (!isEmailChangeAllowed) { - return { - status: "SIGN_IN_UP_NOT_ALLOWED", - reason: - "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)", - }; - } } const preAuthChecks = await authUtils_1.AuthUtils.preAuthChecks({ authenticatingAccountInfo: { @@ -218,6 +152,10 @@ function getAPIInterface() { tenantId, userContext, }); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + // In this case we do not need to do mapping, since the recipe function already has the right response shape. + return response; + } if (response.status !== "OK") { logger_1.logDebugMessage("signInUpPOST: erroring out because signInUp returned " + response.status); return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( diff --git a/lib/build/recipe/thirdparty/recipeImplementation.js b/lib/build/recipe/thirdparty/recipeImplementation.js index 90f1b2dbf..df6887fc9 100644 --- a/lib/build/recipe/thirdparty/recipeImplementation.js +++ b/lib/build/recipe/thirdparty/recipeImplementation.js @@ -24,6 +24,32 @@ function getRecipeImplementation(querier, providers) { session, userContext, }) { + const accountLinking = recipe_1.default.getInstance(); + const users = await __1.listUsersByAccountInfo( + tenantId, + { thirdParty: { id: thirdPartyId, userId: thirdPartyUserId } }, + false, + userContext + ); + const user = users[0]; + if (user !== undefined) { + const isEmailChangeAllowed = await accountLinking.isEmailChangeAllowed({ + user, + isVerified: isVerified, + newEmail: email, + session, + userContext: userContext, + }); + if (!isEmailChangeAllowed.allowed) { + return { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", + reason: + isEmailChangeAllowed.reason === "PRIMARY_USER_CONFLICT" + ? "Email already associated with another primary user." + : "New email cannot be applied to existing account because of account takeover risks.", + }; + } + } let response = await querier.sendPostRequest( new normalisedURLPath_1.default(`/${tenantId}/recipe/signinup`), { @@ -89,7 +115,9 @@ function getRecipeImplementation(querier, providers) { return { status: "SIGN_IN_UP_NOT_ALLOWED", reason: - "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)", + response.reason === "Email already associated with another primary user." + ? "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)" + : "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_024)", }; } if (response.status === "OK") { diff --git a/lib/build/recipe/totp/recipeImplementation.js b/lib/build/recipe/totp/recipeImplementation.js index 5465f884c..5ad44874b 100644 --- a/lib/build/recipe/totp/recipeImplementation.js +++ b/lib/build/recipe/totp/recipeImplementation.js @@ -100,6 +100,7 @@ function getRecipeInterface(querier, config) { existingDeviceName: input.existingDeviceName, newDeviceName: input.newDeviceName, }, + {}, input.userContext ); }, diff --git a/lib/build/recipe/usermetadata/recipeImplementation.js b/lib/build/recipe/usermetadata/recipeImplementation.js index e3aa25a5f..2fd78898b 100644 --- a/lib/build/recipe/usermetadata/recipeImplementation.js +++ b/lib/build/recipe/usermetadata/recipeImplementation.js @@ -36,6 +36,7 @@ function getRecipeInterface(querier) { userId, metadataUpdate, }, + {}, userContext ); }, diff --git a/lib/build/recipe/userroles/recipeImplementation.js b/lib/build/recipe/userroles/recipeImplementation.js index ca12893f9..47dbdc1ba 100644 --- a/lib/build/recipe/userroles/recipeImplementation.js +++ b/lib/build/recipe/userroles/recipeImplementation.js @@ -29,6 +29,7 @@ function getRecipeInterface(querier) { `/${tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId}/recipe/user/role` ), { userId, role }, + {}, userContext ); }, @@ -63,6 +64,7 @@ function getRecipeInterface(querier) { return querier.sendPutRequest( new normalisedURLPath_1.default("/recipe/role"), { role, permissions }, + {}, userContext ); }, diff --git a/lib/build/supertokens.js b/lib/build/supertokens.js index 30486abfa..07b52a330 100644 --- a/lib/build/supertokens.js +++ b/lib/build/supertokens.js @@ -135,6 +135,7 @@ class SuperTokens { userIdType: input.userIdType, externalUserIdInfo: input.externalUserIdInfo, }, + {}, input.userContext ); } else { diff --git a/lib/build/utils.js b/lib/build/utils.js index d5d36c203..913001f36 100644 --- a/lib/build/utils.js +++ b/lib/build/utils.js @@ -58,6 +58,7 @@ const doFetch = async (input, init) => { ); init = { cache: "no-cache", + redirect: "manual", }; } else { if (init.cache === undefined) { @@ -65,6 +66,7 @@ const doFetch = async (input, init) => { processState_1.PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH ); init.cache = "no-cache"; + init.redirect = "manual"; } } const fetchFunction = typeof fetch !== "undefined" ? fetch : cross_fetch_1.default; diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index 7bcff5193..828a4cd6b 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -22,9 +22,9 @@ import { logDebugMessage } from "./logger"; import { UserContext } from "./types"; import { NetworkInterceptor } from "./types"; -const hydraPubDomain = process.env.HYDRA_PUB ?? "http://localhost:4444"; // This will be used as a domain for paths starting with hydraPubPathPrefix +export const hydraPubDomain = process.env.HYDRA_PUB ?? "http://localhost:4444"; // This will be used as a domain for paths starting with hydraPubPathPrefix const hydraAdmDomain = process.env.HYDRA_ADM ?? "http://localhost:4445"; // This will be used as a domain for paths starting with hydraAdmPathPrefix -const hydraPubPathPrefix = "/recipe/oauth2/pub"; // Replaced with "/oauth2" when sending the request (/recipe/oauth2/pub/token -> /oauth2/token) +export const hydraPubPathPrefix = "/recipe/oauth2/pub"; // Replaced with "/oauth2" when sending the request (/recipe/oauth2/pub/token -> /oauth2/token) const hydraAdmPathPrefix = "/recipe/oauth2/admin"; // Replaced with "/admin" when sending the request (/recipe/oauth2/admin/clients -> /admin/clients) export class Querier { @@ -130,6 +130,8 @@ export class Querier { // path should start with "/" sendPostRequest = async (path: NormalisedURLPath, body: any, userContext: UserContext): Promise => { this.invalidateCoreCallCache(userContext); + // TODO: remove FormData + const isForm = body !== undefined && body instanceof FormData; const { body: respBody } = await this.sendRequestHelper( path, @@ -138,8 +140,10 @@ export class Querier { let apiVersion = await this.getAPIVersion(); let headers: any = { "cdi-version": apiVersion, - "content-type": "application/json; charset=utf-8", }; + if (!isForm) { + headers["content-type"] = "application/json; charset=utf-8"; + } if (Querier.apiKey !== undefined) { headers = { ...headers, @@ -170,7 +174,7 @@ export class Querier { } return doFetch(url, { method: "POST", - body: body !== undefined ? JSON.stringify(body) : undefined, + body: isForm ? body : body !== undefined ? JSON.stringify(body) : undefined, headers, }); }, @@ -319,12 +323,15 @@ export class Querier { finalURL.search = searchParams.toString(); // Update cache and return - let response = await doFetch(finalURL.toString(), { method: "GET", headers, }); + if (response.status === 302) { + return response; + } + if (response.status === 200 && !Querier.disableCache) { // If the request was successful, we save the result into the cache // plus we update the cache tag @@ -349,6 +356,7 @@ export class Querier { sendGetRequestWithResponseHeaders = async ( path: NormalisedURLPath, params: Record, + inpHeaders: Record | undefined, userContext: UserContext ): Promise<{ body: any; headers: Headers }> => { return await this.sendRequestHelper( @@ -356,7 +364,9 @@ export class Querier { "GET", async (url: string) => { let apiVersion = await this.getAPIVersion(); - let headers: any = { "cdi-version": apiVersion }; + let headers: any = inpHeaders ?? {}; + headers["cdi-version"] = apiVersion; + if (Querier.apiKey !== undefined) { headers = { ...headers, @@ -400,7 +410,12 @@ export class Querier { }; // path should start with "/" - sendPutRequest = async (path: NormalisedURLPath, body: any, userContext: UserContext): Promise => { + sendPutRequest = async ( + path: NormalisedURLPath, + body: any, + params: Record, + userContext: UserContext + ): Promise => { this.invalidateCoreCallCache(userContext); const { body: respBody } = await this.sendRequestHelper( @@ -428,6 +443,7 @@ export class Querier { method: "put", headers: headers, body: body, + params: params, }, userContext ); @@ -438,7 +454,13 @@ export class Querier { } } - return doFetch(url, { + const finalURL = new URL(url); + const searchParams = new URLSearchParams( + Object.entries(params).filter(([_, value]) => value !== undefined) as string[][] + ); + finalURL.search = searchParams.toString(); + + return doFetch(finalURL.toString(), { method: "PUT", body: body !== undefined ? JSON.stringify(body) : undefined, headers, @@ -549,11 +571,13 @@ export class Querier { if (strPath.startsWith(hydraPubPathPrefix)) { currentDomain = hydraPubDomain; + currentBasePath = ""; strPath = strPath.replace(hydraPubPathPrefix, "/oauth2"); } if (strPath.startsWith(hydraAdmPathPrefix)) { currentDomain = hydraAdmDomain; + currentBasePath = ""; strPath = strPath.replace(hydraAdmPathPrefix, "/admin"); } @@ -652,5 +676,5 @@ async function handleHydraAPICall(response: Response) { }; } - return { body: { status: response.ok ? "OK" : "ERROR", headers: response.headers } }; + return { body: { status: response.ok ? "OK" : "ERROR" }, headers: response.headers }; } diff --git a/lib/ts/recipe/accountlinking/index.ts b/lib/ts/recipe/accountlinking/index.ts index fb82f3138..995a341e0 100644 --- a/lib/ts/recipe/accountlinking/index.ts +++ b/lib/ts/recipe/accountlinking/index.ts @@ -165,13 +165,14 @@ export default class Wrapper { ) { const user = await getUser(recipeUserId.getAsString(), userContext); - return await Recipe.getInstance().isEmailChangeAllowed({ + const res = await Recipe.getInstance().isEmailChangeAllowed({ user, newEmail, isVerified, session, userContext: getUserContext(userContext), }); + return res.allowed; } } diff --git a/lib/ts/recipe/accountlinking/recipe.ts b/lib/ts/recipe/accountlinking/recipe.ts index 2b40199e7..5f7ae50c4 100644 --- a/lib/ts/recipe/accountlinking/recipe.ts +++ b/lib/ts/recipe/accountlinking/recipe.ts @@ -528,7 +528,7 @@ export default class Recipe extends RecipeModule { isVerified: boolean; session: SessionContainerInterface | undefined; userContext: UserContext; - }): Promise => { + }): Promise<{ allowed: true } | { allowed: false; reason: "PRIMARY_USER_CONFLICT" | "ACCOUNT_TAKEOVER_RISK" }> => { /** * The purpose of this function is to check that if a recipe user ID's email * can be changed or not. There are two conditions for when it can't be changed: @@ -544,15 +544,15 @@ export default class Recipe extends RecipeModule { * in account take over if this recipe user is malicious. */ - let user = input.user; + let inputUser = input.user; - if (user === undefined) { + if (inputUser === undefined) { throw new Error("Passed in recipe user id does not exist"); } - for (const tenantId of user.tenantIds) { + for (const tenantId of inputUser.tenantIds) { let existingUsersWithNewEmail = await this.recipeInterfaceImpl.listUsersByAccountInfo({ - tenantId: user.tenantIds[0], + tenantId: inputUser.tenantIds[0], accountInfo: { email: input.newEmail, }, @@ -560,23 +560,62 @@ export default class Recipe extends RecipeModule { userContext: input.userContext, }); - let primaryUserForNewEmail = existingUsersWithNewEmail.filter((u) => u.isPrimaryUser); - if (primaryUserForNewEmail.length > 1) { + let otherUsersWithNewEmail = existingUsersWithNewEmail.filter((u) => u.id !== inputUser!.id); + + let otherPrimaryUserForNewEmail = otherUsersWithNewEmail.filter((u) => u.isPrimaryUser); + if (otherPrimaryUserForNewEmail.length > 1) { throw new Error("You found a bug. Please report it on github.com/supertokens/supertokens-node"); } - if (user.isPrimaryUser) { + if (inputUser.isPrimaryUser) { // this is condition one from the above comment. - if (primaryUserForNewEmail.length === 1 && primaryUserForNewEmail[0].id !== user.id) { + if (otherPrimaryUserForNewEmail.length !== 0) { logDebugMessage( - "isEmailChangeAllowed: returning false cause email change will lead to two primary users having same email" + `isEmailChangeAllowed: returning false cause email change will lead to two primary users having same email on ${tenantId}` ); - return false; + return { allowed: false, reason: "PRIMARY_USER_CONFLICT" }; + } + if (input.isVerified) { + logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified and doesn't belong to any other primary user` + ); + continue; + } + if (inputUser.loginMethods.some((lm) => lm.hasSameEmailAs(input.newEmail) && lm.verified)) { + logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary, new email is verified in another login method and doesn't belong to any other primary user` + ); + continue; + } + if (otherUsersWithNewEmail.length === 0) { + logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary and the new email doesn't belong to any other user (primary or non-primary)` + ); + continue; + } + + const shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( + otherUsersWithNewEmail[0].loginMethods[0], + inputUser, + input.session, + tenantId, + input.userContext + ); + if (!shouldDoAccountLinking.shouldAutomaticallyLink) { + logDebugMessage(`isEmailChangeAllowed: can change on ${tenantId} cause linking is disabled`); + continue; + } + + if (!shouldDoAccountLinking.shouldRequireVerification) { + logDebugMessage( + `isEmailChangeAllowed: can change on ${tenantId} cause linking is doesn't require email verification` + ); + continue; } logDebugMessage( - `isEmailChangeAllowed: can change on ${tenantId} cause input user is primary and new email doesn't belong to any other primary user` + `isEmailChangeAllowed: returning false because the user hasn't verified the new email address and there exists another user with it on ${tenantId} and linking requires verification` ); - continue; + return { allowed: false, reason: "ACCOUNT_TAKEOVER_RISK" }; } else { if (input.isVerified) { logDebugMessage( @@ -585,17 +624,17 @@ export default class Recipe extends RecipeModule { continue; } - if (user.loginMethods[0].hasSameEmailAs(input.newEmail)) { + if (inputUser.loginMethods[0].hasSameEmailAs(input.newEmail)) { logDebugMessage( `isEmailChangeAllowed: can change on ${tenantId} cause input user is not a primary and new email is same as the older one` ); continue; } - if (primaryUserForNewEmail.length === 1) { + if (otherPrimaryUserForNewEmail.length === 1) { let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( - user.loginMethods[0], - primaryUserForNewEmail[0], + inputUser.loginMethods[0], + otherPrimaryUserForNewEmail[0], input.session, tenantId, input.userContext @@ -618,7 +657,7 @@ export default class Recipe extends RecipeModule { logDebugMessage( "isEmailChangeAllowed: returning false cause input user is not a primary there exists a primary user exists with the new email." ); - return false; + return { allowed: false, reason: "ACCOUNT_TAKEOVER_RISK" }; } logDebugMessage( @@ -630,7 +669,7 @@ export default class Recipe extends RecipeModule { logDebugMessage( "isEmailChangeAllowed: returning true cause email change can happen on all tenants the user is part of" ); - return true; + return { allowed: true }; }; verifyEmailForRecipeUserIfLinkedAccountsAreVerified = async (input: { @@ -780,9 +819,20 @@ export default class Recipe extends RecipeModule { return { status: "NO_LINK" }; } - if (shouldDoAccountLinking.shouldRequireVerification && !inputUser.loginMethods[0].verified) { + const accountInfoVerifiedInPrimUser = primaryUserThatCanBeLinkedToTheInputUser.loginMethods.some( + (lm) => + (inputUser.loginMethods[0].email !== undefined && + lm.hasSameEmailAs(inputUser.loginMethods[0].email)) || + (inputUser.loginMethods[0].phoneNumber !== undefined && + lm.hasSamePhoneNumberAs(inputUser.loginMethods[0].phoneNumber) && + lm.verified) + ); + if ( + shouldDoAccountLinking.shouldRequireVerification && + (!inputUser.loginMethods[0].verified || !accountInfoVerifiedInPrimUser) + ) { logDebugMessage( - "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified" + "tryLinkingByAccountInfoOrCreatePrimaryUser: not linking because shouldRequireVerification is true but the login method is not verified in the new or the primary user" ); return { status: "NO_LINK" }; } diff --git a/lib/ts/recipe/emailpassword/api/implementation.ts b/lib/ts/recipe/emailpassword/api/implementation.ts index b17c2cb2c..843e270cd 100644 --- a/lib/ts/recipe/emailpassword/api/implementation.ts +++ b/lib/ts/recipe/emailpassword/api/implementation.ts @@ -165,6 +165,34 @@ export default function getAPIImplementation(): APIInterface { ); } + // Next we check if there is any login method in which the input email is verified. + // If that is the case, then it's proven that the user owns the email and we can + // trust linking of the email password account. + let emailVerified = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + return lm.hasSameEmailAs(email) && lm.verified; + }) !== undefined; + + // finally, we check if the primary user has any other email / phone number + // associated with this account - and if it does, then it means that + // there is a risk of account takeover, so we do not allow the token to be generated + let hasOtherEmailOrPhone = + primaryUserAssociatedWithEmail.loginMethods.find((lm) => { + // we do the extra undefined check below cause + // hasSameEmailAs returns false if the lm.email is undefined, and + // we want to check that the email is different as opposed to email + // not existing in lm. + return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; + }) !== undefined; + + if (!emailVerified && hasOtherEmailOrPhone) { + return { + status: "PASSWORD_RESET_NOT_ALLOWED", + reason: + "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + }; + } + let shouldDoAccountLinkingResponse = await AccountLinking.getInstance().config.shouldDoAutomaticAccountLinking( emailPasswordAccount !== undefined ? emailPasswordAccount @@ -280,54 +308,10 @@ export default function getAPIImplementation(): APIInterface { ); } - // Now we start the required security checks. First we check if the primary user - // it has just one linked account. And if that's true, then we continue - // cause then there is no scope for account takeover - if (primaryUserAssociatedWithEmail.loginMethods.length === 1) { - return await generateAndSendPasswordResetToken( - primaryUserAssociatedWithEmail.id, - emailPasswordAccount.recipeUserId - ); - } - - // Next we check if there is any login method in which the input email is verified. - // If that is the case, then it's proven that the user owns the email and we can - // trust linking of the email password account. - let emailVerified = - primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - return lm.hasSameEmailAs(email) && lm.verified; - }) !== undefined; - - if (emailVerified) { - return await generateAndSendPasswordResetToken( - primaryUserAssociatedWithEmail.id, - emailPasswordAccount.recipeUserId - ); - } - - // finally, we check if the primary user has any other email / phone number - // associated with this account - and if it does, then it means that - // there is a risk of account takeover, so we do not allow the token to be generated - let hasOtherEmailOrPhone = - primaryUserAssociatedWithEmail.loginMethods.find((lm) => { - // we do the extra undefined check below cause - // hasSameEmailAs returns false if the lm.email is undefined, and - // we want to check that the email is different as opposed to email - // not existing in lm. - return (lm.email !== undefined && !lm.hasSameEmailAs(email)) || lm.phoneNumber !== undefined; - }) !== undefined; - if (hasOtherEmailOrPhone) { - return { - status: "PASSWORD_RESET_NOT_ALLOWED", - reason: - "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", - }; - } else { - return await generateAndSendPasswordResetToken( - primaryUserAssociatedWithEmail.id, - emailPasswordAccount.recipeUserId - ); - } + return await generateAndSendPasswordResetToken( + primaryUserAssociatedWithEmail.id, + emailPasswordAccount.recipeUserId + ); }, passwordResetPOST: async function ({ formFields, diff --git a/lib/ts/recipe/emailpassword/recipeImplementation.ts b/lib/ts/recipe/emailpassword/recipeImplementation.ts index e1f44b073..618f2eb2e 100644 --- a/lib/ts/recipe/emailpassword/recipeImplementation.ts +++ b/lib/ts/recipe/emailpassword/recipeImplementation.ts @@ -1,5 +1,6 @@ import { RecipeInterface, TypeNormalisedInput } from "./types"; import AccountLinking from "../accountlinking/recipe"; +import EmailVerification from "../emailverification/recipe"; import { Querier } from "../../querier"; import NormalisedURLPath from "../../normalisedURLPath"; import { getUser } from "../.."; @@ -252,6 +253,41 @@ export default function getRecipeInterface( } | { status: "PASSWORD_POLICY_VIOLATED_ERROR"; failureReason: string } > { + const accountLinking = AccountLinking.getInstance(); + if (input.email) { + const user = await getUser(input.recipeUserId.getAsString(), input.userContext); + + if (user === undefined) { + return { status: "UNKNOWN_USER_ID_ERROR" }; + } + + const evInstance = EmailVerification.getInstance(); + + let isEmailVerified = false; + if (evInstance) { + isEmailVerified = await evInstance.recipeInterfaceImpl.isEmailVerified({ + recipeUserId: input.recipeUserId, + email: input.email, + userContext: input.userContext, + }); + } + const isEmailChangeAllowed = await accountLinking.isEmailChangeAllowed({ + user, + isVerified: isEmailVerified, + newEmail: input.email, + session: undefined, + userContext: input.userContext, + }); + if (!isEmailChangeAllowed.allowed) { + return { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", + reason: + isEmailChangeAllowed.reason === "ACCOUNT_TAKEOVER_RISK" + ? "New email cannot be applied to existing account because of account takeover risks." + : "New email cannot be applied to existing account because of there is another primary user with the same email address.", + }; + } + } if (input.applyPasswordPolicy || input.applyPasswordPolicy === undefined) { let formFields = getEmailPasswordConfig().signUpFeature.formFields; if (input.password !== undefined) { @@ -284,6 +320,7 @@ export default function getRecipeInterface( email: input.email, password: input.password, }, + {}, input.userContext ); diff --git a/lib/ts/recipe/jwt/api/implementation.ts b/lib/ts/recipe/jwt/api/implementation.ts index 4308c7cf1..5a029d4a1 100644 --- a/lib/ts/recipe/jwt/api/implementation.ts +++ b/lib/ts/recipe/jwt/api/implementation.ts @@ -31,6 +31,16 @@ export default function getAPIImplementation(): APIInterface { options.res.setHeader("Cache-Control", `max-age=${resp.validityInSeconds}, must-revalidate`, false); } + const oauth2 = require("../../oauth2").getInstance(); + // TODO: dirty hack until we get core support + if (oauth2 !== undefined) { + const oauth2JWKSRes = await fetch("http://localhost:4444/.well-known/jwks.json"); + if (oauth2JWKSRes.ok) { + const oauth2RespBody = await oauth2JWKSRes.json(); + resp.keys = resp.keys.concat(oauth2RespBody.keys); + } + } + return { keys: resp.keys, }; diff --git a/lib/ts/recipe/jwt/recipeImplementation.ts b/lib/ts/recipe/jwt/recipeImplementation.ts index dc8656124..fa937c881 100644 --- a/lib/ts/recipe/jwt/recipeImplementation.ts +++ b/lib/ts/recipe/jwt/recipeImplementation.ts @@ -78,6 +78,7 @@ export default function getRecipeInterface( const { body, headers } = await querier.sendGetRequestWithResponseHeaders( new NormalisedURLPath("/.well-known/jwks.json"), {}, + undefined, userContext ); let validityInSeconds = defaultJWKSMaxAge; diff --git a/lib/ts/recipe/multitenancy/recipeImplementation.ts b/lib/ts/recipe/multitenancy/recipeImplementation.ts index c6a655b84..c311b41c3 100644 --- a/lib/ts/recipe/multitenancy/recipeImplementation.ts +++ b/lib/ts/recipe/multitenancy/recipeImplementation.ts @@ -16,6 +16,7 @@ export default function getRecipeInterface(querier: Querier): RecipeInterface { tenantId, ...config, }, + {}, userContext ); @@ -68,6 +69,7 @@ export default function getRecipeInterface(querier: Querier): RecipeInterface { config, skipValidation, }, + {}, userContext ); return response; diff --git a/lib/ts/recipe/oauth2/api/auth.ts b/lib/ts/recipe/oauth2/api/auth.ts new file mode 100644 index 000000000..50eba1b00 --- /dev/null +++ b/lib/ts/recipe/oauth2/api/auth.ts @@ -0,0 +1,71 @@ +/* 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. + */ + +import { send200Response } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +import setCookieParser from "set-cookie-parser"; +import Session from "../../session"; + +export default async function authGET( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.authGET === undefined) { + return false; + } + const origURL = options.req.getOriginalURL(); + const splitURL = origURL.split("?"); + const params = new URLSearchParams(splitURL[1]); + let session; + try { + session = await Session.getSession(options.req, options.res, { sessionRequired: false }, userContext); + } catch { + // TODO: explain + // ignore + } + + let response = await apiImplementation.authGET({ + options, + params: Object.fromEntries(params.entries()), + cookie: options.req.getHeaderValue("cookie"), + session, + userContext, + }); + if ("redirectTo" in response) { + // TODO: + 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); + } else { + send200Response(options.res, response); + } + return true; +} diff --git a/lib/ts/recipe/oauth2/api/consent.ts b/lib/ts/recipe/oauth2/api/consent.ts new file mode 100644 index 000000000..efc254432 --- /dev/null +++ b/lib/ts/recipe/oauth2/api/consent.ts @@ -0,0 +1,66 @@ +/* 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. + */ + +import { normaliseHttpMethod, send200Response } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; + +// TODO: separate post and get? +export default async function consent( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise { + if (normaliseHttpMethod(options.req.getMethod()) === "post") { + if (apiImplementation.consentPOST === undefined) { + return false; + } + const reqBody = await options.req.getJSONBody(); + let response = await apiImplementation.consentPOST({ + options, + accept: reqBody.accept, + consentChallenge: reqBody.consentChallenge, + grantScope: reqBody.grantScope, + remember: reqBody.remember, + userContext, + }); + if ("status" in response) { + send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } else { + if (apiImplementation.consentGET === undefined) { + return false; + } + const consentChallenge = + options.req.getKeyValueFromQuery("consentChallenge") ?? + options.req.getKeyValueFromQuery("consent_challenge"); + if (consentChallenge === undefined) { + throw new Error("TODO"); + } + let response = await apiImplementation.consentGET({ + options, + consentChallenge, + userContext, + }); + if ("status" in response) { + send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } + return true; +} diff --git a/lib/ts/recipe/oauth2/api/implementation.ts b/lib/ts/recipe/oauth2/api/implementation.ts index 3cba9819d..69c865874 100644 --- a/lib/ts/recipe/oauth2/api/implementation.ts +++ b/lib/ts/recipe/oauth2/api/implementation.ts @@ -13,8 +13,180 @@ * under the License. */ +import SuperTokens from "../../../supertokens"; import { APIInterface } from "../types"; export default function getAPIImplementation(): APIInterface { - return {}; + return { + loginGET: async ({ loginChallenge, options, session, userContext }) => { + const request = await options.recipeImplementation.getLoginRequest({ + challenge: loginChallenge, + userContext, + }); + + if (request.skip) { + const accept = await options.recipeImplementation.acceptLoginRequest({ + challenge: loginChallenge, + subject: request.subject, + userContext, + }); + return { redirectTo: accept.redirectTo }; + } else if (session) { + if (session.getUserId() !== request.subject) { + // TODO? + } + const accept = await options.recipeImplementation.acceptLoginRequest({ + challenge: loginChallenge, + subject: session.getUserId(), + identityProviderSessionId: session.getHandle(), + userContext, + }); + return { redirectTo: accept.redirectTo }; + } + const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; + const websiteDomain = appInfo + .getOrigin({ + request: options.req, + userContext: userContext, + }) + .getAsStringDangerous(); + const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); + // TODO: + return { + redirectTo: + websiteDomain + + websiteBasePath + + `?hint=${request.oidcContext?.login_hint ?? ""}&loginChallenge=${loginChallenge}`, + }; + }, + loginPOST: async ({ loginChallenge, accept, options, session, userContext }) => { + const res = accept + ? await options.recipeImplementation.acceptLoginRequest({ + challenge: loginChallenge, + subject: session.getUserId(), + userContext, + }) + : await options.recipeImplementation.rejectLoginRequest({ + challenge: loginChallenge, + error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, + userContext, + }); + return { redirectTo: res.redirectTo }; + }, + + logoutGET: async ({ logoutChallenge, options, userContext }) => { + const request = await options.recipeImplementation.getLogoutRequest({ + challenge: logoutChallenge, + userContext, + }); + + const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; + return { + redirectTo: + appInfo + .getOrigin({ + request: options.req, + userContext: userContext, + }) + .getAsStringDangerous() + + appInfo.websiteBasePath.getAsStringDangerous() + + `/logout?challenge=${request.challenge}`, + }; + }, + + logoutPOST: async ({ logoutChallenge, accept, options, userContext }) => { + if (accept) { + const res = await options.recipeImplementation.acceptLogoutRequest({ + challenge: logoutChallenge, + userContext, + }); + return { redirectTo: res.redirectTo }; + } + await options.recipeImplementation.rejectLogoutRequest({ + challenge: logoutChallenge, + // error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, + userContext, + }); + const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; + return { + redirectTo: + appInfo + .getOrigin({ + request: options.req, + userContext: userContext, + }) + .getAsStringDangerous() + appInfo.websiteBasePath.getAsStringDangerous(), + }; + }, + + consentGET: async ({ consentChallenge, options, userContext }) => { + const request = await options.recipeImplementation.getConsentRequest({ + challenge: consentChallenge, + userContext, + }); + + const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; + return { + redirectTo: + appInfo + .getOrigin({ + request: options.req, + userContext: userContext, + }) + .getAsStringDangerous() + + appInfo.websiteBasePath.getAsStringDangerous() + + `/consent?challenge=${request.challenge}&scopes=${request.requestedScope}&client=${request.client}&`, + }; + }, + + consentPOST: async ({ consentChallenge, accept, remember, grantScope, options, userContext }) => { + const request = await options.recipeImplementation.getConsentRequest({ + challenge: consentChallenge, + userContext, + }); + const res = accept + ? await options.recipeImplementation.acceptConsentRequest({ + challenge: consentChallenge, + grantAccessTokenAudience: request.requestedAccessTokenAudience, + remember, + grantScope, + userContext, + }) + : await options.recipeImplementation.rejectConsentRequest({ + challenge: consentChallenge, + error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, + userContext, + }); + return { redirectTo: res.redirectTo }; + }, + authGET: async (input) => { + const res = await input.options.recipeImplementation.authorization({ + params: input.params, + cookies: input.cookie, + session: input.session, + userContext: input.userContext, + }); + return res; + }, + tokenPOST: async (input) => { + return input.options.recipeImplementation.token({ body: input.body, userContext: input.userContext }); + }, + loginInfoGET: async ({ loginChallenge, options, userContext }) => { + const { client } = await options.recipeImplementation.getLoginRequest({ + challenge: loginChallenge, + userContext, + }); + + return { + status: "OK", + info: { + clientName: client.clientName, + tosUri: client.tosUri, + policyUri: client.policyUri, + logoUri: client.logoUri, + metadata: client.metadata, + }, + }; + }, + }; } diff --git a/lib/ts/recipe/oauth2/api/login.ts b/lib/ts/recipe/oauth2/api/login.ts new file mode 100644 index 000000000..f1912059a --- /dev/null +++ b/lib/ts/recipe/oauth2/api/login.ts @@ -0,0 +1,76 @@ +/* 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. + */ + +import { normaliseHttpMethod, send200Response } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import Session from "../../session"; +import { UserContext } from "../../../types"; + +// TODO: separate post and get? +export default async function login( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise { + if (normaliseHttpMethod(options.req.getMethod()) === "post") { + if (apiImplementation.loginPOST === undefined) { + return false; + } + const session = await Session.getSession(options.req, options.res, { sessionRequired: true }, userContext); + const reqBody = await options.req.getJSONBody(); + let response = await apiImplementation.loginPOST({ + options, + accept: reqBody.accept, + loginChallenge: reqBody.loginChallenge, + session, + userContext, + }); + if ("status" in response) { + send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } else { + if (apiImplementation.loginGET === undefined) { + return false; + } + + let session; + try { + session = await Session.getSession(options.req, options.res, { sessionRequired: false }, userContext); + } catch { + // TODO: Claim validation failure + } + + // TODO: take only one + const loginChallenge = + options.req.getKeyValueFromQuery("login_challenge") ?? options.req.getKeyValueFromQuery("loginChallenge"); + if (loginChallenge === undefined) { + throw new Error("TODO"); + } + let response = await apiImplementation.loginGET({ + options, + loginChallenge, + session, + userContext, + }); + if ("status" in response) { + send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } + return true; +} diff --git a/lib/ts/recipe/oauth2/api/loginInfo.ts b/lib/ts/recipe/oauth2/api/loginInfo.ts new file mode 100644 index 000000000..b0d6241be --- /dev/null +++ b/lib/ts/recipe/oauth2/api/loginInfo.ts @@ -0,0 +1,44 @@ +/* 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. + */ + +import { send200Response } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; + +export default async function loginInfoGET( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.loginInfoGET === undefined) { + return false; + } + + const loginChallenge = + options.req.getKeyValueFromQuery("login_challenge") ?? options.req.getKeyValueFromQuery("loginChallenge"); + + if (loginChallenge === undefined) { + throw new Error("TODO"); + } + + let response = await apiImplementation.loginInfoGET({ + options, + loginChallenge, + userContext, + }); + + send200Response(options.res, response); + return true; +} diff --git a/lib/ts/recipe/oauth2/api/logout.ts b/lib/ts/recipe/oauth2/api/logout.ts new file mode 100644 index 000000000..c827a42e4 --- /dev/null +++ b/lib/ts/recipe/oauth2/api/logout.ts @@ -0,0 +1,63 @@ +/* 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. + */ + +import { normaliseHttpMethod, send200Response } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; + +// TODO: separate post and get? +export default async function logout( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise { + if (normaliseHttpMethod(options.req.getMethod()) === "post") { + if (apiImplementation.logoutPOST === undefined) { + return false; + } + const reqBody = await options.req.getJSONBody(); + let response = await apiImplementation.logoutPOST({ + options, + accept: reqBody.accept, + logoutChallenge: reqBody.logoutChallenge, + userContext, + }); + if ("status" in response) { + send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } else { + if (apiImplementation.logoutGET === undefined) { + return false; + } + + const logoutChallenge = options.req.getKeyValueFromQuery("logoutChallenge"); + if (logoutChallenge === undefined) { + throw new Error("TODO"); + } + let response = await apiImplementation.logoutGET({ + options, + logoutChallenge, + userContext, + }); + if ("status" in response) { + send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); + } + } + return true; +} diff --git a/lib/ts/recipe/oauth2/api/token.ts b/lib/ts/recipe/oauth2/api/token.ts new file mode 100644 index 000000000..91f2d3cc6 --- /dev/null +++ b/lib/ts/recipe/oauth2/api/token.ts @@ -0,0 +1,37 @@ +/* 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. + */ + +import { send200Response } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; + +export default async function tokenPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.tokenPOST === undefined) { + return false; + } + + let response = await apiImplementation.tokenPOST({ + options, + body: await options.req.getFormData(), + userContext, + }); + + send200Response(options.res, response); + return true; +} diff --git a/lib/ts/recipe/oauth2/constants.ts b/lib/ts/recipe/oauth2/constants.ts index 172ca6987..ddfdef4d6 100644 --- a/lib/ts/recipe/oauth2/constants.ts +++ b/lib/ts/recipe/oauth2/constants.ts @@ -14,3 +14,10 @@ */ export const OAUTH2_BASE_PATH = "/oauth2/"; + +export const LOGIN_PATH = "/oauth2/login"; +export const LOGOUT_PATH = "/oauth2/logout"; +export const CONSENT_PATH = "/oauth2/consent"; +export const AUTH_PATH = "/oauth2/auth"; +export const TOKEN_PATH = "/oauth2/token"; +export const LOGIN_INFO_PATH = "/oauth2/login/info"; diff --git a/lib/ts/recipe/oauth2/index.ts b/lib/ts/recipe/oauth2/index.ts index 6962a0e09..c1bf5cef3 100644 --- a/lib/ts/recipe/oauth2/index.ts +++ b/lib/ts/recipe/oauth2/index.ts @@ -13,7 +13,7 @@ * under the License. */ -import { UserContext } from "../../types"; +import { getUserContext } from "../../utils"; import Recipe from "./recipe"; import { APIInterface, @@ -28,17 +28,29 @@ import { export default class Wrapper { static init = Recipe.init; - static async getOAuth2Clients(input: GetOAuth2ClientsInput, userContext: UserContext) { - return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Clients(input, userContext); + static async getOAuth2Clients(input: GetOAuth2ClientsInput, userContext?: Record) { + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Clients( + input, + getUserContext(userContext) + ); } - static async createOAuth2Client(input: CreateOAuth2ClientInput, userContext: UserContext) { - return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createOAuth2Client(input, userContext); + static async createOAuth2Client(input: CreateOAuth2ClientInput, userContext?: Record) { + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createOAuth2Client( + input, + getUserContext(userContext) + ); } - static async updateOAuth2Client(input: UpdateOAuth2ClientInput, userContext: UserContext) { - return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.updateOAuth2Client(input, userContext); + static async updateOAuth2Client(input: UpdateOAuth2ClientInput, userContext?: Record) { + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.updateOAuth2Client( + input, + getUserContext(userContext) + ); } - static async deleteOAuth2Client(input: DeleteOAuth2ClientInput, userContext: UserContext) { - return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.deleteOAuth2Client(input, userContext); + static async deleteOAuth2Client(input: DeleteOAuth2ClientInput, userContext?: Record) { + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.deleteOAuth2Client( + input, + getUserContext(userContext) + ); } } diff --git a/lib/ts/recipe/oauth2/recipe.ts b/lib/ts/recipe/oauth2/recipe.ts index 1b0c5aa5d..5aa8a3888 100644 --- a/lib/ts/recipe/oauth2/recipe.ts +++ b/lib/ts/recipe/oauth2/recipe.ts @@ -19,16 +19,25 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import NormalisedURLPath from "../../normalisedURLPath"; import { Querier } from "../../querier"; import RecipeModule from "../../recipeModule"; -import { APIHandled, HTTPMethod, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; +import { APIHandled, HTTPMethod, JSONObject, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; +import authGET from "./api/auth"; +import consentAPI from "./api/consent"; import APIImplementation from "./api/implementation"; +import loginAPI from "./api/login"; +import logoutAPI from "./api/logout"; +import tokenPOST from "./api/token"; +import loginInfoGET from "./api/loginInfo"; +import { AUTH_PATH, CONSENT_PATH, LOGIN_INFO_PATH, LOGIN_PATH, LOGOUT_PATH, TOKEN_PATH } from "./constants"; import RecipeImplementation from "./recipeImplementation"; -import { APIInterface, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; +import { APIInterface, PayloadBuilderFunction, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; import { validateAndNormaliseUserInput } from "./utils"; import OverrideableBuilder from "supertokens-js-override"; +import { User } from "../../user"; export default class Recipe extends RecipeModule { static RECIPE_ID = "oauth2"; private static instance: Recipe | undefined = undefined; + private idTokenBuilders: PayloadBuilderFunction[] = []; config: TypeNormalisedInput; recipeInterfaceImpl: RecipeInterface; @@ -42,7 +51,12 @@ export default class Recipe extends RecipeModule { { let builder = new OverrideableBuilder( - RecipeImplementation(Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo) + RecipeImplementation( + Querier.getNewInstanceOrThrowError(recipeId), + this.config, + appInfo, + this.getDefaultIdTokenPayload + ) ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); } @@ -54,6 +68,9 @@ export default class Recipe extends RecipeModule { /* Init functions */ + static getInstance(): Recipe | undefined { + return Recipe.instance; + } static getInstanceOrThrowError(): Recipe { if (Recipe.instance !== undefined) { return Recipe.instance; @@ -82,28 +99,101 @@ export default class Recipe extends RecipeModule { /* RecipeModule functions */ getAPIsHandled(): APIHandled[] { - return []; + return [ + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(LOGIN_PATH), + id: LOGIN_PATH, + disabled: this.apiImpl.loginPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new NormalisedURLPath(LOGIN_PATH), + id: LOGIN_PATH, + disabled: this.apiImpl.loginGET === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(LOGOUT_PATH), + id: LOGOUT_PATH, + disabled: this.apiImpl.logoutPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new NormalisedURLPath(LOGOUT_PATH), + id: LOGOUT_PATH, + disabled: this.apiImpl.logoutGET === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(CONSENT_PATH), + id: CONSENT_PATH, + disabled: this.apiImpl.consentPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new NormalisedURLPath(CONSENT_PATH), + id: CONSENT_PATH, + disabled: this.apiImpl.consentGET === undefined, + }, + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(TOKEN_PATH), + id: TOKEN_PATH, + disabled: this.apiImpl.tokenPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new NormalisedURLPath(AUTH_PATH), + id: AUTH_PATH, + disabled: this.apiImpl.authGET === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new NormalisedURLPath(LOGIN_INFO_PATH), + id: LOGIN_INFO_PATH, + disabled: this.apiImpl.loginInfoGET === undefined, + }, + ]; } handleAPIRequest = async ( - _id: string, + id: string, _tenantId: string | undefined, - _req: BaseRequest, - _res: BaseResponse, + req: BaseRequest, + res: BaseResponse, _path: NormalisedURLPath, _method: HTTPMethod, - _userContext: UserContext + userContext: UserContext ): Promise => { - // let options = { - // config: this.config, - // recipeId: this.getRecipeId(), - // isInServerlessEnv: this.isInServerlessEnv, - // recipeImplementation: this.recipeInterfaceImpl, - // req, - // res, - // }; - - throw new Error("Not implemented"); + let options = { + config: this.config, + recipeId: this.getRecipeId(), + isInServerlessEnv: this.isInServerlessEnv, + recipeImplementation: this.recipeInterfaceImpl, + req, + res, + }; + + if (id === LOGIN_PATH) { + return loginAPI(this.apiImpl, options, userContext); + } + if (id === LOGOUT_PATH) { + return logoutAPI(this.apiImpl, options, userContext); + } + if (id === CONSENT_PATH) { + return consentAPI(this.apiImpl, options, userContext); + } + if (id === TOKEN_PATH) { + return tokenPOST(this.apiImpl, options, userContext); + } + if (id === AUTH_PATH) { + return authGET(this.apiImpl, options, userContext); + } + if (id === LOGIN_INFO_PATH) { + return loginInfoGET(this.apiImpl, options, userContext); + } + throw new Error("Should never come here: handleAPIRequest called with unknown id"); }; handleError(error: error, _: BaseRequest, __: BaseResponse, _userContext: UserContext): Promise { @@ -117,4 +207,27 @@ export default class Recipe extends RecipeModule { isErrorFromThisRecipe(err: any): err is error { return SuperTokensError.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; } + + async getDefaultIdTokenPayload(user: User, scopes: string[], userContext: UserContext) { + let payload: JSONObject = {}; + if (scopes.includes("email")) { + payload.email = user?.emails[0]; + payload.email_verified = user.loginMethods.some((lm) => lm.hasSameEmailAs(user?.emails[0]) && lm.verified); + } + if (scopes.includes("phoneNumber")) { + payload.phoneNumber = user?.phoneNumbers[0]; + payload.phoneNumber_verified = user.loginMethods.some( + (lm) => lm.hasSamePhoneNumberAs(user?.phoneNumbers[0]) && lm.verified + ); + } + + for (const fn of this.idTokenBuilders) { + payload = { + ...payload, + ...(await fn(user, scopes, userContext)), + }; + } + + return payload; + } } diff --git a/lib/ts/recipe/oauth2/recipeImplementation.ts b/lib/ts/recipe/oauth2/recipeImplementation.ts index 9a341d53c..0f5e62274 100644 --- a/lib/ts/recipe/oauth2/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2/recipeImplementation.ts @@ -14,36 +14,317 @@ */ import NormalisedURLPath from "../../normalisedURLPath"; -import { Querier } from "../../querier"; +import { Querier, hydraPubDomain } from "../../querier"; import { NormalisedAppinfo } from "../../types"; +import { + RecipeInterface, + TypeNormalisedInput, + ConsentRequest, + LoginRequest, + LogoutRequest, + PayloadBuilderFunction, +} from "./types"; import { toSnakeCase, transformObjectKeys } from "../../utils"; import { OAuth2Client } from "./OAuth2Client"; -import { RecipeInterface, TypeNormalisedInput } from "./types"; +import { getUser } from "../.."; export default function getRecipeInterface( querier: Querier, _config: TypeNormalisedInput, - _appInfo: NormalisedAppinfo + _appInfo: NormalisedAppinfo, + getDefaultIdTokenPayload: PayloadBuilderFunction ): RecipeInterface { return { + getLoginRequest: async function (this: RecipeInterface, input): Promise { + const resp = await querier.sendGetRequest( + new NormalisedURLPath("/recipe/oauth2/admin/oauth2/auth/requests/login"), + { login_challenge: input.challenge }, + input.userContext + ); + + return { + challenge: resp.data.challenge, + client: OAuth2Client.fromAPIResponse(resp.data.client), + oidcContext: resp.data.oidc_context, + requestUrl: resp.data.request_url, + requestedAccessTokenAudience: resp.data.requested_access_token_audience, + requestedScope: resp.data.requested_scope, + sessionId: resp.data.session_id, + skip: resp.data.skip, + subject: resp.data.subject, + }; + }, + acceptLoginRequest: async function (this: RecipeInterface, input): Promise<{ redirectTo: string }> { + const resp = await querier.sendPutRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/login/accept`), + { + acr: input.acr, + amr: input.amr, + context: input.context, + extend_session_lifespan: input.extendSessionLifespan, + force_subject_identifier: input.forceSubjectIdentifier, + identity_provider_session_id: input.identityProviderSessionId, + remember: input.remember, + remember_for: input.rememberFor, + subject: input.subject, + }, + { + login_challenge: input.challenge, + }, + input.userContext + ); + + return { + // TODO: FIXME!!! + redirectTo: resp.data.redirect_to.replace( + hydraPubDomain, + _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + ), + }; + }, + rejectLoginRequest: async function (this: RecipeInterface, input): Promise<{ redirectTo: string }> { + const resp = await querier.sendPutRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/login/reject`), + { + error: input.error.error, + error_debug: input.error.errorDebug, + error_description: input.error.errorDescription, + error_hint: input.error.errorHint, + status_code: input.error.statusCode, + }, + { + login_challenge: input.challenge, + }, + input.userContext + ); + + return { + // TODO: FIXME!!! + redirectTo: resp.data.redirect_to.replace( + hydraPubDomain, + _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + ), + }; + }, + getConsentRequest: async function (this: RecipeInterface, input): Promise { + const resp = await querier.sendGetRequest( + new NormalisedURLPath("/recipe/oauth2/admin/oauth2/auth/requests/consent"), + { consent_challenge: input.challenge }, + input.userContext + ); + + return { + acr: resp.data.acr, + amr: resp.data.amr, + challenge: resp.data.challenge, + client: OAuth2Client.fromAPIResponse(resp.data.client), + context: resp.data.context, + loginChallenge: resp.data.login_challenge, + loginSessionId: resp.data.login_session_id, + oidcContext: resp.data.oidc_context, + requestUrl: resp.data.request_url, + requestedAccessTokenAudience: resp.data.requested_access_token_audience, + requestedScope: resp.data.requested_scope, + skip: resp.data.skip, + subject: resp.data.subject, + }; + }, + acceptConsentRequest: async function (this: RecipeInterface, input): Promise<{ redirectTo: string }> { + const resp = await querier.sendPutRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/consent/accept`), + { + context: input.context, + grant_access_token_audience: input.grantAccessTokenAudience, + grant_scope: input.grantScope, + handled_at: input.handledAt, + remember: input.remember, + remember_for: input.rememberFor, + session: input.session, + }, + { + consent_challenge: input.challenge, + }, + input.userContext + ); + + return { + // TODO: FIXME!!! + redirectTo: resp.data.redirect_to.replace( + hydraPubDomain, + _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + ), + }; + }, + + rejectConsentRequest: async function (this: RecipeInterface, input) { + const resp = await querier.sendPutRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/consent/reject`), + { + error: input.error.error, + error_debug: input.error.errorDebug, + error_description: input.error.errorDescription, + error_hint: input.error.errorHint, + status_code: input.error.statusCode, + }, + { + consent_challenge: input.challenge, + }, + input.userContext + ); + + return { + // TODO: FIXME!!! + redirectTo: resp.data.redirect_to.replace( + hydraPubDomain, + _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + ), + }; + }, + + getLogoutRequest: async function (this: RecipeInterface, input): Promise { + const resp = await querier.sendGetRequest( + new NormalisedURLPath("/recipe/oauth2/admin/oauth2/auth/requests/logout"), + { logout_challenge: input.challenge }, + input.userContext + ); + + return { + challenge: resp.data.challenge, + client: OAuth2Client.fromAPIResponse(resp.data.client), + requestUrl: resp.data.request_url, + rpInitiated: resp.data.rp_initiated, + sid: resp.data.sid, + subject: resp.data.subject, + }; + }, + acceptLogoutRequest: async function (this: RecipeInterface, input): Promise<{ redirectTo: string }> { + const resp = await querier.sendPutRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/consent/logout/accept`), + {}, + { + logout_challenge: input.challenge, + }, + input.userContext + ); + + return { + // TODO: FIXME!!! + redirectTo: resp.data.redirect_to.replace( + hydraPubDomain, + _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + ), + }; + }, + rejectLogoutRequest: async function (this: RecipeInterface, input): Promise { + await querier.sendPutRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/consent/logout/reject`), + {}, + { + logout_challenge: input.challenge, + }, + input.userContext + ); + }, + authorization: async function (this: RecipeInterface, input) { + const resp = await querier.sendGetRequestWithResponseHeaders( + new NormalisedURLPath(`/recipe/oauth2/pub/auth`), + input.params, + { + Cookie: `${input.cookies}`, + }, + input.userContext + ); + + const redirectTo = resp.headers.get("Location")!; + if (redirectTo === undefined) { + throw new Error(resp.body); + } + const redirectToURL = new URL(redirectTo); + const consentChallenge = redirectToURL.searchParams.get("consent_challenge"); + if (consentChallenge !== null && input.session !== undefined) { + const consentRequest = await this.getConsentRequest({ + challenge: consentChallenge, + userContext: input.userContext, + }); + + const user = await getUser(input.session.getUserId()); + if (!user) { + throw new Error("Should not happen"); + } + if (consentRequest.skip || consentRequest.client?.skipConsent) { + const idToken = this.buildIdTokenPayload({ + user, + session: input.session, + defaultPayload: await getDefaultIdTokenPayload( + user, + consentRequest.requestedScope ?? [], + input.userContext + ), + scopes: consentRequest.requestedScope || [], + userContext: input.userContext, + }); + + const accessTokenPayload = this.buildAccessTokenPayload({ + user, + session: input.session, + defaultPayload: input.session.getAccessTokenPayload(input.userContext), // TODO: validate + userContext: input.userContext, + scopes: consentRequest.requestedScope || [], + }); + + const consentRes = await this.acceptConsentRequest({ + ...input, + challenge: consentRequest.challenge, + grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, + grantScope: consentRequest.requestedScope, + session: { + id_token: idToken, + access_token: accessTokenPayload, + }, + }); + + return { + redirectTo: consentRes.redirectTo, + setCookie: resp.headers.get("set-cookie") ?? undefined, + }; + } + } + return { redirectTo, setCookie: resp.headers.get("set-cookie") ?? undefined }; + }, + + token: async function (this: RecipeInterface, input) { + const body = new FormData(); // TODO: we ideally want to avoid using formdata, the core can do the translation + for (const key in input.body) { + body.append(key, input.body[key]); + } + const res = await querier.sendPostRequest( + new NormalisedURLPath(`/recipe/oauth2/pub/token`), + body, + input.userContext + ); + + return res.data; + }, + getOAuth2Clients: async function (input, userContext) { - let response = await querier.sendGetRequest( + let response = await querier.sendGetRequestWithResponseHeaders( new NormalisedURLPath(`/recipe/oauth2/admin/clients`), { ...transformObjectKeys(input, "snake-case"), page_token: input.paginationToken, }, + {}, userContext ); - if (response.status === "OK") { + if (response.body.status === "OK") { // Pagination info is in the Link header, containing comma-separated links: // "first", "next" (if applicable). // Example: Link: ; rel="first", ; rel="next" // We parse the nextPaginationToken from the Link header using RegExp let nextPaginationToken: string | undefined; - const linkHeader = response.headers.get("link"); + const linkHeader = response.headers.get("link") ?? ""; const nextLinkMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/); if (nextLinkMatch) { @@ -54,14 +335,14 @@ export default function getRecipeInterface( return { status: "OK", - clients: response.data.map((client: any) => OAuth2Client.fromAPIResponse(client)), + clients: response.body.data.map((client: any) => OAuth2Client.fromAPIResponse(client)), nextPaginationToken, }; } else { return { status: "ERROR", - error: response.data.error, - errorHint: response.data.errorHint, + error: response.body.data.error, + errorHint: response.body.data.errorHint, }; } }, @@ -136,5 +417,14 @@ export default function getRecipeInterface( }; } }, + buildAccessTokenPayload: async function (input) { + return input.defaultPayload; + }, + buildIdTokenPayload: async function (input) { + return input.defaultPayload; + }, + buildUserInfo: async function (input) { + return input.user.toJson(); // Proper impl + }, }; } diff --git a/lib/ts/recipe/oauth2/types.ts b/lib/ts/recipe/oauth2/types.ts index 0558366c6..f88435f43 100644 --- a/lib/ts/recipe/oauth2/types.ts +++ b/lib/ts/recipe/oauth2/types.ts @@ -15,8 +15,10 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; -import { NonNullableProperties, UserContext } from "../../types"; +import { GeneralErrorResponse, JSONObject, NonNullableProperties, UserContext } from "../../types"; +import { SessionContainerInterface } from "../session/types"; import { OAuth2Client } from "./OAuth2Client"; +import { User } from "../../user"; export type TypeInput = { override?: { @@ -47,7 +49,231 @@ export type APIOptions = { res: BaseResponse; }; +export type ErrorOAuth2 = { + // The error should follow the OAuth2 error format (e.g. invalid_request, login_required). + // Defaults to request_denied. + error: string; + + // Description of the error in a human readable format. + errorDescription: string; + + // Debug contains information to help resolve the problem as a developer. Usually not exposed to the public but only in the server logs. + errorDebug?: string; + + // Hint to help resolve the error. + errorHint?: string; + + // Represents the HTTP status code of the error (e.g. 401 or 403) + // Defaults to 400 + statusCode?: number; +}; + +export type ConsentRequest = { + // ACR represents the Authentication AuthorizationContext Class Reference value for this authentication session. You can use it to express that, for example, a user authenticated using two factor authentication. + acr?: string; + + // Array of strings (StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage.) + amr?: string[]; + + // ID is the identifier ("authorization challenge") of the consent authorization request. It is used to identify the session. + challenge: string; + + // OAuth 2.0 Clients are used to perform OAuth 2.0 and OpenID Connect flows. Usually, OAuth 2.0 clients are generated for applications which want to consume your OAuth 2.0 or OpenID Connect capabilities. + client?: OAuth2Client; + + // any (JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger.) + context?: JSONObject; + + // LoginChallenge is the login challenge this consent challenge belongs to. It can be used to associate a login and consent request in the login & consent app. + loginChallenge?: string; + + // LoginSessionID is the login session ID. If the user-agent reuses a login session (via cookie / remember flag) this ID will remain the same. If the user-agent did not have an existing authentication session (e.g. remember is false) this will be a new random value. This value is used as the "sid" parameter in the ID Token and in OIDC Front-/Back- channel logout. It's value can generally be used to associate consecutive login requests by a certain user. + loginSessionId?: string; + + // object (Contains optional information about the OpenID Connect request.) + oidcContext?: any; + + // RequestURL is the original OAuth 2.0 Authorization URL requested by the OAuth 2.0 client. It is the URL which initiates the OAuth 2.0 Authorization Code or OAuth 2.0 Implicit flow. This URL is typically not needed, but might come in handy if you want to deal with additional request parameters. + requestUrl?: string; + + // Array of strings (StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage.) + requestedAccessTokenAudience?: string[]; + + // Array of strings (StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage.) + requestedScope?: string[]; + + // Skip, if true, implies that the client has requested the same scopes from the same user previously. If true, you must not ask the user to grant the requested scopes. You must however either allow or deny the consent request using the usual API call. + skip?: boolean; + + // Subject is the user ID of the end-user that authenticated. Now, that end user needs to grant or deny the scope requested by the OAuth 2.0 client. + subject?: string; +}; + +export type LoginRequest = { + // ID is the identifier ("login challenge") of the login request. It is used to identify the session. + challenge: string; + + // OAuth 2.0 Clients are used to perform OAuth 2.0 and OpenID Connect flows. Usually, OAuth 2.0 clients are generated for applications which want to consume your OAuth 2.0 or OpenID Connect capabilities. + client: OAuth2Client; + + // object (Contains optional information about the OpenID Connect request.) + oidcContext?: any; + + // RequestURL is the original OAuth 2.0 Authorization URL requested by the OAuth 2.0 client. It is the URL which initiates the OAuth 2.0 Authorization Code or OAuth 2.0 Implicit flow. This URL is typically not needed, but might come in handy if you want to deal with additional request parameters. + requestUrl: string; + + // Array of strings (StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage.) + requestedAccessTokenAudience?: string[]; + + // Array of strings (StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage.) + requestedScope?: string[]; + + // SessionID is the login session ID. If the user-agent reuses a login session (via cookie / remember flag) this ID will remain the same. If the user-agent did not have an existing authentication session (e.g. remember is false) this will be a new random value. This value is used as the "sid" parameter in the ID Token and in OIDC Front-/Back- channel logout. It's value can generally be used to associate consecutive login requests by a certain user. + sessionId?: string; + + // Skip, if true, implies that the client has requested the same scopes from the same user previously. If true, you can skip asking the user to grant the requested scopes, and simply forward the user to the redirect URL. + // This feature allows you to update / set session information. + skip: boolean; + + // Subject is the user ID of the end-user that authenticated. Now, that end user needs to grant or deny the scope requested by the OAuth 2.0 client. If this value is set and skip is true, you MUST include this subject type when accepting the login request, or the request will fail. + subject: string; +}; + +export type LogoutRequest = { + // Challenge is the identifier ("logout challenge") of the logout authentication request. It is used to identify the session. + challenge: string; + + // OAuth 2.0 Clients are used to perform OAuth 2.0 and OpenID Connect flows. Usually, OAuth 2.0 clients are generated for applications which want to consume your OAuth 2.0 or OpenID Connect capabilities. + client: OAuth2Client; + + // RequestURL is the original Logout URL requested. + requestUrl: string; + + // RPInitiated is set to true if the request was initiated by a Relying Party (RP), also known as an OAuth 2.0 Client. + rpInitiated: boolean; + + // SessionID is the login session ID that was requested to log out. + sid: string; + + // Subject is the user for whom the logout was request. + subject: string; +}; + +export type TokenInfo = { + // The access token issued by the authorization server. + access_token: string; + // The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will expire in one hour from the time the response was generated. + // integer + expires_in: number; + // To retrieve a refresh token request the id_token scope. + id_token: string; + // The refresh token, which can be used to obtain new access tokens. To retrieve it add the scope "offline" to your access token request. + refresh_token: string; + // The scope of the access token + scope: string; + // The type of the token issued + token_type: string; +}; + +export type LoginInfo = { + // The name of the client. + clientName: string; + // The URI of the client's terms of service. + tosUri: string; + // The URI of the client's privacy policy. + policyUri: string; + // The URI of the client's logo. + logoUri: string; + // The metadata associated with the client. + metadata?: Record | null; +}; + export type RecipeInterface = { + authorization(input: { + params: any; + cookies: string | undefined; + session: SessionContainerInterface | undefined; + userContext: UserContext; + }): Promise<{ redirectTo: string; setCookie: string | undefined }>; + token(input: { body: any; userContext: UserContext }): Promise; + getConsentRequest(input: { challenge: string; userContext: UserContext }): Promise; + acceptConsentRequest(input: { + challenge: string; + + // any (JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger.) + context?: any; + // Array of strings (StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage.) + grantAccessTokenAudience?: string[]; + // Array of strings (StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage.) + grantScope?: string[]; + // string (NullTime implements sql.NullTime functionality.) + handledAt?: string[]; + // Remember, if set to true, tells ORY Hydra to remember this consent authorization and reuse it if the same client asks the same user for the same, or a subset of, scope. + remember?: boolean; + + // RememberFor sets how long the consent authorization should be remembered for in seconds. If set to 0, the authorization will be remembered indefinitely. integer + rememberFor?: number; + + // object (Pass session data to a consent request.) + session?: any; + userContext: UserContext; + }): Promise<{ redirectTo: string }>; + + rejectConsentRequest(input: { + challenge: string; + error: ErrorOAuth2; + userContext: UserContext; + }): Promise<{ redirectTo: string }>; + + getLoginRequest(input: { challenge: string; userContext: UserContext }): Promise; + acceptLoginRequest(input: { + challenge: string; + + // ACR sets the Authentication AuthorizationContext Class Reference value for this authentication session. You can use it to express that, for example, a user authenticated using two factor authentication. + acr?: string; + + // Array of strings (StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage.) + amr?: string[]; + + // any (JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger.) + context?: any; + + // Extend OAuth2 authentication session lifespan + // If set to true, the OAuth2 authentication cookie lifespan is extended. This is for example useful if you want the user to be able to use prompt=none continuously. + // This value can only be set to true if the user has an authentication, which is the case if the skip value is true. + extendSessionLifespan?: boolean; + + // ForceSubjectIdentifier forces the "pairwise" user ID of the end-user that authenticated. The "pairwise" user ID refers to the (Pairwise Identifier Algorithm)[http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg] of the OpenID Connect specification. It allows you to set an obfuscated subject ("user") identifier that is unique to the client. + // Please note that this changes the user ID on endpoint /userinfo and sub claim of the ID Token. It does not change the sub claim in the OAuth 2.0 Introspection. + forceSubjectIdentifier?: string; + + // Per default, ORY Hydra handles this value with its own algorithm. In case you want to set this yourself you can use this field. Please note that setting this field has no effect if pairwise is not configured in ORY Hydra or the OAuth 2.0 Client does not expect a pairwise identifier (set via subject_type key in the client's configuration). + // Please also be aware that ORY Hydra is unable to properly compute this value during authentication. This implies that you have to compute this value on every authentication process (probably depending on the client ID or some other unique value). + // If you fail to compute the proper value, then authentication processes which have id_token_hint set might fail. + + // IdentityProviderSessionID is the session ID of the end-user that authenticated. If specified, we will use this value to propagate the logout. + identityProviderSessionId?: string; + + // Remember, if set to true, tells ORY Hydra to remember this user by telling the user agent (browser) to store a cookie with authentication data. If the same user performs another OAuth 2.0 Authorization Request, he/she will not be asked to log in again. + remember?: boolean; + + // RememberFor sets how long the authentication should be remembered for in seconds. If set to 0, the authorization will be remembered for the duration of the browser session (using a session cookie). integer + rememberFor?: number; + + // Subject is the user ID of the end-user that authenticated. + subject: string; + userContext: UserContext; + }): Promise<{ redirectTo: string }>; + rejectLoginRequest(input: { + challenge: string; + error: ErrorOAuth2; + userContext: UserContext; + }): Promise<{ redirectTo: string }>; + + getLogoutRequest(input: { challenge: string; userContext: UserContext }): Promise; + acceptLogoutRequest(input: { challenge: string; userContext: UserContext }): Promise<{ redirectTo: string }>; + rejectLogoutRequest(input: { challenge: string; userContext: UserContext }): Promise; + getOAuth2Clients( input: GetOAuth2ClientsInput, userContext: UserContext @@ -108,9 +334,120 @@ export type RecipeInterface = { errorHint: string; } >; + + buildAccessTokenPayload(input: { + user: User; + session: SessionContainerInterface; + scopes: string[]; + defaultPayload: JSONObject; + userContext: UserContext; + }): Promise; + buildIdTokenPayload(input: { + user: User; + session: SessionContainerInterface; + scopes: string[]; + defaultPayload: JSONObject; + userContext: UserContext; + }): Promise; + buildUserInfo(input: { + user: User; + accessTokenPayload: JSONObject; + scopes: string[]; + defaultInfo: JSONObject; + userContext: UserContext; + }): Promise; }; -export type APIInterface = {}; +export type APIInterface = { + // TODO: add json versions? + loginGET: + | undefined + | ((input: { + loginChallenge: string; + options: APIOptions; + session?: SessionContainerInterface; + userContext: UserContext; + }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); + + loginPOST: + | undefined + | ((input: { + loginChallenge: string; + accept: boolean; + session: SessionContainerInterface; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); + + logoutGET: + | undefined + | ((input: { + logoutChallenge: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); + + logoutPOST: + | undefined + | ((input: { + logoutChallenge: string; + accept: boolean; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); + + consentGET: + | undefined + | ((input: { + consentChallenge: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); + + consentPOST: + | undefined + | ((input: { + consentChallenge: string; + accept: boolean; + grantScope: string[]; + remember: boolean; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); + authGET: + | undefined + | ((input: { + params: any; + cookie: string | undefined; + session: SessionContainerInterface | undefined; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ redirectTo: string; setCookie: string | undefined } | ErrorOAuth2 | GeneralErrorResponse>); + tokenPOST: + | undefined + | ((input: { + body: any; + options: APIOptions; + userContext: UserContext; + }) => Promise); + loginInfoGET: + | undefined + | ((input: { + loginChallenge: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ status: "OK"; info: LoginInfo } | GeneralErrorResponse>); + // userInfoGET: + // | undefined + // | ((input: { + // accessTokenPayload: JSONObject; // after validating the access token passed to the endpoint + // user: User; // ge + // scopes: string[]; + // defaultInfo: JSONObject; + // options: APIOptions; + // userContext: UserContext; + // }) => Promise<{ status: "OK"; info: LoginInfo } | GeneralErrorResponse>); +}; export type OAuth2ClientOptions = { clientId: string; @@ -218,3 +555,5 @@ export type UpdateOAuth2ClientInput = NonNullableProperties< export type DeleteOAuth2ClientInput = { clientId: string; }; + +export type PayloadBuilderFunction = (user: User, scopes: string[], userContext: UserContext) => Promise; diff --git a/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts b/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts index 90c291574..7a331169b 100644 --- a/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts +++ b/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts @@ -34,6 +34,11 @@ export default async function getOpenIdDiscoveryConfiguration( send200Response(options.res, { issuer: result.issuer, jwks_uri: result.jwks_uri, + authorization_endpoint: result.authorization_endpoint, + token_endpoint: result.token_endpoint, + subject_types_supported: result.subject_types_supported, + id_token_signing_alg_values_supported: result.id_token_signing_alg_values_supported, + response_types_supported: result.response_types_supported, }); } else { send200Response(options.res, result); diff --git a/lib/ts/recipe/openid/api/implementation.ts b/lib/ts/recipe/openid/api/implementation.ts index ee1f83e21..72921dffd 100644 --- a/lib/ts/recipe/openid/api/implementation.ts +++ b/lib/ts/recipe/openid/api/implementation.ts @@ -12,18 +12,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { APIInterface, APIOptions } from "../types"; -import { GeneralErrorResponse, UserContext } from "../../../types"; +import { APIInterface } from "../types"; export default function getAPIImplementation(): APIInterface { return { - getOpenIdDiscoveryConfigurationGET: async function ({ - options, - userContext, - }: { - options: APIOptions; - userContext: UserContext; - }): Promise<{ status: "OK"; issuer: string; jwks_uri: string } | GeneralErrorResponse> { + getOpenIdDiscoveryConfigurationGET: async function ({ options, userContext }) { return await options.recipeImplementation.getOpenIdDiscoveryConfiguration({ userContext }); }, }; diff --git a/lib/ts/recipe/openid/recipe.ts b/lib/ts/recipe/openid/recipe.ts index 1dc802de5..1726f9c43 100644 --- a/lib/ts/recipe/openid/recipe.ts +++ b/lib/ts/recipe/openid/recipe.ts @@ -44,7 +44,9 @@ export default class OpenIdRecipe extends RecipeModule { override: this.config.override.jwtFeature, }); - let builder = new OverrideableBuilder(RecipeImplementation(this.config, this.jwtRecipe.recipeInterfaceImpl)); + let builder = new OverrideableBuilder( + RecipeImplementation(this.config, this.jwtRecipe.recipeInterfaceImpl, appInfo) + ); this.recipeImplementation = builder.override(this.config.override.functions).build(); diff --git a/lib/ts/recipe/openid/recipeImplementation.ts b/lib/ts/recipe/openid/recipeImplementation.ts index f161e8d23..2ed40f6f5 100644 --- a/lib/ts/recipe/openid/recipeImplementation.ts +++ b/lib/ts/recipe/openid/recipeImplementation.ts @@ -16,26 +16,31 @@ import { RecipeInterface, TypeNormalisedInput } from "./types"; import { RecipeInterface as JWTRecipeInterface, JsonWebKey } from "../jwt/types"; import NormalisedURLPath from "../../normalisedURLPath"; import { GET_JWKS_API } from "../jwt/constants"; -import { UserContext } from "../../types"; +import { NormalisedAppinfo, UserContext } from "../../types"; +import { AUTH_PATH, TOKEN_PATH } from "../oauth2/constants"; export default function getRecipeInterface( config: TypeNormalisedInput, - jwtRecipeImplementation: JWTRecipeInterface + jwtRecipeImplementation: JWTRecipeInterface, + appInfo: NormalisedAppinfo ): RecipeInterface { return { - getOpenIdDiscoveryConfiguration: async function (): Promise<{ - status: "OK"; - issuer: string; - jwks_uri: string; - }> { + getOpenIdDiscoveryConfiguration: async function () { let issuer = config.issuerDomain.getAsStringDangerous() + config.issuerPath.getAsStringDangerous(); let jwks_uri = config.issuerDomain.getAsStringDangerous() + config.issuerPath.appendPath(new NormalisedURLPath(GET_JWKS_API)).getAsStringDangerous(); + + const apiBasePath = appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); return { status: "OK", issuer, jwks_uri, + authorization_endpoint: apiBasePath + AUTH_PATH, + token_endpoint: apiBasePath + TOKEN_PATH, + subject_types_supported: ["public"], + id_token_signing_alg_values_supported: ["RS256"], + response_types_supported: ["code", "id_token", "id_token token"], }; }, createJWT: async function ({ diff --git a/lib/ts/recipe/openid/types.ts b/lib/ts/recipe/openid/types.ts index c303cb0ca..22aa651a5 100644 --- a/lib/ts/recipe/openid/types.ts +++ b/lib/ts/recipe/openid/types.ts @@ -83,6 +83,11 @@ export type APIInterface = { status: "OK"; issuer: string; jwks_uri: string; + authorization_endpoint: string; + token_endpoint: string; + subject_types_supported: string[]; + id_token_signing_alg_values_supported: string[]; + response_types_supported: string[]; } | GeneralErrorResponse >); @@ -95,6 +100,11 @@ export type RecipeInterface = { status: "OK"; issuer: string; jwks_uri: string; + authorization_endpoint: string; + token_endpoint: string; + subject_types_supported: string[]; + id_token_signing_alg_values_supported: string[]; + response_types_supported: string[]; }>; createJWT(input: { payload?: any; diff --git a/lib/ts/recipe/passwordless/recipeImplementation.ts b/lib/ts/recipe/passwordless/recipeImplementation.ts index 71ab078b0..37110ace2 100644 --- a/lib/ts/recipe/passwordless/recipeImplementation.ts +++ b/lib/ts/recipe/passwordless/recipeImplementation.ts @@ -1,6 +1,7 @@ import { RecipeInterface } from "./types"; import { Querier } from "../../querier"; import AccountLinking from "../accountlinking/recipe"; +import EmailVerification from "../emailverification/recipe"; import NormalisedURLPath from "../../normalisedURLPath"; import { logDebugMessage } from "../../logger"; import { User } from "../../user"; @@ -161,9 +162,45 @@ export default function getRecipeInterface(querier: Querier): RecipeInterface { return { status: "OK" }; }, updateUser: async function (input) { + const accountLinking = AccountLinking.getInstance(); + if (input.email) { + const user = await getUser(input.recipeUserId.getAsString(), input.userContext); + + if (user === undefined) { + return { status: "UNKNOWN_USER_ID_ERROR" }; + } + + const evInstance = EmailVerification.getInstance(); + + let isEmailVerified = false; + if (evInstance) { + isEmailVerified = await evInstance.recipeInterfaceImpl.isEmailVerified({ + recipeUserId: input.recipeUserId, + email: input.email, + userContext: input.userContext, + }); + } + const isEmailChangeAllowed = await accountLinking.isEmailChangeAllowed({ + user, + isVerified: isEmailVerified, + newEmail: input.email, + session: undefined, + userContext: input.userContext, + }); + if (!isEmailChangeAllowed.allowed) { + return { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", + reason: + isEmailChangeAllowed.reason === "ACCOUNT_TAKEOVER_RISK" + ? "New email cannot be applied to existing account because of account takeover risks." + : "New email cannot be applied to existing account because of there is another primary user with the same email address.", + }; + } + } let response = await querier.sendPutRequest( new NormalisedURLPath(`/recipe/user`), copyAndRemoveUserContextAndTenantId(input), + {}, input.userContext ); if (response.status !== "OK") { diff --git a/lib/ts/recipe/session/sessionFunctions.ts b/lib/ts/recipe/session/sessionFunctions.ts index 8dae9d1b1..30d1726e9 100644 --- a/lib/ts/recipe/session/sessionFunctions.ts +++ b/lib/ts/recipe/session/sessionFunctions.ts @@ -500,6 +500,7 @@ export async function updateSessionDataInDatabase( sessionHandle, userDataInDatabase: newSessionData, }, + {}, userContext ); if (response.status === "UNAUTHORISED") { @@ -522,6 +523,7 @@ export async function updateAccessTokenPayload( sessionHandle, userDataInJWT: newAccessTokenPayload, }, + {}, userContext ); if (response.status === "UNAUTHORISED") { diff --git a/lib/ts/recipe/session/sessionRequestFunctions.ts b/lib/ts/recipe/session/sessionRequestFunctions.ts index dbfe15051..de4c5b3bb 100644 --- a/lib/ts/recipe/session/sessionRequestFunctions.ts +++ b/lib/ts/recipe/session/sessionRequestFunctions.ts @@ -314,7 +314,7 @@ export async function refreshSessionInRequest({ throw new SessionError({ message: "Refresh token not found. Are you sending the refresh token in the request?", payload: { - clearTokens: false, + clearTokens: true, }, type: SessionError.UNAUTHORISED, }); @@ -338,7 +338,7 @@ export async function refreshSessionInRequest({ message: "anti-csrf check failed. Please pass 'rid: \"session\"' header in the request.", type: SessionError.UNAUTHORISED, payload: { - clearTokens: false, // see https://github.com/supertokens/supertokens-node/issues/141 + clearTokens: true, // see https://github.com/supertokens/supertokens-node/issues/141 }, }); } diff --git a/lib/ts/recipe/thirdparty/api/implementation.ts b/lib/ts/recipe/thirdparty/api/implementation.ts index f9ea8884c..e91bb2dc3 100644 --- a/lib/ts/recipe/thirdparty/api/implementation.ts +++ b/lib/ts/recipe/thirdparty/api/implementation.ts @@ -1,5 +1,4 @@ import { APIInterface } from "../"; -import AccountLinking from "../../accountlinking/recipe"; import EmailVerification from "../../emailverification"; import EmailVerificationRecipe from "../../emailverification/recipe"; import { AuthUtils } from "../../../authUtils"; @@ -35,7 +34,7 @@ export default function getAPIInterface(): APIInterface { "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_023)", }, }; - const { provider, tenantId, options, session, userContext } = input; + const { provider, tenantId, options, userContext } = input; let oAuthTokensToUse: any = {}; @@ -99,9 +98,9 @@ export default function getAPIInterface(): APIInterface { // recipe function checks to a minimum so that the dev has complete control of // what they can do. - // The isEmailChangeAllowed function takes in a isVerified boolean. Now, even though - // we already have that from the input, that's just what the provider says. If the - // provider says that the email is NOT verified, it could have been that the email + // The isEmailChangeAllowed and preAuthChecks functions take an isVerified boolean. + // Now, even though we already have that from the input, that's just what the provider says. + // If the provider says that the email is NOT verified, it could have been that the email // is verified on the user's account via supertokens on a previous sign in / up. // So we just check that as well before calling isEmailChangeAllowed @@ -114,74 +113,6 @@ export default function getAPIInterface(): APIInterface { userContext ); } - - /** - * In this API, during only a sign in, we check for isEmailChangeAllowed first, then - * change the email by calling the recipe function, then check if is sign in allowed. - * This may result in a few states where email change is allowed, but still, sign in - * is not allowed: - * - * Various outcomes of isSignInAllowed vs isEmailChangeAllowed - * isSignInAllowed result: - * - is primary user -> TRUE - * - is recipe user - * - other recipe user exists - * - no -> TRUE - * - yes - * - email verified -> TRUE - * - email unverified -> FALSE - * - other primary user exists - * - no -> TRUE - * - yes - * - email verification status - * - this && primary -> TRUE - * - !this && !primary -> FALSE - * - this && !primary -> FALSE - * - !this && primary -> FALSE - * - * isEmailChangeAllowed result: - * - is primary user -> TRUE - * - is recipe user - * - other recipe user exists - * - no -> TRUE - * - yes - * - email verified -> TRUE - * - email unverified -> TRUE - * - other primary user exists - * - no -> TRUE - * - yes - * - email verification status - * - this && primary -> TRUE - * - !this && !primary -> FALSE - * - this && !primary -> TRUE - * - !this && primary -> FALSE - * - * Based on the above, isEmailChangeAllowed can return true, but isSignInAllowed will return false - * in the following situations: - * - If a recipe user is signing in with a new email, other recipe users with the same email exist, - * and one of them is unverfied. In this case, the email change will happen in the social login - * recipe, but the user will not be able to login anyway. - * - * - If the recipe user is signing in with a new email, there exists a primary user with the same - * email, but this new email is verified for the recipe user already, but the primary user's email - * is not verified. - */ - - let isEmailChangeAllowed = await AccountLinking.getInstance().isEmailChangeAllowed({ - user: authenticatingUser.user, - isVerified: emailInfo.isVerified, - newEmail: emailInfo.id, - session, - userContext, - }); - - if (!isEmailChangeAllowed) { - return { - status: "SIGN_IN_UP_NOT_ALLOWED", - reason: - "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)", - }; - } } const preAuthChecks = await AuthUtils.preAuthChecks({ @@ -232,6 +163,11 @@ export default function getAPIInterface(): APIInterface { userContext, }); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + // In this case we do not need to do mapping, since the recipe function already has the right response shape. + return response; + } + if (response.status !== "OK") { logDebugMessage("signInUpPOST: erroring out because signInUp returned " + response.status); return AuthUtils.getErrorStatusResponseWithReason(response, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); diff --git a/lib/ts/recipe/thirdparty/recipeImplementation.ts b/lib/ts/recipe/thirdparty/recipeImplementation.ts index 58dbbc248..f730537a3 100644 --- a/lib/ts/recipe/thirdparty/recipeImplementation.ts +++ b/lib/ts/recipe/thirdparty/recipeImplementation.ts @@ -5,7 +5,7 @@ import { findAndCreateProviderInstance, mergeProvidersFromCoreAndStatic } from " import AccountLinking from "../accountlinking/recipe"; import MultitenancyRecipe from "../multitenancy/recipe"; import RecipeUserId from "../../recipeUserId"; -import { getUser } from "../.."; +import { getUser, listUsersByAccountInfo } from "../.."; import { User as UserType } from "../../types"; import { User } from "../../user"; import { AuthUtils } from "../../authUtils"; @@ -16,6 +16,33 @@ export default function getRecipeImplementation(querier: Querier, providers: Pro this: RecipeInterface, { thirdPartyId, thirdPartyUserId, email, isVerified, tenantId, session, userContext } ) { + const accountLinking = AccountLinking.getInstance(); + const users = await listUsersByAccountInfo( + tenantId, + { thirdParty: { id: thirdPartyId, userId: thirdPartyUserId } }, + false, + userContext + ); + + const user = users[0]; + if (user !== undefined) { + const isEmailChangeAllowed = await accountLinking.isEmailChangeAllowed({ + user, + isVerified: isVerified, + newEmail: email, + session, + userContext: userContext, + }); + if (!isEmailChangeAllowed.allowed) { + return { + status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR", + reason: + isEmailChangeAllowed.reason === "PRIMARY_USER_CONFLICT" + ? "Email already associated with another primary user." + : "New email cannot be applied to existing account because of account takeover risks.", + }; + } + } let response = await querier.sendPostRequest( new NormalisedURLPath(`/${tenantId}/recipe/signinup`), { @@ -115,7 +142,9 @@ export default function getRecipeImplementation(querier: Querier, providers: Pro return { status: "SIGN_IN_UP_NOT_ALLOWED", reason: - "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)", + response.reason === "Email already associated with another primary user." + ? "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)" + : "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_024)", }; } diff --git a/lib/ts/recipe/totp/recipeImplementation.ts b/lib/ts/recipe/totp/recipeImplementation.ts index 745f23cf2..a87a35c39 100644 --- a/lib/ts/recipe/totp/recipeImplementation.ts +++ b/lib/ts/recipe/totp/recipeImplementation.ts @@ -123,6 +123,7 @@ export default function getRecipeInterface(querier: Querier, config: TypeNormali existingDeviceName: input.existingDeviceName, newDeviceName: input.newDeviceName, }, + {}, input.userContext ); }, diff --git a/lib/ts/recipe/usermetadata/recipeImplementation.ts b/lib/ts/recipe/usermetadata/recipeImplementation.ts index 51ab77ea5..2ab9d24e2 100644 --- a/lib/ts/recipe/usermetadata/recipeImplementation.ts +++ b/lib/ts/recipe/usermetadata/recipeImplementation.ts @@ -30,6 +30,7 @@ export default function getRecipeInterface(querier: Querier): RecipeInterface { userId, metadataUpdate, }, + {}, userContext ); }, diff --git a/lib/ts/recipe/userroles/recipeImplementation.ts b/lib/ts/recipe/userroles/recipeImplementation.ts index eaee083f7..7141a8bfe 100644 --- a/lib/ts/recipe/userroles/recipeImplementation.ts +++ b/lib/ts/recipe/userroles/recipeImplementation.ts @@ -24,6 +24,7 @@ export default function getRecipeInterface(querier: Querier): RecipeInterface { return querier.sendPutRequest( new NormalisedURLPath(`/${tenantId === undefined ? DEFAULT_TENANT_ID : tenantId}/recipe/user/role`), { userId, role }, + {}, userContext ); }, @@ -55,7 +56,12 @@ export default function getRecipeInterface(querier: Querier): RecipeInterface { }, createNewRoleOrAddPermissions: function ({ role, permissions, userContext }) { - return querier.sendPutRequest(new NormalisedURLPath("/recipe/role"), { role, permissions }, userContext); + return querier.sendPutRequest( + new NormalisedURLPath("/recipe/role"), + { role, permissions }, + {}, + userContext + ); }, getPermissionsForRole: function ({ role, userContext }) { diff --git a/lib/ts/supertokens.ts b/lib/ts/supertokens.ts index fe95fc396..104542b6e 100644 --- a/lib/ts/supertokens.ts +++ b/lib/ts/supertokens.ts @@ -333,6 +333,7 @@ export default class SuperTokens { userIdType: input.userIdType, externalUserIdInfo: input.externalUserIdInfo, }, + {}, input.userContext ); } else { diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts index 68abea62a..6a7afde9c 100644 --- a/lib/ts/utils.ts +++ b/lib/ts/utils.ts @@ -18,11 +18,13 @@ export const doFetch: typeof fetch = async (input: RequestInfo | URL, init?: Req ProcessState.getInstance().addState(PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH); init = { cache: "no-cache", + redirect: "manual", }; } else { if (init.cache === undefined) { ProcessState.getInstance().addState(PROCESS_STATE.ADDING_NO_CACHE_HEADER_IN_FETCH); init.cache = "no-cache"; + init.redirect = "manual"; } } const fetchFunction = typeof fetch !== "undefined" ? fetch : crossFetch; diff --git a/package-lock.json b/package-lock.json index d4f1b4611..d439ee1f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "nodemailer": "^6.7.2", "pkce-challenge": "^3.0.0", "psl": "1.8.0", + "set-cookie-parser": "^2.6.0", "supertokens-js-override": "^0.0.4", "twilio": "^4.19.3" }, @@ -39,6 +40,7 @@ "@types/koa-bodyparser": "^4.3.3", "@types/nodemailer": "^6.4.4", "@types/psl": "1.1.0", + "@types/set-cookie-parser": "^2.4.9", "@types/validator": "10.11.0", "aws-sdk-mock": "^5.4.0", "body-parser": "1.20.1", @@ -1713,6 +1715,15 @@ "@types/node": "*" } }, + "node_modules/@types/set-cookie-parser": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.9.tgz", + "integrity": "sha512-bCorlULvl0xTdjj4BPUHX4cqs9I+go2TfW/7Do1nnFYWS0CPP429Qr1AY42kiFhCwLpvAkWFr1XIBHd8j6/MCQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/type-is": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.6.tgz", @@ -7010,8 +7021,7 @@ "node_modules/set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", - "dev": true + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" }, "node_modules/set-function-length": { "version": "1.2.2", @@ -9568,6 +9578,15 @@ "@types/node": "*" } }, + "@types/set-cookie-parser": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.9.tgz", + "integrity": "sha512-bCorlULvl0xTdjj4BPUHX4cqs9I+go2TfW/7Do1nnFYWS0CPP429Qr1AY42kiFhCwLpvAkWFr1XIBHd8j6/MCQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/type-is": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.6.tgz", @@ -13642,8 +13661,7 @@ "set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", - "dev": true + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" }, "set-function-length": { "version": "1.2.2", diff --git a/package.json b/package.json index 8f5377af5..2022a0fc6 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "nodemailer": "^6.7.2", "pkce-challenge": "^3.0.0", "psl": "1.8.0", + "set-cookie-parser": "^2.6.0", "supertokens-js-override": "^0.0.4", "twilio": "^4.19.3" }, @@ -143,6 +144,7 @@ "@types/koa-bodyparser": "^4.3.3", "@types/nodemailer": "^6.4.4", "@types/psl": "1.1.0", + "@types/set-cookie-parser": "^2.4.9", "@types/validator": "10.11.0", "aws-sdk-mock": "^5.4.0", "body-parser": "1.20.1", diff --git a/test/auth-modes.test.js b/test/auth-modes.test.js index 067346d64..0ed01e580 100644 --- a/test/auth-modes.test.js +++ b/test/auth-modes.test.js @@ -893,15 +893,15 @@ describe(`auth-modes: ${printPath("[test/auth-modes.test.js]")}`, function () { describe("from behaviour table", () => { // prettier-ignore const behaviourTable = [ - { getTokenTransferMethodRes: "any", authHeader: false, authCookie: false, output: "unauthorised", setTokens: "none", clearedTokens: "none" }, - { getTokenTransferMethodRes: "header", authHeader: false, authCookie: false, output: "unauthorised", setTokens: "none", clearedTokens: "none" }, - { getTokenTransferMethodRes: "cookie", authHeader: false, authCookie: false, output: "unauthorised", setTokens: "none", clearedTokens: "none" }, + { getTokenTransferMethodRes: "any", authHeader: false, authCookie: false, output: "unauthorised", setTokens: "none", clearedTokens: "both" }, + { getTokenTransferMethodRes: "header", authHeader: false, authCookie: false, output: "unauthorised", setTokens: "none", clearedTokens: "both" }, + { getTokenTransferMethodRes: "cookie", authHeader: false, authCookie: false, output: "unauthorised", setTokens: "none", clearedTokens: "both" }, { getTokenTransferMethodRes: "any", authHeader: false, authCookie: true, output: "validatecookie", setTokens: "cookies", clearedTokens: "none" }, - { getTokenTransferMethodRes: "header", authHeader: false, authCookie: true, output: "unauthorised", setTokens: "none", clearedTokens: "none" }, // 5 + { getTokenTransferMethodRes: "header", authHeader: false, authCookie: true, output: "unauthorised", setTokens: "none", clearedTokens: "both" }, // 5 { getTokenTransferMethodRes: "cookie", authHeader: false, authCookie: true, output: "validatecookie", setTokens: "cookies", clearedTokens: "none" }, { getTokenTransferMethodRes: "any", authHeader: true, authCookie: false, output: "validateheader", setTokens: "headers", clearedTokens: "none" }, { getTokenTransferMethodRes: "header", authHeader: true, authCookie: false, output: "validateheader", setTokens: "headers", clearedTokens: "none" }, - { getTokenTransferMethodRes: "cookie", authHeader: true, authCookie: false, output: "unauthorised", setTokens: "none", clearedTokens: "none" }, // 9 + { getTokenTransferMethodRes: "cookie", authHeader: true, authCookie: false, output: "unauthorised", setTokens: "none", clearedTokens: "both" }, // 9 { getTokenTransferMethodRes: "any", authHeader: true, authCookie: true, output: "validateheader", setTokens: "headers", clearedTokens: "cookies" }, { getTokenTransferMethodRes: "header", authHeader: true, authCookie: true, output: "validateheader", setTokens: "headers", clearedTokens: "cookies" }, { getTokenTransferMethodRes: "cookie", authHeader: true, authCookie: true, output: "validatecookie", setTokens: "cookies", clearedTokens: "headers" }, // 12 @@ -964,6 +964,13 @@ describe(`auth-modes: ${printPath("[test/auth-modes.test.js]")}`, function () { assert.strictEqual(refreshRes.accessTokenExpiry, "Thu, 01 Jan 1970 00:00:00 GMT"); assert.strictEqual(refreshRes.refreshToken, ""); assert.strictEqual(refreshRes.refreshTokenExpiry, "Thu, 01 Jan 1970 00:00:00 GMT"); + } else if (conf.clearedTokens === "both") { + assert.strictEqual(refreshRes.accessTokenFromHeader, ""); + assert.strictEqual(refreshRes.refreshTokenFromHeader, ""); + assert.strictEqual(refreshRes.accessToken, ""); + assert.strictEqual(refreshRes.accessTokenExpiry, "Thu, 01 Jan 1970 00:00:00 GMT"); + assert.strictEqual(refreshRes.refreshToken, ""); + assert.strictEqual(refreshRes.refreshTokenExpiry, "Thu, 01 Jan 1970 00:00:00 GMT"); } switch (conf.setTokens) { @@ -985,15 +992,17 @@ describe(`auth-modes: ${printPath("[test/auth-modes.test.js]")}`, function () { } break; } - if (conf.setTokens !== "cookies" && conf.clearedTokens !== "cookies") { - assert.strictEqual(refreshRes.accessToken, undefined); - assert.strictEqual(refreshRes.accessTokenExpiry, undefined); - assert.strictEqual(refreshRes.refreshToken, undefined); - assert.strictEqual(refreshRes.refreshTokenExpiry, undefined); - } - if (conf.setTokens !== "headers" && conf.clearedTokens !== "headers") { - assert.strictEqual(refreshRes.accessTokenFromHeader, undefined); - assert.strictEqual(refreshRes.refreshTokenFromHeader, undefined); + if (conf.clearedTokens !== "both") { + if (conf.setTokens !== "cookies" && conf.clearedTokens !== "cookies") { + assert.strictEqual(refreshRes.accessToken, undefined); + assert.strictEqual(refreshRes.accessTokenExpiry, undefined); + assert.strictEqual(refreshRes.refreshToken, undefined); + assert.strictEqual(refreshRes.refreshTokenExpiry, undefined); + } + if (conf.setTokens !== "headers" && conf.clearedTokens !== "headers") { + assert.strictEqual(refreshRes.accessTokenFromHeader, undefined); + assert.strictEqual(refreshRes.refreshTokenFromHeader, undefined); + } } }); } diff --git a/test/emailpassword/updateEmailPass.test.js b/test/emailpassword/updateEmailPass.test.js index 55c0ac765..11b350187 100644 --- a/test/emailpassword/updateEmailPass.test.js +++ b/test/emailpassword/updateEmailPass.test.js @@ -127,7 +127,7 @@ describe(`updateEmailPassTest: ${printPath("[test/emailpassword/updateEmailPass. let res = await signIn("public", "test@gmail.com", "testPass123"); const res2 = await updateEmailOrPassword({ - userId: STExpress.convertToRecipeUserId(res.user.id), + recipeUserId: STExpress.convertToRecipeUserId(res.user.id), email: "test2@gmail.com", password: "test", }); @@ -224,7 +224,7 @@ describe(`updateEmailPassTest: ${printPath("[test/emailpassword/updateEmailPass. let res = await signIn("public", "test@gmail.com", "testPass123"); const res2 = await updateEmailOrPassword({ - userId: STExpress.convertToRecipeUserId(res.user.id), + recipeUserId: STExpress.convertToRecipeUserId(res.user.id), email: "test2@gmail.com", password: "1", }); diff --git a/test/querier.test.js b/test/querier.test.js index 22c9646da..64ff73480 100644 --- a/test/querier.test.js +++ b/test/querier.test.js @@ -193,7 +193,7 @@ describe(`Querier: ${printPath("[test/querier.test.js]")}`, function () { assert.equal(await q.sendPostRequest(new NormalisedURLPath("/hello"), {}, {}), "Hello\n"); let hostsAlive = q.getHostsAliveForTesting(); assert.equal(hostsAlive.size, 2); - assert.equal(await q.sendPutRequest(new NormalisedURLPath("/hello"), {}, {}), "Hello\n"); // this will be the 4th API call + assert.equal(await q.sendPutRequest(new NormalisedURLPath("/hello"), {}, {}, {}), "Hello\n"); // this will be the 4th API call hostsAlive = q.getHostsAliveForTesting(); assert.equal(hostsAlive.size, 2); assert.equal(hostsAlive.has(connectionURI), true); diff --git a/test/test-server/src/index.ts b/test/test-server/src/index.ts index 2856ffa26..6402e5ab7 100644 --- a/test/test-server/src/index.ts +++ b/test/test-server/src/index.ts @@ -19,6 +19,8 @@ import ThirdPartyRecipe from "../../../lib/build/recipe/thirdparty/recipe"; import { TypeInput as ThirdPartyTypeInput } from "../../../lib/build/recipe/thirdparty/types"; import { TypeInput as MFATypeInput } from "../../../lib/build/recipe/multifactorauth/types"; import TOTPRecipe from "../../../lib/build/recipe/totp/recipe"; +import OAuth2Recipe from "../../../lib/build/recipe/oauth2/recipe"; +import { TypeInput as OAuth2TypeInput } from "../../../lib/build/recipe/oauth2/types"; import UserMetadataRecipe from "../../../lib/build/recipe/usermetadata/recipe"; import SuperTokensRecipe from "../../../lib/build/supertokens"; import { RecipeListFunction } from "../../../lib/build/types"; @@ -32,6 +34,7 @@ import Session from "../../../recipe/session"; import { verifySession } from "../../../recipe/session/framework/express"; import ThirdParty from "../../../recipe/thirdparty"; import TOTP from "../../../recipe/totp"; +import OAuth2 from "../../../recipe/oauth2"; import accountlinkingRoutes from "./accountlinking"; import emailpasswordRoutes from "./emailpassword"; import emailverificationRoutes from "./emailverification"; @@ -39,12 +42,15 @@ import { logger } from "./logger"; import multiFactorAuthRoutes from "./multifactorauth"; import multitenancyRoutes from "./multitenancy"; import passwordlessRoutes from "./passwordless"; +import oAuth2Routes from "./oauth2"; import sessionRoutes from "./session"; import supertokensRoutes from "./supertokens"; import thirdPartyRoutes from "./thirdparty"; import userMetadataRoutes from "./usermetadata"; import TOTPRoutes from "./totp"; import { getFunc, resetOverrideParams, getOverrideParams } from "./testFunctionMapper"; +import OverrideableBuilder from "supertokens-js-override"; +import { resetOverrideLogs, logOverrideEvent, getOverrideLogs } from "./overrideLogging"; const { logDebugMessage } = logger("com.supertokens:node-test-server"); @@ -69,6 +75,7 @@ defaultSTInit(); function STReset() { resetOverrideParams(); + resetOverrideLogs(); EmailPasswordRecipe.reset(); SessionRecipe.reset(); @@ -81,6 +88,7 @@ function STReset() { ProcessState.getInstance().reset(); MultiFactorAuthRecipe.reset(); TOTPRecipe.reset(); + OAuth2Recipe.reset(); SuperTokensRecipe.reset(); } @@ -89,55 +97,73 @@ function initST(config: any) { const recipeList: RecipeListFunction[] = []; - const settings = JSON.parse(config); - logDebugMessage("initST %j", settings); + const parsedConfig = JSON.parse(config); + const init = { + ...parsedConfig, + }; + logDebugMessage("initST %j", init); - settings.recipeList.forEach((recipe) => { + init.recipeList.forEach((recipe) => { const config = recipe.config ? JSON.parse(recipe.config) : undefined; if (recipe.recipeId === "emailpassword") { let init: EmailPasswordTypeInput = { ...config, }; - if (config?.override?.apis) { - init.override = { - ...init.override, - apis: getFunc(`${config?.override.apis}`), - }; - } - - if (config?.emailDelivery?.override) { - init.emailDelivery = { - ...config?.emailDelivery, - override: getFunc(`${config?.emailDelivery.override}`), - }; - } - - recipeList.push(EmailPassword.init(init)); + recipeList.push( + EmailPassword.init({ + ...init, + emailDelivery: { + ...config?.emailDelivery, + override: overrideBuilderWithLogging( + "EmailPassword.emailDelivery.override", + config?.emailDelivery?.override + ), + }, + override: { + apis: overrideBuilderWithLogging("EmailPassword.override.apis", config?.override?.apis), + functions: overrideBuilderWithLogging( + "EmailPassword.override.functions", + config?.override?.functions + ), + }, + }) + ); } if (recipe.recipeId === "session") { - let init: SessionTypeInput = { - ...config, - }; - if (config?.override?.functions) { - init.override = { - ...init.override, - functions: getFunc(`${config?.override.functions}`), - }; - } - recipeList.push(Session.init(init)); + recipeList.push( + Session.init({ + ...config, + override: { + apis: overrideBuilderWithLogging("Session.override.apis", config?.override?.apis), + functions: overrideBuilderWithLogging( + "Session.override.functions", + config?.override?.functions + ), + }, + }) + ); } if (recipe.recipeId === "accountlinking") { - let init: AccountLinkingTypeInput = { - ...config, - }; - if (config?.shouldDoAutomaticAccountLinking) { - init.shouldDoAutomaticAccountLinking = getFunc(`${config.shouldDoAutomaticAccountLinking}`); - } - if (config?.onAccountLinked) { - init.onAccountLinked = getFunc(`${config.onAccountLinked}`); - } - recipeList.push(AccountLinking.init(init)); + recipeList.push( + AccountLinking.init({ + ...config, + shouldDoAutomaticAccountLinking: callbackWithLog( + "AccountLinking.shouldDoAutomaticAccountLinking", + config.shouldDoAutomaticAccountLinking, + { + shouldAutomaticallyLink: false, + } + ), + onAccountLinked: callbackWithLog("AccountLinking.onAccountLinked", config.onAccountLinked), + override: { + functions: overrideBuilderWithLogging( + "AccountLinking.override.functions", + config?.override?.functions + ), + }, + }) + ); } if (recipe.recipeId === "thirdparty") { let init: ThirdPartyTypeInput = { @@ -148,76 +174,127 @@ function initST(config: any) { ...config.signInAndUpFeature, providers: config.signInAndUpFeature.providers.map((p) => ({ ...p, - ...(p.override ? { override: getFunc(`${p.override}`) } : {}), + override: p.override && getFunc(p.override), })), }; } - if (config?.override?.apis) { - init.override = { - ...init.override, - ...(config?.override.apis ? { apis: getFunc(`${config?.override.apis}`) } : {}), - }; - } - recipeList.push(ThirdParty.init(init)); + recipeList.push( + ThirdParty.init({ + ...init, + override: { + apis: overrideBuilderWithLogging("ThirdParty.override.apis", config?.override?.apis), + functions: overrideBuilderWithLogging( + "ThirdParty.override.functions", + config?.override?.functions + ), + }, + }) + ); } if (recipe.recipeId === "emailverification") { - let init: EmailVerificationTypeInput = { - ...config, - }; - if (config?.emailDelivery?.override) { - init.emailDelivery = { - ...config?.emailDelivery, - override: getFunc(`${config?.emailDelivery.override}`), - }; - } - if (config?.getEmailForRecipeUserId) { - init.getEmailForRecipeUserId = getFunc(`${config?.getEmailForRecipeUserId}`); - } - if (config?.override?.functions) { - init.override = { - ...init.override, - functions: getFunc(`${config?.override.functions}`), - }; - } - recipeList.push(EmailVerification.init(init)); + recipeList.push( + EmailVerification.init({ + ...config, + emailDelivery: { + ...config?.emailDelivery, + override: overrideBuilderWithLogging( + "EmailVerification.emailDelivery", + config?.emailDelivery?.override + ), + }, + getEmailForRecipeUserId: callbackWithLog( + "EmailVerification.getEmailForRecipeUserId", + config?.getEmailForRecipeUserId, + { status: "UNKNOWN_USER_ID_ERROR" } + ), + override: { + apis: overrideBuilderWithLogging("EmailVerification.override.apis", config?.override?.apis), + functions: overrideBuilderWithLogging( + "EmailVerification.override.functions", + config?.override?.functions + ), + }, + }) + ); } if (recipe.recipeId === "multitenancy") { - recipeList.push(Multitenancy.init(config)); + recipeList.push( + Multitenancy.init({ + ...config, + override: { + apis: overrideBuilderWithLogging("Multitenancy.override.apis", config?.override?.apis), + functions: overrideBuilderWithLogging( + "Multitenancy.override.functions", + config?.override?.functions + ), + }, + }) + ); } if (recipe.recipeId === "passwordless") { - let init: PasswordlessTypeInput = { - ...config, - }; - - if (config?.emailDelivery?.service?.sendEmail) { - init.emailDelivery = { - ...config?.emailDelivery, - service: { - ...config?.emailDelivery?.service, - sendEmail: getFunc(`${config?.emailDelivery?.service?.sendEmail}`), + recipeList.push( + Passwordless.init({ + ...config, + emailDelivery: { + ...config?.emailDelivery, + override: overrideBuilderWithLogging("Passwordless.emailDelivery", undefined), + service: { + ...config?.emailDelivery?.service, + sendEmail: + config?.emailDelivery?.service?.sendEmail && + getFunc(config?.emailDelivery?.service?.sendEmail), + }, }, - }; - } - if (config?.smsDelivery?.service?.sendSms) { - init.smsDelivery = { - ...config?.smsDelivery, - service: { - ...config?.smsDelivery?.service, - sendSms: getFunc(`${config?.smsDelivery?.service?.sendSms}`), + smsDelivery: { + ...config?.smsDelivery, + override: overrideBuilderWithLogging("Passwordless.smsDelivery", undefined), + service: { + ...config?.smsDelivery?.service, + sendSms: + config?.smsDelivery?.service?.sendSms && getFunc(config?.smsDelivery?.service?.sendSms), + }, }, - }; - } - if (config?.override?.apis) { - init.override = { - ...init.override, - ...(config?.override.apis ? { apis: getFunc(`${config?.override.apis}`) } : {}), - }; - } - recipeList.push(Passwordless.init(init)); + override: { + apis: overrideBuilderWithLogging("Passwordless.override.apis", config?.override?.apis), + functions: overrideBuilderWithLogging( + "Passwordless.override.functions", + config?.override?.functions + ), + }, + }) + ); } if (recipe.recipeId === "multifactorauth") { - let initConfig: MFATypeInput = { + recipeList.push( + MultiFactorAuth.init({ + ...config, + override: { + apis: overrideBuilderWithLogging("MultiFactorAuth.override.apis", config?.override?.apis), + functions: overrideBuilderWithLogging( + "MultiFactorAuth.override.functions", + config?.override?.functions + ), + }, + }) + ); + } + if (recipe.recipeId === "totp") { + recipeList.push( + TOTP.init({ + ...config, + override: { + apis: overrideBuilderWithLogging("Multitenancy.override.apis", config?.override?.apis), + functions: overrideBuilderWithLogging( + "Multitenancy.override.functions", + config?.override?.functions + ), + }, + }) + ); + } + if (recipe.recipeId === "oauth2") { + let initConfig: OAuth2TypeInput = { ...config, }; if (initConfig.override?.functions) { @@ -232,20 +309,22 @@ function initST(config: any) { apis: getFunc(`${initConfig.override.apis}`), }; } - recipeList.push(MultiFactorAuth.init(initConfig)); - } - if (recipe.recipeId === "totp") { - recipeList.push(TOTP.init(config)); + recipeList.push(OAuth2.init(initConfig)); } }); - settings.recipeList = recipeList; + init.recipeList = recipeList; - if (settings.supertokens?.networkInterceptor) { - settings.supertokens.networkInterceptor = getFunc(`${settings.supertokens.networkInterceptor}`); - } + const interceptor = + parsedConfig?.supertokens?.networkInterceptor && getFunc(parsedConfig.supertokens.networkInterceptor); + init.supertokens.networkInterceptor = loggingOverrideFuncSync("networkInterceptor", (req, userContext) => { + if (interceptor) { + return interceptor(req, userContext); + } + return req; + }); - supertokens.init(settings); + supertokens.init(init); } const app = express(); @@ -280,6 +359,7 @@ app.get("/test/featureflag", async (req, res, next) => { app.post("/test/resetoverrideparams", async (req, res, next) => { resetOverrideParams(); + resetOverrideLogs(); res.json({ ok: true }); }); @@ -289,6 +369,10 @@ app.post("/test/mockexternalapi", async (req, res, next) => { res.json({ ok: true }); }); +app.get("/test/getoverridelogs", async (req, res, next) => { + res.json({ logs: getOverrideLogs() }); +}); + app.get("/test/waitforevent", async (req, res, next) => { try { logDebugMessage("ProcessState:waitForEvent %j", req.query); @@ -318,6 +402,7 @@ app.use("/test/multifactorauth", multiFactorAuthRoutes); app.use("/test/thirdparty", thirdPartyRoutes); app.use("/test/totp", TOTPRoutes); app.use("/test/usermetadata", userMetadataRoutes); +app.use("/test/oauth2", oAuth2Routes); // *** Custom routes to help with session tests *** app.post("/create", async (req, res, next) => { @@ -371,3 +456,59 @@ app.use((err, req, res, next) => { app.listen(API_PORT, "localhost", () => { logDebugMessage(`node-test-server-server started on localhost:${API_PORT}`); }); + +function loggingOverrideFuncSync(name: string, originalImpl: (...args: any[]) => Promise) { + return function (...args: any[]) { + logOverrideEvent(name, "CALL", args); + try { + const res = originalImpl(...args); + logOverrideEvent(name, "RES", res); + return res; + } catch (err) { + logOverrideEvent(name, "REJ", err); + throw err; + } + }; +} +function loggingOverrideFunc(name: string, originalImpl: (...args: any[]) => Promise) { + return async function (...args: any[]) { + logOverrideEvent(name, "CALL", args); + try { + const res = await originalImpl(...args); + logOverrideEvent(name, "RES", res); + return res; + } catch (err) { + logOverrideEvent(name, "REJ", err); + throw err; + } + }; +} + +function callbackWithLog(name: string, overrideName: string, defaultValue?: T) { + const impl = overrideName ? getFunc(overrideName) : () => defaultValue; + return loggingOverrideFunc(name, impl); +} + +function overrideBuilderWithLogging any) | undefined>>( + name: string, + overrideName?: string +) { + return (originalImpl: T, builder: OverrideableBuilder) => { + builder.override((oI) => { + const keys = Object.keys(oI); + // this would be hard to type (and ultimately not worth it) because it is a generic type + // and writing to those through an index is not allowed by default + const ret: any = {}; // Partial + + for (const k of keys) { + ret[k] = loggingOverrideFunc(`${name}.${k}`, oI[k]!.bind(oI)); + } + return ret; + }); + + if (overrideName !== undefined) { + builder.override(getFunc(overrideName)); + } + return originalImpl; + }; +} diff --git a/test/test-server/src/oauth2.ts b/test/test-server/src/oauth2.ts new file mode 100644 index 000000000..d54dff6fe --- /dev/null +++ b/test/test-server/src/oauth2.ts @@ -0,0 +1,46 @@ +import { Router } from "express"; +import OAuth2 from "../../../recipe/oauth2"; +import { logger } from "./logger"; + +const namespace = "com.supertokens:node-test-server:oauth2"; +const { logDebugMessage } = logger(namespace); + +const router = Router() + .post("/getoauth2clients", async (req, res, next) => { + try { + logDebugMessage("OAuth2:getOAuth2Clients %j", req.body); + const response = await OAuth2.getOAuth2Clients(req.body.input, req.body.userContext); + res.json(response); + } catch (e) { + next(e); + } + }) + .post("/createoauth2client", async (req, res, next) => { + try { + logDebugMessage("OAuth2:createOAuth2Client %j", req.body); + const response = await OAuth2.createOAuth2Client(req.body.input, req.body.userContext); + res.json(response); + } catch (e) { + next(e); + } + }) + .post("/updateoauth2client", async (req, res, next) => { + try { + logDebugMessage("OAuth2:updateOAuth2Client %j", req.body); + const response = await OAuth2.updateOAuth2Client(req.body.input, req.body.userContext); + res.json(response); + } catch (e) { + next(e); + } + }) + .post("/deleteoauth2client", async (req, res, next) => { + try { + logDebugMessage("OAuth2:deleteOAuth2Client %j", req.body); + const response = await OAuth2.deleteOAuth2Client(req.body.input, req.body.userContext); + res.json(response); + } catch (e) { + next(e); + } + }); + +export default router; diff --git a/test/test-server/src/overrideLogging.ts b/test/test-server/src/overrideLogging.ts new file mode 100644 index 000000000..f8dd686e4 --- /dev/null +++ b/test/test-server/src/overrideLogging.ts @@ -0,0 +1,68 @@ +import { IncomingMessage } from "http"; + +let overrideLogs: { t: number; name: string; type: "RES" | "REJ" | "CALL"; data: unknown }[] = []; + +export function resetOverrideLogs() { + overrideLogs = []; +} + +export function getOverrideLogs() { + return overrideLogs; +} + +export function logOverrideEvent(name: string, type: "RES" | "REJ" | "CALL", data: unknown) { + overrideLogs.push({ + t: Date.now(), + type, + name, + data: transformLoggedData(data), + }); +} + +function transformLoggedData(data: any, visited: Set = new Set()) { + if (typeof data !== "object") { + return data; + } + if (data === null) { + return data; + } + + if (data instanceof IncomingMessage) { + // We do not actually want these in the logs because: + // 1. They are not useful + // 2. They are very big objects + // 3. They contain circular references + return "IncomingMessage obj"; + } + + if (visited.has(data)) { + return "VISITED"; + } + visited.add(data); + + if (data instanceof Array) { + return data.map((i) => transformLoggedData(i, visited)); + } + if ("toJson" in data) { + return data.toJson(); + } + if ("getAsString" in data) { + return data.getAsString(); + } + if ("getAsStringDangerous" in data) { + return data.getAsStringDangerous(); + } + if ("getAllSessionTokensDangerously" in data) { + return data.getAllSessionTokensDangerously(); + } + + return Object.fromEntries( + Array.from(Object.keys(data)).map((k) => { + if (Object.getOwnPropertyDescriptor(data, k)?.get === undefined) { + return [k, transformLoggedData(data[k], visited)]; + } else { + return [k, "prop is a getter for lazy loading"]; + } + }) + ); +} diff --git a/test/test-server/src/passwordless.ts b/test/test-server/src/passwordless.ts index 35a8381d3..cc8c7fd99 100644 --- a/test/test-server/src/passwordless.ts +++ b/test/test-server/src/passwordless.ts @@ -1,4 +1,5 @@ import { Router } from "express"; +import SuperTokens from "../../.."; import Passwordless from "../../../recipe/passwordless"; import { convertRequestSessionToSessionObject, serializeRecipeUserId, serializeUser } from "./utils"; import { logger } from "./logger"; @@ -67,6 +68,24 @@ const router = Router() } catch (e) { next(e); } + }) + .post("/updateuser", async (req, res, next) => { + try { + logDebugMessage("Passwordless:updateUser %j", req.body); + const response = await Passwordless.updateUser({ + recipeUserId: SuperTokens.convertToRecipeUserId(req.body.recipeUserId), + email: req.body.email, + phoneNumber: req.body.phoneNumber, + userContext: req.body.userContext, + }); + res.json({ + ...response, + ...serializeUser(response), + ...serializeRecipeUserId(response), + }); + } catch (e) { + next(e); + } }); export default router; diff --git a/test/test-server/src/session.ts b/test/test-server/src/session.ts index 838f13038..a6f6ac9c3 100644 --- a/test/test-server/src/session.ts +++ b/test/test-server/src/session.ts @@ -1,10 +1,11 @@ import { Router } from "express"; import Session from "../../../recipe/session"; import * as supertokens from "../../../lib/build"; -import { PrimitiveClaim } from "../../../lib/build/recipe/session/claims"; import SessionRecipe from "../../../lib/build/recipe/session/recipe"; import { logger } from "./logger"; import { getFunc } from "./testFunctionMapper"; +import { convertRequestSessionToSessionObject, deserializeClaim, deserializeValidator } from "./utils"; +import { logOverrideEvent } from "./overrideLogging"; const namespace = "com.supertokens:node-test-server:session"; const { logDebugMessage } = logger(namespace); @@ -110,10 +111,7 @@ const router = Router() .post("/fetchandsetclaim", async (req, res, next) => { try { logDebugMessage("Session.fetchAndSetClaim %j", req.body); - let claim = new PrimitiveClaim({ - key: req.body.claim.key, - fetchValue: getFunc(`${req.body.claim.fetchValue}`), - }); + let claim = deserializeClaim(req.body.claim); const response = await Session.fetchAndSetClaim(req.body.sessionHandle, claim, req.body.userContext); res.json(response); } catch (e) { @@ -147,6 +145,328 @@ const router = Router() } catch (e) { next(e); } + }) + .post("/sessionobject/revokesession", async (req, res, next) => { + logDebugMessage("Session.sessionobject.revokesession %j", req.body); + logOverrideEvent("sessionobject.revokesession", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.revokeSession(req.body.userContext); // : Promise; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.revokesession", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.revokesession", "REJ", e); + next(e); + } + }) + .post("/sessionobject/getsessiondatafromdatabase", async (req, res, next) => { + logDebugMessage("Session.sessionobject.getsessiondatafromdatabase %j", req.body); + logOverrideEvent("sessionobject.getsessiondatafromdatabase", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getSessionDataFromDatabase(req.body.userContext); // : Promise; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.getsessiondatafromdatabase", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.getsessiondatafromdatabase", "REJ", e); + next(e); + } + }) + .post("/sessionobject/updatesessiondataindatabase", async (req, res, next) => { + logDebugMessage("Session.sessionobject.updatesessiondataindatabase %j", req.body); + logOverrideEvent("sessionobject.updatesessiondataindatabase", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.updateSessionDataInDatabase(req.body.newSessionData, req.body.userContext); // : Promise; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.updatesessiondataindatabase", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.updatesessiondataindatabase", "REJ", e); + next(e); + } + }) + .post("/sessionobject/getuserid", async (req, res, next) => { + logDebugMessage("Session.sessionobject.getuserid %j", req.body); + logOverrideEvent("sessionobject.getuserid", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getUserId(req.body.userContext); // : string; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.getuserid", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.getuserid", "REJ", e); + next(e); + } + }) + .post("/sessionobject/getrecipeuserid", async (req, res, next) => { + logDebugMessage("Session.sessionobject.getrecipeuserid %j", req.body); + logOverrideEvent("sessionobject.getrecipeuserid", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getRecipeUserId(req.body.userContext); // : RecipeUserId; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.getrecipeuserid", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.getrecipeuserid", "REJ", e); + next(e); + } + }) + .post("/sessionobject/gettenantid", async (req, res, next) => { + logDebugMessage("Session.sessionobject.gettenantid %j", req.body); + logOverrideEvent("sessionobject.gettenantid", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getTenantId(req.body.userContext); // : string; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.gettenantid", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.gettenantid", "REJ", e); + next(e); + } + }) + .post("/sessionobject/getaccesstokenpayload", async (req, res, next) => { + logDebugMessage("Session.sessionobject.getaccesstokenpayload %j", req.body); + logOverrideEvent("sessionobject.getaccesstokenpayload", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getAccessTokenPayload(req.body.userContext); // : any; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.getaccesstokenpayload", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.getaccesstokenpayload", "REJ", e); + next(e); + } + }) + .post("/sessionobject/gethandle", async (req, res, next) => { + logDebugMessage("Session.sessionobject.gethandle %j", req.body); + logOverrideEvent("sessionobject.gethandle", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getHandle(req.body.userContext); // : string; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.gethandle", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.gethandle", "REJ", e); + next(e); + } + }) + .post("/sessionobject/getallsessiontokensdangerously", async (req, res, next) => { + logDebugMessage("Session.sessionobject.getallsessiontokensdangerously %j", req.body); + logOverrideEvent("sessionobject.getallsessiontokensdangerously", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getAllSessionTokensDangerously(); // : Promise<{}>; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.getallsessiontokensdangerously", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.getallsessiontokensdangerously", "REJ", e); + next(e); + } + }) + .post("/sessionobject/getaccesstoken", async (req, res, next) => { + logDebugMessage("Session.sessionobject.getaccesstoken %j", req.body); + logOverrideEvent("sessionobject.getaccesstoken", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getAccessToken(req.body.userContext); // : string; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.getaccesstoken", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.getaccesstoken", "REJ", e); + next(e); + } + }) + .post("/sessionobject/mergeintoaccesstokenpayload", async (req, res, next) => { + logDebugMessage("Session.sessionobject.mergeintoaccesstokenpayload %j", req.body); + logOverrideEvent("sessionobject.mergeintoaccesstokenpayload", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.mergeIntoAccessTokenPayload( + req.body.accessTokenPayloadUpdate, + req.body.userContext + ); // : Promise; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.mergeintoaccesstokenpayload", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.mergeintoaccesstokenpayload", "REJ", e); + next(e); + } + }) + .post("/sessionobject/gettimecreated", async (req, res, next) => { + logDebugMessage("Session.sessionobject.gettimecreated %j", req.body); + logOverrideEvent("sessionobject.gettimecreated", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getTimeCreated(req.body.userContext); // : Promise; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.gettimecreated", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.gettimecreated", "REJ", e); + next(e); + } + }) + .post("/sessionobject/getexpiry", async (req, res, next) => { + logDebugMessage("Session.sessionobject.getexpiry %j", req.body); + logOverrideEvent("sessionobject.getexpiry", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getExpiry(req.body.userContext); // : Promise; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.getexpiry", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.getexpiry", "REJ", e); + next(e); + } + }) + .post("/sessionobject/assertclaims", async (req, res, next) => { + logDebugMessage("Session.sessionobject.assertclaims %j", req.body); + logOverrideEvent("sessionobject.assertclaims", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.assertClaims( + req.body.claimValidators.map(deserializeValidator), + req.body.userContext + ); // : Promise; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.assertclaims", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.assertclaims", "REJ", e); + next(e); + } + }) + .post("/sessionobject/fetchandsetclaim", async (req, res, next) => { + logDebugMessage("Session.sessionobject.fetchandsetclaim %j", req.body); + logOverrideEvent("sessionobject.fetchandsetclaim", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + + const retVal = await session.fetchAndSetClaim(deserializeClaim(req.body.claim), req.body.userContext); // : Promise; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.fetchandsetclaim", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.fetchandsetclaim", "REJ", e); + next(e); + } + }) + .post("/sessionobject/setclaimvalue", async (req, res, next) => { + logDebugMessage("Session.sessionobject.setclaimvalue %j", req.body); + logOverrideEvent("sessionobject.setclaimvalue", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.setClaimValue( + deserializeClaim(req.body.claim), + req.body.value, + req.body.userContext + ); // : Promise; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.setclaimvalue", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.setclaimvalue", "REJ", e); + next(e); + } + }) + .post("/sessionobject/removeclaim", async (req, res, next) => { + logDebugMessage("Session.sessionobject.removeClaim %j", req.body); + logOverrideEvent("sessionobject.removeClaim", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + await session.removeClaim(deserializeClaim(req.body.claim), req.body.userContext); // : Promise; + res.json({ updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.removeClaim", "RES", undefined); + } catch (e) { + logOverrideEvent("sessionobject.removeClaim", "REJ", e); + next(e); + } + }) + .post("/sessionobject/getclaimvalue", async (req, res, next) => { + logDebugMessage("Session.sessionobject.getclaimvalue %j", req.body); + logOverrideEvent("sessionobject.getclaimvalue", "CALL", req.body); + try { + const session = await convertRequestSessionToSessionObject(req.body.session); + if (!session) { + throw new Error("This should never happen: failed to deserialize session"); + } + const retVal = await session.getClaimValue(deserializeClaim(req.body.claim), req.body.userContext); // : Promise; + res.json({ retVal, updatedSession: { ...session } }); + + logOverrideEvent("sessionobject.getclaimvalue", "RES", retVal); + } catch (e) { + logOverrideEvent("sessionobject.getclaimvalue", "REJ", e); + next(e); + } + }) + .post("/sessionobject/attachtorequestresponse", async (req, res, next) => { + logDebugMessage("Session.sessionobject.attachtorequestresponse %j", req.body); + logOverrideEvent("sessionobject.attachtorequestresponse", "CALL", req.body); + throw new Error("This should never happen: attachToRequestResponse called on remote-test session obj"); }); export default router; diff --git a/test/test-server/src/utils.ts b/test/test-server/src/utils.ts index 96c28f4e0..69ecb21ce 100644 --- a/test/test-server/src/utils.ts +++ b/test/test-server/src/utils.ts @@ -1,19 +1,140 @@ import Session from "../../../recipe/session"; import * as supertokens from "../../../lib/build"; +import { parseJWTWithoutSignatureVerification } from "../../../lib/build/recipe/session/jwt"; +import SessionClass from "../../../lib/build/recipe/session/sessionClass"; +import SessionRecipe from "../../../lib/build/recipe/session/recipe"; +import { SessionClaimValidator, TokenInfo } from "../../../lib/build/recipe/session/types"; +import { BooleanClaim, PrimitiveArrayClaim, PrimitiveClaim } from "../../../lib/build/recipe/session/claims"; +import { EmailVerificationClaim } from "../../../recipe/emailverification"; +import { MultiFactorAuthClaim } from "../../../recipe/multifactorauth"; +import { PermissionClaim, UserRoleClaim } from "../../../recipe/userroles"; +import { logOverrideEvent } from "./overrideLogging"; + +const testClaimSetups = { + "st-true": () => + new BooleanClaim({ + key: "st-true", + fetchValue: () => true, + }), + "st-undef": () => + new BooleanClaim({ + key: "st-undef", + fetchValue: () => undefined, + }), +}; + +// Add all built-in claims +for (const c of [EmailVerificationClaim, MultiFactorAuthClaim, UserRoleClaim, PermissionClaim]) { + testClaimSetups[c.key] = () => c; +} + +const mockClaimBuilder = ({ key, values }) => { + const claim = new PrimitiveClaim({ + key: key ?? "st-stub-primitive", + fetchValue: (userId, recipeUserId, tenantId, currentPayload, userContext) => { + logOverrideEvent(`claim-${key}.fetchValue`, "CALL", { + userId, + recipeUserId, + tenantId, + currentPayload, + userContext, + }); + + // Here we can't reuse popOrUseVal because the values are arrays themselves + const retVal = + userContext["st-stub-arr-value"] ?? + (values instanceof Array && values[0] instanceof Array ? values.pop() : values); + logOverrideEvent(`claim-${key}.fetchValue`, "RES", retVal); + + return retVal; + }, + }); + + return claim; +}; + +export function deserializeClaim(serializedClaim: { key: string; values: any }) { + if (serializedClaim.key.startsWith("st-stub-")) { + return mockClaimBuilder({ ...serializedClaim, key: serializedClaim.key.replace(/^st-stub-/, "") }); + } + return testClaimSetups[serializedClaim.key](serializedClaim); +} + +export function deserializeValidator( + serializedValidator: { key: string } & ( + | { validatorName: string; args: any[] } + | { id?: string; shouldRefetchRes: boolean | boolean[]; validateRes: any | any[] } + ) +): SessionClaimValidator { + const claim = testClaimSetups[serializedValidator.key](serializedValidator); + if ("validatorName" in serializedValidator) { + return claim.validators[serializedValidator.validatorName](...serializedValidator.args); + } + return { + id: serializedValidator.id ?? serializedValidator.key, + claim, + shouldRefetch: (payload, ctx) => { + logOverrideEvent(`${serializedValidator.key}-shouldRefetch`, "CALL", { payload, ctx }); + const retVal = + ctx[`${serializedValidator.key}-shouldRefetch-res`] ?? + popOrUseVal(serializedValidator.shouldRefetchRes); + logOverrideEvent(`${serializedValidator.key}-shouldRefetch`, "RES", { retVal }); + + return retVal; + }, + validate: (payload, ctx) => { + logOverrideEvent(`${serializedValidator.key}-validate`, "CALL", { payload, ctx }); + const retVal = + ctx[`${serializedValidator.key}-validate-res`] ?? popOrUseVal(serializedValidator.validateRes); + logOverrideEvent(`${serializedValidator.key}-validate`, "RES", { retVal }); + return retVal; + }, + }; +} export async function convertRequestSessionToSessionObject( - session: { [key: string]: any } | undefined + tokens: + | { + accessToken: string; + frontToken: string; + refreshToken: TokenInfo | undefined; + antiCsrfToken: string | undefined; + } + | undefined ): Promise { - if (session !== undefined) { - return await Session.getSessionWithoutRequestResponse( - session.accessToken, - session.userDataInAccessToken?.antiCsrfToken, - { - overrideGlobalClaimValidators: () => [], - } + if (tokens !== undefined) { + const helpers = { + config: SessionRecipe.getInstanceOrThrowError().config, + getRecipeImpl: () => SessionRecipe.getInstanceOrThrowError().recipeInterfaceImpl, + }; + + const jwtInfo = parseJWTWithoutSignatureVerification(tokens.accessToken); + const jwtPayload = jwtInfo.payload; + + let userId = jwtInfo.version === 2 ? jwtPayload.userId! : jwtPayload.sub!; + let sessionHandle = jwtPayload.sessionHandle!; + + let recipeUserId = new supertokens.RecipeUserId(jwtPayload.rsub ?? userId); + let antiCsrfToken = jwtPayload.antiCsrfToken; + let tenantId = jwtInfo.version >= 4 ? jwtPayload.tId! : "public"; + + const session = new SessionClass( + helpers as any, + tokens.accessToken, + tokens.frontToken, + tokens.refreshToken, + antiCsrfToken, + sessionHandle, + userId, + recipeUserId, + jwtPayload, + undefined, + false, + tenantId ); + return session; } - return session; + return tokens; } export function serializeUser(response) { @@ -35,3 +156,13 @@ export function serializeRecipeUserId(response) { : {}), }; } + +function popOrUseVal(arrOrValue: T | T[]): T { + if (arrOrValue instanceof Array) { + if (arrOrValue.length === 0) { + throw new Error("Ran out of values"); + } + return arrOrValue.pop()!; + } + return arrOrValue; +} diff --git a/test/with-typescript/index.ts b/test/with-typescript/index.ts index f16582249..4a2a3d500 100644 --- a/test/with-typescript/index.ts +++ b/test/with-typescript/index.ts @@ -1593,6 +1593,11 @@ Session.init({ getOpenIdDiscoveryConfiguration: async (input) => ({ issuer: "your issuer", jwks_uri: "https://your.api.domain/auth/jwt/jwks.json", + token_endpoint: "http://localhost:3000/auth/oauth2/token", + authorization_endpoint: "http://localhost:3000/auth/oauth2/auth", + id_token_signing_alg_values_supported: [], + response_types_supported: [], + subject_types_supported: [], status: "OK", }), }), From e84eb49b578d5021933a8eccc7ee87208706c2fb Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Tue, 23 Jul 2024 13:04:50 +0000 Subject: [PATCH 08/35] 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 --- lib/build/recipe/jwt/api/implementation.js | 2 +- lib/build/recipe/oauth2/recipe.js | 2 +- .../oauth2client/api/authorisationUrl.d.ts | 9 + .../oauth2client/api/authorisationUrl.js | 44 ++++ .../oauth2client/api/implementation.d.ts | 3 + .../recipe/oauth2client/api/implementation.js | 66 ++++++ lib/build/recipe/oauth2client/api/signin.d.ts | 9 + lib/build/recipe/oauth2client/api/signin.js | 81 +++++++ lib/build/recipe/oauth2client/constants.d.ts | 3 + lib/build/recipe/oauth2client/constants.js | 19 ++ lib/build/recipe/oauth2client/index.d.ts | 30 +++ lib/build/recipe/oauth2client/index.js | 65 ++++++ lib/build/recipe/oauth2client/recipe.d.ts | 38 +++ lib/build/recipe/oauth2client/recipe.js | 116 +++++++++ .../oauth2client/recipeImplementation.d.ts | 4 + .../oauth2client/recipeImplementation.js | 165 +++++++++++++ lib/build/recipe/oauth2client/types.d.ts | 182 +++++++++++++++ lib/build/recipe/oauth2client/types.js | 16 ++ lib/build/recipe/oauth2client/utils.d.ts | 7 + lib/build/recipe/oauth2client/utils.js | 49 ++++ .../recipe/thirdparty/providers/bitbucket.js | 6 +- .../recipe/thirdparty/providers/custom.js | 8 +- .../recipe/thirdparty/providers/github.js | 16 +- .../recipe/thirdparty/providers/linkedin.js | 4 +- .../recipe/thirdparty/providers/twitter.js | 4 +- .../recipe/thirdparty/providers/utils.d.ts | 32 --- .../recipe/thirdparty/providers/utils.js | 131 +---------- lib/build/thirdpartyUtils.d.ts | 34 +++ lib/build/thirdpartyUtils.js | 130 +++++++++++ lib/ts/recipe/jwt/api/implementation.ts | 3 +- lib/ts/recipe/oauth2/recipe.ts | 2 +- lib/ts/recipe/oauth2/types.ts | 10 - .../oauth2client/api/authorisationUrl.ts | 49 ++++ .../recipe/oauth2client/api/implementation.ts | 72 ++++++ lib/ts/recipe/oauth2client/api/signin.ts | 92 ++++++++ lib/ts/recipe/oauth2client/constants.ts | 18 ++ lib/ts/recipe/oauth2client/index.ts | 78 +++++++ lib/ts/recipe/oauth2client/recipe.ts | 146 ++++++++++++ .../oauth2client/recipeImplementation.ts | 221 ++++++++++++++++++ lib/ts/recipe/oauth2client/types.ts | 183 +++++++++++++++ lib/ts/recipe/oauth2client/utils.ts | 53 +++++ .../recipe/thirdparty/providers/bitbucket.ts | 2 +- lib/ts/recipe/thirdparty/providers/custom.ts | 2 +- lib/ts/recipe/thirdparty/providers/github.ts | 2 +- .../recipe/thirdparty/providers/linkedin.ts | 2 +- lib/ts/recipe/thirdparty/providers/twitter.ts | 2 +- lib/ts/recipe/thirdparty/providers/utils.ts | 124 +--------- lib/ts/thirdpartyUtils.ts | 122 ++++++++++ recipe/oauth2client/index.d.ts | 10 + recipe/oauth2client/index.js | 6 + recipe/oauth2client/types/index.d.ts | 10 + recipe/oauth2client/types/index.js | 6 + test/test-server/src/index.ts | 23 ++ 53 files changed, 2196 insertions(+), 317 deletions(-) create mode 100644 lib/build/recipe/oauth2client/api/authorisationUrl.d.ts create mode 100644 lib/build/recipe/oauth2client/api/authorisationUrl.js create mode 100644 lib/build/recipe/oauth2client/api/implementation.d.ts create mode 100644 lib/build/recipe/oauth2client/api/implementation.js create mode 100644 lib/build/recipe/oauth2client/api/signin.d.ts create mode 100644 lib/build/recipe/oauth2client/api/signin.js create mode 100644 lib/build/recipe/oauth2client/constants.d.ts create mode 100644 lib/build/recipe/oauth2client/constants.js create mode 100644 lib/build/recipe/oauth2client/index.d.ts create mode 100644 lib/build/recipe/oauth2client/index.js create mode 100644 lib/build/recipe/oauth2client/recipe.d.ts create mode 100644 lib/build/recipe/oauth2client/recipe.js create mode 100644 lib/build/recipe/oauth2client/recipeImplementation.d.ts create mode 100644 lib/build/recipe/oauth2client/recipeImplementation.js create mode 100644 lib/build/recipe/oauth2client/types.d.ts create mode 100644 lib/build/recipe/oauth2client/types.js create mode 100644 lib/build/recipe/oauth2client/utils.d.ts create mode 100644 lib/build/recipe/oauth2client/utils.js create mode 100644 lib/build/thirdpartyUtils.d.ts create mode 100644 lib/build/thirdpartyUtils.js create mode 100644 lib/ts/recipe/oauth2client/api/authorisationUrl.ts create mode 100644 lib/ts/recipe/oauth2client/api/implementation.ts create mode 100644 lib/ts/recipe/oauth2client/api/signin.ts create mode 100644 lib/ts/recipe/oauth2client/constants.ts create mode 100644 lib/ts/recipe/oauth2client/index.ts create mode 100644 lib/ts/recipe/oauth2client/recipe.ts create mode 100644 lib/ts/recipe/oauth2client/recipeImplementation.ts create mode 100644 lib/ts/recipe/oauth2client/types.ts create mode 100644 lib/ts/recipe/oauth2client/utils.ts create mode 100644 lib/ts/thirdpartyUtils.ts create mode 100644 recipe/oauth2client/index.d.ts create mode 100644 recipe/oauth2client/index.js create mode 100644 recipe/oauth2client/types/index.d.ts create mode 100644 recipe/oauth2client/types/index.js diff --git a/lib/build/recipe/jwt/api/implementation.js b/lib/build/recipe/jwt/api/implementation.js index 4e8bc342f..ab43be07c 100644 --- a/lib/build/recipe/jwt/api/implementation.js +++ b/lib/build/recipe/jwt/api/implementation.js @@ -21,7 +21,7 @@ function getAPIImplementation() { if (resp.validityInSeconds !== undefined) { options.res.setHeader("Cache-Control", `max-age=${resp.validityInSeconds}, must-revalidate`, false); } - const oauth2 = require("../../oauth2").getInstance(); + const oauth2 = require("../../oauth2/recipe").default.getInstance(); // TODO: dirty hack until we get core support if (oauth2 !== undefined) { const oauth2JWKSRes = await fetch("http://localhost:4444/.well-known/jwks.json"); diff --git a/lib/build/recipe/oauth2/recipe.js b/lib/build/recipe/oauth2/recipe.js index 9a7a01277..4e6d0d78c 100644 --- a/lib/build/recipe/oauth2/recipe.js +++ b/lib/build/recipe/oauth2/recipe.js @@ -75,7 +75,7 @@ class Recipe extends recipeModule_1.default { querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo, - this.getDefaultIdTokenPayload + this.getDefaultIdTokenPayload.bind(this) ) ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); diff --git a/lib/build/recipe/oauth2client/api/authorisationUrl.d.ts b/lib/build/recipe/oauth2client/api/authorisationUrl.d.ts new file mode 100644 index 000000000..61b231e1a --- /dev/null +++ b/lib/build/recipe/oauth2client/api/authorisationUrl.d.ts @@ -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; diff --git a/lib/build/recipe/oauth2client/api/authorisationUrl.js b/lib/build/recipe/oauth2client/api/authorisationUrl.js new file mode 100644 index 000000000..e4544b31c --- /dev/null +++ b/lib/build/recipe/oauth2client/api/authorisationUrl.js @@ -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; diff --git a/lib/build/recipe/oauth2client/api/implementation.d.ts b/lib/build/recipe/oauth2client/api/implementation.d.ts new file mode 100644 index 000000000..dd40e7025 --- /dev/null +++ b/lib/build/recipe/oauth2client/api/implementation.d.ts @@ -0,0 +1,3 @@ +// @ts-nocheck +import { APIInterface } from "../"; +export default function getAPIInterface(): APIInterface; diff --git a/lib/build/recipe/oauth2client/api/implementation.js b/lib/build/recipe/oauth2client/api/implementation.js new file mode 100644 index 000000000..5b642629a --- /dev/null +++ b/lib/build/recipe/oauth2client/api/implementation.js @@ -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; diff --git a/lib/build/recipe/oauth2client/api/signin.d.ts b/lib/build/recipe/oauth2client/api/signin.d.ts new file mode 100644 index 000000000..72cd6e46b --- /dev/null +++ b/lib/build/recipe/oauth2client/api/signin.d.ts @@ -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; diff --git a/lib/build/recipe/oauth2client/api/signin.js b/lib/build/recipe/oauth2client/api/signin.js new file mode 100644 index 000000000..0a0fadc45 --- /dev/null +++ b/lib/build/recipe/oauth2client/api/signin.js @@ -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; diff --git a/lib/build/recipe/oauth2client/constants.d.ts b/lib/build/recipe/oauth2client/constants.d.ts new file mode 100644 index 000000000..fd2bef07d --- /dev/null +++ b/lib/build/recipe/oauth2client/constants.d.ts @@ -0,0 +1,3 @@ +// @ts-nocheck +export declare const AUTHORISATION_API = "/oauth2client/authorisationurl"; +export declare const SIGN_IN_API = "/oauth2client/signin"; diff --git a/lib/build/recipe/oauth2client/constants.js b/lib/build/recipe/oauth2client/constants.js new file mode 100644 index 000000000..e8156ec89 --- /dev/null +++ b/lib/build/recipe/oauth2client/constants.js @@ -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"; diff --git a/lib/build/recipe/oauth2client/index.d.ts b/lib/build/recipe/oauth2client/index.d.ts new file mode 100644 index 000000000..a16f0bb05 --- /dev/null +++ b/lib/build/recipe/oauth2client/index.d.ts @@ -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 + ): Promise<{ + urlWithQueryParams: string; + pkceCodeVerifier?: string | undefined; + }>; + static exchangeAuthCodeForOAuthTokens( + redirectURIInfo: { + redirectURIOnProviderDashboard: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string | undefined; + }, + userContext?: Record + ): Promise; + static getUserInfo( + oAuthTokens: OAuthTokens, + userContext?: Record + ): Promise; +} +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 }; diff --git a/lib/build/recipe/oauth2client/index.js b/lib/build/recipe/oauth2client/index.js new file mode 100644 index 000000000..e9481bbfa --- /dev/null +++ b/lib/build/recipe/oauth2client/index.js @@ -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; diff --git a/lib/build/recipe/oauth2client/recipe.d.ts b/lib/build/recipe/oauth2client/recipe.d.ts new file mode 100644 index 000000000..180227169 --- /dev/null +++ b/lib/build/recipe/oauth2client/recipe.d.ts @@ -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; + handleError: (err: STError, _request: BaseRequest, _response: BaseResponse) => Promise; + getAllCORSHeaders: () => string[]; + isErrorFromThisRecipe: (err: any) => err is STError; +} diff --git a/lib/build/recipe/oauth2client/recipe.js b/lib/build/recipe/oauth2client/recipe.js new file mode 100644 index 000000000..d3ad7c29a --- /dev/null +++ b/lib/build/recipe/oauth2client/recipe.js @@ -0,0 +1,116 @@ +"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 recipeModule_1 = __importDefault(require("../../recipeModule")); +const utils_1 = require("./utils"); +const error_1 = __importDefault(require("../../error")); +const constants_1 = require("./constants"); +const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); +const signin_1 = __importDefault(require("./api/signin")); +const authorisationUrl_1 = __importDefault(require("./api/authorisationUrl")); +const recipeImplementation_1 = __importDefault(require("./recipeImplementation")); +const implementation_1 = __importDefault(require("./api/implementation")); +const querier_1 = require("../../querier"); +const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); +class Recipe extends recipeModule_1.default { + constructor(recipeId, appInfo, isInServerlessEnv, config, _recipes) { + super(recipeId, appInfo); + this.getAPIsHandled = () => { + return [ + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.SIGN_IN_API), + id: constants_1.SIGN_IN_API, + disabled: this.apiImpl.signInPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.AUTHORISATION_API), + id: constants_1.AUTHORISATION_API, + disabled: this.apiImpl.authorisationUrlGET === undefined, + }, + ]; + }; + this.handleAPIRequest = async (id, tenantId, req, res, _path, _method, userContext) => { + let options = { + config: this.config, + recipeId: this.getRecipeId(), + isInServerlessEnv: this.isInServerlessEnv, + recipeImplementation: this.recipeInterfaceImpl, + req, + res, + appInfo: this.getAppInfo(), + }; + if (id === constants_1.SIGN_IN_API) { + return await signin_1.default(this.apiImpl, tenantId, options, userContext); + } else if (id === constants_1.AUTHORISATION_API) { + return await authorisationUrl_1.default(this.apiImpl, tenantId, options, userContext); + } + return false; + }; + this.handleError = async (err, _request, _response) => { + throw err; + }; + this.getAllCORSHeaders = () => { + return []; + }; + this.isErrorFromThisRecipe = (err) => { + return error_1.default.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; + }; + this.config = utils_1.validateAndNormaliseUserInput(appInfo, config); + this.isInServerlessEnv = isInServerlessEnv; + { + let builder = new supertokens_js_override_1.default( + recipeImplementation_1.default(querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config) + ); + this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); + } + { + let builder = new supertokens_js_override_1.default(implementation_1.default()); + this.apiImpl = builder.override(this.config.override.apis).build(); + } + } + static init(config) { + return (appInfo, isInServerlessEnv) => { + if (Recipe.instance === undefined) { + Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config, {}); + return Recipe.instance; + } else { + throw new Error("OAuth2Client recipe has already been initialised. Please check your code for bugs."); + } + }; + } + static getInstanceOrThrowError() { + if (Recipe.instance !== undefined) { + return Recipe.instance; + } + throw new Error("Initialisation not done. Did you forget to call the OAuth2Client.init function?"); + } + static reset() { + if (process.env.TEST_MODE !== "testing") { + throw new Error("calling testing function in non testing env"); + } + Recipe.instance = undefined; + } +} +exports.default = Recipe; +Recipe.instance = undefined; +Recipe.RECIPE_ID = "oauth2client"; diff --git a/lib/build/recipe/oauth2client/recipeImplementation.d.ts b/lib/build/recipe/oauth2client/recipeImplementation.d.ts new file mode 100644 index 000000000..24599a2c7 --- /dev/null +++ b/lib/build/recipe/oauth2client/recipeImplementation.d.ts @@ -0,0 +1,4 @@ +// @ts-nocheck +import { RecipeInterface, TypeNormalisedInput } from "./types"; +import { Querier } from "../../querier"; +export default function getRecipeImplementation(_querier: Querier, config: TypeNormalisedInput): RecipeInterface; diff --git a/lib/build/recipe/oauth2client/recipeImplementation.js b/lib/build/recipe/oauth2client/recipeImplementation.js new file mode 100644 index 000000000..8343d5d61 --- /dev/null +++ b/lib/build/recipe/oauth2client/recipeImplementation.js @@ -0,0 +1,165 @@ +"use strict"; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const recipeUserId_1 = __importDefault(require("../../recipeUserId")); +const thirdpartyUtils_1 = require("../../thirdpartyUtils"); +const pkce_challenge_1 = __importDefault(require("pkce-challenge")); +const __1 = require("../.."); +const logger_1 = require("../../logger"); +const jose_1 = require("jose"); +function getRecipeImplementation(_querier, config) { + let providerConfigWithOIDCInfo = null; + return { + getAuthorisationRedirectURL: async function ({ providerConfig, redirectURIOnProviderDashboard }) { + const queryParams = { + client_id: providerConfig.clientId, + redirect_uri: redirectURIOnProviderDashboard, + response_type: "code", + }; + if (providerConfig.scope !== undefined) { + queryParams.scope = providerConfig.scope.join(" "); + } + let pkceCodeVerifier = undefined; + if (providerConfig.clientSecret === undefined || providerConfig.forcePKCE) { + const { code_challenge, code_verifier } = pkce_challenge_1.default(64); // According to https://www.rfc-editor.org/rfc/rfc7636, length must be between 43 and 128 + queryParams["code_challenge"] = code_challenge; + queryParams["code_challenge_method"] = "S256"; + pkceCodeVerifier = code_verifier; + } + const urlObj = new URL(providerConfig.authorizationEndpoint); + for (const [key, value] of Object.entries(queryParams)) { + urlObj.searchParams.set(key, value); + } + return { + urlWithQueryParams: urlObj.toString(), + pkceCodeVerifier: pkceCodeVerifier, + }; + }, + signIn: async function ({ userId, tenantId, userContext, oAuthTokens, rawUserInfoFromProvider }) { + const user = await __1.getUser(userId, userContext); + if (user === undefined) { + throw new Error(`Failed to getUser from the userId ${userId} in the ${tenantId} tenant`); + } + return { + status: "OK", + user, + recipeUserId: new recipeUserId_1.default(userId), + oAuthTokens, + rawUserInfoFromProvider, + }; + }, + getProviderConfig: async function () { + if (providerConfigWithOIDCInfo !== null) { + return providerConfigWithOIDCInfo; + } + const oidcInfo = await thirdpartyUtils_1.getOIDCDiscoveryInfo(config.providerConfig.oidcDiscoveryEndpoint); + if (oidcInfo.authorization_endpoint === undefined) { + throw new Error("Failed to authorization_endpoint from the oidcDiscoveryEndpoint."); + } + if (oidcInfo.token_endpoint === undefined) { + throw new Error("Failed to token_endpoint from the oidcDiscoveryEndpoint."); + } + // TODO: We currently don't have this + // if (oidcInfo.userinfo_endpoint === undefined) { + // throw new Error("Failed to userinfo_endpoint from the oidcDiscoveryEndpoint."); + // } + if (oidcInfo.jwks_uri === undefined) { + throw new Error("Failed to jwks_uri from the oidcDiscoveryEndpoint."); + } + providerConfigWithOIDCInfo = Object.assign(Object.assign({}, config.providerConfig), { + authorizationEndpoint: oidcInfo.authorization_endpoint, + tokenEndpoint: oidcInfo.token_endpoint, + userInfoEndpoint: oidcInfo.userinfo_endpoint, + jwksURI: oidcInfo.jwks_uri, + }); + return providerConfigWithOIDCInfo; + }, + exchangeAuthCodeForOAuthTokens: async function ({ providerConfig, redirectURIInfo }) { + if (providerConfig.tokenEndpoint === undefined) { + throw new Error("OAuth2Client provider's tokenEndpoint is not configured."); + } + const tokenAPIURL = providerConfig.tokenEndpoint; + const accessTokenAPIParams = { + client_id: providerConfig.clientId, + redirect_uri: redirectURIInfo.redirectURIOnProviderDashboard, + code: redirectURIInfo.redirectURIQueryParams["code"], + grant_type: "authorization_code", + }; + if (providerConfig.clientSecret !== undefined) { + accessTokenAPIParams["client_secret"] = providerConfig.clientSecret; + } + if (redirectURIInfo.pkceCodeVerifier !== undefined) { + accessTokenAPIParams["code_verifier"] = redirectURIInfo.pkceCodeVerifier; + } + const tokenResponse = await thirdpartyUtils_1.doPostRequest(tokenAPIURL, accessTokenAPIParams); + if (tokenResponse.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` + ); + throw new Error( + `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` + ); + } + return tokenResponse.jsonResponse; + }, + getUserInfo: async function ({ providerConfig, oAuthTokens }) { + let jwks; + const accessToken = oAuthTokens["access_token"]; + const idToken = oAuthTokens["id_token"]; + let rawUserInfoFromProvider = { + fromUserInfoAPI: {}, + fromIdTokenPayload: {}, + }; + if (idToken && providerConfig.jwksURI !== undefined) { + if (jwks === undefined) { + jwks = jose_1.createRemoteJWKSet(new URL(providerConfig.jwksURI)); + } + rawUserInfoFromProvider.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload( + idToken, + jwks, + { + audience: providerConfig.clientId, + } + ); + } + if (accessToken && providerConfig.userInfoEndpoint !== undefined) { + const headers = { + Authorization: "Bearer " + accessToken, + }; + const queryParams = {}; + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( + providerConfig.userInfoEndpoint, + queryParams, + headers + ); + if (userInfoFromAccessToken.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); + throw new Error( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); + } + rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; + } + let userId = undefined; + if (rawUserInfoFromProvider.fromIdTokenPayload !== undefined) { + userId = rawUserInfoFromProvider.fromIdTokenPayload["sub"]; + } else if (rawUserInfoFromProvider.fromUserInfoAPI !== undefined) { + userId = rawUserInfoFromProvider.fromUserInfoAPI["sub"]; + } + if (userId === undefined) { + throw new Error(`Failed to get userId from both the idToken and userInfo endpoint.`); + } + return { + userId, + rawUserInfoFromProvider, + }; + }, + }; +} +exports.default = getRecipeImplementation; diff --git a/lib/build/recipe/oauth2client/types.d.ts b/lib/build/recipe/oauth2client/types.d.ts new file mode 100644 index 000000000..d00a4a069 --- /dev/null +++ b/lib/build/recipe/oauth2client/types.d.ts @@ -0,0 +1,182 @@ +// @ts-nocheck +import type { BaseRequest, BaseResponse } from "../../framework"; +import { NormalisedAppinfo, UserContext } from "../../types"; +import OverrideableBuilder from "supertokens-js-override"; +import { SessionContainerInterface } from "../session/types"; +import { GeneralErrorResponse, User } from "../../types"; +import RecipeUserId from "../../recipeUserId"; +export declare type UserInfo = { + userId: string; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { + [key: string]: any; + }; + fromUserInfoAPI?: { + [key: string]: any; + }; + }; +}; +export declare type ProviderConfigInput = { + clientId: string; + clientSecret: string; + authorizationEndpointQueryParams?: { + [key: string]: string | null; + }; + oidcDiscoveryEndpoint: string; + scope?: string[]; + forcePKCE?: boolean; +}; +export declare type ProviderConfigWithOIDCInfo = ProviderConfigInput & { + authorizationEndpoint: string; + tokenEndpoint: string; + userInfoEndpoint: string; + jwksURI: string; +}; +export declare type OAuthTokens = { + access_token?: string; + id_token?: string; +}; +export declare type OAuthTokenResponse = { + access_token: string; + id_token?: string; + refresh_token?: string; + expires_in: number; + scope?: string; + token_type: string; +}; +export declare type TypeInput = { + providerConfig: ProviderConfigInput; + override?: { + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; +export declare type TypeNormalisedInput = { + providerConfig: ProviderConfigInput; + override: { + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; +export declare type RecipeInterface = { + getAuthorisationRedirectURL(input: { + providerConfig: ProviderConfigWithOIDCInfo; + redirectURIOnProviderDashboard: string; + userContext: UserContext; + }): Promise<{ + urlWithQueryParams: string; + pkceCodeVerifier?: string; + }>; + getProviderConfig(input: { userContext: UserContext }): Promise; + signIn(input: { + userId: string; + oAuthTokens: OAuthTokens; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { + [key: string]: any; + }; + fromUserInfoAPI?: { + [key: string]: any; + }; + }; + tenantId: string; + userContext: UserContext; + }): Promise<{ + status: "OK"; + recipeUserId: RecipeUserId; + user: User; + oAuthTokens: OAuthTokens; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { + [key: string]: any; + }; + fromUserInfoAPI?: { + [key: string]: any; + }; + }; + }>; + exchangeAuthCodeForOAuthTokens(input: { + providerConfig: ProviderConfigWithOIDCInfo; + redirectURIInfo: { + redirectURIOnProviderDashboard: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string | undefined; + }; + userContext: UserContext; + }): Promise; + getUserInfo(input: { + providerConfig: ProviderConfigWithOIDCInfo; + oAuthTokens: OAuthTokens; + userContext: UserContext; + }): Promise; +}; +export declare type APIOptions = { + recipeImplementation: RecipeInterface; + config: TypeNormalisedInput; + recipeId: string; + isInServerlessEnv: boolean; + req: BaseRequest; + res: BaseResponse; + appInfo: NormalisedAppinfo; +}; +export declare type APIInterface = { + authorisationUrlGET: + | undefined + | ((input: { + redirectURIOnProviderDashboard: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + urlWithQueryParams: string; + pkceCodeVerifier?: string; + } + | GeneralErrorResponse + >); + signInPOST: ( + input: { + tenantId: string; + session: SessionContainerInterface | undefined; + options: APIOptions; + userContext: UserContext; + } & ( + | { + redirectURIInfo: { + redirectURIOnProviderDashboard: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string; + }; + } + | { + oAuthTokens: { + [key: string]: any; + }; + } + ) + ) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + oAuthTokens: { + [key: string]: any; + }; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { + [key: string]: any; + }; + fromUserInfoAPI?: { + [key: string]: any; + }; + }; + } + | GeneralErrorResponse + >; +}; diff --git a/lib/build/recipe/oauth2client/types.js b/lib/build/recipe/oauth2client/types.js new file mode 100644 index 000000000..9f1237319 --- /dev/null +++ b/lib/build/recipe/oauth2client/types.js @@ -0,0 +1,16 @@ +"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 }); diff --git a/lib/build/recipe/oauth2client/utils.d.ts b/lib/build/recipe/oauth2client/utils.d.ts new file mode 100644 index 000000000..6a930e641 --- /dev/null +++ b/lib/build/recipe/oauth2client/utils.d.ts @@ -0,0 +1,7 @@ +// @ts-nocheck +import { NormalisedAppinfo } from "../../types"; +import { TypeInput, TypeNormalisedInput } from "./types"; +export declare function validateAndNormaliseUserInput( + _appInfo: NormalisedAppinfo, + config: TypeInput +): TypeNormalisedInput; diff --git a/lib/build/recipe/oauth2client/utils.js b/lib/build/recipe/oauth2client/utils.js new file mode 100644 index 000000000..cc35b347f --- /dev/null +++ b/lib/build/recipe/oauth2client/utils.js @@ -0,0 +1,49 @@ +"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.validateAndNormaliseUserInput = void 0; +function validateAndNormaliseUserInput(_appInfo, config) { + if (config === undefined || config.providerConfig === undefined) { + throw new Error("Please pass providerConfig argument in the OAuth2Client recipe."); + } + if (config.providerConfig.clientId === undefined) { + throw new Error("Please pass clientId argument in the OAuth2Client providerConfig."); + } + // TODO: Decide on the prefix and also if we will allow users to customise clientIds + // if (!config.providerConfig.clientId.startsWith("supertokens_")) { + // throw new Error( + // `Only Supertokens OAuth ClientIds are supported in the OAuth2Client recipe. For any other OAuth Clients use the thirdparty recipe.` + // ); + // } + if (config.providerConfig.clientSecret === undefined) { + throw new Error("Please pass clientSecret argument in the OAuth2Client providerConfig."); + } + if (config.providerConfig.oidcDiscoveryEndpoint === undefined) { + throw new Error("Please pass oidcDiscoveryEndpoint argument in the OAuth2Client providerConfig."); + } + let override = Object.assign( + { + functions: (originalImplementation) => originalImplementation, + apis: (originalImplementation) => originalImplementation, + }, + config === null || config === void 0 ? void 0 : config.override + ); + return { + providerConfig: config.providerConfig, + override, + }; +} +exports.validateAndNormaliseUserInput = validateAndNormaliseUserInput; diff --git a/lib/build/recipe/thirdparty/providers/bitbucket.js b/lib/build/recipe/thirdparty/providers/bitbucket.js index 27588fc51..6c5a60d23 100644 --- a/lib/build/recipe/thirdparty/providers/bitbucket.js +++ b/lib/build/recipe/thirdparty/providers/bitbucket.js @@ -19,7 +19,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -const utils_1 = require("./utils"); +const thirdpartyUtils_1 = require("../../../thirdpartyUtils"); const custom_1 = __importDefault(require("./custom")); const logger_1 = require("../../../logger"); function Bitbucket(input) { @@ -59,7 +59,7 @@ function Bitbucket(input) { fromUserInfoAPI: {}, fromIdTokenPayload: {}, }; - const userInfoFromAccessToken = await utils_1.doGetRequest( + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( "https://api.bitbucket.org/2.0/user", undefined, headers @@ -73,7 +73,7 @@ function Bitbucket(input) { ); } rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; - const userInfoFromEmail = await utils_1.doGetRequest( + const userInfoFromEmail = await thirdpartyUtils_1.doGetRequest( "https://api.bitbucket.org/2.0/user/emails", undefined, headers diff --git a/lib/build/recipe/thirdparty/providers/custom.js b/lib/build/recipe/thirdparty/providers/custom.js index 3aead6ffc..ddd2b7a00 100644 --- a/lib/build/recipe/thirdparty/providers/custom.js +++ b/lib/build/recipe/thirdparty/providers/custom.js @@ -6,7 +6,7 @@ var __importDefault = }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getActualClientIdFromDevelopmentClientId = exports.isUsingDevelopmentClientId = exports.DEV_OAUTH_REDIRECT_URL = void 0; -const utils_1 = require("./utils"); +const thirdpartyUtils_1 = require("../../../thirdpartyUtils"); const pkce_challenge_1 = __importDefault(require("pkce-challenge")); const configUtils_1 = require("./configUtils"); const jose_1 = require("jose"); @@ -251,7 +251,7 @@ function NewProvider(input) { accessTokenAPIParams["redirect_uri"] = exports.DEV_OAUTH_REDIRECT_URL; } /* Transformation needed for dev keys END */ - const tokenResponse = await utils_1.doPostRequest(tokenAPIURL, accessTokenAPIParams); + const tokenResponse = await thirdpartyUtils_1.doPostRequest(tokenAPIURL, accessTokenAPIParams); if (tokenResponse.status >= 400) { logger_1.logDebugMessage( `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` @@ -273,7 +273,7 @@ function NewProvider(input) { if (jwks === undefined) { jwks = jose_1.createRemoteJWKSet(new URL(impl.config.jwksURI)); } - rawUserInfoFromProvider.fromIdTokenPayload = await utils_1.verifyIdTokenFromJWKSEndpointAndGetPayload( + rawUserInfoFromProvider.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload( idToken, jwks, { @@ -318,7 +318,7 @@ function NewProvider(input) { } } } - const userInfoFromAccessToken = await utils_1.doGetRequest( + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( impl.config.userInfoEndpoint, queryParams, headers diff --git a/lib/build/recipe/thirdparty/providers/github.js b/lib/build/recipe/thirdparty/providers/github.js index a656dd895..1f3c86029 100644 --- a/lib/build/recipe/thirdparty/providers/github.js +++ b/lib/build/recipe/thirdparty/providers/github.js @@ -6,7 +6,7 @@ var __importDefault = }; Object.defineProperty(exports, "__esModule", { value: true }); const custom_1 = __importDefault(require("./custom")); -const utils_1 = require("./utils"); +const thirdpartyUtils_1 = require("../../../thirdpartyUtils"); function getSupertokensUserInfoFromRawUserInfoResponseForGithub(rawUserInfoResponse) { if (rawUserInfoResponse.fromUserInfoAPI === undefined) { throw new Error("rawUserInfoResponse.fromUserInfoAPI is not available"); @@ -44,7 +44,7 @@ function Github(input) { const basicAuthToken = Buffer.from( `${clientConfig.clientId}:${clientConfig.clientSecret === undefined ? "" : clientConfig.clientSecret}` ).toString("base64"); - const applicationResponse = await utils_1.doPostRequest( + const applicationResponse = await thirdpartyUtils_1.doPostRequest( `https://api.github.com/applications/${clientConfig.clientId}/token`, { access_token: accessToken, @@ -81,14 +81,22 @@ function Github(input) { Accept: "application/vnd.github.v3+json", }; const rawResponse = {}; - const emailInfoResp = await utils_1.doGetRequest("https://api.github.com/user/emails", undefined, headers); + const emailInfoResp = await thirdpartyUtils_1.doGetRequest( + "https://api.github.com/user/emails", + undefined, + headers + ); if (emailInfoResp.status >= 400) { throw new Error( `Getting userInfo failed with ${emailInfoResp.status}: ${emailInfoResp.stringResponse}` ); } rawResponse.emails = emailInfoResp.jsonResponse; - const userInfoResp = await utils_1.doGetRequest("https://api.github.com/user", undefined, headers); + const userInfoResp = await thirdpartyUtils_1.doGetRequest( + "https://api.github.com/user", + undefined, + headers + ); if (userInfoResp.status >= 400) { throw new Error(`Getting userInfo failed with ${userInfoResp.status}: ${userInfoResp.stringResponse}`); } diff --git a/lib/build/recipe/thirdparty/providers/linkedin.js b/lib/build/recipe/thirdparty/providers/linkedin.js index bce0eeaf4..defa0739c 100644 --- a/lib/build/recipe/thirdparty/providers/linkedin.js +++ b/lib/build/recipe/thirdparty/providers/linkedin.js @@ -21,7 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); */ const logger_1 = require("../../../logger"); const custom_1 = __importDefault(require("./custom")); -const utils_1 = require("./utils"); +const thirdpartyUtils_1 = require("../../../thirdpartyUtils"); function Linkedin(input) { if (input.config.name === undefined) { input.config.name = "LinkedIn"; @@ -56,7 +56,7 @@ function Linkedin(input) { fromIdTokenPayload: {}, }; // https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2?context=linkedin%2Fconsumer%2Fcontext#sample-api-response - const userInfoFromAccessToken = await utils_1.doGetRequest( + const userInfoFromAccessToken = await thirdpartyUtils_1.doGetRequest( "https://api.linkedin.com/v2/userinfo", undefined, headers diff --git a/lib/build/recipe/thirdparty/providers/twitter.js b/lib/build/recipe/thirdparty/providers/twitter.js index 3e54592c7..7a7078b51 100644 --- a/lib/build/recipe/thirdparty/providers/twitter.js +++ b/lib/build/recipe/thirdparty/providers/twitter.js @@ -52,7 +52,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); */ const logger_1 = require("../../../logger"); const custom_1 = __importStar(require("./custom")); -const utils_1 = require("./utils"); +const thirdpartyUtils_1 = require("../../../thirdpartyUtils"); function Twitter(input) { var _a; if (input.config.name === undefined) { @@ -116,7 +116,7 @@ function Twitter(input) { }, originalImplementation.config.tokenEndpointBodyParams ); - const tokenResponse = await utils_1.doPostRequest( + const tokenResponse = await thirdpartyUtils_1.doPostRequest( originalImplementation.config.tokenEndpoint, twitterOauthTokenParams, { diff --git a/lib/build/recipe/thirdparty/providers/utils.d.ts b/lib/build/recipe/thirdparty/providers/utils.d.ts index c43eb396d..26592d125 100644 --- a/lib/build/recipe/thirdparty/providers/utils.d.ts +++ b/lib/build/recipe/thirdparty/providers/utils.d.ts @@ -1,35 +1,3 @@ // @ts-nocheck -import * as jose from "jose"; import { ProviderConfigForClientType } from "../types"; -export declare function doGetRequest( - url: string, - queryParams?: { - [key: string]: string; - }, - headers?: { - [key: string]: string; - } -): Promise<{ - jsonResponse: Record | undefined; - status: number; - stringResponse: string; -}>; -export declare function doPostRequest( - url: string, - params: { - [key: string]: any; - }, - headers?: { - [key: string]: string; - } -): Promise<{ - jsonResponse: Record | undefined; - status: number; - stringResponse: string; -}>; -export declare function verifyIdTokenFromJWKSEndpointAndGetPayload( - idToken: string, - jwks: jose.JWTVerifyGetKey, - otherOptions: jose.JWTVerifyOptions -): Promise; export declare function discoverOIDCEndpoints(config: ProviderConfigForClientType): Promise; diff --git a/lib/build/recipe/thirdparty/providers/utils.js b/lib/build/recipe/thirdparty/providers/utils.js index 0fc79f65a..90640abce 100644 --- a/lib/build/recipe/thirdparty/providers/utils.js +++ b/lib/build/recipe/thirdparty/providers/utils.js @@ -1,135 +1,10 @@ "use strict"; -var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); -var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - } - : function (o, v) { - o["default"] = v; - }); -var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.discoverOIDCEndpoints = exports.verifyIdTokenFromJWKSEndpointAndGetPayload = exports.doPostRequest = exports.doGetRequest = void 0; -const jose = __importStar(require("jose")); -const normalisedURLDomain_1 = __importDefault(require("../../../normalisedURLDomain")); -const normalisedURLPath_1 = __importDefault(require("../../../normalisedURLPath")); -const logger_1 = require("../../../logger"); -const utils_1 = require("../../../utils"); -async function doGetRequest(url, queryParams, headers) { - logger_1.logDebugMessage( - `GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}` - ); - if ((headers === null || headers === void 0 ? void 0 : headers["Accept"]) === undefined) { - headers = Object.assign(Object.assign({}, headers), { Accept: "application/json" }); - } - const finalURL = new URL(url); - finalURL.search = new URLSearchParams(queryParams).toString(); - let response = await utils_1.doFetch(finalURL.toString(), { - headers: headers, - }); - const stringResponse = await response.text(); - let jsonResponse = undefined; - if (response.status < 400) { - jsonResponse = JSON.parse(stringResponse); - } - logger_1.logDebugMessage(`Received response with status ${response.status} and body ${stringResponse}`); - return { - stringResponse, - status: response.status, - jsonResponse, - }; -} -exports.doGetRequest = doGetRequest; -async function doPostRequest(url, params, headers) { - if (headers === undefined) { - headers = {}; - } - headers["Content-Type"] = "application/x-www-form-urlencoded"; - headers["Accept"] = "application/json"; // few providers like github don't send back json response by default - logger_1.logDebugMessage( - `POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}` - ); - const body = new URLSearchParams(params).toString(); - let response = await utils_1.doFetch(url, { - method: "POST", - body, - headers, - }); - const stringResponse = await response.text(); - let jsonResponse = undefined; - if (response.status < 400) { - jsonResponse = JSON.parse(stringResponse); - } - logger_1.logDebugMessage(`Received response with status ${response.status} and body ${stringResponse}`); - return { - stringResponse, - status: response.status, - jsonResponse, - }; -} -exports.doPostRequest = doPostRequest; -async function verifyIdTokenFromJWKSEndpointAndGetPayload(idToken, jwks, otherOptions) { - const { payload } = await jose.jwtVerify(idToken, jwks, otherOptions); - return payload; -} -exports.verifyIdTokenFromJWKSEndpointAndGetPayload = verifyIdTokenFromJWKSEndpointAndGetPayload; -// OIDC utils -var oidcInfoMap = {}; -async function getOIDCDiscoveryInfo(issuer) { - const normalizedDomain = new normalisedURLDomain_1.default(issuer); - let normalizedPath = new normalisedURLPath_1.default(issuer); - const openIdConfigPath = new normalisedURLPath_1.default("/.well-known/openid-configuration"); - normalizedPath = normalizedPath.appendPath(openIdConfigPath); - if (oidcInfoMap[issuer] !== undefined) { - return oidcInfoMap[issuer]; - } - const oidcInfo = await doGetRequest( - normalizedDomain.getAsStringDangerous() + normalizedPath.getAsStringDangerous() - ); - if (oidcInfo.status >= 400) { - logger_1.logDebugMessage( - `Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}` - ); - throw new Error(`Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}`); - } - oidcInfoMap[issuer] = oidcInfo.jsonResponse; - return oidcInfo.jsonResponse; -} +exports.discoverOIDCEndpoints = void 0; +const thirdpartyUtils_1 = require("../../../thirdpartyUtils"); async function discoverOIDCEndpoints(config) { if (config.oidcDiscoveryEndpoint !== undefined) { - const oidcInfo = await getOIDCDiscoveryInfo(config.oidcDiscoveryEndpoint); + const oidcInfo = await thirdpartyUtils_1.getOIDCDiscoveryInfo(config.oidcDiscoveryEndpoint); if (oidcInfo.authorization_endpoint !== undefined && config.authorizationEndpoint === undefined) { config.authorizationEndpoint = oidcInfo.authorization_endpoint; } diff --git a/lib/build/thirdpartyUtils.d.ts b/lib/build/thirdpartyUtils.d.ts new file mode 100644 index 000000000..84517d5e2 --- /dev/null +++ b/lib/build/thirdpartyUtils.d.ts @@ -0,0 +1,34 @@ +// @ts-nocheck +import * as jose from "jose"; +export declare function doGetRequest( + url: string, + queryParams?: { + [key: string]: string; + }, + headers?: { + [key: string]: string; + } +): Promise<{ + jsonResponse: Record | undefined; + status: number; + stringResponse: string; +}>; +export declare function doPostRequest( + url: string, + params: { + [key: string]: any; + }, + headers?: { + [key: string]: string; + } +): Promise<{ + jsonResponse: Record | undefined; + status: number; + stringResponse: string; +}>; +export declare function verifyIdTokenFromJWKSEndpointAndGetPayload( + idToken: string, + jwks: jose.JWTVerifyGetKey, + otherOptions: jose.JWTVerifyOptions +): Promise; +export declare function getOIDCDiscoveryInfo(issuer: string): Promise; diff --git a/lib/build/thirdpartyUtils.js b/lib/build/thirdpartyUtils.js new file mode 100644 index 000000000..ee7a2fcb1 --- /dev/null +++ b/lib/build/thirdpartyUtils.js @@ -0,0 +1,130 @@ +"use strict"; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getOIDCDiscoveryInfo = exports.verifyIdTokenFromJWKSEndpointAndGetPayload = exports.doPostRequest = exports.doGetRequest = void 0; +const jose = __importStar(require("jose")); +const logger_1 = require("./logger"); +const utils_1 = require("./utils"); +const normalisedURLDomain_1 = __importDefault(require("./normalisedURLDomain")); +const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); +async function doGetRequest(url, queryParams, headers) { + logger_1.logDebugMessage( + `GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}` + ); + if ((headers === null || headers === void 0 ? void 0 : headers["Accept"]) === undefined) { + headers = Object.assign(Object.assign({}, headers), { Accept: "application/json" }); + } + const finalURL = new URL(url); + finalURL.search = new URLSearchParams(queryParams).toString(); + let response = await utils_1.doFetch(finalURL.toString(), { + headers: headers, + }); + const stringResponse = await response.text(); + let jsonResponse = undefined; + if (response.status < 400) { + jsonResponse = JSON.parse(stringResponse); + } + logger_1.logDebugMessage(`Received response with status ${response.status} and body ${stringResponse}`); + return { + stringResponse, + status: response.status, + jsonResponse, + }; +} +exports.doGetRequest = doGetRequest; +async function doPostRequest(url, params, headers) { + if (headers === undefined) { + headers = {}; + } + headers["Content-Type"] = "application/x-www-form-urlencoded"; + headers["Accept"] = "application/json"; + logger_1.logDebugMessage( + `POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}` + ); + const body = new URLSearchParams(params).toString(); + let response = await utils_1.doFetch(url, { + method: "POST", + body, + headers, + }); + const stringResponse = await response.text(); + let jsonResponse = undefined; + if (response.status < 400) { + jsonResponse = JSON.parse(stringResponse); + } + logger_1.logDebugMessage(`Received response with status ${response.status} and body ${stringResponse}`); + return { + stringResponse, + status: response.status, + jsonResponse, + }; +} +exports.doPostRequest = doPostRequest; +async function verifyIdTokenFromJWKSEndpointAndGetPayload(idToken, jwks, otherOptions) { + const { payload } = await jose.jwtVerify(idToken, jwks, otherOptions); + return payload; +} +exports.verifyIdTokenFromJWKSEndpointAndGetPayload = verifyIdTokenFromJWKSEndpointAndGetPayload; +// OIDC utils +var oidcInfoMap = {}; +async function getOIDCDiscoveryInfo(issuer) { + const normalizedDomain = new normalisedURLDomain_1.default(issuer); + let normalizedPath = new normalisedURLPath_1.default(issuer); + const openIdConfigPath = new normalisedURLPath_1.default("/.well-known/openid-configuration"); + normalizedPath = normalizedPath.appendPath(openIdConfigPath); + if (oidcInfoMap[issuer] !== undefined) { + return oidcInfoMap[issuer]; + } + const oidcInfo = await doGetRequest( + normalizedDomain.getAsStringDangerous() + normalizedPath.getAsStringDangerous() + ); + if (oidcInfo.status >= 400) { + logger_1.logDebugMessage( + `Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}` + ); + throw new Error(`Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}`); + } + oidcInfoMap[issuer] = oidcInfo.jsonResponse; + return oidcInfo.jsonResponse; +} +exports.getOIDCDiscoveryInfo = getOIDCDiscoveryInfo; diff --git a/lib/ts/recipe/jwt/api/implementation.ts b/lib/ts/recipe/jwt/api/implementation.ts index 5a029d4a1..17871e963 100644 --- a/lib/ts/recipe/jwt/api/implementation.ts +++ b/lib/ts/recipe/jwt/api/implementation.ts @@ -31,7 +31,8 @@ export default function getAPIImplementation(): APIInterface { options.res.setHeader("Cache-Control", `max-age=${resp.validityInSeconds}, must-revalidate`, false); } - const oauth2 = require("../../oauth2").getInstance(); + const oauth2 = require("../../oauth2/recipe").default.getInstance(); + // TODO: dirty hack until we get core support if (oauth2 !== undefined) { const oauth2JWKSRes = await fetch("http://localhost:4444/.well-known/jwks.json"); diff --git a/lib/ts/recipe/oauth2/recipe.ts b/lib/ts/recipe/oauth2/recipe.ts index 5aa8a3888..a209cff1c 100644 --- a/lib/ts/recipe/oauth2/recipe.ts +++ b/lib/ts/recipe/oauth2/recipe.ts @@ -55,7 +55,7 @@ export default class Recipe extends RecipeModule { Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo, - this.getDefaultIdTokenPayload + this.getDefaultIdTokenPayload.bind(this) ) ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); diff --git a/lib/ts/recipe/oauth2/types.ts b/lib/ts/recipe/oauth2/types.ts index f88435f43..f2a79b025 100644 --- a/lib/ts/recipe/oauth2/types.ts +++ b/lib/ts/recipe/oauth2/types.ts @@ -437,16 +437,6 @@ export type APIInterface = { options: APIOptions; userContext: UserContext; }) => Promise<{ status: "OK"; info: LoginInfo } | GeneralErrorResponse>); - // userInfoGET: - // | undefined - // | ((input: { - // accessTokenPayload: JSONObject; // after validating the access token passed to the endpoint - // user: User; // ge - // scopes: string[]; - // defaultInfo: JSONObject; - // options: APIOptions; - // userContext: UserContext; - // }) => Promise<{ status: "OK"; info: LoginInfo } | GeneralErrorResponse>); }; export type OAuth2ClientOptions = { diff --git a/lib/ts/recipe/oauth2client/api/authorisationUrl.ts b/lib/ts/recipe/oauth2client/api/authorisationUrl.ts new file mode 100644 index 000000000..80d43138c --- /dev/null +++ b/lib/ts/recipe/oauth2client/api/authorisationUrl.ts @@ -0,0 +1,49 @@ +/* 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. + */ + +import { send200Response } from "../../../utils"; +import STError from "../../../error"; +import { APIInterface, APIOptions } from "../"; +import { UserContext } from "../../../types"; + +export default async function authorisationUrlAPI( + apiImplementation: APIInterface, + _tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + 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 STError({ + type: STError.BAD_INPUT_ERROR, + message: "Please provide the redirectURIOnProviderDashboard as a GET param", + }); + } + + let result = await apiImplementation.authorisationUrlGET({ + redirectURIOnProviderDashboard, + options, + userContext, + }); + + send200Response(options.res, result); + return true; +} diff --git a/lib/ts/recipe/oauth2client/api/implementation.ts b/lib/ts/recipe/oauth2client/api/implementation.ts new file mode 100644 index 000000000..a6f14e0cb --- /dev/null +++ b/lib/ts/recipe/oauth2client/api/implementation.ts @@ -0,0 +1,72 @@ +import { APIInterface } from "../"; +import Session from "../../session"; +import { OAuthTokens } from "../types"; + +export default function getAPIInterface(): APIInterface { + return { + authorisationUrlGET: async function ({ options, redirectURIOnProviderDashboard, userContext }) { + const providerConfig = await options.recipeImplementation.getProviderConfig({ userContext }); + + const authUrl = await options.recipeImplementation.getAuthorisationRedirectURL({ + providerConfig, + redirectURIOnProviderDashboard, + userContext, + }); + return { + status: "OK", + ...authUrl, + }; + }, + signInPOST: async function (input) { + const { options, tenantId, userContext } = input; + + const providerConfig = await options.recipeImplementation.getProviderConfig({ userContext }); + + let oAuthTokensToUse: OAuthTokens = {}; + + 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.createNewSession( + options.req, + options.res, + tenantId, + recipeUserId, + undefined, + undefined, + userContext + ); + + return { + status: "OK", + user, + session, + oAuthTokens: oAuthTokensToUse, + rawUserInfoFromProvider, + }; + }, + }; +} diff --git a/lib/ts/recipe/oauth2client/api/signin.ts b/lib/ts/recipe/oauth2client/api/signin.ts new file mode 100644 index 000000000..663af60e4 --- /dev/null +++ b/lib/ts/recipe/oauth2client/api/signin.ts @@ -0,0 +1,92 @@ +/* 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. + */ + +import STError from "../../../error"; +import { getBackwardsCompatibleUserInfo, send200Response } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +import Session from "../../session"; + +export default async function signInAPI( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.signInPOST === undefined) { + return false; + } + + const bodyParams = await options.req.getJSONBody(); + + let redirectURIInfo: + | undefined + | { + redirectURIOnProviderDashboard: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string; + }; + let oAuthTokens: any; + + if (bodyParams.redirectURIInfo !== undefined) { + if (bodyParams.redirectURIInfo.redirectURIOnProviderDashboard === undefined) { + throw new STError({ + type: STError.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 STError({ + type: STError.BAD_INPUT_ERROR, + message: "Please provide one of redirectURIInfo or oAuthTokens in the request body", + }); + } + + let session = await Session.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") { + send200Response(options.res, { + status: result.status, + ...getBackwardsCompatibleUserInfo(options.req, result, userContext), + }); + } else { + send200Response(options.res, result); + } + return true; +} diff --git a/lib/ts/recipe/oauth2client/constants.ts b/lib/ts/recipe/oauth2client/constants.ts new file mode 100644 index 000000000..545ef08f1 --- /dev/null +++ b/lib/ts/recipe/oauth2client/constants.ts @@ -0,0 +1,18 @@ +/* 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. + */ + +export const AUTHORISATION_API = "/oauth2client/authorisationurl"; + +export const SIGN_IN_API = "/oauth2client/signin"; diff --git a/lib/ts/recipe/oauth2client/index.ts b/lib/ts/recipe/oauth2client/index.ts new file mode 100644 index 000000000..0ae790b4d --- /dev/null +++ b/lib/ts/recipe/oauth2client/index.ts @@ -0,0 +1,78 @@ +/* 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. + */ + +import { getUserContext } from "../../utils"; +import Recipe from "./recipe"; +import { RecipeInterface, APIInterface, APIOptions, OAuthTokens } from "./types"; + +export default class Wrapper { + static init = Recipe.init; + + static async getAuthorisationRedirectURL( + redirectURIOnProviderDashboard: string, + userContext?: Record + ) { + const recipeInterfaceImpl = Recipe.getInstanceOrThrowError().recipeInterfaceImpl; + const providerConfig = await recipeInterfaceImpl.getProviderConfig({ + userContext: getUserContext(userContext), + }); + return await recipeInterfaceImpl.getAuthorisationRedirectURL({ + providerConfig, + redirectURIOnProviderDashboard, + userContext: getUserContext(userContext), + }); + } + + static async exchangeAuthCodeForOAuthTokens( + redirectURIInfo: { + redirectURIOnProviderDashboard: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string | undefined; + }, + userContext?: Record + ) { + const recipeInterfaceImpl = Recipe.getInstanceOrThrowError().recipeInterfaceImpl; + const providerConfig = await recipeInterfaceImpl.getProviderConfig({ + userContext: getUserContext(userContext), + }); + return await recipeInterfaceImpl.exchangeAuthCodeForOAuthTokens({ + providerConfig, + redirectURIInfo, + userContext: getUserContext(userContext), + }); + } + + static async getUserInfo(oAuthTokens: OAuthTokens, userContext?: Record) { + const recipeInterfaceImpl = Recipe.getInstanceOrThrowError().recipeInterfaceImpl; + const providerConfig = await recipeInterfaceImpl.getProviderConfig({ + userContext: getUserContext(userContext), + }); + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getUserInfo({ + providerConfig, + oAuthTokens, + userContext: getUserContext(userContext), + }); + } +} + +export let init = Wrapper.init; + +export let getAuthorisationRedirectURL = Wrapper.getAuthorisationRedirectURL; + +export let exchangeAuthCodeForOAuthTokens = Wrapper.exchangeAuthCodeForOAuthTokens; + +export let getUserInfo = Wrapper.getUserInfo; + +export type { RecipeInterface, APIInterface, APIOptions }; diff --git a/lib/ts/recipe/oauth2client/recipe.ts b/lib/ts/recipe/oauth2client/recipe.ts new file mode 100644 index 000000000..9dfc98a90 --- /dev/null +++ b/lib/ts/recipe/oauth2client/recipe.ts @@ -0,0 +1,146 @@ +/* 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. + */ + +import RecipeModule from "../../recipeModule"; +import { NormalisedAppinfo, APIHandled, RecipeListFunction, HTTPMethod, UserContext } from "../../types"; +import { TypeInput, TypeNormalisedInput, RecipeInterface, APIInterface } from "./types"; +import { validateAndNormaliseUserInput } from "./utils"; +import STError from "../../error"; +import { SIGN_IN_API, AUTHORISATION_API } from "./constants"; +import NormalisedURLPath from "../../normalisedURLPath"; +import signInAPI from "./api/signin"; +import authorisationUrlAPI from "./api/authorisationUrl"; +import RecipeImplementation from "./recipeImplementation"; +import APIImplementation from "./api/implementation"; +import { Querier } from "../../querier"; +import type { BaseRequest, BaseResponse } from "../../framework"; +import OverrideableBuilder from "supertokens-js-override"; + +export default class Recipe extends RecipeModule { + private static instance: Recipe | undefined = undefined; + static RECIPE_ID = "oauth2client"; + + config: TypeNormalisedInput; + + recipeInterfaceImpl: RecipeInterface; + + apiImpl: APIInterface; + + isInServerlessEnv: boolean; + + constructor( + recipeId: string, + appInfo: NormalisedAppinfo, + isInServerlessEnv: boolean, + config: TypeInput, + _recipes: {} + ) { + super(recipeId, appInfo); + this.config = validateAndNormaliseUserInput(appInfo, config); + this.isInServerlessEnv = isInServerlessEnv; + + { + let builder = new OverrideableBuilder( + RecipeImplementation(Querier.getNewInstanceOrThrowError(recipeId), this.config) + ); + this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); + } + { + let builder = new OverrideableBuilder(APIImplementation()); + this.apiImpl = builder.override(this.config.override.apis).build(); + } + } + + static init(config: TypeInput): RecipeListFunction { + return (appInfo, isInServerlessEnv) => { + if (Recipe.instance === undefined) { + Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config, {}); + + return Recipe.instance; + } else { + throw new Error("OAuth2Client recipe has already been initialised. Please check your code for bugs."); + } + }; + } + + static getInstanceOrThrowError(): Recipe { + if (Recipe.instance !== undefined) { + return Recipe.instance; + } + throw new Error("Initialisation not done. Did you forget to call the OAuth2Client.init function?"); + } + + static reset() { + if (process.env.TEST_MODE !== "testing") { + throw new Error("calling testing function in non testing env"); + } + Recipe.instance = undefined; + } + + getAPIsHandled = (): APIHandled[] => { + return [ + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(SIGN_IN_API), + id: SIGN_IN_API, + disabled: this.apiImpl.signInPOST === undefined, + }, + { + method: "get", + pathWithoutApiBasePath: new NormalisedURLPath(AUTHORISATION_API), + id: AUTHORISATION_API, + disabled: this.apiImpl.authorisationUrlGET === undefined, + }, + ]; + }; + + handleAPIRequest = async ( + id: string, + tenantId: string, + req: BaseRequest, + res: BaseResponse, + _path: NormalisedURLPath, + _method: HTTPMethod, + userContext: UserContext + ): Promise => { + let options = { + config: this.config, + recipeId: this.getRecipeId(), + isInServerlessEnv: this.isInServerlessEnv, + recipeImplementation: this.recipeInterfaceImpl, + req, + res, + appInfo: this.getAppInfo(), + }; + if (id === SIGN_IN_API) { + return await signInAPI(this.apiImpl, tenantId, options, userContext); + } else if (id === AUTHORISATION_API) { + return await authorisationUrlAPI(this.apiImpl, tenantId, options, userContext); + } + return false; + }; + + handleError = async (err: STError, _request: BaseRequest, _response: BaseResponse): Promise => { + throw err; + }; + + getAllCORSHeaders = (): string[] => { + return []; + }; + + isErrorFromThisRecipe = (err: any): err is STError => { + return STError.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; + }; +} diff --git a/lib/ts/recipe/oauth2client/recipeImplementation.ts b/lib/ts/recipe/oauth2client/recipeImplementation.ts new file mode 100644 index 000000000..ef374b8f6 --- /dev/null +++ b/lib/ts/recipe/oauth2client/recipeImplementation.ts @@ -0,0 +1,221 @@ +import { + OAuthTokenResponse, + OAuthTokens, + ProviderConfigWithOIDCInfo, + RecipeInterface, + TypeNormalisedInput, + UserInfo, +} from "./types"; +import { Querier } from "../../querier"; +import RecipeUserId from "../../recipeUserId"; +import { User as UserType } from "../../types"; +import { + doGetRequest, + doPostRequest, + getOIDCDiscoveryInfo, + verifyIdTokenFromJWKSEndpointAndGetPayload, +} from "../../thirdpartyUtils"; +import pkceChallenge from "pkce-challenge"; +import { getUser } from "../.."; +import { logDebugMessage } from "../../logger"; +import { JWTVerifyGetKey, createRemoteJWKSet } from "jose"; + +export default function getRecipeImplementation(_querier: Querier, config: TypeNormalisedInput): RecipeInterface { + let providerConfigWithOIDCInfo: ProviderConfigWithOIDCInfo | null = null; + + return { + getAuthorisationRedirectURL: async function ( + this: RecipeInterface, + { providerConfig, redirectURIOnProviderDashboard } + ) { + const queryParams: { [key: string]: string } = { + client_id: providerConfig.clientId, + redirect_uri: redirectURIOnProviderDashboard, + response_type: "code", + }; + + if (providerConfig.scope !== undefined) { + queryParams.scope = providerConfig.scope.join(" "); + } + + let pkceCodeVerifier: string | undefined = undefined; + + if (providerConfig.clientSecret === undefined || providerConfig.forcePKCE) { + const { code_challenge, code_verifier } = pkceChallenge(64); // According to https://www.rfc-editor.org/rfc/rfc7636, length must be between 43 and 128 + queryParams["code_challenge"] = code_challenge; + queryParams["code_challenge_method"] = "S256"; + pkceCodeVerifier = code_verifier; + } + + const urlObj = new URL(providerConfig.authorizationEndpoint); + + for (const [key, value] of Object.entries(queryParams)) { + urlObj.searchParams.set(key, value); + } + + return { + urlWithQueryParams: urlObj.toString(), + pkceCodeVerifier: pkceCodeVerifier, + }; + }, + signIn: async function ({ + userId, + tenantId, + userContext, + oAuthTokens, + rawUserInfoFromProvider, + }): Promise<{ + status: "OK"; + user: UserType; + recipeUserId: RecipeUserId; + oAuthTokens: OAuthTokens; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { [key: string]: any }; + fromUserInfoAPI?: { [key: string]: any }; + }; + }> { + const user = await getUser(userId, userContext); + + if (user === undefined) { + throw new Error(`Failed to getUser from the userId ${userId} in the ${tenantId} tenant`); + } + + return { + status: "OK", + user, + recipeUserId: new RecipeUserId(userId), + oAuthTokens, + rawUserInfoFromProvider, + }; + }, + getProviderConfig: async function () { + if (providerConfigWithOIDCInfo !== null) { + return providerConfigWithOIDCInfo; + } + const oidcInfo = await getOIDCDiscoveryInfo(config.providerConfig.oidcDiscoveryEndpoint); + + if (oidcInfo.authorization_endpoint === undefined) { + throw new Error("Failed to authorization_endpoint from the oidcDiscoveryEndpoint."); + } + if (oidcInfo.token_endpoint === undefined) { + throw new Error("Failed to token_endpoint from the oidcDiscoveryEndpoint."); + } + // TODO: We currently don't have this + // if (oidcInfo.userinfo_endpoint === undefined) { + // throw new Error("Failed to userinfo_endpoint from the oidcDiscoveryEndpoint."); + // } + if (oidcInfo.jwks_uri === undefined) { + throw new Error("Failed to jwks_uri from the oidcDiscoveryEndpoint."); + } + + providerConfigWithOIDCInfo = { + ...config.providerConfig, + authorizationEndpoint: oidcInfo.authorization_endpoint, + tokenEndpoint: oidcInfo.token_endpoint, + userInfoEndpoint: oidcInfo.userinfo_endpoint, + jwksURI: oidcInfo.jwks_uri, + }; + return providerConfigWithOIDCInfo; + }, + exchangeAuthCodeForOAuthTokens: async function (this: RecipeInterface, { providerConfig, redirectURIInfo }) { + if (providerConfig.tokenEndpoint === undefined) { + throw new Error("OAuth2Client provider's tokenEndpoint is not configured."); + } + const tokenAPIURL = providerConfig.tokenEndpoint; + const accessTokenAPIParams: { [key: string]: string } = { + client_id: providerConfig.clientId, + redirect_uri: redirectURIInfo.redirectURIOnProviderDashboard, + code: redirectURIInfo.redirectURIQueryParams["code"], + grant_type: "authorization_code", + }; + if (providerConfig.clientSecret !== undefined) { + accessTokenAPIParams["client_secret"] = providerConfig.clientSecret; + } + if (redirectURIInfo.pkceCodeVerifier !== undefined) { + accessTokenAPIParams["code_verifier"] = redirectURIInfo.pkceCodeVerifier; + } + + const tokenResponse = await doPostRequest(tokenAPIURL, accessTokenAPIParams); + + if (tokenResponse.status >= 400) { + logDebugMessage( + `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` + ); + throw new Error( + `Received response with status ${tokenResponse.status} and body ${tokenResponse.stringResponse}` + ); + } + + return tokenResponse.jsonResponse as OAuthTokenResponse; + }, + getUserInfo: async function ({ providerConfig, oAuthTokens }): Promise { + let jwks: JWTVerifyGetKey | undefined; + + const accessToken = oAuthTokens["access_token"]; + const idToken = oAuthTokens["id_token"]; + + let rawUserInfoFromProvider: { + fromUserInfoAPI: any; + fromIdTokenPayload: any; + } = { + fromUserInfoAPI: {}, + fromIdTokenPayload: {}, + }; + + if (idToken && providerConfig.jwksURI !== undefined) { + if (jwks === undefined) { + jwks = createRemoteJWKSet(new URL(providerConfig.jwksURI)); + } + + rawUserInfoFromProvider.fromIdTokenPayload = await verifyIdTokenFromJWKSEndpointAndGetPayload( + idToken, + jwks, + { + audience: providerConfig.clientId, + } + ); + } + + if (accessToken && providerConfig.userInfoEndpoint !== undefined) { + const headers: { [key: string]: string } = { + Authorization: "Bearer " + accessToken, + }; + const queryParams: { [key: string]: string } = {}; + + const userInfoFromAccessToken = await doGetRequest( + providerConfig.userInfoEndpoint, + queryParams, + headers + ); + + if (userInfoFromAccessToken.status >= 400) { + logDebugMessage( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); + throw new Error( + `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` + ); + } + + rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; + } + + let userId: string | undefined = undefined; + + if (rawUserInfoFromProvider.fromIdTokenPayload !== undefined) { + userId = rawUserInfoFromProvider.fromIdTokenPayload["sub"]; + } else if (rawUserInfoFromProvider.fromUserInfoAPI !== undefined) { + userId = rawUserInfoFromProvider.fromUserInfoAPI["sub"]; + } + + if (userId === undefined) { + throw new Error(`Failed to get userId from both the idToken and userInfo endpoint.`); + } + + return { + userId, + rawUserInfoFromProvider, + }; + }, + }; +} diff --git a/lib/ts/recipe/oauth2client/types.ts b/lib/ts/recipe/oauth2client/types.ts new file mode 100644 index 000000000..75d3a55c2 --- /dev/null +++ b/lib/ts/recipe/oauth2client/types.ts @@ -0,0 +1,183 @@ +/* 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. + */ + +import type { BaseRequest, BaseResponse } from "../../framework"; +import { NormalisedAppinfo, UserContext } from "../../types"; +import OverrideableBuilder from "supertokens-js-override"; +import { SessionContainerInterface } from "../session/types"; +import { GeneralErrorResponse, User } from "../../types"; +import RecipeUserId from "../../recipeUserId"; + +export type UserInfo = { + userId: string; + rawUserInfoFromProvider: { fromIdTokenPayload?: { [key: string]: any }; fromUserInfoAPI?: { [key: string]: any } }; +}; + +export type ProviderConfigInput = { + clientId: string; + clientSecret: string; + authorizationEndpointQueryParams?: { [key: string]: string | null }; + oidcDiscoveryEndpoint: string; + scope?: string[]; + forcePKCE?: boolean; +}; + +export type ProviderConfigWithOIDCInfo = ProviderConfigInput & { + authorizationEndpoint: string; + tokenEndpoint: string; + userInfoEndpoint: string; + jwksURI: string; +}; + +export type OAuthTokens = { + access_token?: string; + id_token?: string; +}; + +export type OAuthTokenResponse = { + access_token: string; + id_token?: string; + refresh_token?: string; + expires_in: number; + scope?: string; + token_type: string; +}; + +export type TypeInput = { + providerConfig: ProviderConfigInput; + override?: { + functions?: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis?: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; + +export type TypeNormalisedInput = { + providerConfig: ProviderConfigInput; + override: { + functions: ( + originalImplementation: RecipeInterface, + builder?: OverrideableBuilder + ) => RecipeInterface; + apis: (originalImplementation: APIInterface, builder?: OverrideableBuilder) => APIInterface; + }; +}; + +export type RecipeInterface = { + getAuthorisationRedirectURL(input: { + providerConfig: ProviderConfigWithOIDCInfo; + redirectURIOnProviderDashboard: string; + userContext: UserContext; + }): Promise<{ + urlWithQueryParams: string; + pkceCodeVerifier?: string; + }>; + getProviderConfig(input: { userContext: UserContext }): Promise; + + signIn(input: { + userId: string; + oAuthTokens: OAuthTokens; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { [key: string]: any }; + fromUserInfoAPI?: { [key: string]: any }; + }; + tenantId: string; + userContext: UserContext; + }): Promise<{ + status: "OK"; + recipeUserId: RecipeUserId; + user: User; + oAuthTokens: OAuthTokens; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { [key: string]: any }; + fromUserInfoAPI?: { [key: string]: any }; + }; + }>; + exchangeAuthCodeForOAuthTokens(input: { + providerConfig: ProviderConfigWithOIDCInfo; + redirectURIInfo: { + redirectURIOnProviderDashboard: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string | undefined; + }; + userContext: UserContext; + }): Promise; + getUserInfo(input: { + providerConfig: ProviderConfigWithOIDCInfo; + oAuthTokens: OAuthTokens; + userContext: UserContext; + }): Promise; +}; + +export type APIOptions = { + recipeImplementation: RecipeInterface; + config: TypeNormalisedInput; + recipeId: string; + isInServerlessEnv: boolean; + req: BaseRequest; + res: BaseResponse; + appInfo: NormalisedAppinfo; +}; + +export type APIInterface = { + authorisationUrlGET: + | undefined + | ((input: { + redirectURIOnProviderDashboard: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + urlWithQueryParams: string; + pkceCodeVerifier?: string; + } + | GeneralErrorResponse + >); + + signInPOST: ( + input: { + tenantId: string; + session: SessionContainerInterface | undefined; + options: APIOptions; + userContext: UserContext; + } & ( + | { + redirectURIInfo: { + redirectURIOnProviderDashboard: string; + redirectURIQueryParams: any; + pkceCodeVerifier?: string; + }; + } + | { + oAuthTokens: { [key: string]: any }; + } + ) + ) => Promise< + | { + status: "OK"; + user: User; + session: SessionContainerInterface; + oAuthTokens: { [key: string]: any }; + rawUserInfoFromProvider: { + fromIdTokenPayload?: { [key: string]: any }; + fromUserInfoAPI?: { [key: string]: any }; + }; + } + | GeneralErrorResponse + >; +}; diff --git a/lib/ts/recipe/oauth2client/utils.ts b/lib/ts/recipe/oauth2client/utils.ts new file mode 100644 index 000000000..925d67d66 --- /dev/null +++ b/lib/ts/recipe/oauth2client/utils.ts @@ -0,0 +1,53 @@ +/* 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. + */ + +import { NormalisedAppinfo } from "../../types"; +import { TypeInput, TypeNormalisedInput, RecipeInterface, APIInterface } from "./types"; + +export function validateAndNormaliseUserInput(_appInfo: NormalisedAppinfo, config: TypeInput): TypeNormalisedInput { + if (config === undefined || config.providerConfig === undefined) { + throw new Error("Please pass providerConfig argument in the OAuth2Client recipe."); + } + + if (config.providerConfig.clientId === undefined) { + throw new Error("Please pass clientId argument in the OAuth2Client providerConfig."); + } + + // TODO: Decide on the prefix and also if we will allow users to customise clientIds + // if (!config.providerConfig.clientId.startsWith("supertokens_")) { + // throw new Error( + // `Only Supertokens OAuth ClientIds are supported in the OAuth2Client recipe. For any other OAuth Clients use the thirdparty recipe.` + // ); + // } + + if (config.providerConfig.clientSecret === undefined) { + throw new Error("Please pass clientSecret argument in the OAuth2Client providerConfig."); + } + + if (config.providerConfig.oidcDiscoveryEndpoint === undefined) { + throw new Error("Please pass oidcDiscoveryEndpoint argument in the OAuth2Client providerConfig."); + } + + let override = { + functions: (originalImplementation: RecipeInterface) => originalImplementation, + apis: (originalImplementation: APIInterface) => originalImplementation, + ...config?.override, + }; + + return { + providerConfig: config.providerConfig, + override, + }; +} diff --git a/lib/ts/recipe/thirdparty/providers/bitbucket.ts b/lib/ts/recipe/thirdparty/providers/bitbucket.ts index 56cc8073f..6d5ace5e5 100644 --- a/lib/ts/recipe/thirdparty/providers/bitbucket.ts +++ b/lib/ts/recipe/thirdparty/providers/bitbucket.ts @@ -14,7 +14,7 @@ */ import { ProviderInput, TypeProvider } from "../types"; -import { doGetRequest } from "./utils"; +import { doGetRequest } from "../../../thirdpartyUtils"; import NewProvider from "./custom"; import { logDebugMessage } from "../../../logger"; diff --git a/lib/ts/recipe/thirdparty/providers/custom.ts b/lib/ts/recipe/thirdparty/providers/custom.ts index 6e24fc0b6..ba7226836 100644 --- a/lib/ts/recipe/thirdparty/providers/custom.ts +++ b/lib/ts/recipe/thirdparty/providers/custom.ts @@ -1,5 +1,5 @@ import { TypeProvider, ProviderInput, UserInfo, ProviderConfigForClientType } from "../types"; -import { doGetRequest, doPostRequest, verifyIdTokenFromJWKSEndpointAndGetPayload } from "./utils"; +import { doGetRequest, doPostRequest, verifyIdTokenFromJWKSEndpointAndGetPayload } from "../../../thirdpartyUtils"; import pkceChallenge from "pkce-challenge"; import { getProviderConfigForClient } from "./configUtils"; import { JWTVerifyGetKey, createRemoteJWKSet } from "jose"; diff --git a/lib/ts/recipe/thirdparty/providers/github.ts b/lib/ts/recipe/thirdparty/providers/github.ts index f556eba71..e1f218143 100644 --- a/lib/ts/recipe/thirdparty/providers/github.ts +++ b/lib/ts/recipe/thirdparty/providers/github.ts @@ -14,7 +14,7 @@ */ import { ProviderInput, TypeProvider, UserInfo } from "../types"; import NewProvider from "./custom"; -import { doGetRequest, doPostRequest } from "./utils"; +import { doGetRequest, doPostRequest } from "../../../thirdpartyUtils"; function getSupertokensUserInfoFromRawUserInfoResponseForGithub(rawUserInfoResponse: { fromIdTokenPayload?: any; diff --git a/lib/ts/recipe/thirdparty/providers/linkedin.ts b/lib/ts/recipe/thirdparty/providers/linkedin.ts index 5aa79976f..c179b0269 100644 --- a/lib/ts/recipe/thirdparty/providers/linkedin.ts +++ b/lib/ts/recipe/thirdparty/providers/linkedin.ts @@ -15,7 +15,7 @@ import { logDebugMessage } from "../../../logger"; import { ProviderInput, TypeProvider } from "../types"; import NewProvider from "./custom"; -import { doGetRequest } from "./utils"; +import { doGetRequest } from "../../../thirdpartyUtils"; export default function Linkedin(input: ProviderInput): TypeProvider { if (input.config.name === undefined) { diff --git a/lib/ts/recipe/thirdparty/providers/twitter.ts b/lib/ts/recipe/thirdparty/providers/twitter.ts index cb60db8d3..083cf821f 100644 --- a/lib/ts/recipe/thirdparty/providers/twitter.ts +++ b/lib/ts/recipe/thirdparty/providers/twitter.ts @@ -19,7 +19,7 @@ import NewProvider, { getActualClientIdFromDevelopmentClientId, isUsingDevelopmentClientId, } from "./custom"; -import { doPostRequest } from "./utils"; +import { doPostRequest } from "../../../thirdpartyUtils"; export default function Twitter(input: ProviderInput): TypeProvider { if (input.config.name === undefined) { diff --git a/lib/ts/recipe/thirdparty/providers/utils.ts b/lib/ts/recipe/thirdparty/providers/utils.ts index a4e4a7d7b..347c04942 100644 --- a/lib/ts/recipe/thirdparty/providers/utils.ts +++ b/lib/ts/recipe/thirdparty/providers/utils.ts @@ -1,127 +1,5 @@ -import * as jose from "jose"; - import { ProviderConfigForClientType } from "../types"; -import NormalisedURLDomain from "../../../normalisedURLDomain"; -import NormalisedURLPath from "../../../normalisedURLPath"; -import { logDebugMessage } from "../../../logger"; -import { doFetch } from "../../../utils"; - -export async function doGetRequest( - url: string, - queryParams?: { [key: string]: string }, - headers?: { [key: string]: string } -): Promise<{ - jsonResponse: Record | undefined; - status: number; - stringResponse: string; -}> { - logDebugMessage( - `GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}` - ); - if (headers?.["Accept"] === undefined) { - headers = { - ...headers, - Accept: "application/json", // few providers like github don't send back json response by default - }; - } - const finalURL = new URL(url); - finalURL.search = new URLSearchParams(queryParams).toString(); - let response = await doFetch(finalURL.toString(), { - headers: headers, - }); - - const stringResponse = await response.text(); - let jsonResponse: Record | undefined = undefined; - - if (response.status < 400) { - jsonResponse = JSON.parse(stringResponse); - } - - logDebugMessage(`Received response with status ${response.status} and body ${stringResponse}`); - return { - stringResponse, - status: response.status, - jsonResponse, - }; -} - -export async function doPostRequest( - url: string, - params: { [key: string]: any }, - headers?: { [key: string]: string } -): Promise<{ - jsonResponse: Record | undefined; - status: number; - stringResponse: string; -}> { - if (headers === undefined) { - headers = {}; - } - - headers["Content-Type"] = "application/x-www-form-urlencoded"; - headers["Accept"] = "application/json"; // few providers like github don't send back json response by default - - logDebugMessage( - `POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}` - ); - - const body = new URLSearchParams(params).toString(); - let response = await doFetch(url, { - method: "POST", - body, - headers, - }); - - const stringResponse = await response.text(); - let jsonResponse: Record | undefined = undefined; - - if (response.status < 400) { - jsonResponse = JSON.parse(stringResponse); - } - - logDebugMessage(`Received response with status ${response.status} and body ${stringResponse}`); - return { - stringResponse, - status: response.status, - jsonResponse, - }; -} - -export async function verifyIdTokenFromJWKSEndpointAndGetPayload( - idToken: string, - jwks: jose.JWTVerifyGetKey, - otherOptions: jose.JWTVerifyOptions -): Promise { - const { payload } = await jose.jwtVerify(idToken, jwks, otherOptions); - - return payload; -} - -// OIDC utils -var oidcInfoMap: { [key: string]: any } = {}; - -async function getOIDCDiscoveryInfo(issuer: string): Promise { - const normalizedDomain = new NormalisedURLDomain(issuer); - let normalizedPath = new NormalisedURLPath(issuer); - const openIdConfigPath = new NormalisedURLPath("/.well-known/openid-configuration"); - - normalizedPath = normalizedPath.appendPath(openIdConfigPath); - - if (oidcInfoMap[issuer] !== undefined) { - return oidcInfoMap[issuer]; - } - const oidcInfo = await doGetRequest( - normalizedDomain.getAsStringDangerous() + normalizedPath.getAsStringDangerous() - ); - - if (oidcInfo.status >= 400) { - logDebugMessage(`Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}`); - throw new Error(`Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}`); - } - - oidcInfoMap[issuer] = oidcInfo.jsonResponse!; - return oidcInfo.jsonResponse!; -} +import { getOIDCDiscoveryInfo } from "../../../thirdpartyUtils"; export async function discoverOIDCEndpoints(config: ProviderConfigForClientType): Promise { if (config.oidcDiscoveryEndpoint !== undefined) { diff --git a/lib/ts/thirdpartyUtils.ts b/lib/ts/thirdpartyUtils.ts new file mode 100644 index 000000000..349d29ad0 --- /dev/null +++ b/lib/ts/thirdpartyUtils.ts @@ -0,0 +1,122 @@ +import * as jose from "jose"; +import { logDebugMessage } from "./logger"; +import { doFetch } from "./utils"; +import NormalisedURLDomain from "./normalisedURLDomain"; +import NormalisedURLPath from "./normalisedURLPath"; + +export async function doGetRequest( + url: string, + queryParams?: { [key: string]: string }, + headers?: { [key: string]: string } +): Promise<{ + jsonResponse: Record | undefined; + status: number; + stringResponse: string; +}> { + logDebugMessage( + `GET request to ${url}, with query params ${JSON.stringify(queryParams)} and headers ${JSON.stringify(headers)}` + ); + if (headers?.["Accept"] === undefined) { + headers = { + ...headers, + Accept: "application/json", + }; + } + const finalURL = new URL(url); + finalURL.search = new URLSearchParams(queryParams).toString(); + let response = await doFetch(finalURL.toString(), { + headers: headers, + }); + + const stringResponse = await response.text(); + let jsonResponse: Record | undefined = undefined; + + if (response.status < 400) { + jsonResponse = JSON.parse(stringResponse); + } + + logDebugMessage(`Received response with status ${response.status} and body ${stringResponse}`); + return { + stringResponse, + status: response.status, + jsonResponse, + }; +} + +export async function doPostRequest( + url: string, + params: { [key: string]: any }, + headers?: { [key: string]: string } +): Promise<{ + jsonResponse: Record | undefined; + status: number; + stringResponse: string; +}> { + if (headers === undefined) { + headers = {}; + } + + headers["Content-Type"] = "application/x-www-form-urlencoded"; + headers["Accept"] = "application/json"; + + logDebugMessage( + `POST request to ${url}, with params ${JSON.stringify(params)} and headers ${JSON.stringify(headers)}` + ); + + const body = new URLSearchParams(params).toString(); + let response = await doFetch(url, { + method: "POST", + body, + headers, + }); + + const stringResponse = await response.text(); + let jsonResponse: Record | undefined = undefined; + + if (response.status < 400) { + jsonResponse = JSON.parse(stringResponse); + } + + logDebugMessage(`Received response with status ${response.status} and body ${stringResponse}`); + return { + stringResponse, + status: response.status, + jsonResponse, + }; +} + +export async function verifyIdTokenFromJWKSEndpointAndGetPayload( + idToken: string, + jwks: jose.JWTVerifyGetKey, + otherOptions: jose.JWTVerifyOptions +): Promise { + const { payload } = await jose.jwtVerify(idToken, jwks, otherOptions); + + return payload; +} + +// OIDC utils +var oidcInfoMap: { [key: string]: any } = {}; + +export async function getOIDCDiscoveryInfo(issuer: string): Promise { + const normalizedDomain = new NormalisedURLDomain(issuer); + let normalizedPath = new NormalisedURLPath(issuer); + const openIdConfigPath = new NormalisedURLPath("/.well-known/openid-configuration"); + + normalizedPath = normalizedPath.appendPath(openIdConfigPath); + + if (oidcInfoMap[issuer] !== undefined) { + return oidcInfoMap[issuer]; + } + const oidcInfo = await doGetRequest( + normalizedDomain.getAsStringDangerous() + normalizedPath.getAsStringDangerous() + ); + + if (oidcInfo.status >= 400) { + logDebugMessage(`Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}`); + throw new Error(`Received response with status ${oidcInfo.status} and body ${oidcInfo.stringResponse}`); + } + + oidcInfoMap[issuer] = oidcInfo.jsonResponse!; + return oidcInfo.jsonResponse!; +} diff --git a/recipe/oauth2client/index.d.ts b/recipe/oauth2client/index.d.ts new file mode 100644 index 000000000..89f4241f8 --- /dev/null +++ b/recipe/oauth2client/index.d.ts @@ -0,0 +1,10 @@ +export * from "../../lib/build/recipe/oauth2client"; +/** + * 'export *' does not re-export a default. + * import NextJS from "supertokens-node/nextjs"; + * the above import statement won't be possible unless either + * - user add "esModuleInterop": true in their tsconfig.json file + * - we do the following change: + */ +import * as _default from "../../lib/build/recipe/oauth2client"; +export default _default; diff --git a/recipe/oauth2client/index.js b/recipe/oauth2client/index.js new file mode 100644 index 000000000..f1b31d6db --- /dev/null +++ b/recipe/oauth2client/index.js @@ -0,0 +1,6 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +exports.__esModule = true; +__export(require("../../lib/build/recipe/oauth2client")); diff --git a/recipe/oauth2client/types/index.d.ts b/recipe/oauth2client/types/index.d.ts new file mode 100644 index 000000000..e475d4576 --- /dev/null +++ b/recipe/oauth2client/types/index.d.ts @@ -0,0 +1,10 @@ +export * from "../../../lib/build/recipe/oauth2client/types"; +/** + * 'export *' does not re-export a default. + * import NextJS from "supertokens-node/nextjs"; + * the above import statement won't be possible unless either + * - user add "esModuleInterop": true in their tsconfig.json file + * - we do the following change: + */ +import * as _default from "../../../lib/build/recipe/oauth2client/types"; +export default _default; diff --git a/recipe/oauth2client/types/index.js b/recipe/oauth2client/types/index.js new file mode 100644 index 000000000..01b5c40c6 --- /dev/null +++ b/recipe/oauth2client/types/index.js @@ -0,0 +1,6 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +exports.__esModule = true; +__export(require("../../../lib/build/recipe/oauth2client/types")); diff --git a/test/test-server/src/index.ts b/test/test-server/src/index.ts index 6402e5ab7..7ae66ec90 100644 --- a/test/test-server/src/index.ts +++ b/test/test-server/src/index.ts @@ -21,6 +21,9 @@ import { TypeInput as MFATypeInput } from "../../../lib/build/recipe/multifactor import TOTPRecipe from "../../../lib/build/recipe/totp/recipe"; import OAuth2Recipe from "../../../lib/build/recipe/oauth2/recipe"; import { TypeInput as OAuth2TypeInput } from "../../../lib/build/recipe/oauth2/types"; +import OAuth2ClientRecipe from "../../../lib/build/recipe/oauth2client/recipe"; +import { TypeInput as OAuth2ClientTypeInput } from "../../../lib/build/recipe/oauth2client/types"; +import { TypeInput as OpenIdRecipeTypeInput } from "../../../lib/build/recipe/openid/types"; import UserMetadataRecipe from "../../../lib/build/recipe/usermetadata/recipe"; import SuperTokensRecipe from "../../../lib/build/supertokens"; import { RecipeListFunction } from "../../../lib/build/types"; @@ -35,6 +38,7 @@ import { verifySession } from "../../../recipe/session/framework/express"; import ThirdParty from "../../../recipe/thirdparty"; import TOTP from "../../../recipe/totp"; import OAuth2 from "../../../recipe/oauth2"; +import OAuth2Client from "../../../recipe/oauth2client"; import accountlinkingRoutes from "./accountlinking"; import emailpasswordRoutes from "./emailpassword"; import emailverificationRoutes from "./emailverification"; @@ -89,6 +93,7 @@ function STReset() { MultiFactorAuthRecipe.reset(); TOTPRecipe.reset(); OAuth2Recipe.reset(); + OAuth2ClientRecipe.reset(); SuperTokensRecipe.reset(); } @@ -311,6 +316,24 @@ function initST(config: any) { } recipeList.push(OAuth2.init(initConfig)); } + if (recipe.recipeId === "oauth2client") { + let initConfig: OAuth2ClientTypeInput = { + ...config, + }; + if (initConfig.override?.functions) { + initConfig.override = { + ...initConfig.override, + functions: getFunc(`${initConfig.override.functions}`), + }; + } + if (initConfig.override?.apis) { + initConfig.override = { + ...initConfig.override, + apis: getFunc(`${initConfig.override.apis}`), + }; + } + recipeList.push(OAuth2Client.init(initConfig)); + } }); init.recipeList = recipeList; From 9c7a22d19d61480c4dcd8eb27285012ed25d353e Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 26 Jul 2024 10:09:33 +0000 Subject: [PATCH 09/35] fix: Remove internal redirects in the OAuth2 flow (#896) * fix: Remove internal redirects in the OAuth2 flow * fix: PR changes --- lib/build/recipe/oauth2/api/implementation.js | 74 +++----- lib/build/recipe/oauth2/api/utils.d.ts | 39 ++++ lib/build/recipe/oauth2/api/utils.js | 146 +++++++++++++++ lib/ts/recipe/oauth2/api/implementation.ts | 70 +++---- lib/ts/recipe/oauth2/api/utils.ts | 176 ++++++++++++++++++ 5 files changed, 412 insertions(+), 93 deletions(-) create mode 100644 lib/build/recipe/oauth2/api/utils.d.ts create mode 100644 lib/build/recipe/oauth2/api/utils.js create mode 100644 lib/ts/recipe/oauth2/api/utils.ts diff --git a/lib/build/recipe/oauth2/api/implementation.js b/lib/build/recipe/oauth2/api/implementation.js index b4bdc6cad..abf6acffa 100644 --- a/lib/build/recipe/oauth2/api/implementation.js +++ b/lib/build/recipe/oauth2/api/implementation.js @@ -20,53 +20,23 @@ var __importDefault = }; Object.defineProperty(exports, "__esModule", { value: true }); const supertokens_1 = __importDefault(require("../../../supertokens")); +const utils_1 = require("./utils"); function getAPIImplementation() { return { loginGET: async ({ loginChallenge, options, session, userContext }) => { - var _a, _b; - const request = await options.recipeImplementation.getLoginRequest({ - challenge: loginChallenge, + const response = await utils_1.loginGET({ + recipeImplementation: options.recipeImplementation, + loginChallenge, + session, + userContext, + }); + return utils_1.handleInternalRedirects({ + response, + cookie: options.req.getHeaderValue("cookie"), + recipeImplementation: options.recipeImplementation, + session, userContext, }); - if (request.skip) { - const accept = await options.recipeImplementation.acceptLoginRequest({ - challenge: loginChallenge, - subject: request.subject, - userContext, - }); - return { redirectTo: accept.redirectTo }; - } else if (session) { - if (session.getUserId() !== request.subject) { - // TODO? - } - const accept = await options.recipeImplementation.acceptLoginRequest({ - challenge: loginChallenge, - subject: session.getUserId(), - identityProviderSessionId: session.getHandle(), - userContext, - }); - return { redirectTo: accept.redirectTo }; - } - const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; - const websiteDomain = appInfo - .getOrigin({ - request: options.req, - userContext: userContext, - }) - .getAsStringDangerous(); - const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); - // TODO: - return { - redirectTo: - websiteDomain + - websiteBasePath + - `?hint=${ - (_b = (_a = request.oidcContext) === null || _a === void 0 ? void 0 : _a.login_hint) !== null && - _b !== void 0 - ? _b - : "" - }&loginChallenge=${loginChallenge}`, - }; }, loginPOST: async ({ loginChallenge, accept, options, session, userContext }) => { const res = accept @@ -162,14 +132,20 @@ function getAPIImplementation() { }); return { redirectTo: res.redirectTo }; }, - authGET: async (input) => { - const res = await input.options.recipeImplementation.authorization({ - params: input.params, - cookies: input.cookie, - session: input.session, - userContext: input.userContext, + authGET: async ({ options, params, cookie, session, userContext }) => { + const response = await options.recipeImplementation.authorization({ + params, + cookies: cookie, + session, + userContext, + }); + return utils_1.handleInternalRedirects({ + response, + recipeImplementation: options.recipeImplementation, + cookie, + session, + userContext, }); - return res; }, tokenPOST: async (input) => { return input.options.recipeImplementation.token({ body: input.body, userContext: input.userContext }); diff --git a/lib/build/recipe/oauth2/api/utils.d.ts b/lib/build/recipe/oauth2/api/utils.d.ts new file mode 100644 index 000000000..0a430568c --- /dev/null +++ b/lib/build/recipe/oauth2/api/utils.d.ts @@ -0,0 +1,39 @@ +// @ts-nocheck +import { UserContext } from "../../../types"; +import { SessionContainerInterface } from "../../session/types"; +import { RecipeInterface } from "../types"; +export declare function loginGET({ + recipeImplementation, + loginChallenge, + session, + setCookie, + userContext, +}: { + recipeImplementation: RecipeInterface; + loginChallenge: string; + session?: SessionContainerInterface; + setCookie?: string; + userContext: UserContext; +}): Promise<{ + redirectTo: string; + setCookie: string | undefined; +}>; +export declare function handleInternalRedirects({ + response, + recipeImplementation, + session, + cookie, + userContext, +}: { + response: { + redirectTo: string; + setCookie: string | undefined; + }; + recipeImplementation: RecipeInterface; + session?: SessionContainerInterface; + cookie?: string; + userContext: UserContext; +}): Promise<{ + redirectTo: string; + setCookie: string | undefined; +}>; diff --git a/lib/build/recipe/oauth2/api/utils.js b/lib/build/recipe/oauth2/api/utils.js new file mode 100644 index 000000000..a0144bd3f --- /dev/null +++ b/lib/build/recipe/oauth2/api/utils.js @@ -0,0 +1,146 @@ +"use strict"; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handleInternalRedirects = exports.loginGET = void 0; +const supertokens_1 = __importDefault(require("../../../supertokens")); +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 }) { + var _a, _b; + const request = await recipeImplementation.getLoginRequest({ + challenge: loginChallenge, + userContext, + }); + if (request.skip) { + const accept = await recipeImplementation.acceptLoginRequest({ + challenge: loginChallenge, + subject: request.subject, + userContext, + }); + return { redirectTo: accept.redirectTo, setCookie }; + } else if (session) { + if (session.getUserId() !== request.subject) { + // TODO? + } + const accept = await recipeImplementation.acceptLoginRequest({ + challenge: loginChallenge, + subject: session.getUserId(), + identityProviderSessionId: session.getHandle(), + userContext, + }); + return { redirectTo: accept.redirectTo, setCookie }; + } + const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; + const websiteDomain = appInfo + .getOrigin({ + request: undefined, + userContext: userContext, + }) + .getAsStringDangerous(); + const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); + return { + redirectTo: + websiteDomain + + websiteBasePath + + `?hint=${ + (_b = (_a = request.oidcContext) === null || _a === void 0 ? void 0 : _a.login_hint) !== null && + _b !== void 0 + ? _b + : "" + }&loginChallenge=${loginChallenge}`, + setCookie, + }; +} +exports.loginGET = loginGET; +function getMergedCookies({ cookie = "", setCookie }) { + if (!setCookie) { + return cookie; + } + const cookieMap = cookie.split(";").reduce((acc, curr) => { + const [name, value] = curr.split("="); + return Object.assign(Object.assign({}, acc), { [name.trim()]: value }); + }, {}); + const setCookies = set_cookie_parser_1.default.parse(set_cookie_parser_1.default.splitCookiesString(setCookie)); + for (const { name, value, expires } of setCookies) { + if (expires && new Date(expires) < new Date()) { + delete cookieMap[name]; + } else { + cookieMap[name] = value; + } + } + return Object.entries(cookieMap) + .map(([key, value]) => `${key}=${value}`) + .join(";"); +} +function mergeSetCookieHeaders(setCookie1, setCookie2) { + if (!setCookie1) { + return setCookie2 || ""; + } + if (!setCookie2 || setCookie1 === setCookie2) { + return setCookie1; + } + return `${setCookie1};${setCookie2}`; +} +function isInternalRedirect(redirectTo) { + const { apiDomain, apiBasePath } = supertokens_1.default.getInstanceOrThrowError().appInfo; + const basePath = `${apiDomain.getAsStringDangerous()}${apiBasePath.getAsStringDangerous()}`; + return [constants_1.LOGIN_PATH, constants_1.AUTH_PATH].some((path) => redirectTo.startsWith(`${basePath}${path}`)); +} +// In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. +// If an internal redirect is identified, it's handled directly by this function. +// Currently, we only need to handle redirects to /oauth2/login and /oauth2/auth endpoints. +async function handleInternalRedirects({ response, recipeImplementation, session, cookie = "", userContext }) { + var _a; + if (!isInternalRedirect(response.redirectTo)) { + return response; + } + // Typically, there are no more than 2 internal redirects per API call but we are allowing upto 10. + // This safety net prevents infinite redirect loops in case there are more redirects than expected. + const maxRedirects = 10; + let redirectCount = 0; + while (redirectCount < maxRedirects && isInternalRedirect(response.redirectTo)) { + cookie = getMergedCookies({ cookie, setCookie: response.setCookie }); + const queryString = response.redirectTo.split("?")[1]; + const params = new URLSearchParams(queryString); + if (response.redirectTo.includes(constants_1.LOGIN_PATH)) { + const loginChallenge = + (_a = params.get("login_challenge")) !== null && _a !== void 0 ? _a : params.get("loginChallenge"); + if (!loginChallenge) { + throw new Error(`Expected loginChallenge in ${response.redirectTo}`); + } + const loginRes = await loginGET({ + recipeImplementation, + loginChallenge, + session, + setCookie: response.setCookie, + userContext, + }); + response = { + redirectTo: loginRes.redirectTo, + setCookie: mergeSetCookieHeaders(loginRes.setCookie, response.setCookie), + }; + } else if (response.redirectTo.includes(constants_1.AUTH_PATH)) { + const authRes = await recipeImplementation.authorization({ + params: Object.fromEntries(params.entries()), + cookies: cookie, + session, + userContext, + }); + response = { + redirectTo: authRes.redirectTo, + setCookie: mergeSetCookieHeaders(authRes.setCookie, response.setCookie), + }; + } else { + throw new Error(`Unexpected internal redirect ${response.redirectTo}`); + } + redirectCount++; + } + return response; +} +exports.handleInternalRedirects = handleInternalRedirects; diff --git a/lib/ts/recipe/oauth2/api/implementation.ts b/lib/ts/recipe/oauth2/api/implementation.ts index 69c865874..577655a12 100644 --- a/lib/ts/recipe/oauth2/api/implementation.ts +++ b/lib/ts/recipe/oauth2/api/implementation.ts @@ -15,49 +15,24 @@ import SuperTokens from "../../../supertokens"; import { APIInterface } from "../types"; +import { handleInternalRedirects, loginGET } from "./utils"; export default function getAPIImplementation(): APIInterface { return { loginGET: async ({ loginChallenge, options, session, userContext }) => { - const request = await options.recipeImplementation.getLoginRequest({ - challenge: loginChallenge, + const response = await loginGET({ + recipeImplementation: options.recipeImplementation, + loginChallenge, + session, + userContext, + }); + return handleInternalRedirects({ + response, + cookie: options.req.getHeaderValue("cookie"), + recipeImplementation: options.recipeImplementation, + session, userContext, }); - - if (request.skip) { - const accept = await options.recipeImplementation.acceptLoginRequest({ - challenge: loginChallenge, - subject: request.subject, - userContext, - }); - return { redirectTo: accept.redirectTo }; - } else if (session) { - if (session.getUserId() !== request.subject) { - // TODO? - } - const accept = await options.recipeImplementation.acceptLoginRequest({ - challenge: loginChallenge, - subject: session.getUserId(), - identityProviderSessionId: session.getHandle(), - userContext, - }); - return { redirectTo: accept.redirectTo }; - } - const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; - const websiteDomain = appInfo - .getOrigin({ - request: options.req, - userContext: userContext, - }) - .getAsStringDangerous(); - const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); - // TODO: - return { - redirectTo: - websiteDomain + - websiteBasePath + - `?hint=${request.oidcContext?.login_hint ?? ""}&loginChallenge=${loginChallenge}`, - }; }, loginPOST: async ({ loginChallenge, accept, options, session, userContext }) => { const res = accept @@ -159,14 +134,21 @@ export default function getAPIImplementation(): APIInterface { }); return { redirectTo: res.redirectTo }; }, - authGET: async (input) => { - const res = await input.options.recipeImplementation.authorization({ - params: input.params, - cookies: input.cookie, - session: input.session, - userContext: input.userContext, + authGET: async ({ options, params, cookie, session, userContext }) => { + const response = await options.recipeImplementation.authorization({ + params, + cookies: cookie, + session, + userContext, + }); + + return handleInternalRedirects({ + response, + recipeImplementation: options.recipeImplementation, + cookie, + session, + userContext, }); - return res; }, tokenPOST: async (input) => { return input.options.recipeImplementation.token({ body: input.body, userContext: input.userContext }); diff --git a/lib/ts/recipe/oauth2/api/utils.ts b/lib/ts/recipe/oauth2/api/utils.ts new file mode 100644 index 000000000..48789773b --- /dev/null +++ b/lib/ts/recipe/oauth2/api/utils.ts @@ -0,0 +1,176 @@ +import SuperTokens from "../../../supertokens"; +import { UserContext } from "../../../types"; +import { SessionContainerInterface } from "../../session/types"; +import { AUTH_PATH, LOGIN_PATH } from "../constants"; +import { RecipeInterface } from "../types"; +import setCookieParser from "set-cookie-parser"; + +// API implementation for the loginGET function. +// Extracted for use in both apiImplementation and handleInternalRedirects. +export async function loginGET({ + recipeImplementation, + loginChallenge, + session, + setCookie, + userContext, +}: { + recipeImplementation: RecipeInterface; + loginChallenge: string; + session?: SessionContainerInterface; + setCookie?: string; + userContext: UserContext; +}) { + const request = await recipeImplementation.getLoginRequest({ + challenge: loginChallenge, + userContext, + }); + + if (request.skip) { + const accept = await recipeImplementation.acceptLoginRequest({ + challenge: loginChallenge, + subject: request.subject, + userContext, + }); + + return { redirectTo: accept.redirectTo, setCookie }; + } else if (session) { + if (session.getUserId() !== request.subject) { + // TODO? + } + const accept = await recipeImplementation.acceptLoginRequest({ + challenge: loginChallenge, + subject: session.getUserId(), + identityProviderSessionId: session.getHandle(), + userContext, + }); + return { redirectTo: accept.redirectTo, setCookie }; + } + const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; + const websiteDomain = appInfo + .getOrigin({ + request: undefined, + userContext: userContext, + }) + .getAsStringDangerous(); + const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); + + return { + redirectTo: + websiteDomain + + websiteBasePath + + `?hint=${request.oidcContext?.login_hint ?? ""}&loginChallenge=${loginChallenge}`, + setCookie, + }; +} + +function getMergedCookies({ cookie = "", setCookie }: { cookie?: string; setCookie?: string }): string { + if (!setCookie) { + return cookie; + } + + const cookieMap = cookie.split(";").reduce((acc, curr) => { + const [name, value] = curr.split("="); + return { ...acc, [name.trim()]: value }; + }, {} as Record); + + const setCookies = setCookieParser.parse(setCookieParser.splitCookiesString(setCookie)); + + for (const { name, value, expires } of setCookies) { + if (expires && new Date(expires) < new Date()) { + delete cookieMap[name]; + } else { + cookieMap[name] = value; + } + } + + return Object.entries(cookieMap) + .map(([key, value]) => `${key}=${value}`) + .join(";"); +} + +function mergeSetCookieHeaders(setCookie1?: string, setCookie2?: string): string { + if (!setCookie1) { + return setCookie2 || ""; + } + if (!setCookie2 || setCookie1 === setCookie2) { + return setCookie1; + } + return `${setCookie1};${setCookie2}`; +} + +function isInternalRedirect(redirectTo: string): boolean { + const { apiDomain, apiBasePath } = SuperTokens.getInstanceOrThrowError().appInfo; + const basePath = `${apiDomain.getAsStringDangerous()}${apiBasePath.getAsStringDangerous()}`; + + return [LOGIN_PATH, AUTH_PATH].some((path) => redirectTo.startsWith(`${basePath}${path}`)); +} + +// In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. +// If an internal redirect is identified, it's handled directly by this function. +// Currently, we only need to handle redirects to /oauth2/login and /oauth2/auth endpoints. +export async function handleInternalRedirects({ + response, + recipeImplementation, + session, + cookie = "", + userContext, +}: { + response: { redirectTo: string; setCookie: string | undefined }; + recipeImplementation: RecipeInterface; + session?: SessionContainerInterface; + cookie?: string; + userContext: UserContext; +}): Promise<{ redirectTo: string; setCookie: string | undefined }> { + if (!isInternalRedirect(response.redirectTo)) { + return response; + } + + // Typically, there are no more than 2 internal redirects per API call but we are allowing upto 10. + // This safety net prevents infinite redirect loops in case there are more redirects than expected. + const maxRedirects = 10; + let redirectCount = 0; + + while (redirectCount < maxRedirects && isInternalRedirect(response.redirectTo)) { + cookie = getMergedCookies({ cookie, setCookie: response.setCookie }); + + const queryString = response.redirectTo.split("?")[1]; + const params = new URLSearchParams(queryString); + + if (response.redirectTo.includes(LOGIN_PATH)) { + const loginChallenge = params.get("login_challenge") ?? params.get("loginChallenge"); + if (!loginChallenge) { + throw new Error(`Expected loginChallenge in ${response.redirectTo}`); + } + + const loginRes = await loginGET({ + recipeImplementation, + loginChallenge, + session, + setCookie: response.setCookie, + userContext, + }); + + response = { + redirectTo: loginRes.redirectTo, + setCookie: mergeSetCookieHeaders(loginRes.setCookie, response.setCookie), + }; + } else if (response.redirectTo.includes(AUTH_PATH)) { + const authRes = await recipeImplementation.authorization({ + params: Object.fromEntries(params.entries()), + cookies: cookie, + session, + userContext, + }); + + response = { + redirectTo: authRes.redirectTo, + setCookie: mergeSetCookieHeaders(authRes.setCookie, response.setCookie), + }; + } else { + throw new Error(`Unexpected internal redirect ${response.redirectTo}`); + } + + redirectCount++; + } + return response; +} From 0b39ad9825dd27c073bf33fca717456ec9bbe35f Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 26 Jul 2024 10:14:49 +0000 Subject: [PATCH 10/35] fix: Prefer exact api path match in the middleware (#892) --- lib/build/recipeModule.d.ts | 1 + lib/build/recipeModule.js | 4 +-- lib/build/supertokens.js | 62 ++++++++++++++++++++++------------ lib/ts/recipeModule.ts | 6 ++-- lib/ts/supertokens.ts | 67 ++++++++++++++++++++++++++----------- 5 files changed, 95 insertions(+), 45 deletions(-) diff --git a/lib/build/recipeModule.d.ts b/lib/build/recipeModule.d.ts index 778cd14ee..dcf2f48d4 100644 --- a/lib/build/recipeModule.d.ts +++ b/lib/build/recipeModule.d.ts @@ -17,6 +17,7 @@ export default abstract class RecipeModule { | { id: string; tenantId: string; + exactMatch: boolean; } | undefined >; diff --git a/lib/build/recipeModule.js b/lib/build/recipeModule.js index 7b3eef889..75c3219ae 100644 --- a/lib/build/recipeModule.js +++ b/lib/build/recipeModule.js @@ -54,7 +54,7 @@ class RecipeModule { tenantIdFromFrontend: constants_1.DEFAULT_TENANT_ID, userContext, }); - return { id: currAPI.id, tenantId: finalTenantId }; + return { id: currAPI.id, tenantId: finalTenantId, exactMatch: true }; } else if ( remainingPath !== undefined && this.appInfo.apiBasePath @@ -65,7 +65,7 @@ class RecipeModule { tenantIdFromFrontend: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, userContext, }); - return { id: currAPI.id, tenantId: finalTenantId }; + return { id: currAPI.id, tenantId: finalTenantId, exactMatch: false }; } } } diff --git a/lib/build/supertokens.js b/lib/build/supertokens.js index 07b52a330..62e146628 100644 --- a/lib/build/supertokens.js +++ b/lib/build/supertokens.js @@ -163,6 +163,7 @@ class SuperTokens { requestRID = undefined; } async function handleWithoutRid(recipeModules) { + let bestMatch = undefined; for (let i = 0; i < recipeModules.length; i++) { logger_1.logDebugMessage( "middleware: Checking recipe ID for match: " + @@ -174,25 +175,39 @@ class SuperTokens { ); let idResult = await recipeModules[i].returnAPIIdIfCanHandleRequest(path, method, userContext); if (idResult !== undefined) { - logger_1.logDebugMessage("middleware: Request being handled by recipe. ID is: " + idResult.id); - let requestHandled = await recipeModules[i].handleAPIRequest( - idResult.id, - idResult.tenantId, - request, - response, - path, - method, - userContext - ); - if (!requestHandled) { - logger_1.logDebugMessage( - "middleware: Not handled because API returned requestHandled as false" - ); - return false; + // The request path may or may not include the tenantId. `returnAPIIdIfCanHandleRequest` handles both cases. + // If one recipe matches with tenantId and another matches exactly, we prefer the exact match. + if (bestMatch === undefined || idResult.exactMatch) { + bestMatch = { + recipeModule: recipeModules[i], + idResult: idResult, + }; } - logger_1.logDebugMessage("middleware: Ended"); - return true; + if (idResult.exactMatch) { + break; + } + } + } + if (bestMatch !== undefined) { + const { idResult, recipeModule } = bestMatch; + logger_1.logDebugMessage("middleware: Request being handled by recipe. ID is: " + idResult.id); + let requestHandled = await recipeModule.handleAPIRequest( + idResult.id, + idResult.tenantId, + request, + response, + path, + method, + userContext + ); + if (!requestHandled) { + logger_1.logDebugMessage( + "middleware: Not handled because API returned requestHandled as false" + ); + return false; } + logger_1.logDebugMessage("middleware: Ended"); + return true; } logger_1.logDebugMessage("middleware: Not handling because no recipe matched"); return false; @@ -239,13 +254,18 @@ class SuperTokens { // the path and methods of the APIs exposed via those recipes is unique. let currIdResult = await matchedRecipe[i].returnAPIIdIfCanHandleRequest(path, method, userContext); if (currIdResult !== undefined) { - if (idResult !== undefined) { + if ( + idResult === undefined || + // The request path may or may not include the tenantId. `returnAPIIdIfCanHandleRequest` handles both cases. + // If one recipe matches with tenantId and another matches exactly, we prefer the exact match. + (currIdResult.exactMatch === true && idResult.exactMatch === false) + ) { + finalMatchedRecipe = matchedRecipe[i]; + idResult = currIdResult; + } else { throw new Error( "Two recipes have matched the same API path and method! This is a bug in the SDK. Please contact support." ); - } else { - finalMatchedRecipe = matchedRecipe[i]; - idResult = currIdResult; } } } diff --git a/lib/ts/recipeModule.ts b/lib/ts/recipeModule.ts index 3b73692c8..e800c7f00 100644 --- a/lib/ts/recipeModule.ts +++ b/lib/ts/recipeModule.ts @@ -41,7 +41,7 @@ export default abstract class RecipeModule { path: NormalisedURLPath, method: HTTPMethod, userContext: UserContext - ): Promise<{ id: string; tenantId: string } | undefined> => { + ): Promise<{ id: string; tenantId: string; exactMatch: boolean } | undefined> => { let apisHandled = this.getAPIsHandled(); const basePathStr = this.appInfo.apiBasePath.getAsStringDangerous(); @@ -71,7 +71,7 @@ export default abstract class RecipeModule { tenantIdFromFrontend: DEFAULT_TENANT_ID, userContext, }); - return { id: currAPI.id, tenantId: finalTenantId }; + return { id: currAPI.id, tenantId: finalTenantId, exactMatch: true }; } else if ( remainingPath !== undefined && this.appInfo.apiBasePath @@ -82,7 +82,7 @@ export default abstract class RecipeModule { tenantIdFromFrontend: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, userContext, }); - return { id: currAPI.id, tenantId: finalTenantId }; + return { id: currAPI.id, tenantId: finalTenantId, exactMatch: false }; } } } diff --git a/lib/ts/supertokens.ts b/lib/ts/supertokens.ts index 104542b6e..69bd96f30 100644 --- a/lib/ts/supertokens.ts +++ b/lib/ts/supertokens.ts @@ -363,6 +363,13 @@ export default class SuperTokens { } async function handleWithoutRid(recipeModules: RecipeModule[]) { + let bestMatch: + | { + recipeModule: RecipeModule; + idResult: { id: string; tenantId: string; exactMatch: boolean }; + } + | undefined = undefined; + for (let i = 0; i < recipeModules.length; i++) { logDebugMessage( "middleware: Checking recipe ID for match: " + @@ -374,23 +381,39 @@ export default class SuperTokens { ); let idResult = await recipeModules[i].returnAPIIdIfCanHandleRequest(path, method, userContext); if (idResult !== undefined) { - logDebugMessage("middleware: Request being handled by recipe. ID is: " + idResult.id); - let requestHandled = await recipeModules[i].handleAPIRequest( - idResult.id, - idResult.tenantId, - request, - response, - path, - method, - userContext - ); - if (!requestHandled) { - logDebugMessage("middleware: Not handled because API returned requestHandled as false"); - return false; + // The request path may or may not include the tenantId. `returnAPIIdIfCanHandleRequest` handles both cases. + // If one recipe matches with tenantId and another matches exactly, we prefer the exact match. + if (bestMatch === undefined || idResult.exactMatch) { + bestMatch = { + recipeModule: recipeModules[i], + idResult: idResult, + }; } - logDebugMessage("middleware: Ended"); - return true; + + if (idResult.exactMatch) { + break; + } + } + } + + if (bestMatch !== undefined) { + const { idResult, recipeModule } = bestMatch; + logDebugMessage("middleware: Request being handled by recipe. ID is: " + idResult.id); + let requestHandled = await recipeModule.handleAPIRequest( + idResult.id, + idResult.tenantId, + request, + response, + path, + method, + userContext + ); + if (!requestHandled) { + logDebugMessage("middleware: Not handled because API returned requestHandled as false"); + return false; } + logDebugMessage("middleware: Ended"); + return true; } logDebugMessage("middleware: Not handling because no recipe matched"); return false; @@ -435,6 +458,7 @@ export default class SuperTokens { | { id: string; tenantId: string; + exactMatch: boolean; } | undefined = undefined; @@ -444,13 +468,18 @@ export default class SuperTokens { // the path and methods of the APIs exposed via those recipes is unique. let currIdResult = await matchedRecipe[i].returnAPIIdIfCanHandleRequest(path, method, userContext); if (currIdResult !== undefined) { - if (idResult !== undefined) { + if ( + idResult === undefined || + // The request path may or may not include the tenantId. `returnAPIIdIfCanHandleRequest` handles both cases. + // If one recipe matches with tenantId and another matches exactly, we prefer the exact match. + (currIdResult.exactMatch === true && idResult.exactMatch === false) + ) { + finalMatchedRecipe = matchedRecipe[i]; + idResult = currIdResult; + } else { throw new Error( "Two recipes have matched the same API path and method! This is a bug in the SDK. Please contact support." ); - } else { - finalMatchedRecipe = matchedRecipe[i]; - idResult = currIdResult; } } } From 92121af3278d32eb891c23c15df77d65fdd93315 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 26 Jul 2024 11:10:59 +0000 Subject: [PATCH 11/35] feat: Add userInfoGET endpoint (#890) * 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 * feat: Add userInfoGET endpoint * fix: PR changes * fix: PR changes * fix: PR changes --------- Co-authored-by: Mihaly Lengyel --- lib/build/recipe/oauth2/api/implementation.js | 13 +++ lib/build/recipe/oauth2/api/userInfo.d.ts | 9 ++ lib/build/recipe/oauth2/api/userInfo.js | 82 ++++++++++++++++ lib/build/recipe/oauth2/constants.d.ts | 1 + lib/build/recipe/oauth2/constants.js | 3 +- lib/build/recipe/oauth2/recipe.d.ts | 20 +++- lib/build/recipe/oauth2/recipe.js | 45 ++++++++- .../recipe/oauth2/recipeImplementation.d.ts | 5 +- .../recipe/oauth2/recipeImplementation.js | 6 +- lib/build/recipe/oauth2/types.d.ts | 35 ++++++- lib/build/recipe/oauth2client/index.js | 15 +-- .../oauth2client/recipeImplementation.js | 7 +- .../recipe/openid/recipeImplementation.js | 1 + lib/build/recipe/userroles/recipe.js | 38 ++++++++ lib/ts/recipe/oauth2/api/implementation.ts | 14 +++ lib/ts/recipe/oauth2/api/userInfo.ts | 96 +++++++++++++++++++ lib/ts/recipe/oauth2/constants.ts | 1 + lib/ts/recipe/oauth2/recipe.ts | 71 +++++++++++++- lib/ts/recipe/oauth2/recipeImplementation.ts | 8 +- lib/ts/recipe/oauth2/types.ts | 30 +++++- lib/ts/recipe/oauth2client/index.ts | 15 +-- .../oauth2client/recipeImplementation.ts | 7 +- lib/ts/recipe/openid/recipeImplementation.ts | 3 +- lib/ts/recipe/userroles/recipe.ts | 51 ++++++++++ 24 files changed, 534 insertions(+), 42 deletions(-) create mode 100644 lib/build/recipe/oauth2/api/userInfo.d.ts create mode 100644 lib/build/recipe/oauth2/api/userInfo.js create mode 100644 lib/ts/recipe/oauth2/api/userInfo.ts diff --git a/lib/build/recipe/oauth2/api/implementation.js b/lib/build/recipe/oauth2/api/implementation.js index abf6acffa..0b70812a4 100644 --- a/lib/build/recipe/oauth2/api/implementation.js +++ b/lib/build/recipe/oauth2/api/implementation.js @@ -166,6 +166,19 @@ function getAPIImplementation() { }, }; }, + userInfoGET: async ({ accessTokenPayload, user, scopes, tenantId, options, userContext }) => { + const userInfo = await options.recipeImplementation.buildUserInfo({ + user, + accessTokenPayload, + scopes, + tenantId, + userContext, + }); + return { + status: "OK", + info: userInfo, + }; + }, }; } exports.default = getAPIImplementation; diff --git a/lib/build/recipe/oauth2/api/userInfo.d.ts b/lib/build/recipe/oauth2/api/userInfo.d.ts new file mode 100644 index 000000000..d0b8cdf4e --- /dev/null +++ b/lib/build/recipe/oauth2/api/userInfo.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function userInfoGET( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2/api/userInfo.js b/lib/build/recipe/oauth2/api/userInfo.js new file mode 100644 index 000000000..dadeb840c --- /dev/null +++ b/lib/build/recipe/oauth2/api/userInfo.js @@ -0,0 +1,82 @@ +"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 }); +const utils_1 = require("../../../utils"); +const __1 = require("../../.."); +// TODO: Replace stub implementation by the actual implementation +async function validateOAuth2AccessToken(accessToken) { + const resp = await fetch(`http://localhost:4445/admin/oauth2/introspect`, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ token: accessToken }), + }); + return await resp.json(); +} +async function userInfoGET(apiImplementation, tenantId, options, userContext) { + if (apiImplementation.userInfoGET === undefined) { + return false; + } + const authHeader = options.req.getHeaderValue("authorization") || options.req.getHeaderValue("Authorization"); + if (authHeader === undefined || !authHeader.startsWith("Bearer ")) { + // TODO: Returning a 400 instead of a 401 to prevent a potential refresh loop in the client SDK. + // When addressing this TODO, review other response codes in this function as well. + utils_1.sendNon200ResponseWithMessage(options.res, "Missing or invalid Authorization header", 400); + return true; + } + const accessToken = authHeader.replace(/^Bearer /, "").trim(); + let accessTokenPayload; + try { + accessTokenPayload = await validateOAuth2AccessToken(accessToken); + } catch (error) { + options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); + utils_1.sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 400); + return true; + } + if ( + accessTokenPayload === null || + typeof accessTokenPayload !== "object" || + typeof accessTokenPayload.sub !== "string" || + typeof accessTokenPayload.scope !== "string" + ) { + options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); + utils_1.sendNon200ResponseWithMessage(options.res, "Malformed access token payload", 400); + return true; + } + const userId = accessTokenPayload.sub; + const user = await __1.getUser(userId, userContext); + if (user === undefined) { + options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); + utils_1.sendNon200ResponseWithMessage( + options.res, + "Couldn't find any user associated with the access token", + 400 + ); + return true; + } + const response = await apiImplementation.userInfoGET({ + accessTokenPayload, + user, + tenantId, + scopes: accessTokenPayload.scope.split(" "), + options, + userContext, + }); + utils_1.send200Response(options.res, response); + return true; +} +exports.default = userInfoGET; diff --git a/lib/build/recipe/oauth2/constants.d.ts b/lib/build/recipe/oauth2/constants.d.ts index e5fea5b40..e5a5c4263 100644 --- a/lib/build/recipe/oauth2/constants.d.ts +++ b/lib/build/recipe/oauth2/constants.d.ts @@ -6,3 +6,4 @@ export declare const CONSENT_PATH = "/oauth2/consent"; export declare const AUTH_PATH = "/oauth2/auth"; export declare const TOKEN_PATH = "/oauth2/token"; export declare const LOGIN_INFO_PATH = "/oauth2/login/info"; +export declare const USER_INFO_PATH = "/oauth2/userinfo"; diff --git a/lib/build/recipe/oauth2/constants.js b/lib/build/recipe/oauth2/constants.js index f249f20d4..2ea314db4 100644 --- a/lib/build/recipe/oauth2/constants.js +++ b/lib/build/recipe/oauth2/constants.js @@ -14,7 +14,7 @@ * under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.CONSENT_PATH = exports.LOGOUT_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; +exports.USER_INFO_PATH = exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.CONSENT_PATH = exports.LOGOUT_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; exports.OAUTH2_BASE_PATH = "/oauth2/"; exports.LOGIN_PATH = "/oauth2/login"; exports.LOGOUT_PATH = "/oauth2/logout"; @@ -22,3 +22,4 @@ exports.CONSENT_PATH = "/oauth2/consent"; exports.AUTH_PATH = "/oauth2/auth"; exports.TOKEN_PATH = "/oauth2/token"; exports.LOGIN_INFO_PATH = "/oauth2/login/info"; +exports.USER_INFO_PATH = "/oauth2/userinfo"; diff --git a/lib/build/recipe/oauth2/recipe.d.ts b/lib/build/recipe/oauth2/recipe.d.ts index 8efd16f20..19a890e21 100644 --- a/lib/build/recipe/oauth2/recipe.d.ts +++ b/lib/build/recipe/oauth2/recipe.d.ts @@ -4,12 +4,20 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import NormalisedURLPath from "../../normalisedURLPath"; import RecipeModule from "../../recipeModule"; import { APIHandled, HTTPMethod, JSONObject, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; -import { APIInterface, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; +import { + APIInterface, + RecipeInterface, + TypeInput, + TypeNormalisedInput, + UserInfo, + UserInfoBuilderFunction, +} from "./types"; import { User } from "../../user"; export default class Recipe extends RecipeModule { static RECIPE_ID: string; private static instance; private idTokenBuilders; + private userInfoBuilders; config: TypeNormalisedInput; recipeInterfaceImpl: RecipeInterface; apiImpl: APIInterface; @@ -19,10 +27,11 @@ export default class Recipe extends RecipeModule { static getInstanceOrThrowError(): Recipe; static init(config?: TypeInput): RecipeListFunction; static reset(): void; + addUserInfoBuilderFromOtherRecipe: (userInfoBuilderFn: UserInfoBuilderFunction) => void; getAPIsHandled(): APIHandled[]; handleAPIRequest: ( id: string, - _tenantId: string | undefined, + tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, @@ -33,4 +42,11 @@ export default class Recipe extends RecipeModule { getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; getDefaultIdTokenPayload(user: User, scopes: string[], userContext: UserContext): Promise; + getDefaultUserInfoPayload( + user: User, + accessTokenPayload: JSONObject, + scopes: string[], + tenantId: string, + userContext: UserContext + ): Promise; } diff --git a/lib/build/recipe/oauth2/recipe.js b/lib/build/recipe/oauth2/recipe.js index 4e6d0d78c..048abe7dd 100644 --- a/lib/build/recipe/oauth2/recipe.js +++ b/lib/build/recipe/oauth2/recipe.js @@ -34,11 +34,16 @@ const constants_1 = require("./constants"); const recipeImplementation_1 = __importDefault(require("./recipeImplementation")); const utils_1 = require("./utils"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); +const userInfo_1 = __importDefault(require("./api/userInfo")); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config) { super(recipeId, appInfo); this.idTokenBuilders = []; - this.handleAPIRequest = async (id, _tenantId, req, res, _path, _method, userContext) => { + this.userInfoBuilders = []; + this.addUserInfoBuilderFromOtherRecipe = (userInfoBuilderFn) => { + this.userInfoBuilders.push(userInfoBuilderFn); + }; + this.handleAPIRequest = async (id, tenantId, req, res, _path, _method, userContext) => { let options = { config: this.config, recipeId: this.getRecipeId(), @@ -65,6 +70,9 @@ class Recipe extends recipeModule_1.default { if (id === constants_1.LOGIN_INFO_PATH) { return loginInfo_1.default(this.apiImpl, options, userContext); } + if (id === constants_1.USER_INFO_PATH) { + return userInfo_1.default(this.apiImpl, tenantId, options, userContext); + } throw new Error("Should never come here: handleAPIRequest called with unknown id"); }; this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); @@ -75,7 +83,8 @@ class Recipe extends recipeModule_1.default { querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo, - this.getDefaultIdTokenPayload.bind(this) + this.getDefaultIdTokenPayload.bind(this), + this.getDefaultUserInfoPayload.bind(this) ) ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); @@ -168,6 +177,12 @@ class Recipe extends recipeModule_1.default { id: constants_1.LOGIN_INFO_PATH, disabled: this.apiImpl.loginInfoGET === undefined, }, + { + method: "get", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.USER_INFO_PATH), + id: constants_1.USER_INFO_PATH, + disabled: this.apiImpl.userInfoGET === undefined, + }, ]; } handleError(error, _, __, _userContext) { @@ -200,6 +215,32 @@ class Recipe extends recipeModule_1.default { } return payload; } + async getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext) { + let payload = { + sub: accessTokenPayload.sub, + }; + if (scopes.includes("email")) { + payload.email = user === null || user === void 0 ? void 0 : user.emails[0]; + payload.email_verified = user.loginMethods.some( + (lm) => lm.hasSameEmailAs(user === null || user === void 0 ? void 0 : user.emails[0]) && lm.verified + ); + } + if (scopes.includes("phoneNumber")) { + payload.phoneNumber = user === null || user === void 0 ? void 0 : user.phoneNumbers[0]; + payload.phoneNumber_verified = user.loginMethods.some( + (lm) => + lm.hasSamePhoneNumberAs(user === null || user === void 0 ? void 0 : user.phoneNumbers[0]) && + lm.verified + ); + } + for (const fn of this.userInfoBuilders) { + payload = Object.assign( + Object.assign({}, payload), + await fn(user, accessTokenPayload, scopes, tenantId, userContext) + ); + } + return payload; + } } exports.default = Recipe; Recipe.RECIPE_ID = "oauth2"; diff --git a/lib/build/recipe/oauth2/recipeImplementation.d.ts b/lib/build/recipe/oauth2/recipeImplementation.d.ts index 0029a9016..273bef941 100644 --- a/lib/build/recipe/oauth2/recipeImplementation.d.ts +++ b/lib/build/recipe/oauth2/recipeImplementation.d.ts @@ -1,10 +1,11 @@ // @ts-nocheck import { Querier } from "../../querier"; import { NormalisedAppinfo } from "../../types"; -import { RecipeInterface, TypeNormalisedInput, PayloadBuilderFunction } from "./types"; +import { RecipeInterface, TypeNormalisedInput, PayloadBuilderFunction, UserInfoBuilderFunction } from "./types"; export default function getRecipeInterface( querier: Querier, _config: TypeNormalisedInput, _appInfo: NormalisedAppinfo, - getDefaultIdTokenPayload: PayloadBuilderFunction + getDefaultIdTokenPayload: PayloadBuilderFunction, + getDefaultUserInfoPayload: UserInfoBuilderFunction ): RecipeInterface; diff --git a/lib/build/recipe/oauth2/recipeImplementation.js b/lib/build/recipe/oauth2/recipeImplementation.js index ac5e17e13..a2158ccb9 100644 --- a/lib/build/recipe/oauth2/recipeImplementation.js +++ b/lib/build/recipe/oauth2/recipeImplementation.js @@ -24,7 +24,7 @@ const querier_1 = require("../../querier"); const utils_1 = require("../../utils"); const OAuth2Client_1 = require("./OAuth2Client"); const __1 = require("../.."); -function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload) { +function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload, getDefaultUserInfoPayload) { return { getLoginRequest: async function (input) { const resp = await querier.sendGetRequest( @@ -394,8 +394,8 @@ function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload buildIdTokenPayload: async function (input) { return input.defaultPayload; }, - buildUserInfo: async function (input) { - return input.user.toJson(); // Proper impl + buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) { + return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext); }, }; } diff --git a/lib/build/recipe/oauth2/types.d.ts b/lib/build/recipe/oauth2/types.d.ts index b95d8f50e..d8db28595 100644 --- a/lib/build/recipe/oauth2/types.d.ts +++ b/lib/build/recipe/oauth2/types.d.ts @@ -1,7 +1,7 @@ // @ts-nocheck import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; -import { GeneralErrorResponse, JSONObject, NonNullableProperties, UserContext } from "../../types"; +import { GeneralErrorResponse, JSONObject, JSONValue, NonNullableProperties, UserContext } from "../../types"; import { SessionContainerInterface } from "../session/types"; import { OAuth2Client } from "./OAuth2Client"; import { User } from "../../user"; @@ -87,6 +87,14 @@ export declare type LoginInfo = { logoUri: string; metadata?: Record | null; }; +export declare type UserInfo = { + sub: string; + email?: string; + email_verified?: boolean; + phoneNumber?: string; + phoneNumber_verified?: boolean; + [key: string]: JSONValue; +}; export declare type RecipeInterface = { authorization(input: { params: any; @@ -224,7 +232,7 @@ export declare type RecipeInterface = { user: User; accessTokenPayload: JSONObject; scopes: string[]; - defaultInfo: JSONObject; + tenantId: string; userContext: UserContext; }): Promise; }; @@ -344,6 +352,22 @@ export declare type APIInterface = { } | GeneralErrorResponse >); + userInfoGET: + | undefined + | ((input: { + accessTokenPayload: JSONObject; + user: User; + scopes: string[]; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise< + | { + status: "OK"; + info: JSONObject; + } + | GeneralErrorResponse + >); }; export declare type OAuth2ClientOptions = { clientId: string; @@ -445,3 +469,10 @@ export declare type PayloadBuilderFunction = ( scopes: string[], userContext: UserContext ) => Promise; +export declare type UserInfoBuilderFunction = ( + user: User, + accessTokenPayload: JSONObject, + scopes: string[], + tenantId: string, + userContext: UserContext +) => Promise; diff --git a/lib/build/recipe/oauth2client/index.js b/lib/build/recipe/oauth2client/index.js index e9481bbfa..cdf968aaf 100644 --- a/lib/build/recipe/oauth2client/index.js +++ b/lib/build/recipe/oauth2client/index.js @@ -25,35 +25,38 @@ const recipe_1 = __importDefault(require("./recipe")); class Wrapper { static async getAuthorisationRedirectURL(redirectURIOnProviderDashboard, userContext) { const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl; + const normalisedUserContext = utils_1.getUserContext(userContext); const providerConfig = await recipeInterfaceImpl.getProviderConfig({ - userContext: utils_1.getUserContext(userContext), + userContext: normalisedUserContext, }); return await recipeInterfaceImpl.getAuthorisationRedirectURL({ providerConfig, redirectURIOnProviderDashboard, - userContext: utils_1.getUserContext(userContext), + userContext: normalisedUserContext, }); } static async exchangeAuthCodeForOAuthTokens(redirectURIInfo, userContext) { const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl; + const normalisedUserContext = utils_1.getUserContext(userContext); const providerConfig = await recipeInterfaceImpl.getProviderConfig({ - userContext: utils_1.getUserContext(userContext), + userContext: normalisedUserContext, }); return await recipeInterfaceImpl.exchangeAuthCodeForOAuthTokens({ providerConfig, redirectURIInfo, - userContext: utils_1.getUserContext(userContext), + userContext: normalisedUserContext, }); } static async getUserInfo(oAuthTokens, userContext) { const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl; + const normalisedUserContext = utils_1.getUserContext(userContext); const providerConfig = await recipeInterfaceImpl.getProviderConfig({ - userContext: utils_1.getUserContext(userContext), + userContext: normalisedUserContext, }); return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getUserInfo({ providerConfig, oAuthTokens, - userContext: utils_1.getUserContext(userContext), + userContext: normalisedUserContext, }); } } diff --git a/lib/build/recipe/oauth2client/recipeImplementation.js b/lib/build/recipe/oauth2client/recipeImplementation.js index 8343d5d61..012749192 100644 --- a/lib/build/recipe/oauth2client/recipeImplementation.js +++ b/lib/build/recipe/oauth2client/recipeImplementation.js @@ -63,10 +63,9 @@ function getRecipeImplementation(_querier, config) { if (oidcInfo.token_endpoint === undefined) { throw new Error("Failed to token_endpoint from the oidcDiscoveryEndpoint."); } - // TODO: We currently don't have this - // if (oidcInfo.userinfo_endpoint === undefined) { - // throw new Error("Failed to userinfo_endpoint from the oidcDiscoveryEndpoint."); - // } + if (oidcInfo.userinfo_endpoint === undefined) { + throw new Error("Failed to userinfo_endpoint from the oidcDiscoveryEndpoint."); + } if (oidcInfo.jwks_uri === undefined) { throw new Error("Failed to jwks_uri from the oidcDiscoveryEndpoint."); } diff --git a/lib/build/recipe/openid/recipeImplementation.js b/lib/build/recipe/openid/recipeImplementation.js index d3b582d2e..f01e8b272 100644 --- a/lib/build/recipe/openid/recipeImplementation.js +++ b/lib/build/recipe/openid/recipeImplementation.js @@ -24,6 +24,7 @@ function getRecipeInterface(config, jwtRecipeImplementation, appInfo) { jwks_uri, authorization_endpoint: apiBasePath + constants_2.AUTH_PATH, token_endpoint: apiBasePath + constants_2.TOKEN_PATH, + userinfo_endpoint: apiBasePath + constants_2.USER_INFO_PATH, subject_types_supported: ["public"], id_token_signing_alg_values_supported: ["RS256"], response_types_supported: ["code", "id_token", "id_token token"], diff --git a/lib/build/recipe/userroles/recipe.js b/lib/build/recipe/userroles/recipe.js index 53f5d56b6..0587a295f 100644 --- a/lib/build/recipe/userroles/recipe.js +++ b/lib/build/recipe/userroles/recipe.js @@ -27,6 +27,7 @@ const utils_1 = require("./utils"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const postSuperTokensInitCallbacks_1 = require("../../postSuperTokensInitCallbacks"); const recipe_1 = __importDefault(require("../session/recipe")); +const recipe_2 = __importDefault(require("../oauth2/recipe")); const userRoleClaim_1 = require("./userRoleClaim"); const permissionClaim_1 = require("./permissionClaim"); class Recipe extends recipeModule_1.default { @@ -51,6 +52,43 @@ class Recipe extends recipeModule_1.default { if (!this.config.skipAddingPermissionsToAccessToken) { recipe_1.default.getInstanceOrThrowError().addClaimFromOtherRecipe(permissionClaim_1.PermissionClaim); } + recipe_2.default + .getInstanceOrThrowError() + .addUserInfoBuilderFromOtherRecipe(async (user, _accessTokenPayload, scopes, tenantId, userContext) => { + let userInfo = {}; + let userRoles = []; + if (scopes.includes("roles") || scopes.includes("permissions")) { + const res = await this.recipeInterfaceImpl.getRolesForUser({ + userId: user.id, + tenantId, + userContext, + }); + if (res.status !== "OK") { + throw new Error("Failed to fetch roles for the user"); + } + userRoles = res.roles; + } + if (scopes.includes("roles")) { + userInfo.roles = userRoles; + } + if (scopes.includes("permissions")) { + const userPermissions = new Set(); + for (const role of userRoles) { + const rolePermissions = await this.recipeInterfaceImpl.getPermissionsForRole({ + role, + userContext, + }); + if (rolePermissions.status !== "OK") { + throw new Error("Failed to fetch permissions for the role"); + } + for (const perm of rolePermissions.permissions) { + userPermissions.add(perm); + } + } + userInfo.permissons = Array.from(userPermissions); + } + return userInfo; + }); }); } /* Init functions */ diff --git a/lib/ts/recipe/oauth2/api/implementation.ts b/lib/ts/recipe/oauth2/api/implementation.ts index 577655a12..e301a1011 100644 --- a/lib/ts/recipe/oauth2/api/implementation.ts +++ b/lib/ts/recipe/oauth2/api/implementation.ts @@ -170,5 +170,19 @@ export default function getAPIImplementation(): APIInterface { }, }; }, + userInfoGET: async ({ accessTokenPayload, user, scopes, tenantId, options, userContext }) => { + const userInfo = await options.recipeImplementation.buildUserInfo({ + user, + accessTokenPayload, + scopes, + tenantId, + userContext, + }); + + return { + status: "OK", + info: userInfo, + }; + }, }; } diff --git a/lib/ts/recipe/oauth2/api/userInfo.ts b/lib/ts/recipe/oauth2/api/userInfo.ts new file mode 100644 index 000000000..b3d601697 --- /dev/null +++ b/lib/ts/recipe/oauth2/api/userInfo.ts @@ -0,0 +1,96 @@ +/* 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. + */ + +import { send200Response, sendNon200ResponseWithMessage } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import { JSONObject, UserContext } from "../../../types"; +import { getUser } from "../../.."; + +// TODO: Replace stub implementation by the actual implementation +async function validateOAuth2AccessToken(accessToken: string) { + const resp = await fetch(`http://localhost:4445/admin/oauth2/introspect`, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ token: accessToken }), + }); + return await resp.json(); +} + +export default async function userInfoGET( + apiImplementation: APIInterface, + tenantId: string, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.userInfoGET === undefined) { + return false; + } + + const authHeader = options.req.getHeaderValue("authorization") || options.req.getHeaderValue("Authorization"); + + if (authHeader === undefined || !authHeader.startsWith("Bearer ")) { + // TODO: Returning a 400 instead of a 401 to prevent a potential refresh loop in the client SDK. + // When addressing this TODO, review other response codes in this function as well. + sendNon200ResponseWithMessage(options.res, "Missing or invalid Authorization header", 400); + return true; + } + + const accessToken = authHeader.replace(/^Bearer /, "").trim(); + + let accessTokenPayload: JSONObject; + + try { + accessTokenPayload = await validateOAuth2AccessToken(accessToken); + } catch (error) { + options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); + sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 400); + return true; + } + + if ( + accessTokenPayload === null || + typeof accessTokenPayload !== "object" || + typeof accessTokenPayload.sub !== "string" || + typeof accessTokenPayload.scope !== "string" + ) { + options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); + sendNon200ResponseWithMessage(options.res, "Malformed access token payload", 400); + return true; + } + + const userId = accessTokenPayload.sub; + + const user = await getUser(userId, userContext); + + if (user === undefined) { + options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); + sendNon200ResponseWithMessage(options.res, "Couldn't find any user associated with the access token", 400); + return true; + } + + const response = await apiImplementation.userInfoGET({ + accessTokenPayload, + user, + tenantId, + scopes: accessTokenPayload.scope.split(" "), + options, + userContext, + }); + + send200Response(options.res, response); + return true; +} diff --git a/lib/ts/recipe/oauth2/constants.ts b/lib/ts/recipe/oauth2/constants.ts index ddfdef4d6..1175abacf 100644 --- a/lib/ts/recipe/oauth2/constants.ts +++ b/lib/ts/recipe/oauth2/constants.ts @@ -21,3 +21,4 @@ export const CONSENT_PATH = "/oauth2/consent"; export const AUTH_PATH = "/oauth2/auth"; export const TOKEN_PATH = "/oauth2/token"; export const LOGIN_INFO_PATH = "/oauth2/login/info"; +export const USER_INFO_PATH = "/oauth2/userinfo"; diff --git a/lib/ts/recipe/oauth2/recipe.ts b/lib/ts/recipe/oauth2/recipe.ts index a209cff1c..bb0948421 100644 --- a/lib/ts/recipe/oauth2/recipe.ts +++ b/lib/ts/recipe/oauth2/recipe.ts @@ -27,17 +27,35 @@ import loginAPI from "./api/login"; import logoutAPI from "./api/logout"; import tokenPOST from "./api/token"; import loginInfoGET from "./api/loginInfo"; -import { AUTH_PATH, CONSENT_PATH, LOGIN_INFO_PATH, LOGIN_PATH, LOGOUT_PATH, TOKEN_PATH } from "./constants"; +import { + AUTH_PATH, + CONSENT_PATH, + LOGIN_INFO_PATH, + LOGIN_PATH, + LOGOUT_PATH, + TOKEN_PATH, + USER_INFO_PATH, +} from "./constants"; import RecipeImplementation from "./recipeImplementation"; -import { APIInterface, PayloadBuilderFunction, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; +import { + APIInterface, + PayloadBuilderFunction, + RecipeInterface, + TypeInput, + TypeNormalisedInput, + UserInfo, + UserInfoBuilderFunction, +} from "./types"; import { validateAndNormaliseUserInput } from "./utils"; import OverrideableBuilder from "supertokens-js-override"; import { User } from "../../user"; +import userInfoGET from "./api/userInfo"; export default class Recipe extends RecipeModule { static RECIPE_ID = "oauth2"; private static instance: Recipe | undefined = undefined; private idTokenBuilders: PayloadBuilderFunction[] = []; + private userInfoBuilders: UserInfoBuilderFunction[] = []; config: TypeNormalisedInput; recipeInterfaceImpl: RecipeInterface; @@ -55,7 +73,8 @@ export default class Recipe extends RecipeModule { Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo, - this.getDefaultIdTokenPayload.bind(this) + this.getDefaultIdTokenPayload.bind(this), + this.getDefaultUserInfoPayload.bind(this) ) ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); @@ -96,6 +115,10 @@ export default class Recipe extends RecipeModule { Recipe.instance = undefined; } + addUserInfoBuilderFromOtherRecipe = (userInfoBuilderFn: UserInfoBuilderFunction) => { + this.userInfoBuilders.push(userInfoBuilderFn); + }; + /* RecipeModule functions */ getAPIsHandled(): APIHandled[] { @@ -154,12 +177,18 @@ export default class Recipe extends RecipeModule { id: LOGIN_INFO_PATH, disabled: this.apiImpl.loginInfoGET === undefined, }, + { + method: "get", + pathWithoutApiBasePath: new NormalisedURLPath(USER_INFO_PATH), + id: USER_INFO_PATH, + disabled: this.apiImpl.userInfoGET === undefined, + }, ]; } handleAPIRequest = async ( id: string, - _tenantId: string | undefined, + tenantId: string, req: BaseRequest, res: BaseResponse, _path: NormalisedURLPath, @@ -193,6 +222,9 @@ export default class Recipe extends RecipeModule { if (id === LOGIN_INFO_PATH) { return loginInfoGET(this.apiImpl, options, userContext); } + if (id === USER_INFO_PATH) { + return userInfoGET(this.apiImpl, tenantId, options, userContext); + } throw new Error("Should never come here: handleAPIRequest called with unknown id"); }; @@ -230,4 +262,35 @@ export default class Recipe extends RecipeModule { return payload; } + + async getDefaultUserInfoPayload( + user: User, + accessTokenPayload: JSONObject, + scopes: string[], + tenantId: string, + userContext: UserContext + ) { + let payload: JSONObject = { + sub: accessTokenPayload.sub, + }; + if (scopes.includes("email")) { + payload.email = user?.emails[0]; + payload.email_verified = user.loginMethods.some((lm) => lm.hasSameEmailAs(user?.emails[0]) && lm.verified); + } + if (scopes.includes("phoneNumber")) { + payload.phoneNumber = user?.phoneNumbers[0]; + payload.phoneNumber_verified = user.loginMethods.some( + (lm) => lm.hasSamePhoneNumberAs(user?.phoneNumbers[0]) && lm.verified + ); + } + + for (const fn of this.userInfoBuilders) { + payload = { + ...payload, + ...(await fn(user, accessTokenPayload, scopes, tenantId, userContext)), + }; + } + + return payload as UserInfo; + } } diff --git a/lib/ts/recipe/oauth2/recipeImplementation.ts b/lib/ts/recipe/oauth2/recipeImplementation.ts index 0f5e62274..db697e68a 100644 --- a/lib/ts/recipe/oauth2/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2/recipeImplementation.ts @@ -23,6 +23,7 @@ import { LoginRequest, LogoutRequest, PayloadBuilderFunction, + UserInfoBuilderFunction, } from "./types"; import { toSnakeCase, transformObjectKeys } from "../../utils"; import { OAuth2Client } from "./OAuth2Client"; @@ -32,7 +33,8 @@ export default function getRecipeInterface( querier: Querier, _config: TypeNormalisedInput, _appInfo: NormalisedAppinfo, - getDefaultIdTokenPayload: PayloadBuilderFunction + getDefaultIdTokenPayload: PayloadBuilderFunction, + getDefaultUserInfoPayload: UserInfoBuilderFunction ): RecipeInterface { return { getLoginRequest: async function (this: RecipeInterface, input): Promise { @@ -423,8 +425,8 @@ export default function getRecipeInterface( buildIdTokenPayload: async function (input) { return input.defaultPayload; }, - buildUserInfo: async function (input) { - return input.user.toJson(); // Proper impl + buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) { + return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext); }, }; } diff --git a/lib/ts/recipe/oauth2/types.ts b/lib/ts/recipe/oauth2/types.ts index f2a79b025..cf113b5d2 100644 --- a/lib/ts/recipe/oauth2/types.ts +++ b/lib/ts/recipe/oauth2/types.ts @@ -15,7 +15,7 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import OverrideableBuilder from "supertokens-js-override"; -import { GeneralErrorResponse, JSONObject, NonNullableProperties, UserContext } from "../../types"; +import { GeneralErrorResponse, JSONObject, JSONValue, NonNullableProperties, UserContext } from "../../types"; import { SessionContainerInterface } from "../session/types"; import { OAuth2Client } from "./OAuth2Client"; import { User } from "../../user"; @@ -188,6 +188,15 @@ export type LoginInfo = { metadata?: Record | null; }; +export type UserInfo = { + sub: string; + email?: string; + email_verified?: boolean; + phoneNumber?: string; + phoneNumber_verified?: boolean; + [key: string]: JSONValue; +}; + export type RecipeInterface = { authorization(input: { params: any; @@ -353,7 +362,7 @@ export type RecipeInterface = { user: User; accessTokenPayload: JSONObject; scopes: string[]; - defaultInfo: JSONObject; + tenantId: string; userContext: UserContext; }): Promise; }; @@ -437,6 +446,16 @@ export type APIInterface = { options: APIOptions; userContext: UserContext; }) => Promise<{ status: "OK"; info: LoginInfo } | GeneralErrorResponse>); + userInfoGET: + | undefined + | ((input: { + accessTokenPayload: JSONObject; + user: User; + scopes: string[]; + tenantId: string; + options: APIOptions; + userContext: UserContext; + }) => Promise<{ status: "OK"; info: JSONObject } | GeneralErrorResponse>); }; export type OAuth2ClientOptions = { @@ -547,3 +566,10 @@ export type DeleteOAuth2ClientInput = { }; export type PayloadBuilderFunction = (user: User, scopes: string[], userContext: UserContext) => Promise; +export type UserInfoBuilderFunction = ( + user: User, + accessTokenPayload: JSONObject, + scopes: string[], + tenantId: string, + userContext: UserContext +) => Promise; diff --git a/lib/ts/recipe/oauth2client/index.ts b/lib/ts/recipe/oauth2client/index.ts index 0ae790b4d..0ae16a19d 100644 --- a/lib/ts/recipe/oauth2client/index.ts +++ b/lib/ts/recipe/oauth2client/index.ts @@ -25,13 +25,14 @@ export default class Wrapper { userContext?: Record ) { const recipeInterfaceImpl = Recipe.getInstanceOrThrowError().recipeInterfaceImpl; + const normalisedUserContext = getUserContext(userContext); const providerConfig = await recipeInterfaceImpl.getProviderConfig({ - userContext: getUserContext(userContext), + userContext: normalisedUserContext, }); return await recipeInterfaceImpl.getAuthorisationRedirectURL({ providerConfig, redirectURIOnProviderDashboard, - userContext: getUserContext(userContext), + userContext: normalisedUserContext, }); } @@ -44,25 +45,27 @@ export default class Wrapper { userContext?: Record ) { const recipeInterfaceImpl = Recipe.getInstanceOrThrowError().recipeInterfaceImpl; + const normalisedUserContext = getUserContext(userContext); const providerConfig = await recipeInterfaceImpl.getProviderConfig({ - userContext: getUserContext(userContext), + userContext: normalisedUserContext, }); return await recipeInterfaceImpl.exchangeAuthCodeForOAuthTokens({ providerConfig, redirectURIInfo, - userContext: getUserContext(userContext), + userContext: normalisedUserContext, }); } static async getUserInfo(oAuthTokens: OAuthTokens, userContext?: Record) { const recipeInterfaceImpl = Recipe.getInstanceOrThrowError().recipeInterfaceImpl; + const normalisedUserContext = getUserContext(userContext); const providerConfig = await recipeInterfaceImpl.getProviderConfig({ - userContext: getUserContext(userContext), + userContext: normalisedUserContext, }); return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getUserInfo({ providerConfig, oAuthTokens, - userContext: getUserContext(userContext), + userContext: normalisedUserContext, }); } } diff --git a/lib/ts/recipe/oauth2client/recipeImplementation.ts b/lib/ts/recipe/oauth2client/recipeImplementation.ts index ef374b8f6..68748247b 100644 --- a/lib/ts/recipe/oauth2client/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2client/recipeImplementation.ts @@ -100,10 +100,9 @@ export default function getRecipeImplementation(_querier: Querier, config: TypeN if (oidcInfo.token_endpoint === undefined) { throw new Error("Failed to token_endpoint from the oidcDiscoveryEndpoint."); } - // TODO: We currently don't have this - // if (oidcInfo.userinfo_endpoint === undefined) { - // throw new Error("Failed to userinfo_endpoint from the oidcDiscoveryEndpoint."); - // } + if (oidcInfo.userinfo_endpoint === undefined) { + throw new Error("Failed to userinfo_endpoint from the oidcDiscoveryEndpoint."); + } if (oidcInfo.jwks_uri === undefined) { throw new Error("Failed to jwks_uri from the oidcDiscoveryEndpoint."); } diff --git a/lib/ts/recipe/openid/recipeImplementation.ts b/lib/ts/recipe/openid/recipeImplementation.ts index 2ed40f6f5..bbb633768 100644 --- a/lib/ts/recipe/openid/recipeImplementation.ts +++ b/lib/ts/recipe/openid/recipeImplementation.ts @@ -17,7 +17,7 @@ import { RecipeInterface as JWTRecipeInterface, JsonWebKey } from "../jwt/types" import NormalisedURLPath from "../../normalisedURLPath"; import { GET_JWKS_API } from "../jwt/constants"; import { NormalisedAppinfo, UserContext } from "../../types"; -import { AUTH_PATH, TOKEN_PATH } from "../oauth2/constants"; +import { AUTH_PATH, TOKEN_PATH, USER_INFO_PATH } from "../oauth2/constants"; export default function getRecipeInterface( config: TypeNormalisedInput, @@ -38,6 +38,7 @@ export default function getRecipeInterface( jwks_uri, authorization_endpoint: apiBasePath + AUTH_PATH, token_endpoint: apiBasePath + TOKEN_PATH, + userinfo_endpoint: apiBasePath + USER_INFO_PATH, subject_types_supported: ["public"], id_token_signing_alg_values_supported: ["RS256"], response_types_supported: ["code", "id_token", "id_token token"], diff --git a/lib/ts/recipe/userroles/recipe.ts b/lib/ts/recipe/userroles/recipe.ts index 0f0176c7b..9a2c4ecff 100644 --- a/lib/ts/recipe/userroles/recipe.ts +++ b/lib/ts/recipe/userroles/recipe.ts @@ -27,6 +27,7 @@ import { validateAndNormaliseUserInput } from "./utils"; import OverrideableBuilder from "supertokens-js-override"; import { PostSuperTokensInitCallbacks } from "../../postSuperTokensInitCallbacks"; import SessionRecipe from "../session/recipe"; +import OAuth2Recipe from "../oauth2/recipe"; import { UserRoleClaim } from "./userRoleClaim"; import { PermissionClaim } from "./permissionClaim"; @@ -55,6 +56,56 @@ export default class Recipe extends RecipeModule { if (!this.config.skipAddingPermissionsToAccessToken) { SessionRecipe.getInstanceOrThrowError().addClaimFromOtherRecipe(PermissionClaim); } + + OAuth2Recipe.getInstanceOrThrowError().addUserInfoBuilderFromOtherRecipe( + async (user, _accessTokenPayload, scopes, tenantId, userContext) => { + let userInfo: { + roles?: string[]; + permissons?: string[]; + } = {}; + + let userRoles: string[] = []; + + if (scopes.includes("roles") || scopes.includes("permissions")) { + const res = await this.recipeInterfaceImpl.getRolesForUser({ + userId: user.id, + tenantId, + userContext, + }); + + if (res.status !== "OK") { + throw new Error("Failed to fetch roles for the user"); + } + userRoles = res.roles; + } + + if (scopes.includes("roles")) { + userInfo.roles = userRoles; + } + + if (scopes.includes("permissions")) { + const userPermissions = new Set(); + for (const role of userRoles) { + const rolePermissions = await this.recipeInterfaceImpl.getPermissionsForRole({ + role, + userContext, + }); + + if (rolePermissions.status !== "OK") { + throw new Error("Failed to fetch permissions for the role"); + } + + for (const perm of rolePermissions.permissions) { + userPermissions.add(perm); + } + } + + userInfo.permissons = Array.from(userPermissions); + } + + return userInfo; + } + ); }); } From 4ab2410e1c5a94c8fe32e0974d90a380317b17bd Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Sun, 28 Jul 2024 02:22:23 +0200 Subject: [PATCH 12/35] feat: add functions to validate oauth2 tokens --- lib/build/combinedRemoteJWKSet.d.ts | 19 +++++ lib/build/combinedRemoteJWKSet.js | 55 +++++++++++++ lib/build/querier.js | 11 ++- lib/build/recipe/oauth2/recipe.js | 2 + .../recipe/oauth2/recipeImplementation.d.ts | 2 +- .../recipe/oauth2/recipeImplementation.js | 82 +++++++++++++++++-- lib/build/recipe/oauth2/types.d.ts | 16 ++++ lib/build/recipe/session/recipe.js | 2 + .../recipe/session/recipeImplementation.d.ts | 2 - .../recipe/session/recipeImplementation.js | 32 -------- lib/build/recipe/session/sessionFunctions.js | 3 +- lib/build/supertokens.js | 8 ++ lib/ts/combinedRemoteJWKSet.ts | 54 ++++++++++++ lib/ts/querier.ts | 11 ++- lib/ts/recipe/oauth2/recipe.ts | 2 + lib/ts/recipe/oauth2/recipeImplementation.ts | 55 +++++++++++-- lib/ts/recipe/oauth2/types.ts | 11 +++ lib/ts/recipe/session/recipe.ts | 2 + lib/ts/recipe/session/recipeImplementation.ts | 39 +-------- lib/ts/recipe/session/sessionFunctions.ts | 3 +- lib/ts/supertokens.ts | 8 ++ test/oauth2/config.test.js | 4 +- 22 files changed, 326 insertions(+), 97 deletions(-) create mode 100644 lib/build/combinedRemoteJWKSet.d.ts create mode 100644 lib/build/combinedRemoteJWKSet.js create mode 100644 lib/ts/combinedRemoteJWKSet.ts diff --git a/lib/build/combinedRemoteJWKSet.d.ts b/lib/build/combinedRemoteJWKSet.d.ts new file mode 100644 index 000000000..1ff4b01dd --- /dev/null +++ b/lib/build/combinedRemoteJWKSet.d.ts @@ -0,0 +1,19 @@ +// @ts-nocheck +/** + * We need this to reset the combinedJWKS in tests because we need to create a new instance of the combinedJWKS + * for each test to avoid caching issues. + * This is called when the session recipe is reset and when the oauth2 recipe is reset. + * Calling this multiple times doesn't cause an issue. + */ +export declare function resetCombinedJWKS(): void; +/** + The function returned by this getter fetches all JWKs from the first available core instance. + This combines the other JWKS functions to become error resistant. + + Every core instance a backend is connected to is expected to connect to the same database and use the same key set for + token verification. Otherwise, the result of session verification would depend on which core is currently available. +*/ +export declare function getCombinedJWKS(): ( + protectedHeader?: import("jose").JWSHeaderParameters | undefined, + token?: import("jose").FlattenedJWSInput | undefined +) => Promise; diff --git a/lib/build/combinedRemoteJWKSet.js b/lib/build/combinedRemoteJWKSet.js new file mode 100644 index 000000000..5d8eb65ea --- /dev/null +++ b/lib/build/combinedRemoteJWKSet.js @@ -0,0 +1,55 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getCombinedJWKS = exports.resetCombinedJWKS = void 0; +const jose_1 = require("jose"); +const constants_1 = require("./recipe/session/constants"); +const querier_1 = require("./querier"); +let combinedJWKS; +/** + * We need this to reset the combinedJWKS in tests because we need to create a new instance of the combinedJWKS + * for each test to avoid caching issues. + * This is called when the session recipe is reset and when the oauth2 recipe is reset. + * Calling this multiple times doesn't cause an issue. + */ +function resetCombinedJWKS() { + combinedJWKS = undefined; +} +exports.resetCombinedJWKS = resetCombinedJWKS; +/** + The function returned by this getter fetches all JWKs from the first available core instance. + This combines the other JWKS functions to become error resistant. + + Every core instance a backend is connected to is expected to connect to the same database and use the same key set for + token verification. Otherwise, the result of session verification would depend on which core is currently available. +*/ +function getCombinedJWKS() { + if (combinedJWKS === undefined) { + const JWKS = querier_1.Querier.getNewInstanceOrThrowError(undefined) + .getAllCoreUrlsForPath("/.well-known/jwks.json") + .map((url) => + jose_1.createRemoteJWKSet(new URL(url), { + cooldownDuration: constants_1.JWKCacheCooldownInMs, + cacheMaxAge: constants_1.JWKCacheMaxAgeInMs, + }) + ); + combinedJWKS = async (...args) => { + let lastError = undefined; + if (JWKS.length === 0) { + throw Error( + "No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using." + ); + } + for (const jwks of JWKS) { + try { + // We await before returning to make sure we catch the error + return await jwks(...args); + } catch (ex) { + lastError = ex; + } + } + throw lastError; + }; + } + return combinedJWKS; +} +exports.getCombinedJWKS = getCombinedJWKS; diff --git a/lib/build/querier.js b/lib/build/querier.js index dc289267f..381491527 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -85,7 +85,10 @@ class Querier { var _a; this.invalidateCoreCallCache(userContext); // TODO: remove FormData - const isForm = body !== undefined && body instanceof FormData; + const isForm = body !== undefined && body["$isFormData"]; + if (isForm) { + delete body["$isFormData"]; + } const { body: respBody } = await this.sendRequestHelper( path, "POST", @@ -121,7 +124,11 @@ class Querier { } return utils_1.doFetch(url, { method: "POST", - body: isForm ? body : body !== undefined ? JSON.stringify(body) : undefined, + body: isForm + ? new URLSearchParams(Object.entries(body)).toString() + : body !== undefined + ? JSON.stringify(body) + : undefined, headers, }); }, diff --git a/lib/build/recipe/oauth2/recipe.js b/lib/build/recipe/oauth2/recipe.js index 048abe7dd..aefa95649 100644 --- a/lib/build/recipe/oauth2/recipe.js +++ b/lib/build/recipe/oauth2/recipe.js @@ -35,6 +35,7 @@ const recipeImplementation_1 = __importDefault(require("./recipeImplementation") const utils_1 = require("./utils"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const userInfo_1 = __importDefault(require("./api/userInfo")); +const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config) { super(recipeId, appInfo); @@ -118,6 +119,7 @@ class Recipe extends recipeModule_1.default { if (process.env.TEST_MODE !== "testing") { throw new Error("calling testing function in non testing env"); } + combinedRemoteJWKSet_1.resetCombinedJWKS(); Recipe.instance = undefined; } /* RecipeModule functions */ diff --git a/lib/build/recipe/oauth2/recipeImplementation.d.ts b/lib/build/recipe/oauth2/recipeImplementation.d.ts index 273bef941..3d7982106 100644 --- a/lib/build/recipe/oauth2/recipeImplementation.d.ts +++ b/lib/build/recipe/oauth2/recipeImplementation.d.ts @@ -5,7 +5,7 @@ import { RecipeInterface, TypeNormalisedInput, PayloadBuilderFunction, UserInfoB export default function getRecipeInterface( querier: Querier, _config: TypeNormalisedInput, - _appInfo: NormalisedAppinfo, + appInfo: NormalisedAppinfo, getDefaultIdTokenPayload: PayloadBuilderFunction, getDefaultUserInfoPayload: UserInfoBuilderFunction ): RecipeInterface; diff --git a/lib/build/recipe/oauth2/recipeImplementation.js b/lib/build/recipe/oauth2/recipeImplementation.js index a2158ccb9..4d7f76a64 100644 --- a/lib/build/recipe/oauth2/recipeImplementation.js +++ b/lib/build/recipe/oauth2/recipeImplementation.js @@ -13,18 +13,56 @@ * License for the specific language governing permissions and limitations * under the License. */ +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; var __importDefault = (this && this.__importDefault) || function (mod) { return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); +const jose = __importStar(require("jose")); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const querier_1 = require("../../querier"); const utils_1 = require("../../utils"); const OAuth2Client_1 = require("./OAuth2Client"); const __1 = require("../.."); -function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload, getDefaultUserInfoPayload) { +const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); +function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, getDefaultUserInfoPayload) { return { getLoginRequest: async function (input) { const resp = await querier.sendGetRequest( @@ -67,7 +105,7 @@ function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload // TODO: FIXME!!! redirectTo: resp.data.redirect_to.replace( querier_1.hydraPubDomain, - _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ), }; }, @@ -90,7 +128,7 @@ function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload // TODO: FIXME!!! redirectTo: resp.data.redirect_to.replace( querier_1.hydraPubDomain, - _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ), }; }, @@ -137,7 +175,7 @@ function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload // TODO: FIXME!!! redirectTo: resp.data.redirect_to.replace( querier_1.hydraPubDomain, - _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ), }; }, @@ -160,7 +198,7 @@ function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload // TODO: FIXME!!! redirectTo: resp.data.redirect_to.replace( querier_1.hydraPubDomain, - _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ), }; }, @@ -192,7 +230,7 @@ function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload // TODO: FIXME!!! redirectTo: resp.data.redirect_to.replace( querier_1.hydraPubDomain, - _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ), }; }, @@ -276,9 +314,9 @@ function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload }; }, token: async function (input) { - const body = new FormData(); // TODO: we ideally want to avoid using formdata, the core can do the translation + const body = { $isFormData: true }; // TODO: we ideally want to avoid using formdata, the core can do the translation for (const key in input.body) { - body.append(key, input.body[key]); + body[key] = input.body[key]; } const res = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth2/pub/token`), @@ -397,6 +435,34 @@ function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) { return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext); }, + validateOAuth2AccessToken: async function (input) { + const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS())).payload; + // TODO: make this configurable? + const expectedIssuer = + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); + if (payload.iss !== expectedIssuer) { + throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); + } + if (input.expectedAudience !== undefined && payload.aud !== input.expectedAudience) { + throw new Error("Audience mismatch: this token doesn't belong to the specified client"); + } + // TODO: add a check to make sure this is the right token type as they can be signed with the same key + return { status: "OK", payload: payload }; + }, + validateOAuth2IdToken: async function (input) { + const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS())).payload; + // TODO: make this configurable? + const expectedIssuer = + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); + if (input.expectedAudience !== undefined && payload.iss !== expectedIssuer) { + throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); + } + if (input.expectedAudience !== undefined && payload.aud !== input.expectedAudience) { + throw new Error("Audience mismatch: this token doesn't belong to the specified client"); + } + // TODO: add a check to make sure this is the right token type as they can be signed with the same key + return { status: "OK", payload: payload }; + }, }; } exports.default = getRecipeInterface; diff --git a/lib/build/recipe/oauth2/types.d.ts b/lib/build/recipe/oauth2/types.d.ts index d8db28595..cfde910ac 100644 --- a/lib/build/recipe/oauth2/types.d.ts +++ b/lib/build/recipe/oauth2/types.d.ts @@ -214,6 +214,22 @@ export declare type RecipeInterface = { errorHint: string; } >; + validateOAuth2AccessToken(input: { + token: string; + expectedAudience?: string; + userContext: UserContext; + }): Promise<{ + status: "OK"; + payload: JSONObject; + }>; + validateOAuth2IdToken(input: { + token: string; + expectedAudience?: string; + userContext: UserContext; + }): Promise<{ + status: "OK"; + payload: JSONObject; + }>; buildAccessTokenPayload(input: { user: User; session: SessionContainerInterface; diff --git a/lib/build/recipe/session/recipe.js b/lib/build/recipe/session/recipe.js index 77afe511a..b8436a829 100644 --- a/lib/build/recipe/session/recipe.js +++ b/lib/build/recipe/session/recipe.js @@ -33,6 +33,7 @@ const implementation_1 = __importDefault(require("./api/implementation")); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const recipe_1 = __importDefault(require("../openid/recipe")); const logger_1 = require("../../logger"); +const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); // For Express class SessionRecipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config) { @@ -238,6 +239,7 @@ class SessionRecipe extends recipeModule_1.default { throw new Error("calling testing function in non testing env"); } SessionRecipe.instance = undefined; + combinedRemoteJWKSet_1.resetCombinedJWKS(); } } exports.default = SessionRecipe; diff --git a/lib/build/recipe/session/recipeImplementation.d.ts b/lib/build/recipe/session/recipeImplementation.d.ts index 9c1969e84..df95edba8 100644 --- a/lib/build/recipe/session/recipeImplementation.d.ts +++ b/lib/build/recipe/session/recipeImplementation.d.ts @@ -1,11 +1,9 @@ // @ts-nocheck -import { JWTVerifyGetKey } from "jose"; import { RecipeInterface, TypeNormalisedInput } from "./types"; import { Querier } from "../../querier"; import { NormalisedAppinfo } from "../../types"; export declare type Helpers = { querier: Querier; - JWKS: JWTVerifyGetKey; config: TypeNormalisedInput; appInfo: NormalisedAppinfo; getRecipeImpl: () => RecipeInterface; diff --git a/lib/build/recipe/session/recipeImplementation.js b/lib/build/recipe/session/recipeImplementation.js index 26c021ae3..c2aa876da 100644 --- a/lib/build/recipe/session/recipeImplementation.js +++ b/lib/build/recipe/session/recipeImplementation.js @@ -41,7 +41,6 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -const jose_1 = require("jose"); const SessionFunctions = __importStar(require("./sessionFunctions")); const cookieAndHeaders_1 = require("./cookieAndHeaders"); const utils_1 = require("./utils"); @@ -55,36 +54,6 @@ const recipeUserId_1 = __importDefault(require("../../recipeUserId")); const constants_1 = require("../multitenancy/constants"); const constants_2 = require("./constants"); function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverrides) { - const JWKS = querier.getAllCoreUrlsForPath("/.well-known/jwks.json").map((url) => - jose_1.createRemoteJWKSet(new URL(url), { - cooldownDuration: constants_2.JWKCacheCooldownInMs, - cacheMaxAge: constants_2.JWKCacheMaxAgeInMs, - }) - ); - /** - This function fetches all JWKs from the first available core instance. This combines the other JWKS functions to become - error resistant. - - Every core instance a backend is connected to is expected to connect to the same database and use the same key set for - token verification. Otherwise, the result of session verification would depend on which core is currently available. - */ - const combinedJWKS = async (...args) => { - let lastError = undefined; - if (JWKS.length === 0) { - throw Error( - "No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using." - ); - } - for (const jwks of JWKS) { - try { - // We await before returning to make sure we catch the error - return await jwks(...args); - } catch (ex) { - lastError = ex; - } - } - throw lastError; - }; let obj = { createNewSession: async function ({ recipeUserId, @@ -436,7 +405,6 @@ function getRecipeInterface(querier, config, appInfo, getRecipeImplAfterOverride }; let helpers = { querier, - JWKS: combinedJWKS, config, appInfo, getRecipeImpl: getRecipeImplAfterOverrides, diff --git a/lib/build/recipe/session/sessionFunctions.js b/lib/build/recipe/session/sessionFunctions.js index 90c828ce8..fa570ea65 100644 --- a/lib/build/recipe/session/sessionFunctions.js +++ b/lib/build/recipe/session/sessionFunctions.js @@ -29,6 +29,7 @@ const logger_1 = require("../../logger"); const recipeUserId_1 = __importDefault(require("../../recipeUserId")); const constants_1 = require("../multitenancy/constants"); const constants_2 = require("./constants"); +const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); /** * @description call this to "login" a user. */ @@ -91,7 +92,7 @@ async function getSession(helpers, parsedAccessToken, antiCsrfToken, doAntiCsrfC */ accessTokenInfo = await accessToken_1.getInfoFromAccessToken( parsedAccessToken, - helpers.JWKS, + combinedRemoteJWKSet_1.getCombinedJWKS(), helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN" && doAntiCsrfCheck ); } catch (err) { diff --git a/lib/build/supertokens.js b/lib/build/supertokens.js index 62e146628..5075736d1 100644 --- a/lib/build/supertokens.js +++ b/lib/build/supertokens.js @@ -376,6 +376,7 @@ class SuperTokens { let totpFound = false; let userMetadataFound = false; let multiFactorAuthFound = false; + let oauth2Found = false; // Multitenancy recipe is an always initialized recipe and needs to be imported this way // so that there is no circular dependency. Otherwise there would be cyclic dependency // between `supertokens.ts` -> `recipeModule.ts` -> `multitenancy/recipe.ts` @@ -383,6 +384,7 @@ class SuperTokens { let UserMetadataRecipe = require("./recipe/usermetadata/recipe").default; let MultiFactorAuthRecipe = require("./recipe/multifactorauth/recipe").default; let TotpRecipe = require("./recipe/totp/recipe").default; + let OAuth2ProviderRecipe = require("./recipe/oauth2/recipe").default; this.recipeModules = config.recipeList.map((func) => { const recipeModule = func(this.appInfo, this.isInServerlessEnv); if (recipeModule.getRecipeId() === MultitenancyRecipe.RECIPE_ID) { @@ -393,6 +395,8 @@ class SuperTokens { multiFactorAuthFound = true; } else if (recipeModule.getRecipeId() === TotpRecipe.RECIPE_ID) { totpFound = true; + } else if (recipeModule.getRecipeId() === OAuth2ProviderRecipe.RECIPE_ID) { + oauth2Found = true; } return recipeModule; }); @@ -411,6 +415,10 @@ class SuperTokens { // the app doesn't have to do that if they only use TOTP (which shouldn't be that uncommon) // To let those cases function without initializing account linking we do not check it here, but when // the authentication endpoints are called. + // We've decided to always initialize the OAuth2Provider recipe + if (!oauth2Found) { + this.recipeModules.push(OAuth2ProviderRecipe.init()(this.appInfo, this.isInServerlessEnv)); + } this.telemetryEnabled = config.telemetry === undefined ? process.env.TEST_MODE !== "testing" : config.telemetry; } static init(config) { diff --git a/lib/ts/combinedRemoteJWKSet.ts b/lib/ts/combinedRemoteJWKSet.ts new file mode 100644 index 000000000..5d85df882 --- /dev/null +++ b/lib/ts/combinedRemoteJWKSet.ts @@ -0,0 +1,54 @@ +import { createRemoteJWKSet } from "jose"; +import { JWKCacheCooldownInMs, JWKCacheMaxAgeInMs } from "./recipe/session/constants"; +import { Querier } from "./querier"; + +let combinedJWKS: ReturnType | undefined; + +/** + * We need this to reset the combinedJWKS in tests because we need to create a new instance of the combinedJWKS + * for each test to avoid caching issues. + * This is called when the session recipe is reset and when the oauth2 recipe is reset. + * Calling this multiple times doesn't cause an issue. + */ +export function resetCombinedJWKS() { + combinedJWKS = undefined; +} + +/** + The function returned by this getter fetches all JWKs from the first available core instance. + This combines the other JWKS functions to become error resistant. + + Every core instance a backend is connected to is expected to connect to the same database and use the same key set for + token verification. Otherwise, the result of session verification would depend on which core is currently available. +*/ +export function getCombinedJWKS() { + if (combinedJWKS === undefined) { + const JWKS: ReturnType[] = Querier.getNewInstanceOrThrowError(undefined) + .getAllCoreUrlsForPath("/.well-known/jwks.json") + .map((url) => + createRemoteJWKSet(new URL(url), { + cooldownDuration: JWKCacheCooldownInMs, + cacheMaxAge: JWKCacheMaxAgeInMs, + }) + ); + + combinedJWKS = async (...args) => { + let lastError = undefined; + if (JWKS.length === 0) { + throw Error( + "No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using." + ); + } + for (const jwks of JWKS) { + try { + // We await before returning to make sure we catch the error + return await jwks(...args); + } catch (ex) { + lastError = ex; + } + } + throw lastError; + }; + } + return combinedJWKS; +} diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index 828a4cd6b..7832e443a 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -131,7 +131,10 @@ export class Querier { sendPostRequest = async (path: NormalisedURLPath, body: any, userContext: UserContext): Promise => { this.invalidateCoreCallCache(userContext); // TODO: remove FormData - const isForm = body !== undefined && body instanceof FormData; + const isForm = body !== undefined && body["$isFormData"]; + if (isForm) { + delete body["$isFormData"]; + } const { body: respBody } = await this.sendRequestHelper( path, @@ -174,7 +177,11 @@ export class Querier { } return doFetch(url, { method: "POST", - body: isForm ? body : body !== undefined ? JSON.stringify(body) : undefined, + body: isForm + ? new URLSearchParams(Object.entries(body)).toString() + : body !== undefined + ? JSON.stringify(body) + : undefined, headers, }); }, diff --git a/lib/ts/recipe/oauth2/recipe.ts b/lib/ts/recipe/oauth2/recipe.ts index bb0948421..8d8feaae3 100644 --- a/lib/ts/recipe/oauth2/recipe.ts +++ b/lib/ts/recipe/oauth2/recipe.ts @@ -50,6 +50,7 @@ import { validateAndNormaliseUserInput } from "./utils"; import OverrideableBuilder from "supertokens-js-override"; import { User } from "../../user"; import userInfoGET from "./api/userInfo"; +import { resetCombinedJWKS } from "../../combinedRemoteJWKSet"; export default class Recipe extends RecipeModule { static RECIPE_ID = "oauth2"; @@ -112,6 +113,7 @@ export default class Recipe extends RecipeModule { if (process.env.TEST_MODE !== "testing") { throw new Error("calling testing function in non testing env"); } + resetCombinedJWKS(); Recipe.instance = undefined; } diff --git a/lib/ts/recipe/oauth2/recipeImplementation.ts b/lib/ts/recipe/oauth2/recipeImplementation.ts index db697e68a..24c41fcb3 100644 --- a/lib/ts/recipe/oauth2/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2/recipeImplementation.ts @@ -13,6 +13,7 @@ * under the License. */ +import * as jose from "jose"; import NormalisedURLPath from "../../normalisedURLPath"; import { Querier, hydraPubDomain } from "../../querier"; import { NormalisedAppinfo } from "../../types"; @@ -28,11 +29,13 @@ import { import { toSnakeCase, transformObjectKeys } from "../../utils"; import { OAuth2Client } from "./OAuth2Client"; import { getUser } from "../.."; +import { getCombinedJWKS } from "../../combinedRemoteJWKSet"; +import { JSONObject } from "@loopback/core"; export default function getRecipeInterface( querier: Querier, _config: TypeNormalisedInput, - _appInfo: NormalisedAppinfo, + appInfo: NormalisedAppinfo, getDefaultIdTokenPayload: PayloadBuilderFunction, getDefaultUserInfoPayload: UserInfoBuilderFunction ): RecipeInterface { @@ -80,7 +83,7 @@ export default function getRecipeInterface( // TODO: FIXME!!! redirectTo: resp.data.redirect_to.replace( hydraPubDomain, - _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ), }; }, @@ -104,7 +107,7 @@ export default function getRecipeInterface( // TODO: FIXME!!! redirectTo: resp.data.redirect_to.replace( hydraPubDomain, - _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ), }; }, @@ -153,7 +156,7 @@ export default function getRecipeInterface( // TODO: FIXME!!! redirectTo: resp.data.redirect_to.replace( hydraPubDomain, - _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ), }; }, @@ -178,7 +181,7 @@ export default function getRecipeInterface( // TODO: FIXME!!! redirectTo: resp.data.redirect_to.replace( hydraPubDomain, - _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ), }; }, @@ -213,7 +216,7 @@ export default function getRecipeInterface( // TODO: FIXME!!! redirectTo: resp.data.redirect_to.replace( hydraPubDomain, - _appInfo.apiDomain.getAsStringDangerous() + _appInfo.apiBasePath.getAsStringDangerous() + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ), }; }, @@ -295,9 +298,9 @@ export default function getRecipeInterface( }, token: async function (this: RecipeInterface, input) { - const body = new FormData(); // TODO: we ideally want to avoid using formdata, the core can do the translation + const body: any = { $isFormData: true }; // TODO: we ideally want to avoid using formdata, the core can do the translation for (const key in input.body) { - body.append(key, input.body[key]); + body[key] = input.body[key]; } const res = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth2/pub/token`), @@ -428,5 +431,41 @@ export default function getRecipeInterface( buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) { return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext); }, + validateOAuth2AccessToken: async function (input) { + const payload = (await jose.jwtVerify(input.token, getCombinedJWKS())).payload; + + // TODO: make this configurable? + const expectedIssuer = + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); + if (payload.iss !== expectedIssuer) { + throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); + } + + if (input.expectedAudience !== undefined && payload.aud !== input.expectedAudience) { + throw new Error("Audience mismatch: this token doesn't belong to the specified client"); + } + + // TODO: add a check to make sure this is the right token type as they can be signed with the same key + + return { status: "OK", payload: payload as JSONObject }; + }, + validateOAuth2IdToken: async function (input) { + const payload = (await jose.jwtVerify(input.token, getCombinedJWKS())).payload; + + // TODO: make this configurable? + const expectedIssuer = + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); + if (input.expectedAudience !== undefined && payload.iss !== expectedIssuer) { + throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); + } + + if (input.expectedAudience !== undefined && payload.aud !== input.expectedAudience) { + throw new Error("Audience mismatch: this token doesn't belong to the specified client"); + } + + // TODO: add a check to make sure this is the right token type as they can be signed with the same key + + return { status: "OK", payload: payload as JSONObject }; + }, }; } diff --git a/lib/ts/recipe/oauth2/types.ts b/lib/ts/recipe/oauth2/types.ts index cf113b5d2..19adc8771 100644 --- a/lib/ts/recipe/oauth2/types.ts +++ b/lib/ts/recipe/oauth2/types.ts @@ -344,6 +344,17 @@ export type RecipeInterface = { } >; + validateOAuth2AccessToken(input: { + token: string; + expectedAudience?: string; + userContext: UserContext; + }): Promise<{ status: "OK"; payload: JSONObject }>; + validateOAuth2IdToken(input: { + token: string; + expectedAudience?: string; + userContext: UserContext; + }): Promise<{ status: "OK"; payload: JSONObject }>; + buildAccessTokenPayload(input: { user: User; session: SessionContainerInterface; diff --git a/lib/ts/recipe/session/recipe.ts b/lib/ts/recipe/session/recipe.ts index d73ad8ccf..a1762f065 100644 --- a/lib/ts/recipe/session/recipe.ts +++ b/lib/ts/recipe/session/recipe.ts @@ -42,6 +42,7 @@ import OverrideableBuilder from "supertokens-js-override"; import { APIOptions } from "."; import OpenIdRecipe from "../openid/recipe"; import { logDebugMessage } from "../../logger"; +import { resetCombinedJWKS } from "../../combinedRemoteJWKSet"; // For Express export default class SessionRecipe extends RecipeModule { @@ -125,6 +126,7 @@ export default class SessionRecipe extends RecipeModule { throw new Error("calling testing function in non testing env"); } SessionRecipe.instance = undefined; + resetCombinedJWKS(); } addClaimFromOtherRecipe = (claim: SessionClaim) => { diff --git a/lib/ts/recipe/session/recipeImplementation.ts b/lib/ts/recipe/session/recipeImplementation.ts index 5273c45d1..3c4698580 100644 --- a/lib/ts/recipe/session/recipeImplementation.ts +++ b/lib/ts/recipe/session/recipeImplementation.ts @@ -1,4 +1,3 @@ -import { createRemoteJWKSet, JWTVerifyGetKey } from "jose"; import { RecipeInterface, VerifySessionOptions, @@ -22,11 +21,10 @@ import { validateAccessTokenStructure } from "./accessToken"; import SessionError from "./error"; import RecipeUserId from "../../recipeUserId"; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; -import { JWKCacheCooldownInMs, JWKCacheMaxAgeInMs, protectedProps } from "./constants"; +import { protectedProps } from "./constants"; export type Helpers = { querier: Querier; - JWKS: JWTVerifyGetKey; config: TypeNormalisedInput; appInfo: NormalisedAppinfo; getRecipeImpl: () => RecipeInterface; @@ -38,40 +36,6 @@ export default function getRecipeInterface( appInfo: NormalisedAppinfo, getRecipeImplAfterOverrides: () => RecipeInterface ): RecipeInterface { - const JWKS: ReturnType[] = querier - .getAllCoreUrlsForPath("/.well-known/jwks.json") - .map((url) => - createRemoteJWKSet(new URL(url), { - cooldownDuration: JWKCacheCooldownInMs, - cacheMaxAge: JWKCacheMaxAgeInMs, - }) - ); - - /** - This function fetches all JWKs from the first available core instance. This combines the other JWKS functions to become - error resistant. - - Every core instance a backend is connected to is expected to connect to the same database and use the same key set for - token verification. Otherwise, the result of session verification would depend on which core is currently available. - */ - const combinedJWKS: ReturnType = async (...args) => { - let lastError = undefined; - if (JWKS.length === 0) { - throw Error( - "No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using." - ); - } - for (const jwks of JWKS) { - try { - // We await before returning to make sure we catch the error - return await jwks(...args); - } catch (ex) { - lastError = ex; - } - } - throw lastError; - }; - let obj: RecipeInterface = { createNewSession: async function ({ recipeUserId, @@ -600,7 +564,6 @@ export default function getRecipeInterface( let helpers: Helpers = { querier, - JWKS: combinedJWKS, config, appInfo, getRecipeImpl: getRecipeImplAfterOverrides, diff --git a/lib/ts/recipe/session/sessionFunctions.ts b/lib/ts/recipe/session/sessionFunctions.ts index 30d1726e9..aa8e687ab 100644 --- a/lib/ts/recipe/session/sessionFunctions.ts +++ b/lib/ts/recipe/session/sessionFunctions.ts @@ -25,6 +25,7 @@ import RecipeUserId from "../../recipeUserId"; import { DEFAULT_TENANT_ID } from "../multitenancy/constants"; import { JWKCacheMaxAgeInMs } from "./constants"; import { UserContext } from "../../types"; +import { getCombinedJWKS } from "../../combinedRemoteJWKSet"; /** * @description call this to "login" a user. @@ -111,7 +112,7 @@ export async function getSession( */ accessTokenInfo = await getInfoFromAccessToken( parsedAccessToken, - helpers.JWKS, + getCombinedJWKS(), helpers.config.antiCsrfFunctionOrString === "VIA_TOKEN" && doAntiCsrfCheck ); } catch (err) { diff --git a/lib/ts/supertokens.ts b/lib/ts/supertokens.ts index 69bd96f30..d948f2b15 100644 --- a/lib/ts/supertokens.ts +++ b/lib/ts/supertokens.ts @@ -103,6 +103,7 @@ export default class SuperTokens { let totpFound = false; let userMetadataFound = false; let multiFactorAuthFound = false; + let oauth2Found = false; // Multitenancy recipe is an always initialized recipe and needs to be imported this way // so that there is no circular dependency. Otherwise there would be cyclic dependency @@ -111,6 +112,7 @@ export default class SuperTokens { let UserMetadataRecipe = require("./recipe/usermetadata/recipe").default; let MultiFactorAuthRecipe = require("./recipe/multifactorauth/recipe").default; let TotpRecipe = require("./recipe/totp/recipe").default; + let OAuth2ProviderRecipe = require("./recipe/oauth2/recipe").default; this.recipeModules = config.recipeList.map((func) => { const recipeModule = func(this.appInfo, this.isInServerlessEnv); @@ -122,6 +124,8 @@ export default class SuperTokens { multiFactorAuthFound = true; } else if (recipeModule.getRecipeId() === TotpRecipe.RECIPE_ID) { totpFound = true; + } else if (recipeModule.getRecipeId() === OAuth2ProviderRecipe.RECIPE_ID) { + oauth2Found = true; } return recipeModule; }); @@ -142,6 +146,10 @@ export default class SuperTokens { // To let those cases function without initializing account linking we do not check it here, but when // the authentication endpoints are called. + // We've decided to always initialize the OAuth2Provider recipe + if (!oauth2Found) { + this.recipeModules.push(OAuth2ProviderRecipe.init()(this.appInfo, this.isInServerlessEnv)); + } this.telemetryEnabled = config.telemetry === undefined ? process.env.TEST_MODE !== "testing" : config.telemetry; } diff --git a/test/oauth2/config.test.js b/test/oauth2/config.test.js index 88c15957f..0b0ae0ed8 100644 --- a/test/oauth2/config.test.js +++ b/test/oauth2/config.test.js @@ -3,7 +3,7 @@ let assert = require("assert"); const { printPath, setupST, startST, killAllST, cleanST } = require("../utils"); let { ProcessState } = require("../../lib/build/processState"); let STExpress = require("../../"); -const OAuth2Recipe = require("../../lib/build/recipe/OAuth2/recipe").default; +const OAuth2Recipe = require("../../lib/build/recipe/oauth2/recipe").default; let { Querier } = require("../../lib/build/querier"); const { maxVersion } = require("../../lib/build/utils"); @@ -40,6 +40,6 @@ describe(`configTest: ${printPath("[test/oauth2/config.test.js]")}`, function () return; } - let OAuth2Recipe = await OAuth2Recipe.getInstanceOrThrowError(); + OAuth2Recipe.getInstanceOrThrowError(); }); }); From e0cdae54a4dc8c72ac6769e9a4d51750bdb94e13 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Sun, 28 Jul 2024 02:33:45 +0200 Subject: [PATCH 13/35] feat: rename OAuth2 to OAuth2Provider --- CHANGELOG.md | 2 +- lib/build/combinedRemoteJWKSet.d.ts | 2 +- lib/build/combinedRemoteJWKSet.js | 2 +- lib/build/recipe/jwt/api/implementation.js | 4 +- lib/build/recipe/oauth2/constants.d.ts | 9 ---- .../OAuth2Client.d.ts | 0 .../OAuth2Client.js | 0 .../{oauth2 => oauth2provider}/api/auth.d.ts | 0 .../{oauth2 => oauth2provider}/api/auth.js | 0 .../api/consent.d.ts | 0 .../{oauth2 => oauth2provider}/api/consent.js | 0 .../api/implementation.d.ts | 0 .../api/implementation.js | 0 .../{oauth2 => oauth2provider}/api/login.d.ts | 0 .../{oauth2 => oauth2provider}/api/login.js | 0 .../api/loginInfo.d.ts | 0 .../api/loginInfo.js | 0 .../api/logout.d.ts | 0 .../{oauth2 => oauth2provider}/api/logout.js | 0 .../{oauth2 => oauth2provider}/api/token.d.ts | 0 .../{oauth2 => oauth2provider}/api/token.js | 0 .../api/userInfo.d.ts | 0 .../api/userInfo.js | 25 +++++----- .../{oauth2 => oauth2provider}/api/utils.d.ts | 0 .../{oauth2 => oauth2provider}/api/utils.js | 2 +- .../recipe/oauth2provider/constants.d.ts | 9 ++++ .../{oauth2 => oauth2provider}/constants.js | 16 +++---- .../{oauth2 => oauth2provider}/index.d.ts | 0 .../{oauth2 => oauth2provider}/index.js | 0 .../{oauth2 => oauth2provider}/recipe.d.ts | 0 .../{oauth2 => oauth2provider}/recipe.js | 4 +- .../recipeImplementation.d.ts | 0 .../recipeImplementation.js | 0 .../{oauth2 => oauth2provider}/types.d.ts | 0 .../{oauth2 => oauth2provider}/types.js | 0 .../{oauth2 => oauth2provider}/utils.d.ts | 0 .../{oauth2 => oauth2provider}/utils.js | 0 .../recipe/openid/recipeImplementation.js | 2 +- lib/build/recipe/userroles/recipe.js | 2 +- lib/build/supertokens.js | 2 +- lib/ts/combinedRemoteJWKSet.ts | 2 +- lib/ts/recipe/jwt/api/implementation.ts | 4 +- .../OAuth2Client.ts | 0 .../{oauth2 => oauth2provider}/api/auth.ts | 0 .../{oauth2 => oauth2provider}/api/consent.ts | 0 .../api/implementation.ts | 0 .../{oauth2 => oauth2provider}/api/login.ts | 0 .../api/loginInfo.ts | 0 .../{oauth2 => oauth2provider}/api/logout.ts | 0 .../{oauth2 => oauth2provider}/api/token.ts | 0 .../api/userInfo.ts | 21 ++++----- .../{oauth2 => oauth2provider}/api/utils.ts | 2 +- .../{oauth2 => oauth2provider}/constants.ts | 16 +++---- .../{oauth2 => oauth2provider}/index.ts | 0 .../{oauth2 => oauth2provider}/recipe.ts | 4 +- .../recipeImplementation.ts | 0 .../{oauth2 => oauth2provider}/types.ts | 0 .../{oauth2 => oauth2provider}/utils.ts | 0 lib/ts/recipe/openid/recipeImplementation.ts | 2 +- lib/ts/recipe/userroles/recipe.ts | 2 +- lib/ts/supertokens.ts | 2 +- recipe/{oauth2 => oauth2provider}/index.d.ts | 4 +- recipe/{oauth2 => oauth2provider}/index.js | 2 +- .../types/index.d.ts | 4 +- .../{oauth2 => oauth2provider}/types/index.js | 2 +- test/oauth2/oauth2client.test.js | 2 +- test/test-server/src/index.ts | 18 ++++---- test/test-server/src/oauth2.ts | 46 ------------------- test/test-server/src/oauth2provider.ts | 46 +++++++++++++++++++ test/utils.js | 2 +- 70 files changed, 129 insertions(+), 133 deletions(-) delete mode 100644 lib/build/recipe/oauth2/constants.d.ts rename lib/build/recipe/{oauth2 => oauth2provider}/OAuth2Client.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/OAuth2Client.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/auth.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/auth.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/consent.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/consent.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/implementation.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/implementation.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/login.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/login.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/loginInfo.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/loginInfo.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/logout.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/logout.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/token.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/token.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/userInfo.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/userInfo.js (86%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/utils.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/api/utils.js (98%) create mode 100644 lib/build/recipe/oauth2provider/constants.d.ts rename lib/build/recipe/{oauth2 => oauth2provider}/constants.js (71%) rename lib/build/recipe/{oauth2 => oauth2provider}/index.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/index.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/recipe.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/recipe.js (98%) rename lib/build/recipe/{oauth2 => oauth2provider}/recipeImplementation.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/recipeImplementation.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/types.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/types.js (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/utils.d.ts (100%) rename lib/build/recipe/{oauth2 => oauth2provider}/utils.js (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/OAuth2Client.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/api/auth.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/api/consent.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/api/implementation.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/api/login.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/api/loginInfo.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/api/logout.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/api/token.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/api/userInfo.ts (86%) rename lib/ts/recipe/{oauth2 => oauth2provider}/api/utils.ts (98%) rename lib/ts/recipe/{oauth2 => oauth2provider}/constants.ts (61%) rename lib/ts/recipe/{oauth2 => oauth2provider}/index.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/recipe.ts (98%) rename lib/ts/recipe/{oauth2 => oauth2provider}/recipeImplementation.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/types.ts (100%) rename lib/ts/recipe/{oauth2 => oauth2provider}/utils.ts (100%) rename recipe/{oauth2 => oauth2provider}/index.d.ts (70%) rename recipe/{oauth2 => oauth2provider}/index.js (69%) rename recipe/{oauth2 => oauth2provider}/types/index.d.ts (67%) rename recipe/{oauth2 => oauth2provider}/types/index.js (66%) delete mode 100644 test/test-server/src/oauth2.ts create mode 100644 test/test-server/src/oauth2provider.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa6d6469..5523ba948 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## UNRELEASED -- Added OAuth2 recipe +- Added OAuth2Provider recipe ## [19.0.0] - 2024-06-10 diff --git a/lib/build/combinedRemoteJWKSet.d.ts b/lib/build/combinedRemoteJWKSet.d.ts index 1ff4b01dd..88153ee4e 100644 --- a/lib/build/combinedRemoteJWKSet.d.ts +++ b/lib/build/combinedRemoteJWKSet.d.ts @@ -2,7 +2,7 @@ /** * We need this to reset the combinedJWKS in tests because we need to create a new instance of the combinedJWKS * for each test to avoid caching issues. - * This is called when the session recipe is reset and when the oauth2 recipe is reset. + * This is called when the session recipe is reset and when the oauth2provider recipe is reset. * Calling this multiple times doesn't cause an issue. */ export declare function resetCombinedJWKS(): void; diff --git a/lib/build/combinedRemoteJWKSet.js b/lib/build/combinedRemoteJWKSet.js index 5d8eb65ea..845e38415 100644 --- a/lib/build/combinedRemoteJWKSet.js +++ b/lib/build/combinedRemoteJWKSet.js @@ -8,7 +8,7 @@ let combinedJWKS; /** * We need this to reset the combinedJWKS in tests because we need to create a new instance of the combinedJWKS * for each test to avoid caching issues. - * This is called when the session recipe is reset and when the oauth2 recipe is reset. + * This is called when the session recipe is reset and when the oauth2provider recipe is reset. * Calling this multiple times doesn't cause an issue. */ function resetCombinedJWKS() { diff --git a/lib/build/recipe/jwt/api/implementation.js b/lib/build/recipe/jwt/api/implementation.js index ab43be07c..cbef7fd16 100644 --- a/lib/build/recipe/jwt/api/implementation.js +++ b/lib/build/recipe/jwt/api/implementation.js @@ -21,9 +21,9 @@ function getAPIImplementation() { if (resp.validityInSeconds !== undefined) { options.res.setHeader("Cache-Control", `max-age=${resp.validityInSeconds}, must-revalidate`, false); } - const oauth2 = require("../../oauth2/recipe").default.getInstance(); + const oauth2Provider = require("../../oauth2provider/recipe").default.getInstance(); // TODO: dirty hack until we get core support - if (oauth2 !== undefined) { + if (oauth2Provider !== undefined) { const oauth2JWKSRes = await fetch("http://localhost:4444/.well-known/jwks.json"); if (oauth2JWKSRes.ok) { const oauth2RespBody = await oauth2JWKSRes.json(); diff --git a/lib/build/recipe/oauth2/constants.d.ts b/lib/build/recipe/oauth2/constants.d.ts deleted file mode 100644 index e5a5c4263..000000000 --- a/lib/build/recipe/oauth2/constants.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// @ts-nocheck -export declare const OAUTH2_BASE_PATH = "/oauth2/"; -export declare const LOGIN_PATH = "/oauth2/login"; -export declare const LOGOUT_PATH = "/oauth2/logout"; -export declare const CONSENT_PATH = "/oauth2/consent"; -export declare const AUTH_PATH = "/oauth2/auth"; -export declare const TOKEN_PATH = "/oauth2/token"; -export declare const LOGIN_INFO_PATH = "/oauth2/login/info"; -export declare const USER_INFO_PATH = "/oauth2/userinfo"; diff --git a/lib/build/recipe/oauth2/OAuth2Client.d.ts b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts similarity index 100% rename from lib/build/recipe/oauth2/OAuth2Client.d.ts rename to lib/build/recipe/oauth2provider/OAuth2Client.d.ts diff --git a/lib/build/recipe/oauth2/OAuth2Client.js b/lib/build/recipe/oauth2provider/OAuth2Client.js similarity index 100% rename from lib/build/recipe/oauth2/OAuth2Client.js rename to lib/build/recipe/oauth2provider/OAuth2Client.js diff --git a/lib/build/recipe/oauth2/api/auth.d.ts b/lib/build/recipe/oauth2provider/api/auth.d.ts similarity index 100% rename from lib/build/recipe/oauth2/api/auth.d.ts rename to lib/build/recipe/oauth2provider/api/auth.d.ts diff --git a/lib/build/recipe/oauth2/api/auth.js b/lib/build/recipe/oauth2provider/api/auth.js similarity index 100% rename from lib/build/recipe/oauth2/api/auth.js rename to lib/build/recipe/oauth2provider/api/auth.js diff --git a/lib/build/recipe/oauth2/api/consent.d.ts b/lib/build/recipe/oauth2provider/api/consent.d.ts similarity index 100% rename from lib/build/recipe/oauth2/api/consent.d.ts rename to lib/build/recipe/oauth2provider/api/consent.d.ts diff --git a/lib/build/recipe/oauth2/api/consent.js b/lib/build/recipe/oauth2provider/api/consent.js similarity index 100% rename from lib/build/recipe/oauth2/api/consent.js rename to lib/build/recipe/oauth2provider/api/consent.js diff --git a/lib/build/recipe/oauth2/api/implementation.d.ts b/lib/build/recipe/oauth2provider/api/implementation.d.ts similarity index 100% rename from lib/build/recipe/oauth2/api/implementation.d.ts rename to lib/build/recipe/oauth2provider/api/implementation.d.ts diff --git a/lib/build/recipe/oauth2/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js similarity index 100% rename from lib/build/recipe/oauth2/api/implementation.js rename to lib/build/recipe/oauth2provider/api/implementation.js diff --git a/lib/build/recipe/oauth2/api/login.d.ts b/lib/build/recipe/oauth2provider/api/login.d.ts similarity index 100% rename from lib/build/recipe/oauth2/api/login.d.ts rename to lib/build/recipe/oauth2provider/api/login.d.ts diff --git a/lib/build/recipe/oauth2/api/login.js b/lib/build/recipe/oauth2provider/api/login.js similarity index 100% rename from lib/build/recipe/oauth2/api/login.js rename to lib/build/recipe/oauth2provider/api/login.js diff --git a/lib/build/recipe/oauth2/api/loginInfo.d.ts b/lib/build/recipe/oauth2provider/api/loginInfo.d.ts similarity index 100% rename from lib/build/recipe/oauth2/api/loginInfo.d.ts rename to lib/build/recipe/oauth2provider/api/loginInfo.d.ts diff --git a/lib/build/recipe/oauth2/api/loginInfo.js b/lib/build/recipe/oauth2provider/api/loginInfo.js similarity index 100% rename from lib/build/recipe/oauth2/api/loginInfo.js rename to lib/build/recipe/oauth2provider/api/loginInfo.js diff --git a/lib/build/recipe/oauth2/api/logout.d.ts b/lib/build/recipe/oauth2provider/api/logout.d.ts similarity index 100% rename from lib/build/recipe/oauth2/api/logout.d.ts rename to lib/build/recipe/oauth2provider/api/logout.d.ts diff --git a/lib/build/recipe/oauth2/api/logout.js b/lib/build/recipe/oauth2provider/api/logout.js similarity index 100% rename from lib/build/recipe/oauth2/api/logout.js rename to lib/build/recipe/oauth2provider/api/logout.js diff --git a/lib/build/recipe/oauth2/api/token.d.ts b/lib/build/recipe/oauth2provider/api/token.d.ts similarity index 100% rename from lib/build/recipe/oauth2/api/token.d.ts rename to lib/build/recipe/oauth2provider/api/token.d.ts diff --git a/lib/build/recipe/oauth2/api/token.js b/lib/build/recipe/oauth2provider/api/token.js similarity index 100% rename from lib/build/recipe/oauth2/api/token.js rename to lib/build/recipe/oauth2provider/api/token.js diff --git a/lib/build/recipe/oauth2/api/userInfo.d.ts b/lib/build/recipe/oauth2provider/api/userInfo.d.ts similarity index 100% rename from lib/build/recipe/oauth2/api/userInfo.d.ts rename to lib/build/recipe/oauth2provider/api/userInfo.d.ts diff --git a/lib/build/recipe/oauth2/api/userInfo.js b/lib/build/recipe/oauth2provider/api/userInfo.js similarity index 86% rename from lib/build/recipe/oauth2/api/userInfo.js rename to lib/build/recipe/oauth2provider/api/userInfo.js index dadeb840c..b40716527 100644 --- a/lib/build/recipe/oauth2/api/userInfo.js +++ b/lib/build/recipe/oauth2provider/api/userInfo.js @@ -13,20 +13,15 @@ * 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 recipe_1 = __importDefault(require("../recipe")); const utils_1 = require("../../../utils"); const __1 = require("../../.."); -// TODO: Replace stub implementation by the actual implementation -async function validateOAuth2AccessToken(accessToken) { - const resp = await fetch(`http://localhost:4445/admin/oauth2/introspect`, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ token: accessToken }), - }); - return await resp.json(); -} async function userInfoGET(apiImplementation, tenantId, options, userContext) { if (apiImplementation.userInfoGET === undefined) { return false; @@ -41,7 +36,13 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { const accessToken = authHeader.replace(/^Bearer /, "").trim(); let accessTokenPayload; try { - accessTokenPayload = await validateOAuth2AccessToken(accessToken); + accessTokenPayload = await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.validateOAuth2AccessToken({ + token: accessToken, + // TODO: expectedAudience? + userContext, + }); } catch (error) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); utils_1.sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 400); diff --git a/lib/build/recipe/oauth2/api/utils.d.ts b/lib/build/recipe/oauth2provider/api/utils.d.ts similarity index 100% rename from lib/build/recipe/oauth2/api/utils.d.ts rename to lib/build/recipe/oauth2provider/api/utils.d.ts diff --git a/lib/build/recipe/oauth2/api/utils.js b/lib/build/recipe/oauth2provider/api/utils.js similarity index 98% rename from lib/build/recipe/oauth2/api/utils.js rename to lib/build/recipe/oauth2provider/api/utils.js index a0144bd3f..2db5d85fe 100644 --- a/lib/build/recipe/oauth2/api/utils.js +++ b/lib/build/recipe/oauth2provider/api/utils.js @@ -94,7 +94,7 @@ function isInternalRedirect(redirectTo) { } // In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. // If an internal redirect is identified, it's handled directly by this function. -// Currently, we only need to handle redirects to /oauth2/login and /oauth2/auth endpoints. +// Currently, we only need to handle redirects to /oauth2provider/login and /oauth2provider/auth endpoints. async function handleInternalRedirects({ response, recipeImplementation, session, cookie = "", userContext }) { var _a; if (!isInternalRedirect(response.redirectTo)) { diff --git a/lib/build/recipe/oauth2provider/constants.d.ts b/lib/build/recipe/oauth2provider/constants.d.ts new file mode 100644 index 000000000..a27abf732 --- /dev/null +++ b/lib/build/recipe/oauth2provider/constants.d.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +export declare const OAUTH2_BASE_PATH = "/oauth2provider/"; +export declare const LOGIN_PATH = "/oauth2provider/login"; +export declare const LOGOUT_PATH = "/oauth2provider/logout"; +export declare const CONSENT_PATH = "/oauth2provider/consent"; +export declare const AUTH_PATH = "/oauth2provider/auth"; +export declare const TOKEN_PATH = "/oauth2provider/token"; +export declare const LOGIN_INFO_PATH = "/oauth2provider/login/info"; +export declare const USER_INFO_PATH = "/oauth2provider/userinfo"; diff --git a/lib/build/recipe/oauth2/constants.js b/lib/build/recipe/oauth2provider/constants.js similarity index 71% rename from lib/build/recipe/oauth2/constants.js rename to lib/build/recipe/oauth2provider/constants.js index 2ea314db4..87b8fa499 100644 --- a/lib/build/recipe/oauth2/constants.js +++ b/lib/build/recipe/oauth2provider/constants.js @@ -15,11 +15,11 @@ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.USER_INFO_PATH = exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.CONSENT_PATH = exports.LOGOUT_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; -exports.OAUTH2_BASE_PATH = "/oauth2/"; -exports.LOGIN_PATH = "/oauth2/login"; -exports.LOGOUT_PATH = "/oauth2/logout"; -exports.CONSENT_PATH = "/oauth2/consent"; -exports.AUTH_PATH = "/oauth2/auth"; -exports.TOKEN_PATH = "/oauth2/token"; -exports.LOGIN_INFO_PATH = "/oauth2/login/info"; -exports.USER_INFO_PATH = "/oauth2/userinfo"; +exports.OAUTH2_BASE_PATH = "/oauth2provider/"; +exports.LOGIN_PATH = "/oauth2provider/login"; +exports.LOGOUT_PATH = "/oauth2provider/logout"; +exports.CONSENT_PATH = "/oauth2provider/consent"; +exports.AUTH_PATH = "/oauth2provider/auth"; +exports.TOKEN_PATH = "/oauth2provider/token"; +exports.LOGIN_INFO_PATH = "/oauth2provider/login/info"; +exports.USER_INFO_PATH = "/oauth2provider/userinfo"; diff --git a/lib/build/recipe/oauth2/index.d.ts b/lib/build/recipe/oauth2provider/index.d.ts similarity index 100% rename from lib/build/recipe/oauth2/index.d.ts rename to lib/build/recipe/oauth2provider/index.d.ts diff --git a/lib/build/recipe/oauth2/index.js b/lib/build/recipe/oauth2provider/index.js similarity index 100% rename from lib/build/recipe/oauth2/index.js rename to lib/build/recipe/oauth2provider/index.js diff --git a/lib/build/recipe/oauth2/recipe.d.ts b/lib/build/recipe/oauth2provider/recipe.d.ts similarity index 100% rename from lib/build/recipe/oauth2/recipe.d.ts rename to lib/build/recipe/oauth2provider/recipe.d.ts diff --git a/lib/build/recipe/oauth2/recipe.js b/lib/build/recipe/oauth2provider/recipe.js similarity index 98% rename from lib/build/recipe/oauth2/recipe.js rename to lib/build/recipe/oauth2provider/recipe.js index aefa95649..f650e2fb9 100644 --- a/lib/build/recipe/oauth2/recipe.js +++ b/lib/build/recipe/oauth2provider/recipe.js @@ -111,7 +111,7 @@ class Recipe extends recipeModule_1.default { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; } else { - throw new Error("OAuth2 recipe has already been initialised. Please check your code for bugs."); + throw new Error("OAuth2Provider recipe has already been initialised. Please check your code for bugs."); } }; } @@ -245,5 +245,5 @@ class Recipe extends recipeModule_1.default { } } exports.default = Recipe; -Recipe.RECIPE_ID = "oauth2"; +Recipe.RECIPE_ID = "oauth2provider"; Recipe.instance = undefined; diff --git a/lib/build/recipe/oauth2/recipeImplementation.d.ts b/lib/build/recipe/oauth2provider/recipeImplementation.d.ts similarity index 100% rename from lib/build/recipe/oauth2/recipeImplementation.d.ts rename to lib/build/recipe/oauth2provider/recipeImplementation.d.ts diff --git a/lib/build/recipe/oauth2/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js similarity index 100% rename from lib/build/recipe/oauth2/recipeImplementation.js rename to lib/build/recipe/oauth2provider/recipeImplementation.js diff --git a/lib/build/recipe/oauth2/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts similarity index 100% rename from lib/build/recipe/oauth2/types.d.ts rename to lib/build/recipe/oauth2provider/types.d.ts diff --git a/lib/build/recipe/oauth2/types.js b/lib/build/recipe/oauth2provider/types.js similarity index 100% rename from lib/build/recipe/oauth2/types.js rename to lib/build/recipe/oauth2provider/types.js diff --git a/lib/build/recipe/oauth2/utils.d.ts b/lib/build/recipe/oauth2provider/utils.d.ts similarity index 100% rename from lib/build/recipe/oauth2/utils.d.ts rename to lib/build/recipe/oauth2provider/utils.d.ts diff --git a/lib/build/recipe/oauth2/utils.js b/lib/build/recipe/oauth2provider/utils.js similarity index 100% rename from lib/build/recipe/oauth2/utils.js rename to lib/build/recipe/oauth2provider/utils.js diff --git a/lib/build/recipe/openid/recipeImplementation.js b/lib/build/recipe/openid/recipeImplementation.js index f01e8b272..61726ce5d 100644 --- a/lib/build/recipe/openid/recipeImplementation.js +++ b/lib/build/recipe/openid/recipeImplementation.js @@ -7,7 +7,7 @@ var __importDefault = Object.defineProperty(exports, "__esModule", { value: true }); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const constants_1 = require("../jwt/constants"); -const constants_2 = require("../oauth2/constants"); +const constants_2 = require("../oauth2provider/constants"); function getRecipeInterface(config, jwtRecipeImplementation, appInfo) { return { getOpenIdDiscoveryConfiguration: async function () { diff --git a/lib/build/recipe/userroles/recipe.js b/lib/build/recipe/userroles/recipe.js index 0587a295f..f9829586d 100644 --- a/lib/build/recipe/userroles/recipe.js +++ b/lib/build/recipe/userroles/recipe.js @@ -27,7 +27,7 @@ const utils_1 = require("./utils"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const postSuperTokensInitCallbacks_1 = require("../../postSuperTokensInitCallbacks"); const recipe_1 = __importDefault(require("../session/recipe")); -const recipe_2 = __importDefault(require("../oauth2/recipe")); +const recipe_2 = __importDefault(require("../oauth2provider/recipe")); const userRoleClaim_1 = require("./userRoleClaim"); const permissionClaim_1 = require("./permissionClaim"); class Recipe extends recipeModule_1.default { diff --git a/lib/build/supertokens.js b/lib/build/supertokens.js index 5075736d1..6758a1d01 100644 --- a/lib/build/supertokens.js +++ b/lib/build/supertokens.js @@ -384,7 +384,7 @@ class SuperTokens { let UserMetadataRecipe = require("./recipe/usermetadata/recipe").default; let MultiFactorAuthRecipe = require("./recipe/multifactorauth/recipe").default; let TotpRecipe = require("./recipe/totp/recipe").default; - let OAuth2ProviderRecipe = require("./recipe/oauth2/recipe").default; + let OAuth2ProviderRecipe = require("./recipe/oauth2provider/recipe").default; this.recipeModules = config.recipeList.map((func) => { const recipeModule = func(this.appInfo, this.isInServerlessEnv); if (recipeModule.getRecipeId() === MultitenancyRecipe.RECIPE_ID) { diff --git a/lib/ts/combinedRemoteJWKSet.ts b/lib/ts/combinedRemoteJWKSet.ts index 5d85df882..628cd5b8e 100644 --- a/lib/ts/combinedRemoteJWKSet.ts +++ b/lib/ts/combinedRemoteJWKSet.ts @@ -7,7 +7,7 @@ let combinedJWKS: ReturnType | undefined; /** * We need this to reset the combinedJWKS in tests because we need to create a new instance of the combinedJWKS * for each test to avoid caching issues. - * This is called when the session recipe is reset and when the oauth2 recipe is reset. + * This is called when the session recipe is reset and when the oauth2provider recipe is reset. * Calling this multiple times doesn't cause an issue. */ export function resetCombinedJWKS() { diff --git a/lib/ts/recipe/jwt/api/implementation.ts b/lib/ts/recipe/jwt/api/implementation.ts index 17871e963..03bcab7ec 100644 --- a/lib/ts/recipe/jwt/api/implementation.ts +++ b/lib/ts/recipe/jwt/api/implementation.ts @@ -31,10 +31,10 @@ export default function getAPIImplementation(): APIInterface { options.res.setHeader("Cache-Control", `max-age=${resp.validityInSeconds}, must-revalidate`, false); } - const oauth2 = require("../../oauth2/recipe").default.getInstance(); + const oauth2Provider = require("../../oauth2provider/recipe").default.getInstance(); // TODO: dirty hack until we get core support - if (oauth2 !== undefined) { + if (oauth2Provider !== undefined) { const oauth2JWKSRes = await fetch("http://localhost:4444/.well-known/jwks.json"); if (oauth2JWKSRes.ok) { const oauth2RespBody = await oauth2JWKSRes.json(); diff --git a/lib/ts/recipe/oauth2/OAuth2Client.ts b/lib/ts/recipe/oauth2provider/OAuth2Client.ts similarity index 100% rename from lib/ts/recipe/oauth2/OAuth2Client.ts rename to lib/ts/recipe/oauth2provider/OAuth2Client.ts diff --git a/lib/ts/recipe/oauth2/api/auth.ts b/lib/ts/recipe/oauth2provider/api/auth.ts similarity index 100% rename from lib/ts/recipe/oauth2/api/auth.ts rename to lib/ts/recipe/oauth2provider/api/auth.ts diff --git a/lib/ts/recipe/oauth2/api/consent.ts b/lib/ts/recipe/oauth2provider/api/consent.ts similarity index 100% rename from lib/ts/recipe/oauth2/api/consent.ts rename to lib/ts/recipe/oauth2provider/api/consent.ts diff --git a/lib/ts/recipe/oauth2/api/implementation.ts b/lib/ts/recipe/oauth2provider/api/implementation.ts similarity index 100% rename from lib/ts/recipe/oauth2/api/implementation.ts rename to lib/ts/recipe/oauth2provider/api/implementation.ts diff --git a/lib/ts/recipe/oauth2/api/login.ts b/lib/ts/recipe/oauth2provider/api/login.ts similarity index 100% rename from lib/ts/recipe/oauth2/api/login.ts rename to lib/ts/recipe/oauth2provider/api/login.ts diff --git a/lib/ts/recipe/oauth2/api/loginInfo.ts b/lib/ts/recipe/oauth2provider/api/loginInfo.ts similarity index 100% rename from lib/ts/recipe/oauth2/api/loginInfo.ts rename to lib/ts/recipe/oauth2provider/api/loginInfo.ts diff --git a/lib/ts/recipe/oauth2/api/logout.ts b/lib/ts/recipe/oauth2provider/api/logout.ts similarity index 100% rename from lib/ts/recipe/oauth2/api/logout.ts rename to lib/ts/recipe/oauth2provider/api/logout.ts diff --git a/lib/ts/recipe/oauth2/api/token.ts b/lib/ts/recipe/oauth2provider/api/token.ts similarity index 100% rename from lib/ts/recipe/oauth2/api/token.ts rename to lib/ts/recipe/oauth2provider/api/token.ts diff --git a/lib/ts/recipe/oauth2/api/userInfo.ts b/lib/ts/recipe/oauth2provider/api/userInfo.ts similarity index 86% rename from lib/ts/recipe/oauth2/api/userInfo.ts rename to lib/ts/recipe/oauth2provider/api/userInfo.ts index b3d601697..6a7a07828 100644 --- a/lib/ts/recipe/oauth2/api/userInfo.ts +++ b/lib/ts/recipe/oauth2provider/api/userInfo.ts @@ -13,23 +13,12 @@ * under the License. */ +import OAuth2ProviderRecipe from "../recipe"; import { send200Response, sendNon200ResponseWithMessage } from "../../../utils"; import { APIInterface, APIOptions } from ".."; import { JSONObject, UserContext } from "../../../types"; import { getUser } from "../../.."; -// TODO: Replace stub implementation by the actual implementation -async function validateOAuth2AccessToken(accessToken: string) { - const resp = await fetch(`http://localhost:4445/admin/oauth2/introspect`, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ token: accessToken }), - }); - return await resp.json(); -} - export default async function userInfoGET( apiImplementation: APIInterface, tenantId: string, @@ -54,7 +43,13 @@ export default async function userInfoGET( let accessTokenPayload: JSONObject; try { - accessTokenPayload = await validateOAuth2AccessToken(accessToken); + accessTokenPayload = await OAuth2ProviderRecipe.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken( + { + token: accessToken, + // TODO: expectedAudience? + userContext, + } + ); } catch (error) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 400); diff --git a/lib/ts/recipe/oauth2/api/utils.ts b/lib/ts/recipe/oauth2provider/api/utils.ts similarity index 98% rename from lib/ts/recipe/oauth2/api/utils.ts rename to lib/ts/recipe/oauth2provider/api/utils.ts index 48789773b..743fec939 100644 --- a/lib/ts/recipe/oauth2/api/utils.ts +++ b/lib/ts/recipe/oauth2provider/api/utils.ts @@ -107,7 +107,7 @@ function isInternalRedirect(redirectTo: string): boolean { // In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. // If an internal redirect is identified, it's handled directly by this function. -// Currently, we only need to handle redirects to /oauth2/login and /oauth2/auth endpoints. +// Currently, we only need to handle redirects to /oauth2provider/login and /oauth2provider/auth endpoints. export async function handleInternalRedirects({ response, recipeImplementation, diff --git a/lib/ts/recipe/oauth2/constants.ts b/lib/ts/recipe/oauth2provider/constants.ts similarity index 61% rename from lib/ts/recipe/oauth2/constants.ts rename to lib/ts/recipe/oauth2provider/constants.ts index 1175abacf..3cffb00ce 100644 --- a/lib/ts/recipe/oauth2/constants.ts +++ b/lib/ts/recipe/oauth2provider/constants.ts @@ -13,12 +13,12 @@ * under the License. */ -export const OAUTH2_BASE_PATH = "/oauth2/"; +export const OAUTH2_BASE_PATH = "/oauth2provider/"; -export const LOGIN_PATH = "/oauth2/login"; -export const LOGOUT_PATH = "/oauth2/logout"; -export const CONSENT_PATH = "/oauth2/consent"; -export const AUTH_PATH = "/oauth2/auth"; -export const TOKEN_PATH = "/oauth2/token"; -export const LOGIN_INFO_PATH = "/oauth2/login/info"; -export const USER_INFO_PATH = "/oauth2/userinfo"; +export const LOGIN_PATH = "/oauth2provider/login"; +export const LOGOUT_PATH = "/oauth2provider/logout"; +export const CONSENT_PATH = "/oauth2provider/consent"; +export const AUTH_PATH = "/oauth2provider/auth"; +export const TOKEN_PATH = "/oauth2provider/token"; +export const LOGIN_INFO_PATH = "/oauth2provider/login/info"; +export const USER_INFO_PATH = "/oauth2provider/userinfo"; diff --git a/lib/ts/recipe/oauth2/index.ts b/lib/ts/recipe/oauth2provider/index.ts similarity index 100% rename from lib/ts/recipe/oauth2/index.ts rename to lib/ts/recipe/oauth2provider/index.ts diff --git a/lib/ts/recipe/oauth2/recipe.ts b/lib/ts/recipe/oauth2provider/recipe.ts similarity index 98% rename from lib/ts/recipe/oauth2/recipe.ts rename to lib/ts/recipe/oauth2provider/recipe.ts index 8d8feaae3..63b03e5b2 100644 --- a/lib/ts/recipe/oauth2/recipe.ts +++ b/lib/ts/recipe/oauth2provider/recipe.ts @@ -53,7 +53,7 @@ import userInfoGET from "./api/userInfo"; import { resetCombinedJWKS } from "../../combinedRemoteJWKSet"; export default class Recipe extends RecipeModule { - static RECIPE_ID = "oauth2"; + static RECIPE_ID = "oauth2provider"; private static instance: Recipe | undefined = undefined; private idTokenBuilders: PayloadBuilderFunction[] = []; private userInfoBuilders: UserInfoBuilderFunction[] = []; @@ -104,7 +104,7 @@ export default class Recipe extends RecipeModule { Recipe.instance = new Recipe(Recipe.RECIPE_ID, appInfo, isInServerlessEnv, config); return Recipe.instance; } else { - throw new Error("OAuth2 recipe has already been initialised. Please check your code for bugs."); + throw new Error("OAuth2Provider recipe has already been initialised. Please check your code for bugs."); } }; } diff --git a/lib/ts/recipe/oauth2/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts similarity index 100% rename from lib/ts/recipe/oauth2/recipeImplementation.ts rename to lib/ts/recipe/oauth2provider/recipeImplementation.ts diff --git a/lib/ts/recipe/oauth2/types.ts b/lib/ts/recipe/oauth2provider/types.ts similarity index 100% rename from lib/ts/recipe/oauth2/types.ts rename to lib/ts/recipe/oauth2provider/types.ts diff --git a/lib/ts/recipe/oauth2/utils.ts b/lib/ts/recipe/oauth2provider/utils.ts similarity index 100% rename from lib/ts/recipe/oauth2/utils.ts rename to lib/ts/recipe/oauth2provider/utils.ts diff --git a/lib/ts/recipe/openid/recipeImplementation.ts b/lib/ts/recipe/openid/recipeImplementation.ts index bbb633768..a01377cbc 100644 --- a/lib/ts/recipe/openid/recipeImplementation.ts +++ b/lib/ts/recipe/openid/recipeImplementation.ts @@ -17,7 +17,7 @@ import { RecipeInterface as JWTRecipeInterface, JsonWebKey } from "../jwt/types" import NormalisedURLPath from "../../normalisedURLPath"; import { GET_JWKS_API } from "../jwt/constants"; import { NormalisedAppinfo, UserContext } from "../../types"; -import { AUTH_PATH, TOKEN_PATH, USER_INFO_PATH } from "../oauth2/constants"; +import { AUTH_PATH, TOKEN_PATH, USER_INFO_PATH } from "../oauth2provider/constants"; export default function getRecipeInterface( config: TypeNormalisedInput, diff --git a/lib/ts/recipe/userroles/recipe.ts b/lib/ts/recipe/userroles/recipe.ts index 9a2c4ecff..07583783f 100644 --- a/lib/ts/recipe/userroles/recipe.ts +++ b/lib/ts/recipe/userroles/recipe.ts @@ -27,7 +27,7 @@ import { validateAndNormaliseUserInput } from "./utils"; import OverrideableBuilder from "supertokens-js-override"; import { PostSuperTokensInitCallbacks } from "../../postSuperTokensInitCallbacks"; import SessionRecipe from "../session/recipe"; -import OAuth2Recipe from "../oauth2/recipe"; +import OAuth2Recipe from "../oauth2provider/recipe"; import { UserRoleClaim } from "./userRoleClaim"; import { PermissionClaim } from "./permissionClaim"; diff --git a/lib/ts/supertokens.ts b/lib/ts/supertokens.ts index d948f2b15..819e6485a 100644 --- a/lib/ts/supertokens.ts +++ b/lib/ts/supertokens.ts @@ -112,7 +112,7 @@ export default class SuperTokens { let UserMetadataRecipe = require("./recipe/usermetadata/recipe").default; let MultiFactorAuthRecipe = require("./recipe/multifactorauth/recipe").default; let TotpRecipe = require("./recipe/totp/recipe").default; - let OAuth2ProviderRecipe = require("./recipe/oauth2/recipe").default; + let OAuth2ProviderRecipe = require("./recipe/oauth2provider/recipe").default; this.recipeModules = config.recipeList.map((func) => { const recipeModule = func(this.appInfo, this.isInServerlessEnv); diff --git a/recipe/oauth2/index.d.ts b/recipe/oauth2provider/index.d.ts similarity index 70% rename from recipe/oauth2/index.d.ts rename to recipe/oauth2provider/index.d.ts index e82d1a6b5..72d8d7e7d 100644 --- a/recipe/oauth2/index.d.ts +++ b/recipe/oauth2provider/index.d.ts @@ -1,4 +1,4 @@ -export * from "../../lib/build/recipe/oauth2"; +export * from "../../lib/build/recipe/oauth2provider"; /** * 'export *' does not re-export a default. * import NextJS from "supertokens-node/nextjs"; @@ -6,5 +6,5 @@ export * from "../../lib/build/recipe/oauth2"; * - user add "esModuleInterop": true in their tsconfig.json file * - we do the following change: */ -import * as _default from "../../lib/build/recipe/oauth2"; +import * as _default from "../../lib/build/recipe/oauth2provider"; export default _default; diff --git a/recipe/oauth2/index.js b/recipe/oauth2provider/index.js similarity index 69% rename from recipe/oauth2/index.js rename to recipe/oauth2provider/index.js index 01b7d12b6..1b9fa1030 100644 --- a/recipe/oauth2/index.js +++ b/recipe/oauth2provider/index.js @@ -3,4 +3,4 @@ function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } exports.__esModule = true; -__export(require("../../lib/build/recipe/oauth2")); +__export(require("../../lib/build/recipe/oauth2provider")); diff --git a/recipe/oauth2/types/index.d.ts b/recipe/oauth2provider/types/index.d.ts similarity index 67% rename from recipe/oauth2/types/index.d.ts rename to recipe/oauth2provider/types/index.d.ts index 6b7f5a64c..bdfe9d82c 100644 --- a/recipe/oauth2/types/index.d.ts +++ b/recipe/oauth2provider/types/index.d.ts @@ -1,4 +1,4 @@ -export * from "../../../lib/build/recipe/oauth2/types"; +export * from "../../../lib/build/recipe/oauth2provider/types"; /** * 'export *' does not re-export a default. * import NextJS from "supertokens-node/nextjs"; @@ -6,5 +6,5 @@ export * from "../../../lib/build/recipe/oauth2/types"; * - user add "esModuleInterop": true in their tsconfig.json file * - we do the following change: */ -import * as _default from "../../../lib/build/recipe/oauth2/types"; +import * as _default from "../../../lib/build/recipe/oauth2provider/types"; export default _default; diff --git a/recipe/oauth2/types/index.js b/recipe/oauth2provider/types/index.js similarity index 66% rename from recipe/oauth2/types/index.js rename to recipe/oauth2provider/types/index.js index 5f9654705..d508e2566 100644 --- a/recipe/oauth2/types/index.js +++ b/recipe/oauth2provider/types/index.js @@ -3,4 +3,4 @@ function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } exports.__esModule = true; -__export(require("../../../lib/build/recipe/oauth2/types")); +__export(require("../../../lib/build/recipe/oauth2provider/types")); diff --git a/test/oauth2/oauth2client.test.js b/test/oauth2/oauth2client.test.js index a2e806247..36d4008b6 100644 --- a/test/oauth2/oauth2client.test.js +++ b/test/oauth2/oauth2client.test.js @@ -3,7 +3,7 @@ let assert = require("assert"); const { printPath, setupST, startST, killAllST, cleanST } = require("../utils"); let { ProcessState } = require("../../lib/build/processState"); let STExpress = require("../../"); -let OAuth2Recipe = require("../../recipe/oauth2"); +let OAuth2Recipe = require("../../recipe/oauth2provider"); describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}`, function () { beforeEach(async function () { diff --git a/test/test-server/src/index.ts b/test/test-server/src/index.ts index 7ae66ec90..838d82309 100644 --- a/test/test-server/src/index.ts +++ b/test/test-server/src/index.ts @@ -19,8 +19,8 @@ import ThirdPartyRecipe from "../../../lib/build/recipe/thirdparty/recipe"; import { TypeInput as ThirdPartyTypeInput } from "../../../lib/build/recipe/thirdparty/types"; import { TypeInput as MFATypeInput } from "../../../lib/build/recipe/multifactorauth/types"; import TOTPRecipe from "../../../lib/build/recipe/totp/recipe"; -import OAuth2Recipe from "../../../lib/build/recipe/oauth2/recipe"; -import { TypeInput as OAuth2TypeInput } from "../../../lib/build/recipe/oauth2/types"; +import OAuth2ProviderRecipe from "../../../lib/build/recipe/oauth2provider/recipe"; +import { TypeInput as OAuth2ProviderTypeInput } from "../../../lib/build/recipe/oauth2provider/types"; import OAuth2ClientRecipe from "../../../lib/build/recipe/oauth2client/recipe"; import { TypeInput as OAuth2ClientTypeInput } from "../../../lib/build/recipe/oauth2client/types"; import { TypeInput as OpenIdRecipeTypeInput } from "../../../lib/build/recipe/openid/types"; @@ -37,7 +37,7 @@ import Session from "../../../recipe/session"; import { verifySession } from "../../../recipe/session/framework/express"; import ThirdParty from "../../../recipe/thirdparty"; import TOTP from "../../../recipe/totp"; -import OAuth2 from "../../../recipe/oauth2"; +import OAuth2Provider from "../../../recipe/oauth2provider"; import OAuth2Client from "../../../recipe/oauth2client"; import accountlinkingRoutes from "./accountlinking"; import emailpasswordRoutes from "./emailpassword"; @@ -46,7 +46,7 @@ import { logger } from "./logger"; import multiFactorAuthRoutes from "./multifactorauth"; import multitenancyRoutes from "./multitenancy"; import passwordlessRoutes from "./passwordless"; -import oAuth2Routes from "./oauth2"; +import OAuth2ProviderRoutes from "./oauth2provider"; import sessionRoutes from "./session"; import supertokensRoutes from "./supertokens"; import thirdPartyRoutes from "./thirdparty"; @@ -92,7 +92,7 @@ function STReset() { ProcessState.getInstance().reset(); MultiFactorAuthRecipe.reset(); TOTPRecipe.reset(); - OAuth2Recipe.reset(); + OAuth2ProviderRecipe.reset(); OAuth2ClientRecipe.reset(); SuperTokensRecipe.reset(); } @@ -298,8 +298,8 @@ function initST(config: any) { }) ); } - if (recipe.recipeId === "oauth2") { - let initConfig: OAuth2TypeInput = { + if (recipe.recipeId === "oauth2provider") { + let initConfig: OAuth2ProviderTypeInput = { ...config, }; if (initConfig.override?.functions) { @@ -314,7 +314,7 @@ function initST(config: any) { apis: getFunc(`${initConfig.override.apis}`), }; } - recipeList.push(OAuth2.init(initConfig)); + recipeList.push(OAuth2Provider.init(initConfig)); } if (recipe.recipeId === "oauth2client") { let initConfig: OAuth2ClientTypeInput = { @@ -425,7 +425,7 @@ app.use("/test/multifactorauth", multiFactorAuthRoutes); app.use("/test/thirdparty", thirdPartyRoutes); app.use("/test/totp", TOTPRoutes); app.use("/test/usermetadata", userMetadataRoutes); -app.use("/test/oauth2", oAuth2Routes); +app.use("/test/oauth2provider", OAuth2ProviderRoutes); // *** Custom routes to help with session tests *** app.post("/create", async (req, res, next) => { diff --git a/test/test-server/src/oauth2.ts b/test/test-server/src/oauth2.ts deleted file mode 100644 index d54dff6fe..000000000 --- a/test/test-server/src/oauth2.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Router } from "express"; -import OAuth2 from "../../../recipe/oauth2"; -import { logger } from "./logger"; - -const namespace = "com.supertokens:node-test-server:oauth2"; -const { logDebugMessage } = logger(namespace); - -const router = Router() - .post("/getoauth2clients", async (req, res, next) => { - try { - logDebugMessage("OAuth2:getOAuth2Clients %j", req.body); - const response = await OAuth2.getOAuth2Clients(req.body.input, req.body.userContext); - res.json(response); - } catch (e) { - next(e); - } - }) - .post("/createoauth2client", async (req, res, next) => { - try { - logDebugMessage("OAuth2:createOAuth2Client %j", req.body); - const response = await OAuth2.createOAuth2Client(req.body.input, req.body.userContext); - res.json(response); - } catch (e) { - next(e); - } - }) - .post("/updateoauth2client", async (req, res, next) => { - try { - logDebugMessage("OAuth2:updateOAuth2Client %j", req.body); - const response = await OAuth2.updateOAuth2Client(req.body.input, req.body.userContext); - res.json(response); - } catch (e) { - next(e); - } - }) - .post("/deleteoauth2client", async (req, res, next) => { - try { - logDebugMessage("OAuth2:deleteOAuth2Client %j", req.body); - const response = await OAuth2.deleteOAuth2Client(req.body.input, req.body.userContext); - res.json(response); - } catch (e) { - next(e); - } - }); - -export default router; diff --git a/test/test-server/src/oauth2provider.ts b/test/test-server/src/oauth2provider.ts new file mode 100644 index 000000000..8cd6afcc8 --- /dev/null +++ b/test/test-server/src/oauth2provider.ts @@ -0,0 +1,46 @@ +import { Router } from "express"; +import OAuth2Provider from "../../../recipe/oauth2provider"; +import { logger } from "./logger"; + +const namespace = "com.supertokens:node-test-server:oauth2provider"; +const { logDebugMessage } = logger(namespace); + +const router = Router() + .post("/getoauth2clients", async (req, res, next) => { + try { + logDebugMessage("OAuth2Provider:getOAuth2Clients %j", req.body); + const response = await OAuth2Provider.getOAuth2Clients(req.body.input, req.body.userContext); + res.json(response); + } catch (e) { + next(e); + } + }) + .post("/createoauth2client", async (req, res, next) => { + try { + logDebugMessage("OAuth2Provider:createOAuth2Client %j", req.body); + const response = await OAuth2Provider.createOAuth2Client(req.body.input, req.body.userContext); + res.json(response); + } catch (e) { + next(e); + } + }) + .post("/updateoauth2client", async (req, res, next) => { + try { + logDebugMessage("OAuth2Provider:updateOAuth2Client %j", req.body); + const response = await OAuth2Provider.updateOAuth2Client(req.body.input, req.body.userContext); + res.json(response); + } catch (e) { + next(e); + } + }) + .post("/deleteoauth2client", async (req, res, next) => { + try { + logDebugMessage("OAuth2Provider:deleteOAuth2Client %j", req.body); + const response = await OAuth2Provider.deleteOAuth2Client(req.body.input, req.body.userContext); + res.json(response); + } catch (e) { + next(e); + } + }); + +export default router; diff --git a/test/utils.js b/test/utils.js index 6cfa420d9..e3a62f4ff 100644 --- a/test/utils.js +++ b/test/utils.js @@ -31,7 +31,7 @@ let PasswordlessRecipe = require("..//lib/build/recipe/passwordless/recipe").def let MultitenancyRecipe = require("../lib/build/recipe/multitenancy/recipe").default; let MultiFactorAuthRecipe = require("../lib/build/recipe/multifactorauth/recipe").default; const UserRolesRecipe = require("../lib/build/recipe/userroles/recipe").default; -const OAuth2Recipe = require("../lib/build/recipe/oauth2/recipe").default; +const OAuth2Recipe = require("../lib/build/recipe/oauth2provider/recipe").default; let { ProcessState } = require("../lib/build/processState"); let { Querier } = require("../lib/build/querier"); let { maxVersion } = require("../lib/build/utils"); From a463b65e9f6dd64d92e6c479de5c85f341a1bce9 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Sun, 28 Jul 2024 10:11:17 +0200 Subject: [PATCH 14/35] feat: expose token validation functions --- lib/build/recipe/oauth2provider/index.d.ts | 18 ++++++++++++++++++ lib/build/recipe/oauth2provider/index.js | 18 +++++++++++++++++- lib/ts/recipe/oauth2provider/index.ts | 20 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/lib/build/recipe/oauth2provider/index.d.ts b/lib/build/recipe/oauth2provider/index.d.ts index f0e29f04c..e145e31a3 100644 --- a/lib/build/recipe/oauth2provider/index.d.ts +++ b/lib/build/recipe/oauth2provider/index.d.ts @@ -67,10 +67,28 @@ export default class Wrapper { errorHint: string; } >; + static validateOAuth2AccessToken( + token: string, + expectedAudience?: string, + userContext?: Record + ): Promise<{ + status: "OK"; + payload: import("../usermetadata").JSONObject; + }>; + static validateOAuth2IdToken( + token: string, + expectedAudience?: string, + userContext?: Record + ): Promise<{ + status: "OK"; + payload: import("../usermetadata").JSONObject; + }>; } export declare let init: typeof Recipe.init; export declare let getOAuth2Clients: typeof Wrapper.getOAuth2Clients; export declare let createOAuth2Client: typeof Wrapper.createOAuth2Client; export declare let updateOAuth2Client: typeof Wrapper.updateOAuth2Client; export declare let deleteOAuth2Client: typeof Wrapper.deleteOAuth2Client; +export declare let validateOAuth2AccessToken: typeof Wrapper.validateOAuth2AccessToken; +export declare let validateOAuth2IdToken: typeof Wrapper.validateOAuth2IdToken; export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/build/recipe/oauth2provider/index.js b/lib/build/recipe/oauth2provider/index.js index 3a0f72db4..fb2c4554a 100644 --- a/lib/build/recipe/oauth2provider/index.js +++ b/lib/build/recipe/oauth2provider/index.js @@ -19,7 +19,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.init = void 0; +exports.validateOAuth2IdToken = exports.validateOAuth2AccessToken = exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.init = void 0; const utils_1 = require("../../utils"); const recipe_1 = __importDefault(require("./recipe")); class Wrapper { @@ -43,6 +43,20 @@ class Wrapper { .getInstanceOrThrowError() .recipeInterfaceImpl.deleteOAuth2Client(input, utils_1.getUserContext(userContext)); } + static validateOAuth2AccessToken(token, expectedAudience, userContext) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ + token, + expectedAudience, + userContext: utils_1.getUserContext(userContext), + }); + } + static validateOAuth2IdToken(token, expectedAudience, userContext) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2IdToken({ + token, + expectedAudience, + userContext: utils_1.getUserContext(userContext), + }); + } } exports.default = Wrapper; Wrapper.init = recipe_1.default.init; @@ -51,3 +65,5 @@ exports.getOAuth2Clients = Wrapper.getOAuth2Clients; exports.createOAuth2Client = Wrapper.createOAuth2Client; exports.updateOAuth2Client = Wrapper.updateOAuth2Client; exports.deleteOAuth2Client = Wrapper.deleteOAuth2Client; +exports.validateOAuth2AccessToken = Wrapper.validateOAuth2AccessToken; +exports.validateOAuth2IdToken = Wrapper.validateOAuth2IdToken; diff --git a/lib/ts/recipe/oauth2provider/index.ts b/lib/ts/recipe/oauth2provider/index.ts index c1bf5cef3..32f6ac012 100644 --- a/lib/ts/recipe/oauth2provider/index.ts +++ b/lib/ts/recipe/oauth2provider/index.ts @@ -52,6 +52,22 @@ export default class Wrapper { getUserContext(userContext) ); } + + static validateOAuth2AccessToken(token: string, expectedAudience?: string, userContext?: Record) { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ + token, + expectedAudience, + userContext: getUserContext(userContext), + }); + } + + static validateOAuth2IdToken(token: string, expectedAudience?: string, userContext?: Record) { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2IdToken({ + token, + expectedAudience, + userContext: getUserContext(userContext), + }); + } } export let init = Wrapper.init; @@ -64,4 +80,8 @@ export let updateOAuth2Client = Wrapper.updateOAuth2Client; export let deleteOAuth2Client = Wrapper.deleteOAuth2Client; +export let validateOAuth2AccessToken = Wrapper.validateOAuth2AccessToken; + +export let validateOAuth2IdToken = Wrapper.validateOAuth2IdToken; + export type { APIInterface, APIOptions, RecipeInterface }; From b0984c12b087f37a99db8d5d3ecaaee1320811ec Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Sun, 28 Jul 2024 10:11:57 +0200 Subject: [PATCH 15/35] test: update tests --- test/config.test.js | 4 ++-- test/jwt/getJWKS.test.js | 7 ++++++- test/oauth2/config.test.js | 6 +++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/test/config.test.js b/test/config.test.js index d81653329..7824a94ae 100644 --- a/test/config.test.js +++ b/test/config.test.js @@ -234,7 +234,7 @@ describe(`configTest: ${printPath("[test/config.test.js]")}`, function () { recipeList: [Session.init({ getTokenTransferMethod: () => "cookie" })], }); SessionRecipe.getInstanceOrThrowError(); - assert.strictEqual(SuperTokens.getInstanceOrThrowError().recipeModules.length, 3); // multitenancy&usermetadata is initialised by default + assert.strictEqual(SuperTokens.getInstanceOrThrowError().recipeModules.length, 4); // multitenancy&usermetadata&oauth2provider is initialised by default resetAll(); } @@ -252,7 +252,7 @@ describe(`configTest: ${printPath("[test/config.test.js]")}`, function () { }); SessionRecipe.getInstanceOrThrowError(); EmailPasswordRecipe.getInstanceOrThrowError(); - assert(SuperTokens.getInstanceOrThrowError().recipeModules.length === 4); // multitenancy&usermetadata is initialised by default + assert(SuperTokens.getInstanceOrThrowError().recipeModules.length === 5); // multitenancy&usermetadata&oauth2provider is initialised by default resetAll(); } }); diff --git a/test/jwt/getJWKS.test.js b/test/jwt/getJWKS.test.js index 1ec845e8b..e9593cc12 100644 --- a/test/jwt/getJWKS.test.js +++ b/test/jwt/getJWKS.test.js @@ -118,7 +118,12 @@ describe(`getJWKS: ${printPath("[test/jwt/getJWKS.test.js]")}`, function () { assert(Object.keys(response).length === 1); assert(response.keys !== undefined); assert(response.keys.length > 0); - assert.strictEqual(headers["cache-control"], "max-age=60, must-revalidate"); + const cacheControlHeaderParts = headers["cache-control"].split(", "); + assert.strictEqual(cacheControlHeaderParts.length, 2); + assert(cacheControlHeaderParts[0].startsWith("max-age=60")); + const maxAge = Number.parseInt(cacheControlHeaderParts[0].split("=")[1]); + assert(maxAge >= 60); + assert.strictEqual(cacheControlHeaderParts[1], "must-revalidate"); }); it("Test that we can override the Cache-Control header through the function", async function () { diff --git a/test/oauth2/config.test.js b/test/oauth2/config.test.js index 0b0ae0ed8..5d2858925 100644 --- a/test/oauth2/config.test.js +++ b/test/oauth2/config.test.js @@ -3,7 +3,7 @@ let assert = require("assert"); const { printPath, setupST, startST, killAllST, cleanST } = require("../utils"); let { ProcessState } = require("../../lib/build/processState"); let STExpress = require("../../"); -const OAuth2Recipe = require("../../lib/build/recipe/oauth2/recipe").default; +const OAuth2ProviderRecipe = require("../../lib/build/recipe/oauth2provider/recipe").default; let { Querier } = require("../../lib/build/querier"); const { maxVersion } = require("../../lib/build/utils"); @@ -30,7 +30,7 @@ describe(`configTest: ${printPath("[test/oauth2/config.test.js]")}`, function () appName: "SuperTokens", websiteDomain: "supertokens.io", }, - recipeList: [OAuth2Recipe.init()], + recipeList: [OAuth2ProviderRecipe.init()], }); // Only run for version >= 2.9 @@ -40,6 +40,6 @@ describe(`configTest: ${printPath("[test/oauth2/config.test.js]")}`, function () return; } - OAuth2Recipe.getInstanceOrThrowError(); + OAuth2ProviderRecipe.getInstanceOrThrowError(); }); }); From a29ffbee67fd6784c17e778364868f82123d8cb0 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Sun, 28 Jul 2024 12:35:46 +0200 Subject: [PATCH 16/35] fix: add userinfo_endpoint properly --- lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js | 1 + lib/build/recipe/openid/index.d.ts | 1 + lib/build/recipe/openid/types.d.ts | 2 ++ lib/build/recipe/session/index.d.ts | 1 + lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts | 1 + lib/ts/recipe/openid/types.ts | 2 ++ test/with-typescript/index.ts | 1 + 7 files changed, 9 insertions(+) diff --git a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js index 7e2fdb593..6cd6f8ec5 100644 --- a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js +++ b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js @@ -16,6 +16,7 @@ async function getOpenIdDiscoveryConfiguration(apiImplementation, options, userC jwks_uri: result.jwks_uri, authorization_endpoint: result.authorization_endpoint, token_endpoint: result.token_endpoint, + userinfo_endpoint: result.userinfo_endpoint, subject_types_supported: result.subject_types_supported, id_token_signing_alg_values_supported: result.id_token_signing_alg_values_supported, response_types_supported: result.response_types_supported, diff --git a/lib/build/recipe/openid/index.d.ts b/lib/build/recipe/openid/index.d.ts index a7f961b19..6c78c266e 100644 --- a/lib/build/recipe/openid/index.d.ts +++ b/lib/build/recipe/openid/index.d.ts @@ -10,6 +10,7 @@ export default class OpenIdRecipeWrapper { jwks_uri: string; authorization_endpoint: string; token_endpoint: string; + userinfo_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/lib/build/recipe/openid/types.d.ts b/lib/build/recipe/openid/types.d.ts index d99d7d529..246c57ac5 100644 --- a/lib/build/recipe/openid/types.d.ts +++ b/lib/build/recipe/openid/types.d.ts @@ -68,6 +68,7 @@ export declare type APIInterface = { jwks_uri: string; authorization_endpoint: string; token_endpoint: string; + userinfo_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; @@ -84,6 +85,7 @@ export declare type RecipeInterface = { jwks_uri: string; authorization_endpoint: string; token_endpoint: string; + userinfo_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/lib/build/recipe/session/index.d.ts b/lib/build/recipe/session/index.d.ts index b96971a45..73a99d24e 100644 --- a/lib/build/recipe/session/index.d.ts +++ b/lib/build/recipe/session/index.d.ts @@ -179,6 +179,7 @@ export default class SessionWrapper { jwks_uri: string; authorization_endpoint: string; token_endpoint: string; + userinfo_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts b/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts index 7a331169b..2dfe7a2bd 100644 --- a/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts +++ b/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts @@ -36,6 +36,7 @@ export default async function getOpenIdDiscoveryConfiguration( jwks_uri: result.jwks_uri, authorization_endpoint: result.authorization_endpoint, token_endpoint: result.token_endpoint, + userinfo_endpoint: result.userinfo_endpoint, subject_types_supported: result.subject_types_supported, id_token_signing_alg_values_supported: result.id_token_signing_alg_values_supported, response_types_supported: result.response_types_supported, diff --git a/lib/ts/recipe/openid/types.ts b/lib/ts/recipe/openid/types.ts index 22aa651a5..32f3bc958 100644 --- a/lib/ts/recipe/openid/types.ts +++ b/lib/ts/recipe/openid/types.ts @@ -85,6 +85,7 @@ export type APIInterface = { jwks_uri: string; authorization_endpoint: string; token_endpoint: string; + userinfo_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; @@ -102,6 +103,7 @@ export type RecipeInterface = { jwks_uri: string; authorization_endpoint: string; token_endpoint: string; + userinfo_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/test/with-typescript/index.ts b/test/with-typescript/index.ts index 4a2a3d500..d67684458 100644 --- a/test/with-typescript/index.ts +++ b/test/with-typescript/index.ts @@ -1595,6 +1595,7 @@ Session.init({ jwks_uri: "https://your.api.domain/auth/jwt/jwks.json", token_endpoint: "http://localhost:3000/auth/oauth2/token", authorization_endpoint: "http://localhost:3000/auth/oauth2/auth", + userinfo_endpoint: "http://localhost:3000/auth/oauth2/userinfo", id_token_signing_alg_values_supported: [], response_types_supported: [], subject_types_supported: [], From 6950da708f73ee79152be80102478fd6c4b08d4f Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Mon, 29 Jul 2024 00:22:29 +0200 Subject: [PATCH 17/35] feat: removed unnecessary props --- .../recipe/oauth2provider/OAuth2Client.d.ts | 128 ------------- .../recipe/oauth2provider/OAuth2Client.js | 40 ----- .../recipe/oauth2provider/api/consent.d.ts | 8 - .../recipe/oauth2provider/api/consent.js | 63 ------- .../oauth2provider/api/implementation.js | 86 --------- .../recipe/oauth2provider/api/logout.d.ts | 8 - lib/build/recipe/oauth2provider/api/logout.js | 57 ------ .../recipe/oauth2provider/constants.d.ts | 2 - lib/build/recipe/oauth2provider/constants.js | 4 +- lib/build/recipe/oauth2provider/recipe.js | 32 ---- .../oauth2provider/recipeImplementation.js | 123 +++++-------- lib/build/recipe/oauth2provider/types.d.ts | 120 +------------ lib/ts/recipe/oauth2provider/OAuth2Client.ts | 168 ------------------ lib/ts/recipe/oauth2provider/api/consent.ts | 66 ------- .../oauth2provider/api/implementation.ts | 86 --------- lib/ts/recipe/oauth2provider/api/logout.ts | 63 ------- lib/ts/recipe/oauth2provider/constants.ts | 2 - lib/ts/recipe/oauth2provider/recipe.ts | 42 +---- .../oauth2provider/recipeImplementation.ts | 125 +++++-------- lib/ts/recipe/oauth2provider/types.ts | 118 ++---------- 20 files changed, 104 insertions(+), 1237 deletions(-) delete mode 100644 lib/build/recipe/oauth2provider/api/consent.d.ts delete mode 100644 lib/build/recipe/oauth2provider/api/consent.js delete mode 100644 lib/build/recipe/oauth2provider/api/logout.d.ts delete mode 100644 lib/build/recipe/oauth2provider/api/logout.js delete mode 100644 lib/ts/recipe/oauth2provider/api/consent.ts delete mode 100644 lib/ts/recipe/oauth2provider/api/logout.ts diff --git a/lib/build/recipe/oauth2provider/OAuth2Client.d.ts b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts index 3a074bb04..71cb89757 100644 --- a/lib/build/recipe/oauth2provider/OAuth2Client.d.ts +++ b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts @@ -58,11 +58,6 @@ export declare class OAuth2Client { * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ */ implicitGrantIdTokenLifespan: string | null; - /** - * JWT Bearer Grant Access Token Lifespan - * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ - */ - jwtBearerGrantAccessTokenLifespan: string | null; /** * Refresh Token Grant Access Token Lifespan * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ @@ -83,74 +78,12 @@ export declare class OAuth2Client { * Requested Client Authentication method for the Token Endpoint. */ tokenEndpointAuthMethod: string; - /** - * OAuth 2.0 Token Endpoint Signing Algorithm - * Requested Client Authentication signing algorithm for the Token Endpoint. - */ - tokenEndpointAuthSigningAlg?: string; /** * OAuth 2.0 Access Token Strategy * AccessTokenStrategy is the strategy used to generate access tokens. * Valid options are jwt and opaque. */ accessTokenStrategy?: "jwt" | "opaque"; - /** - * OpenID Connect Back-Channel Logout Session Required - * Boolean value specifying whether the RP requires that a sid (session ID) Claim be included in the Logout - * Token to identify the RP session with the OP when the backchannel_logout_uri is used. - * If omitted, the default value is false. - */ - backchannelLogoutSessionRequired?: boolean; - /** - * OpenID Connect Back-Channel Logout URI - * RP URL that will cause the RP to log itself out when sent a Logout Token by the OP. - */ - backchannelLogoutUri?: string; - /** - * OpenID Connect Front-Channel Logout Session Required - * Boolean value specifying whether the RP requires that iss (issuer) and sid (session ID) query parameters be - * included to identify the RP session with the OP when the frontchannel_logout_uri is used. - * If omitted, the default value is false. - */ - frontchannelLogoutSessionRequired?: boolean; - /** - * OpenID Connect Front-Channel Logout URI - * RP URL that will cause the RP to log itself out when rendered in an iframe by the OP. - */ - frontchannelLogoutUri?: string; - /** - * OpenID Connect Request Object Signing Algorithm - * JWS alg algorithm that MUST be used for signing Request Objects sent to the OP. All Request Objects - * from this Client MUST be rejected, if not signed with this algorithm. - */ - requestObjectSigningAlg?: string; - /** - * OpenID Connect Sector Identifier URI - * URL using the https scheme to be used in calculating Pseudonymous Identifiers by the OP. The URL references a - * file with a single JSON array of redirect_uri values. - */ - sectorIdentifierUri?: string; - /** - * OpenID Connect Request Userinfo Signed Response Algorithm - * JWS alg algorithm REQUIRED for signing UserInfo Responses. If this is specified, the response will be JWT - * serialized, and signed using JWS. - */ - userinfoSignedResponseAlg: string; - /** - * OAuth 2.0 Client JSON Web Key Set - * Client's JSON Web Key Set [JWK] document, passed by value. - */ - jwks: Record; - /** - * OAuth 2.0 Client JSON Web Key Set URL - * URL for the Client's JSON Web Key Set [JWK] document. - */ - jwksUri?: string; - /** - * OAuth 2.0 Client Owner - * Owner is a string identifying the owner of the OAuth 2.0 Client. - */ - owner: string; /** * OAuth 2.0 Client URI * ClientURI is a URL string of a web page providing information about the client. @@ -171,26 +104,11 @@ export declare class OAuth2Client { * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. */ grantTypes: string[] | null; - /** - * Array of post-logout redirect URIs - * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. - */ - postLogoutRedirectUris?: string[]; - /** - * Array of request URIs - * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. - */ - requestUris?: string[]; /** * Array of response types * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. */ responseTypes: string[] | null; - /** - * Array of contacts - * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. - */ - contacts: string[] | null; /** * OAuth 2.0 Client Logo URI * A URL string referencing the client's logo. @@ -211,21 +129,6 @@ export declare class OAuth2Client { * authorizing the client. */ tosUri: string; - /** - * SkipConsent skips the consent screen for this client. This field can only - * be set from the admin API. - */ - skipConsent: boolean; - /** - * SkipLogoutConsent skips the logout consent screen for this client. This field can only - * be set from the admin API. - */ - skipLogoutConsent: boolean | null; - /** - * OpenID Connect Subject Type - * Valid types include pairwise and public. - */ - subjectType: string; /** * OAuth 2.0 Client Creation Date * CreatedAt returns the timestamp of the client's creation. @@ -236,17 +139,6 @@ export declare class OAuth2Client { * UpdatedAt returns the timestamp of the last update. */ updatedAt: string; - /** - * OpenID Connect Dynamic Client Registration Access Token - * RegistrationAccessToken can be used to update, get, or delete the OAuth2 Client. It is sent when creating a client - * using Dynamic Client Registration. - */ - registrationAccessToken: string; - /** - * OpenID Connect Dynamic Client Registration URL - * RegistrationClientURI is the URL used to update, get, or delete the OAuth2 Client. - */ - registrationClientUri: string; /** * Metadata - JSON object * JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger. @@ -264,41 +156,21 @@ export declare class OAuth2Client { clientCredentialsGrantAccessTokenLifespan, implicitGrantAccessTokenLifespan, implicitGrantIdTokenLifespan, - jwtBearerGrantAccessTokenLifespan, refreshTokenGrantAccessTokenLifespan, refreshTokenGrantIdTokenLifespan, refreshTokenGrantRefreshTokenLifespan, tokenEndpointAuthMethod, - tokenEndpointAuthSigningAlg, accessTokenStrategy, - backchannelLogoutSessionRequired, - backchannelLogoutUri, - frontchannelLogoutSessionRequired, - frontchannelLogoutUri, - requestObjectSigningAlg, - sectorIdentifierUri, - userinfoSignedResponseAlg, - jwks, - jwksUri, - owner, clientUri, allowedCorsOrigins, audience, grantTypes, - postLogoutRedirectUris, - requestUris, responseTypes, - contacts, logoUri, policyUri, tosUri, - skipConsent, - skipLogoutConsent, - subjectType, createdAt, updatedAt, - registrationAccessToken, - registrationClientUri, metadata, }: OAuth2ClientOptions); static fromAPIResponse(response: any): OAuth2Client; diff --git a/lib/build/recipe/oauth2provider/OAuth2Client.js b/lib/build/recipe/oauth2provider/OAuth2Client.js index af937addc..6876c76a0 100644 --- a/lib/build/recipe/oauth2provider/OAuth2Client.js +++ b/lib/build/recipe/oauth2provider/OAuth2Client.js @@ -29,41 +29,21 @@ class OAuth2Client { clientCredentialsGrantAccessTokenLifespan = null, implicitGrantAccessTokenLifespan = null, implicitGrantIdTokenLifespan = null, - jwtBearerGrantAccessTokenLifespan = null, refreshTokenGrantAccessTokenLifespan = null, refreshTokenGrantIdTokenLifespan = null, refreshTokenGrantRefreshTokenLifespan = null, tokenEndpointAuthMethod, - tokenEndpointAuthSigningAlg, accessTokenStrategy, - backchannelLogoutSessionRequired = false, - backchannelLogoutUri, - frontchannelLogoutSessionRequired = false, - frontchannelLogoutUri, - requestObjectSigningAlg, - sectorIdentifierUri, - userinfoSignedResponseAlg, - jwks = {}, - jwksUri, - owner = "", clientUri = "", allowedCorsOrigins = [], audience = [], grantTypes = null, - postLogoutRedirectUris, - requestUris, responseTypes = null, - contacts = null, logoUri = "", policyUri = "", tosUri = "", - skipConsent = false, - skipLogoutConsent = null, - subjectType, createdAt, updatedAt, - registrationAccessToken, - registrationClientUri, metadata = {}, }) { /** @@ -82,41 +62,21 @@ class OAuth2Client { this.clientCredentialsGrantAccessTokenLifespan = clientCredentialsGrantAccessTokenLifespan; this.implicitGrantAccessTokenLifespan = implicitGrantAccessTokenLifespan; this.implicitGrantIdTokenLifespan = implicitGrantIdTokenLifespan; - this.jwtBearerGrantAccessTokenLifespan = jwtBearerGrantAccessTokenLifespan; this.refreshTokenGrantAccessTokenLifespan = refreshTokenGrantAccessTokenLifespan; this.refreshTokenGrantIdTokenLifespan = refreshTokenGrantIdTokenLifespan; this.refreshTokenGrantRefreshTokenLifespan = refreshTokenGrantRefreshTokenLifespan; this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; - this.tokenEndpointAuthSigningAlg = tokenEndpointAuthSigningAlg; this.accessTokenStrategy = accessTokenStrategy; - this.backchannelLogoutSessionRequired = backchannelLogoutSessionRequired; - this.backchannelLogoutUri = backchannelLogoutUri; - this.frontchannelLogoutSessionRequired = frontchannelLogoutSessionRequired; - this.frontchannelLogoutUri = frontchannelLogoutUri; - this.requestObjectSigningAlg = requestObjectSigningAlg; - this.sectorIdentifierUri = sectorIdentifierUri; - this.userinfoSignedResponseAlg = userinfoSignedResponseAlg; - this.jwks = jwks; - this.jwksUri = jwksUri; - this.owner = owner; this.clientUri = clientUri; this.allowedCorsOrigins = allowedCorsOrigins; this.audience = audience; this.grantTypes = grantTypes; - this.postLogoutRedirectUris = postLogoutRedirectUris; - this.requestUris = requestUris; this.responseTypes = responseTypes; - this.contacts = contacts; this.logoUri = logoUri; this.policyUri = policyUri; this.tosUri = tosUri; - this.skipConsent = skipConsent; - this.skipLogoutConsent = skipLogoutConsent; - this.subjectType = subjectType; this.createdAt = createdAt; this.updatedAt = updatedAt; - this.registrationAccessToken = registrationAccessToken; - this.registrationClientUri = registrationClientUri; this.metadata = metadata; } static fromAPIResponse(response) { diff --git a/lib/build/recipe/oauth2provider/api/consent.d.ts b/lib/build/recipe/oauth2provider/api/consent.d.ts deleted file mode 100644 index b38df7de8..000000000 --- a/lib/build/recipe/oauth2provider/api/consent.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -// @ts-nocheck -import { APIInterface, APIOptions } from ".."; -import { UserContext } from "../../../types"; -export default function consent( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; diff --git a/lib/build/recipe/oauth2provider/api/consent.js b/lib/build/recipe/oauth2provider/api/consent.js deleted file mode 100644 index b95113d71..000000000 --- a/lib/build/recipe/oauth2provider/api/consent.js +++ /dev/null @@ -1,63 +0,0 @@ -"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 }); -const utils_1 = require("../../../utils"); -// TODO: separate post and get? -async function consent(apiImplementation, options, userContext) { - var _a; - if (utils_1.normaliseHttpMethod(options.req.getMethod()) === "post") { - if (apiImplementation.consentPOST === undefined) { - return false; - } - const reqBody = await options.req.getJSONBody(); - let response = await apiImplementation.consentPOST({ - options, - accept: reqBody.accept, - consentChallenge: reqBody.consentChallenge, - grantScope: reqBody.grantScope, - remember: reqBody.remember, - userContext, - }); - if ("status" in response) { - utils_1.send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } - } else { - if (apiImplementation.consentGET === undefined) { - return false; - } - const consentChallenge = - (_a = options.req.getKeyValueFromQuery("consentChallenge")) !== null && _a !== void 0 - ? _a - : options.req.getKeyValueFromQuery("consent_challenge"); - if (consentChallenge === undefined) { - throw new Error("TODO"); - } - let response = await apiImplementation.consentGET({ - options, - consentChallenge, - userContext, - }); - if ("status" in response) { - utils_1.send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } - } - return true; -} -exports.default = consent; diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index 0b70812a4..4a5f9df54 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -13,13 +13,7 @@ * 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 supertokens_1 = __importDefault(require("../../../supertokens")); const utils_1 = require("./utils"); function getAPIImplementation() { return { @@ -52,86 +46,6 @@ function getAPIImplementation() { }); return { redirectTo: res.redirectTo }; }, - logoutGET: async ({ logoutChallenge, options, userContext }) => { - const request = await options.recipeImplementation.getLogoutRequest({ - challenge: logoutChallenge, - userContext, - }); - const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; - return { - redirectTo: - appInfo - .getOrigin({ - request: options.req, - userContext: userContext, - }) - .getAsStringDangerous() + - appInfo.websiteBasePath.getAsStringDangerous() + - `/logout?challenge=${request.challenge}`, - }; - }, - logoutPOST: async ({ logoutChallenge, accept, options, userContext }) => { - if (accept) { - const res = await options.recipeImplementation.acceptLogoutRequest({ - challenge: logoutChallenge, - userContext, - }); - return { redirectTo: res.redirectTo }; - } - await options.recipeImplementation.rejectLogoutRequest({ - challenge: logoutChallenge, - // error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, - userContext, - }); - const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; - return { - redirectTo: - appInfo - .getOrigin({ - request: options.req, - userContext: userContext, - }) - .getAsStringDangerous() + appInfo.websiteBasePath.getAsStringDangerous(), - }; - }, - consentGET: async ({ consentChallenge, options, userContext }) => { - const request = await options.recipeImplementation.getConsentRequest({ - challenge: consentChallenge, - userContext, - }); - const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; - return { - redirectTo: - appInfo - .getOrigin({ - request: options.req, - userContext: userContext, - }) - .getAsStringDangerous() + - appInfo.websiteBasePath.getAsStringDangerous() + - `/consent?challenge=${request.challenge}&scopes=${request.requestedScope}&client=${request.client}&`, - }; - }, - consentPOST: async ({ consentChallenge, accept, remember, grantScope, options, userContext }) => { - const request = await options.recipeImplementation.getConsentRequest({ - challenge: consentChallenge, - userContext, - }); - const res = accept - ? await options.recipeImplementation.acceptConsentRequest({ - challenge: consentChallenge, - grantAccessTokenAudience: request.requestedAccessTokenAudience, - remember, - grantScope, - userContext, - }) - : await options.recipeImplementation.rejectConsentRequest({ - challenge: consentChallenge, - error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, - userContext, - }); - return { redirectTo: res.redirectTo }; - }, authGET: async ({ options, params, cookie, session, userContext }) => { const response = await options.recipeImplementation.authorization({ params, diff --git a/lib/build/recipe/oauth2provider/api/logout.d.ts b/lib/build/recipe/oauth2provider/api/logout.d.ts deleted file mode 100644 index d9242afff..000000000 --- a/lib/build/recipe/oauth2provider/api/logout.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -// @ts-nocheck -import { APIInterface, APIOptions } from ".."; -import { UserContext } from "../../../types"; -export default function logout( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise; diff --git a/lib/build/recipe/oauth2provider/api/logout.js b/lib/build/recipe/oauth2provider/api/logout.js deleted file mode 100644 index ed9d07e8d..000000000 --- a/lib/build/recipe/oauth2provider/api/logout.js +++ /dev/null @@ -1,57 +0,0 @@ -"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 }); -const utils_1 = require("../../../utils"); -// TODO: separate post and get? -async function logout(apiImplementation, options, userContext) { - if (utils_1.normaliseHttpMethod(options.req.getMethod()) === "post") { - if (apiImplementation.logoutPOST === undefined) { - return false; - } - const reqBody = await options.req.getJSONBody(); - let response = await apiImplementation.logoutPOST({ - options, - accept: reqBody.accept, - logoutChallenge: reqBody.logoutChallenge, - userContext, - }); - if ("status" in response) { - utils_1.send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } - } else { - if (apiImplementation.logoutGET === undefined) { - return false; - } - const logoutChallenge = options.req.getKeyValueFromQuery("logoutChallenge"); - if (logoutChallenge === undefined) { - throw new Error("TODO"); - } - let response = await apiImplementation.logoutGET({ - options, - logoutChallenge, - userContext, - }); - if ("status" in response) { - utils_1.send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } - } - return true; -} -exports.default = logout; diff --git a/lib/build/recipe/oauth2provider/constants.d.ts b/lib/build/recipe/oauth2provider/constants.d.ts index a27abf732..f10ea72ec 100644 --- a/lib/build/recipe/oauth2provider/constants.d.ts +++ b/lib/build/recipe/oauth2provider/constants.d.ts @@ -1,8 +1,6 @@ // @ts-nocheck export declare const OAUTH2_BASE_PATH = "/oauth2provider/"; export declare const LOGIN_PATH = "/oauth2provider/login"; -export declare const LOGOUT_PATH = "/oauth2provider/logout"; -export declare const CONSENT_PATH = "/oauth2provider/consent"; export declare const AUTH_PATH = "/oauth2provider/auth"; export declare const TOKEN_PATH = "/oauth2provider/token"; export declare const LOGIN_INFO_PATH = "/oauth2provider/login/info"; diff --git a/lib/build/recipe/oauth2provider/constants.js b/lib/build/recipe/oauth2provider/constants.js index 87b8fa499..73a51502d 100644 --- a/lib/build/recipe/oauth2provider/constants.js +++ b/lib/build/recipe/oauth2provider/constants.js @@ -14,11 +14,9 @@ * under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.USER_INFO_PATH = exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.CONSENT_PATH = exports.LOGOUT_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; +exports.USER_INFO_PATH = exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; exports.OAUTH2_BASE_PATH = "/oauth2provider/"; exports.LOGIN_PATH = "/oauth2provider/login"; -exports.LOGOUT_PATH = "/oauth2provider/logout"; -exports.CONSENT_PATH = "/oauth2provider/consent"; exports.AUTH_PATH = "/oauth2provider/auth"; exports.TOKEN_PATH = "/oauth2provider/token"; exports.LOGIN_INFO_PATH = "/oauth2provider/login/info"; diff --git a/lib/build/recipe/oauth2provider/recipe.js b/lib/build/recipe/oauth2provider/recipe.js index f650e2fb9..8100cc718 100644 --- a/lib/build/recipe/oauth2provider/recipe.js +++ b/lib/build/recipe/oauth2provider/recipe.js @@ -24,10 +24,8 @@ const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const querier_1 = require("../../querier"); const recipeModule_1 = __importDefault(require("../../recipeModule")); const auth_1 = __importDefault(require("./api/auth")); -const consent_1 = __importDefault(require("./api/consent")); const implementation_1 = __importDefault(require("./api/implementation")); const login_1 = __importDefault(require("./api/login")); -const logout_1 = __importDefault(require("./api/logout")); const token_1 = __importDefault(require("./api/token")); const loginInfo_1 = __importDefault(require("./api/loginInfo")); const constants_1 = require("./constants"); @@ -56,12 +54,6 @@ class Recipe extends recipeModule_1.default { if (id === constants_1.LOGIN_PATH) { return login_1.default(this.apiImpl, options, userContext); } - if (id === constants_1.LOGOUT_PATH) { - return logout_1.default(this.apiImpl, options, userContext); - } - if (id === constants_1.CONSENT_PATH) { - return consent_1.default(this.apiImpl, options, userContext); - } if (id === constants_1.TOKEN_PATH) { return token_1.default(this.apiImpl, options, userContext); } @@ -137,30 +129,6 @@ class Recipe extends recipeModule_1.default { id: constants_1.LOGIN_PATH, disabled: this.apiImpl.loginGET === undefined, }, - { - method: "post", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.LOGOUT_PATH), - id: constants_1.LOGOUT_PATH, - disabled: this.apiImpl.logoutPOST === undefined, - }, - { - method: "get", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.LOGOUT_PATH), - id: constants_1.LOGOUT_PATH, - disabled: this.apiImpl.logoutGET === undefined, - }, - { - method: "post", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.CONSENT_PATH), - id: constants_1.CONSENT_PATH, - disabled: this.apiImpl.consentPOST === undefined, - }, - { - method: "get", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.CONSENT_PATH), - id: constants_1.CONSENT_PATH, - disabled: this.apiImpl.consentGET === undefined, - }, { method: "post", pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.TOKEN_PATH), diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 4d7f76a64..88cd6c1ba 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -147,7 +147,6 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, loginChallenge: resp.data.login_challenge, loginSessionId: resp.data.login_session_id, oidcContext: resp.data.oidc_context, - requestUrl: resp.data.request_url, requestedAccessTokenAudience: resp.data.requested_access_token_audience, requestedScope: resp.data.requested_scope, skip: resp.data.skip, @@ -202,50 +201,8 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, ), }; }, - getLogoutRequest: async function (input) { - const resp = await querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/oauth2/admin/oauth2/auth/requests/logout"), - { logout_challenge: input.challenge }, - input.userContext - ); - return { - challenge: resp.data.challenge, - client: OAuth2Client_1.OAuth2Client.fromAPIResponse(resp.data.client), - requestUrl: resp.data.request_url, - rpInitiated: resp.data.rp_initiated, - sid: resp.data.sid, - subject: resp.data.subject, - }; - }, - acceptLogoutRequest: async function (input) { - const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/consent/logout/accept`), - {}, - { - logout_challenge: input.challenge, - }, - input.userContext - ); - return { - // TODO: FIXME!!! - redirectTo: resp.data.redirect_to.replace( - querier_1.hydraPubDomain, - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ), - }; - }, - rejectLogoutRequest: async function (input) { - await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/consent/logout/reject`), - {}, - { - logout_challenge: input.challenge, - }, - input.userContext - ); - }, authorization: async function (input) { - var _a, _b, _c, _d; + var _a, _b, _c; const resp = await querier.sendGetRequestWithResponseHeaders( new normalisedURLPath_1.default(`/recipe/oauth2/pub/auth`), input.params, @@ -269,48 +226,43 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, if (!user) { throw new Error("Should not happen"); } - if ( - consentRequest.skip || - ((_a = consentRequest.client) === null || _a === void 0 ? void 0 : _a.skipConsent) - ) { - const idToken = this.buildIdTokenPayload({ - user, - session: input.session, - defaultPayload: await getDefaultIdTokenPayload( - user, - (_b = consentRequest.requestedScope) !== null && _b !== void 0 ? _b : [], - input.userContext - ), - scopes: consentRequest.requestedScope || [], - userContext: input.userContext, - }); - const accessTokenPayload = this.buildAccessTokenPayload({ + const idToken = this.buildIdTokenPayload({ + user, + session: input.session, + defaultPayload: await getDefaultIdTokenPayload( user, - session: input.session, - defaultPayload: input.session.getAccessTokenPayload(input.userContext), - userContext: input.userContext, - scopes: consentRequest.requestedScope || [], - }); - const consentRes = await this.acceptConsentRequest( - Object.assign(Object.assign({}, input), { - challenge: consentRequest.challenge, - grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, - grantScope: consentRequest.requestedScope, - session: { - id_token: idToken, - access_token: accessTokenPayload, - }, - }) - ); - return { - redirectTo: consentRes.redirectTo, - setCookie: (_c = resp.headers.get("set-cookie")) !== null && _c !== void 0 ? _c : undefined, - }; - } + (_a = consentRequest.requestedScope) !== null && _a !== void 0 ? _a : [], + input.userContext + ), + scopes: consentRequest.requestedScope || [], + userContext: input.userContext, + }); + const accessTokenPayload = this.buildAccessTokenPayload({ + user, + session: input.session, + defaultPayload: input.session.getAccessTokenPayload(input.userContext), + userContext: input.userContext, + scopes: consentRequest.requestedScope || [], + }); + const consentRes = await this.acceptConsentRequest( + Object.assign(Object.assign({}, input), { + challenge: consentRequest.challenge, + grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, + grantScope: consentRequest.requestedScope, + session: { + id_token: idToken, + access_token: accessTokenPayload, + }, + }) + ); + return { + redirectTo: consentRes.redirectTo, + setCookie: (_b = resp.headers.get("set-cookie")) !== null && _b !== void 0 ? _b : undefined, + }; } return { redirectTo, - setCookie: (_d = resp.headers.get("set-cookie")) !== null && _d !== void 0 ? _d : undefined, + setCookie: (_c = resp.headers.get("set-cookie")) !== null && _c !== void 0 ? _c : undefined, }; }, token: async function (input) { @@ -364,7 +316,12 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, createOAuth2Client: async function (input, userContext) { let response = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients`), - utils_1.transformObjectKeys(input, "snake-case"), + Object.assign(Object.assign({}, utils_1.transformObjectKeys(input, "snake-case")), { + // TODO: these defaults should be set/enforced on the core side + accessTokenStrategy: "jwt", + skipConsent: true, + subjectType: "public", + }), userContext ); if (response.status === "OK") { diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index cfde910ac..8b10f63a2 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -47,7 +47,6 @@ export declare type ConsentRequest = { loginChallenge?: string; loginSessionId?: string; oidcContext?: any; - requestUrl?: string; requestedAccessTokenAudience?: string[]; requestedScope?: string[]; skip?: boolean; @@ -64,14 +63,6 @@ export declare type LoginRequest = { skip: boolean; subject: string; }; -export declare type LogoutRequest = { - challenge: string; - client: OAuth2Client; - requestUrl: string; - rpInitiated: boolean; - sid: string; - subject: string; -}; export declare type TokenInfo = { access_token: string; expires_in: number; @@ -150,14 +141,6 @@ export declare type RecipeInterface = { }): Promise<{ redirectTo: string; }>; - getLogoutRequest(input: { challenge: string; userContext: UserContext }): Promise; - acceptLogoutRequest(input: { - challenge: string; - userContext: UserContext; - }): Promise<{ - redirectTo: string; - }>; - rejectLogoutRequest(input: { challenge: string; userContext: UserContext }): Promise; getOAuth2Clients( input: GetOAuth2ClientsInput, userContext: UserContext @@ -280,58 +263,6 @@ export declare type APIInterface = { } | GeneralErrorResponse >); - logoutGET: - | undefined - | ((input: { - logoutChallenge: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - redirectTo: string; - } - | GeneralErrorResponse - >); - logoutPOST: - | undefined - | ((input: { - logoutChallenge: string; - accept: boolean; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - redirectTo: string; - } - | GeneralErrorResponse - >); - consentGET: - | undefined - | ((input: { - consentChallenge: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - redirectTo: string; - } - | GeneralErrorResponse - >); - consentPOST: - | undefined - | ((input: { - consentChallenge: string; - accept: boolean; - grantScope: string[]; - remember: boolean; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - redirectTo: string; - } - | GeneralErrorResponse - >); authGET: | undefined | ((input: { @@ -388,50 +319,30 @@ export declare type APIInterface = { export declare type OAuth2ClientOptions = { clientId: string; clientSecret: string; + createdAt: string; + updatedAt: string; clientName: string; scope: string; redirectUris?: string[] | null; + allowedCorsOrigins?: string[]; authorizationCodeGrantAccessTokenLifespan?: string | null; authorizationCodeGrantIdTokenLifespan?: string | null; authorizationCodeGrantRefreshTokenLifespan?: string | null; clientCredentialsGrantAccessTokenLifespan?: string | null; implicitGrantAccessTokenLifespan?: string | null; implicitGrantIdTokenLifespan?: string | null; - jwtBearerGrantAccessTokenLifespan?: string | null; refreshTokenGrantAccessTokenLifespan?: string | null; refreshTokenGrantIdTokenLifespan?: string | null; refreshTokenGrantRefreshTokenLifespan?: string | null; tokenEndpointAuthMethod: string; - tokenEndpointAuthSigningAlg?: string; accessTokenStrategy?: "jwt" | "opaque"; - backchannelLogoutSessionRequired?: boolean; - backchannelLogoutUri?: string; - frontchannelLogoutSessionRequired?: boolean; - frontchannelLogoutUri?: string; - requestObjectSigningAlg?: string; - sectorIdentifierUri?: string; - userinfoSignedResponseAlg: string; - jwks?: Record; - jwksUri?: string; - owner?: string; - clientUri?: string; - allowedCorsOrigins?: string[]; audience?: string[]; grantTypes?: string[] | null; - postLogoutRedirectUris?: string[]; - requestUris?: string[]; responseTypes?: string[] | null; - contacts?: string[] | null; + clientUri?: string; logoUri?: string; policyUri?: string; tosUri?: string; - skipConsent?: boolean; - skipLogoutConsent?: boolean | null; - subjectType: string; - createdAt: string; - updatedAt: string; - registrationAccessToken: string; - registrationClientUri: string; metadata?: Record; }; export declare type GetOAuth2ClientsInput = { @@ -452,29 +363,16 @@ export declare type GetOAuth2ClientsInput = { */ owner?: string; }; -export declare type CreateOAuth2ClientInput = Partial>; +export declare type CreateOAuth2ClientInput = Partial< + Omit +>; export declare type UpdateOAuth2ClientInput = NonNullableProperties< - Omit< - CreateOAuth2ClientInput, - | "redirectUris" - | "grantTypes" - | "postLogoutRedirectUris" - | "requestUris" - | "responseTypes" - | "contacts" - | "registrationAccessToken" - | "registrationClientUri" - | "metadata" - > + Omit > & { + clientId: string; redirectUris?: string[] | null; grantTypes?: string[] | null; - postLogoutRedirectUris?: string[] | null; - requestUris?: string[] | null; responseTypes?: string[] | null; - contacts?: string[] | null; - registrationAccessToken?: string | null; - registrationClientUri?: string | null; metadata?: Record | null; }; export declare type DeleteOAuth2ClientInput = { diff --git a/lib/ts/recipe/oauth2provider/OAuth2Client.ts b/lib/ts/recipe/oauth2provider/OAuth2Client.ts index 7126f3198..caa8518a5 100644 --- a/lib/ts/recipe/oauth2provider/OAuth2Client.ts +++ b/lib/ts/recipe/oauth2provider/OAuth2Client.ts @@ -85,12 +85,6 @@ export class OAuth2Client { */ implicitGrantIdTokenLifespan: string | null; - /** - * JWT Bearer Grant Access Token Lifespan - * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ - */ - jwtBearerGrantAccessTokenLifespan: string | null; - /** * Refresh Token Grant Access Token Lifespan * NullDuration - ^[0-9]+(ns|us|ms|s|m|h)$ @@ -115,12 +109,6 @@ export class OAuth2Client { */ tokenEndpointAuthMethod: string; - /** - * OAuth 2.0 Token Endpoint Signing Algorithm - * Requested Client Authentication signing algorithm for the Token Endpoint. - */ - tokenEndpointAuthSigningAlg?: string; - /** * OAuth 2.0 Access Token Strategy * AccessTokenStrategy is the strategy used to generate access tokens. @@ -128,73 +116,6 @@ export class OAuth2Client { */ accessTokenStrategy?: "jwt" | "opaque"; - /** - * OpenID Connect Back-Channel Logout Session Required - * Boolean value specifying whether the RP requires that a sid (session ID) Claim be included in the Logout - * Token to identify the RP session with the OP when the backchannel_logout_uri is used. - * If omitted, the default value is false. - */ - backchannelLogoutSessionRequired?: boolean; - - /** - * OpenID Connect Back-Channel Logout URI - * RP URL that will cause the RP to log itself out when sent a Logout Token by the OP. - */ - backchannelLogoutUri?: string; - - /** - * OpenID Connect Front-Channel Logout Session Required - * Boolean value specifying whether the RP requires that iss (issuer) and sid (session ID) query parameters be - * included to identify the RP session with the OP when the frontchannel_logout_uri is used. - * If omitted, the default value is false. - */ - frontchannelLogoutSessionRequired?: boolean; - - /** - * OpenID Connect Front-Channel Logout URI - * RP URL that will cause the RP to log itself out when rendered in an iframe by the OP. - */ - frontchannelLogoutUri?: string; - - /** - * OpenID Connect Request Object Signing Algorithm - * JWS alg algorithm that MUST be used for signing Request Objects sent to the OP. All Request Objects - * from this Client MUST be rejected, if not signed with this algorithm. - */ - requestObjectSigningAlg?: string; - - /** - * OpenID Connect Sector Identifier URI - * URL using the https scheme to be used in calculating Pseudonymous Identifiers by the OP. The URL references a - * file with a single JSON array of redirect_uri values. - */ - sectorIdentifierUri?: string; - - /** - * OpenID Connect Request Userinfo Signed Response Algorithm - * JWS alg algorithm REQUIRED for signing UserInfo Responses. If this is specified, the response will be JWT - * serialized, and signed using JWS. - */ - userinfoSignedResponseAlg: string; - - /** - * OAuth 2.0 Client JSON Web Key Set - * Client's JSON Web Key Set [JWK] document, passed by value. - */ - jwks: Record; - - /** - * OAuth 2.0 Client JSON Web Key Set URL - * URL for the Client's JSON Web Key Set [JWK] document. - */ - jwksUri?: string; - - /** - * OAuth 2.0 Client Owner - * Owner is a string identifying the owner of the OAuth 2.0 Client. - */ - owner: string; - /** * OAuth 2.0 Client URI * ClientURI is a URL string of a web page providing information about the client. @@ -219,30 +140,12 @@ export class OAuth2Client { */ grantTypes: string[] | null; - /** - * Array of post-logout redirect URIs - * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. - */ - postLogoutRedirectUris?: string[]; - - /** - * Array of request URIs - * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. - */ - requestUris?: string[]; - /** * Array of response types * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. */ responseTypes: string[] | null; - /** - * Array of contacts - * StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage. - */ - contacts: string[] | null; - /** * OAuth 2.0 Client Logo URI * A URL string referencing the client's logo. @@ -266,24 +169,6 @@ export class OAuth2Client { */ tosUri: string; - /** - * SkipConsent skips the consent screen for this client. This field can only - * be set from the admin API. - */ - skipConsent: boolean; - - /** - * SkipLogoutConsent skips the logout consent screen for this client. This field can only - * be set from the admin API. - */ - skipLogoutConsent: boolean | null; - - /** - * OpenID Connect Subject Type - * Valid types include pairwise and public. - */ - subjectType: string; - /** * OAuth 2.0 Client Creation Date * CreatedAt returns the timestamp of the client's creation. @@ -296,19 +181,6 @@ export class OAuth2Client { */ updatedAt: string; - /** - * OpenID Connect Dynamic Client Registration Access Token - * RegistrationAccessToken can be used to update, get, or delete the OAuth2 Client. It is sent when creating a client - * using Dynamic Client Registration. - */ - registrationAccessToken: string; - - /** - * OpenID Connect Dynamic Client Registration URL - * RegistrationClientURI is the URL used to update, get, or delete the OAuth2 Client. - */ - registrationClientUri: string; - /** * Metadata - JSON object * JSONRawMessage represents a json.RawMessage that works well with JSON, SQL, and Swagger. @@ -327,41 +199,21 @@ export class OAuth2Client { clientCredentialsGrantAccessTokenLifespan = null, implicitGrantAccessTokenLifespan = null, implicitGrantIdTokenLifespan = null, - jwtBearerGrantAccessTokenLifespan = null, refreshTokenGrantAccessTokenLifespan = null, refreshTokenGrantIdTokenLifespan = null, refreshTokenGrantRefreshTokenLifespan = null, tokenEndpointAuthMethod, - tokenEndpointAuthSigningAlg, accessTokenStrategy, - backchannelLogoutSessionRequired = false, - backchannelLogoutUri, - frontchannelLogoutSessionRequired = false, - frontchannelLogoutUri, - requestObjectSigningAlg, - sectorIdentifierUri, - userinfoSignedResponseAlg, - jwks = {}, - jwksUri, - owner = "", clientUri = "", allowedCorsOrigins = [], audience = [], grantTypes = null, - postLogoutRedirectUris, - requestUris, responseTypes = null, - contacts = null, logoUri = "", policyUri = "", tosUri = "", - skipConsent = false, - skipLogoutConsent = null, - subjectType, createdAt, updatedAt, - registrationAccessToken, - registrationClientUri, metadata = {}, }: OAuth2ClientOptions) { this.clientId = clientId; @@ -375,41 +227,21 @@ export class OAuth2Client { this.clientCredentialsGrantAccessTokenLifespan = clientCredentialsGrantAccessTokenLifespan; this.implicitGrantAccessTokenLifespan = implicitGrantAccessTokenLifespan; this.implicitGrantIdTokenLifespan = implicitGrantIdTokenLifespan; - this.jwtBearerGrantAccessTokenLifespan = jwtBearerGrantAccessTokenLifespan; this.refreshTokenGrantAccessTokenLifespan = refreshTokenGrantAccessTokenLifespan; this.refreshTokenGrantIdTokenLifespan = refreshTokenGrantIdTokenLifespan; this.refreshTokenGrantRefreshTokenLifespan = refreshTokenGrantRefreshTokenLifespan; this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; - this.tokenEndpointAuthSigningAlg = tokenEndpointAuthSigningAlg; this.accessTokenStrategy = accessTokenStrategy; - this.backchannelLogoutSessionRequired = backchannelLogoutSessionRequired; - this.backchannelLogoutUri = backchannelLogoutUri; - this.frontchannelLogoutSessionRequired = frontchannelLogoutSessionRequired; - this.frontchannelLogoutUri = frontchannelLogoutUri; - this.requestObjectSigningAlg = requestObjectSigningAlg; - this.sectorIdentifierUri = sectorIdentifierUri; - this.userinfoSignedResponseAlg = userinfoSignedResponseAlg; - this.jwks = jwks; - this.jwksUri = jwksUri; - this.owner = owner; this.clientUri = clientUri; this.allowedCorsOrigins = allowedCorsOrigins; this.audience = audience; this.grantTypes = grantTypes; - this.postLogoutRedirectUris = postLogoutRedirectUris; - this.requestUris = requestUris; this.responseTypes = responseTypes; - this.contacts = contacts; this.logoUri = logoUri; this.policyUri = policyUri; this.tosUri = tosUri; - this.skipConsent = skipConsent; - this.skipLogoutConsent = skipLogoutConsent; - this.subjectType = subjectType; this.createdAt = createdAt; this.updatedAt = updatedAt; - this.registrationAccessToken = registrationAccessToken; - this.registrationClientUri = registrationClientUri; this.metadata = metadata; } diff --git a/lib/ts/recipe/oauth2provider/api/consent.ts b/lib/ts/recipe/oauth2provider/api/consent.ts deleted file mode 100644 index efc254432..000000000 --- a/lib/ts/recipe/oauth2provider/api/consent.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* 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. - */ - -import { normaliseHttpMethod, send200Response } from "../../../utils"; -import { APIInterface, APIOptions } from ".."; -import { UserContext } from "../../../types"; - -// TODO: separate post and get? -export default async function consent( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise { - if (normaliseHttpMethod(options.req.getMethod()) === "post") { - if (apiImplementation.consentPOST === undefined) { - return false; - } - const reqBody = await options.req.getJSONBody(); - let response = await apiImplementation.consentPOST({ - options, - accept: reqBody.accept, - consentChallenge: reqBody.consentChallenge, - grantScope: reqBody.grantScope, - remember: reqBody.remember, - userContext, - }); - if ("status" in response) { - send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } - } else { - if (apiImplementation.consentGET === undefined) { - return false; - } - const consentChallenge = - options.req.getKeyValueFromQuery("consentChallenge") ?? - options.req.getKeyValueFromQuery("consent_challenge"); - if (consentChallenge === undefined) { - throw new Error("TODO"); - } - let response = await apiImplementation.consentGET({ - options, - consentChallenge, - userContext, - }); - if ("status" in response) { - send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } - } - return true; -} diff --git a/lib/ts/recipe/oauth2provider/api/implementation.ts b/lib/ts/recipe/oauth2provider/api/implementation.ts index e301a1011..47eb72f24 100644 --- a/lib/ts/recipe/oauth2provider/api/implementation.ts +++ b/lib/ts/recipe/oauth2provider/api/implementation.ts @@ -13,7 +13,6 @@ * under the License. */ -import SuperTokens from "../../../supertokens"; import { APIInterface } from "../types"; import { handleInternalRedirects, loginGET } from "./utils"; @@ -49,91 +48,6 @@ export default function getAPIImplementation(): APIInterface { return { redirectTo: res.redirectTo }; }, - logoutGET: async ({ logoutChallenge, options, userContext }) => { - const request = await options.recipeImplementation.getLogoutRequest({ - challenge: logoutChallenge, - userContext, - }); - - const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; - return { - redirectTo: - appInfo - .getOrigin({ - request: options.req, - userContext: userContext, - }) - .getAsStringDangerous() + - appInfo.websiteBasePath.getAsStringDangerous() + - `/logout?challenge=${request.challenge}`, - }; - }, - - logoutPOST: async ({ logoutChallenge, accept, options, userContext }) => { - if (accept) { - const res = await options.recipeImplementation.acceptLogoutRequest({ - challenge: logoutChallenge, - userContext, - }); - return { redirectTo: res.redirectTo }; - } - await options.recipeImplementation.rejectLogoutRequest({ - challenge: logoutChallenge, - // error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, - userContext, - }); - const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; - return { - redirectTo: - appInfo - .getOrigin({ - request: options.req, - userContext: userContext, - }) - .getAsStringDangerous() + appInfo.websiteBasePath.getAsStringDangerous(), - }; - }, - - consentGET: async ({ consentChallenge, options, userContext }) => { - const request = await options.recipeImplementation.getConsentRequest({ - challenge: consentChallenge, - userContext, - }); - - const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; - return { - redirectTo: - appInfo - .getOrigin({ - request: options.req, - userContext: userContext, - }) - .getAsStringDangerous() + - appInfo.websiteBasePath.getAsStringDangerous() + - `/consent?challenge=${request.challenge}&scopes=${request.requestedScope}&client=${request.client}&`, - }; - }, - - consentPOST: async ({ consentChallenge, accept, remember, grantScope, options, userContext }) => { - const request = await options.recipeImplementation.getConsentRequest({ - challenge: consentChallenge, - userContext, - }); - const res = accept - ? await options.recipeImplementation.acceptConsentRequest({ - challenge: consentChallenge, - grantAccessTokenAudience: request.requestedAccessTokenAudience, - remember, - grantScope, - userContext, - }) - : await options.recipeImplementation.rejectConsentRequest({ - challenge: consentChallenge, - error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, - userContext, - }); - return { redirectTo: res.redirectTo }; - }, authGET: async ({ options, params, cookie, session, userContext }) => { const response = await options.recipeImplementation.authorization({ params, diff --git a/lib/ts/recipe/oauth2provider/api/logout.ts b/lib/ts/recipe/oauth2provider/api/logout.ts deleted file mode 100644 index c827a42e4..000000000 --- a/lib/ts/recipe/oauth2provider/api/logout.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* 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. - */ - -import { normaliseHttpMethod, send200Response } from "../../../utils"; -import { APIInterface, APIOptions } from ".."; -import { UserContext } from "../../../types"; - -// TODO: separate post and get? -export default async function logout( - apiImplementation: APIInterface, - options: APIOptions, - userContext: UserContext -): Promise { - if (normaliseHttpMethod(options.req.getMethod()) === "post") { - if (apiImplementation.logoutPOST === undefined) { - return false; - } - const reqBody = await options.req.getJSONBody(); - let response = await apiImplementation.logoutPOST({ - options, - accept: reqBody.accept, - logoutChallenge: reqBody.logoutChallenge, - userContext, - }); - if ("status" in response) { - send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } - } else { - if (apiImplementation.logoutGET === undefined) { - return false; - } - - const logoutChallenge = options.req.getKeyValueFromQuery("logoutChallenge"); - if (logoutChallenge === undefined) { - throw new Error("TODO"); - } - let response = await apiImplementation.logoutGET({ - options, - logoutChallenge, - userContext, - }); - if ("status" in response) { - send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } - } - return true; -} diff --git a/lib/ts/recipe/oauth2provider/constants.ts b/lib/ts/recipe/oauth2provider/constants.ts index 3cffb00ce..eb30a951e 100644 --- a/lib/ts/recipe/oauth2provider/constants.ts +++ b/lib/ts/recipe/oauth2provider/constants.ts @@ -16,8 +16,6 @@ export const OAUTH2_BASE_PATH = "/oauth2provider/"; export const LOGIN_PATH = "/oauth2provider/login"; -export const LOGOUT_PATH = "/oauth2provider/logout"; -export const CONSENT_PATH = "/oauth2provider/consent"; export const AUTH_PATH = "/oauth2provider/auth"; export const TOKEN_PATH = "/oauth2provider/token"; export const LOGIN_INFO_PATH = "/oauth2provider/login/info"; diff --git a/lib/ts/recipe/oauth2provider/recipe.ts b/lib/ts/recipe/oauth2provider/recipe.ts index 63b03e5b2..fac080143 100644 --- a/lib/ts/recipe/oauth2provider/recipe.ts +++ b/lib/ts/recipe/oauth2provider/recipe.ts @@ -21,21 +21,11 @@ import { Querier } from "../../querier"; import RecipeModule from "../../recipeModule"; import { APIHandled, HTTPMethod, JSONObject, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; import authGET from "./api/auth"; -import consentAPI from "./api/consent"; import APIImplementation from "./api/implementation"; import loginAPI from "./api/login"; -import logoutAPI from "./api/logout"; import tokenPOST from "./api/token"; import loginInfoGET from "./api/loginInfo"; -import { - AUTH_PATH, - CONSENT_PATH, - LOGIN_INFO_PATH, - LOGIN_PATH, - LOGOUT_PATH, - TOKEN_PATH, - USER_INFO_PATH, -} from "./constants"; +import { AUTH_PATH, LOGIN_INFO_PATH, LOGIN_PATH, TOKEN_PATH, USER_INFO_PATH } from "./constants"; import RecipeImplementation from "./recipeImplementation"; import { APIInterface, @@ -137,30 +127,6 @@ export default class Recipe extends RecipeModule { id: LOGIN_PATH, disabled: this.apiImpl.loginGET === undefined, }, - { - method: "post", - pathWithoutApiBasePath: new NormalisedURLPath(LOGOUT_PATH), - id: LOGOUT_PATH, - disabled: this.apiImpl.logoutPOST === undefined, - }, - { - method: "get", - pathWithoutApiBasePath: new NormalisedURLPath(LOGOUT_PATH), - id: LOGOUT_PATH, - disabled: this.apiImpl.logoutGET === undefined, - }, - { - method: "post", - pathWithoutApiBasePath: new NormalisedURLPath(CONSENT_PATH), - id: CONSENT_PATH, - disabled: this.apiImpl.consentPOST === undefined, - }, - { - method: "get", - pathWithoutApiBasePath: new NormalisedURLPath(CONSENT_PATH), - id: CONSENT_PATH, - disabled: this.apiImpl.consentGET === undefined, - }, { method: "post", pathWithoutApiBasePath: new NormalisedURLPath(TOKEN_PATH), @@ -209,12 +175,6 @@ export default class Recipe extends RecipeModule { if (id === LOGIN_PATH) { return loginAPI(this.apiImpl, options, userContext); } - if (id === LOGOUT_PATH) { - return logoutAPI(this.apiImpl, options, userContext); - } - if (id === CONSENT_PATH) { - return consentAPI(this.apiImpl, options, userContext); - } if (id === TOKEN_PATH) { return tokenPOST(this.apiImpl, options, userContext); } diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index 24c41fcb3..3ff6c05af 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -22,7 +22,6 @@ import { TypeNormalisedInput, ConsentRequest, LoginRequest, - LogoutRequest, PayloadBuilderFunction, UserInfoBuilderFunction, } from "./types"; @@ -127,7 +126,6 @@ export default function getRecipeInterface( loginChallenge: resp.data.login_challenge, loginSessionId: resp.data.login_session_id, oidcContext: resp.data.oidc_context, - requestUrl: resp.data.request_url, requestedAccessTokenAudience: resp.data.requested_access_token_audience, requestedScope: resp.data.requested_scope, skip: resp.data.skip, @@ -185,51 +183,6 @@ export default function getRecipeInterface( ), }; }, - - getLogoutRequest: async function (this: RecipeInterface, input): Promise { - const resp = await querier.sendGetRequest( - new NormalisedURLPath("/recipe/oauth2/admin/oauth2/auth/requests/logout"), - { logout_challenge: input.challenge }, - input.userContext - ); - - return { - challenge: resp.data.challenge, - client: OAuth2Client.fromAPIResponse(resp.data.client), - requestUrl: resp.data.request_url, - rpInitiated: resp.data.rp_initiated, - sid: resp.data.sid, - subject: resp.data.subject, - }; - }, - acceptLogoutRequest: async function (this: RecipeInterface, input): Promise<{ redirectTo: string }> { - const resp = await querier.sendPutRequest( - new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/consent/logout/accept`), - {}, - { - logout_challenge: input.challenge, - }, - input.userContext - ); - - return { - // TODO: FIXME!!! - redirectTo: resp.data.redirect_to.replace( - hydraPubDomain, - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ), - }; - }, - rejectLogoutRequest: async function (this: RecipeInterface, input): Promise { - await querier.sendPutRequest( - new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/consent/logout/reject`), - {}, - { - logout_challenge: input.challenge, - }, - input.userContext - ); - }, authorization: async function (this: RecipeInterface, input) { const resp = await querier.sendGetRequestWithResponseHeaders( new NormalisedURLPath(`/recipe/oauth2/pub/auth`), @@ -256,43 +209,41 @@ export default function getRecipeInterface( if (!user) { throw new Error("Should not happen"); } - if (consentRequest.skip || consentRequest.client?.skipConsent) { - const idToken = this.buildIdTokenPayload({ - user, - session: input.session, - defaultPayload: await getDefaultIdTokenPayload( - user, - consentRequest.requestedScope ?? [], - input.userContext - ), - scopes: consentRequest.requestedScope || [], - userContext: input.userContext, - }); - - const accessTokenPayload = this.buildAccessTokenPayload({ + const idToken = this.buildIdTokenPayload({ + user, + session: input.session, + defaultPayload: await getDefaultIdTokenPayload( user, - session: input.session, - defaultPayload: input.session.getAccessTokenPayload(input.userContext), // TODO: validate - userContext: input.userContext, - scopes: consentRequest.requestedScope || [], - }); - - const consentRes = await this.acceptConsentRequest({ - ...input, - challenge: consentRequest.challenge, - grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, - grantScope: consentRequest.requestedScope, - session: { - id_token: idToken, - access_token: accessTokenPayload, - }, - }); - - return { - redirectTo: consentRes.redirectTo, - setCookie: resp.headers.get("set-cookie") ?? undefined, - }; - } + consentRequest.requestedScope ?? [], + input.userContext + ), + scopes: consentRequest.requestedScope || [], + userContext: input.userContext, + }); + + const accessTokenPayload = this.buildAccessTokenPayload({ + user, + session: input.session, + defaultPayload: input.session.getAccessTokenPayload(input.userContext), // TODO: validate + userContext: input.userContext, + scopes: consentRequest.requestedScope || [], + }); + + const consentRes = await this.acceptConsentRequest({ + ...input, + challenge: consentRequest.challenge, + grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, + grantScope: consentRequest.requestedScope, + session: { + id_token: idToken, + access_token: accessTokenPayload, + }, + }); + + return { + redirectTo: consentRes.redirectTo, + setCookie: resp.headers.get("set-cookie") ?? undefined, + }; } return { redirectTo, setCookie: resp.headers.get("set-cookie") ?? undefined }; }, @@ -354,7 +305,13 @@ export default function getRecipeInterface( createOAuth2Client: async function (input, userContext) { let response = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth2/admin/clients`), - transformObjectKeys(input, "snake-case"), + { + ...transformObjectKeys(input, "snake-case"), + // TODO: these defaults should be set/enforced on the core side + accessTokenStrategy: "jwt", + skipConsent: true, + subjectType: "public", + }, userContext ); diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index 19adc8771..fdfb4e036 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -93,9 +93,6 @@ export type ConsentRequest = { // object (Contains optional information about the OpenID Connect request.) oidcContext?: any; - // RequestURL is the original OAuth 2.0 Authorization URL requested by the OAuth 2.0 client. It is the URL which initiates the OAuth 2.0 Authorization Code or OAuth 2.0 Implicit flow. This URL is typically not needed, but might come in handy if you want to deal with additional request parameters. - requestUrl?: string; - // Array of strings (StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage.) requestedAccessTokenAudience?: string[]; @@ -139,26 +136,6 @@ export type LoginRequest = { subject: string; }; -export type LogoutRequest = { - // Challenge is the identifier ("logout challenge") of the logout authentication request. It is used to identify the session. - challenge: string; - - // OAuth 2.0 Clients are used to perform OAuth 2.0 and OpenID Connect flows. Usually, OAuth 2.0 clients are generated for applications which want to consume your OAuth 2.0 or OpenID Connect capabilities. - client: OAuth2Client; - - // RequestURL is the original Logout URL requested. - requestUrl: string; - - // RPInitiated is set to true if the request was initiated by a Relying Party (RP), also known as an OAuth 2.0 Client. - rpInitiated: boolean; - - // SessionID is the login session ID that was requested to log out. - sid: string; - - // Subject is the user for whom the logout was request. - subject: string; -}; - export type TokenInfo = { // The access token issued by the authorization server. access_token: string; @@ -279,10 +256,6 @@ export type RecipeInterface = { userContext: UserContext; }): Promise<{ redirectTo: string }>; - getLogoutRequest(input: { challenge: string; userContext: UserContext }): Promise; - acceptLogoutRequest(input: { challenge: string; userContext: UserContext }): Promise<{ redirectTo: string }>; - rejectLogoutRequest(input: { challenge: string; userContext: UserContext }): Promise; - getOAuth2Clients( input: GetOAuth2ClientsInput, userContext: UserContext @@ -379,7 +352,6 @@ export type RecipeInterface = { }; export type APIInterface = { - // TODO: add json versions? loginGET: | undefined | ((input: { @@ -399,41 +371,6 @@ export type APIInterface = { userContext: UserContext; }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); - logoutGET: - | undefined - | ((input: { - logoutChallenge: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); - - logoutPOST: - | undefined - | ((input: { - logoutChallenge: string; - accept: boolean; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); - - consentGET: - | undefined - | ((input: { - consentChallenge: string; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); - - consentPOST: - | undefined - | ((input: { - consentChallenge: string; - accept: boolean; - grantScope: string[]; - remember: boolean; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); authGET: | undefined | ((input: { @@ -472,9 +409,14 @@ export type APIInterface = { export type OAuth2ClientOptions = { clientId: string; clientSecret: string; + createdAt: string; + updatedAt: string; + clientName: string; + scope: string; redirectUris?: string[] | null; + allowedCorsOrigins?: string[]; authorizationCodeGrantAccessTokenLifespan?: string | null; authorizationCodeGrantIdTokenLifespan?: string | null; @@ -482,44 +424,21 @@ export type OAuth2ClientOptions = { clientCredentialsGrantAccessTokenLifespan?: string | null; implicitGrantAccessTokenLifespan?: string | null; implicitGrantIdTokenLifespan?: string | null; - jwtBearerGrantAccessTokenLifespan?: string | null; refreshTokenGrantAccessTokenLifespan?: string | null; refreshTokenGrantIdTokenLifespan?: string | null; refreshTokenGrantRefreshTokenLifespan?: string | null; tokenEndpointAuthMethod: string; - tokenEndpointAuthSigningAlg?: string; accessTokenStrategy?: "jwt" | "opaque"; - backchannelLogoutSessionRequired?: boolean; - backchannelLogoutUri?: string; - frontchannelLogoutSessionRequired?: boolean; - frontchannelLogoutUri?: string; - requestObjectSigningAlg?: string; - sectorIdentifierUri?: string; - userinfoSignedResponseAlg: string; - - jwks?: Record; - jwksUri?: string; - owner?: string; - clientUri?: string; - allowedCorsOrigins?: string[]; audience?: string[]; grantTypes?: string[] | null; - postLogoutRedirectUris?: string[]; - requestUris?: string[]; responseTypes?: string[] | null; - contacts?: string[] | null; + + clientUri?: string; logoUri?: string; policyUri?: string; tosUri?: string; - skipConsent?: boolean; - skipLogoutConsent?: boolean | null; - subjectType: string; - createdAt: string; - updatedAt: string; - registrationAccessToken: string; - registrationClientUri: string; metadata?: Record; }; @@ -545,30 +464,17 @@ export type GetOAuth2ClientsInput = { owner?: string; }; -export type CreateOAuth2ClientInput = Partial>; +export type CreateOAuth2ClientInput = Partial< + Omit +>; export type UpdateOAuth2ClientInput = NonNullableProperties< - Omit< - CreateOAuth2ClientInput, - | "redirectUris" - | "grantTypes" - | "postLogoutRedirectUris" - | "requestUris" - | "responseTypes" - | "contacts" - | "registrationAccessToken" - | "registrationClientUri" - | "metadata" - > + Omit > & { + clientId: string; redirectUris?: string[] | null; grantTypes?: string[] | null; - postLogoutRedirectUris?: string[] | null; - requestUris?: string[] | null; responseTypes?: string[] | null; - contacts?: string[] | null; - registrationAccessToken?: string | null; - registrationClientUri?: string | null; metadata?: Record | null; }; From 69744206cc9a85f4131fe74a4bfc612d92c6f7bc Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Mon, 29 Jul 2024 14:23:48 +0200 Subject: [PATCH 18/35] fix: add workaround to validate access/idtokens --- lib/build/combinedRemoteJWKSet.js | 16 +++++ .../oauth2provider/recipeImplementation.js | 58 +++++++++++++------ lib/ts/combinedRemoteJWKSet.ts | 10 ++++ .../oauth2provider/recipeImplementation.ts | 47 +++++++++------ test/test-server/src/oauth2provider.ts | 26 +++++++++ 5 files changed, 119 insertions(+), 38 deletions(-) diff --git a/lib/build/combinedRemoteJWKSet.js b/lib/build/combinedRemoteJWKSet.js index 845e38415..e1c8a2288 100644 --- a/lib/build/combinedRemoteJWKSet.js +++ b/lib/build/combinedRemoteJWKSet.js @@ -15,6 +15,11 @@ function resetCombinedJWKS() { combinedJWKS = undefined; } exports.resetCombinedJWKS = resetCombinedJWKS; +// TODO: remove this after proper core support +const hydraJWKS = jose_1.createRemoteJWKSet(new URL("http://localhost:4444/.well-known/jwks.json"), { + cooldownDuration: constants_1.JWKCacheCooldownInMs, + cacheMaxAge: constants_1.JWKCacheMaxAgeInMs, +}); /** The function returned by this getter fetches all JWKs from the first available core instance. This combines the other JWKS functions to become error resistant. @@ -33,7 +38,18 @@ function getCombinedJWKS() { }) ); combinedJWKS = async (...args) => { + var _a, _b, _c, _d; let lastError = undefined; + if ( + !((_b = (_a = args[0]) === null || _a === void 0 ? void 0 : _a.kid) === null || _b === void 0 + ? void 0 + : _b.startsWith("s-")) && + !((_d = (_c = args[0]) === null || _c === void 0 ? void 0 : _c.kid) === null || _d === void 0 + ? void 0 + : _d.startsWith("d-")) + ) { + return hydraJWKS(...args); + } if (JWKS.length === 0) { throw Error( "No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using." diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 88cd6c1ba..5b95b9bc1 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -202,7 +202,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, }; }, authorization: async function (input) { - var _a, _b, _c; + var _a, _b, _c, _d, _e; const resp = await querier.sendGetRequestWithResponseHeaders( new normalisedURLPath_1.default(`/recipe/oauth2/pub/auth`), input.params, @@ -240,7 +240,19 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, const accessTokenPayload = this.buildAccessTokenPayload({ user, session: input.session, - defaultPayload: input.session.getAccessTokenPayload(input.userContext), + defaultPayload: Object.assign( + Object.assign({}, input.session.getAccessTokenPayload(input.userContext)), + { + iss: appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(), + scope: + (_c = + (_b = consentRequest.requestedScope) === null || _b === void 0 + ? void 0 + : _b.join(" ")) !== null && _c !== void 0 + ? _c + : "", + } + ), userContext: input.userContext, scopes: consentRequest.requestedScope || [], }); @@ -257,12 +269,12 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, ); return { redirectTo: consentRes.redirectTo, - setCookie: (_b = resp.headers.get("set-cookie")) !== null && _b !== void 0 ? _b : undefined, + setCookie: (_d = resp.headers.get("set-cookie")) !== null && _d !== void 0 ? _d : undefined, }; } return { redirectTo, - setCookie: (_c = resp.headers.get("set-cookie")) !== null && _c !== void 0 ? _c : undefined, + setCookie: (_e = resp.headers.get("set-cookie")) !== null && _e !== void 0 ? _e : undefined, }; }, token: async function (input) { @@ -318,9 +330,9 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients`), Object.assign(Object.assign({}, utils_1.transformObjectKeys(input, "snake-case")), { // TODO: these defaults should be set/enforced on the core side - accessTokenStrategy: "jwt", - skipConsent: true, - subjectType: "public", + access_token_strategy: "jwt", + skip_consent: true, + subject_type: "public", }), userContext ); @@ -395,12 +407,16 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, validateOAuth2AccessToken: async function (input) { const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS())).payload; // TODO: make this configurable? - const expectedIssuer = - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); - if (payload.iss !== expectedIssuer) { - throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); - } - if (input.expectedAudience !== undefined && payload.aud !== input.expectedAudience) { + // const expectedIssuer = + // appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); + // if (payload.iss !== expectedIssuer) { + // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); + // } + if ( + input.expectedAudience !== undefined && + payload.aud !== input.expectedAudience && + !(payload.aud instanceof Array && payload.aud.includes(input.expectedAudience)) + ) { throw new Error("Audience mismatch: this token doesn't belong to the specified client"); } // TODO: add a check to make sure this is the right token type as they can be signed with the same key @@ -409,12 +425,16 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, validateOAuth2IdToken: async function (input) { const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS())).payload; // TODO: make this configurable? - const expectedIssuer = - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); - if (input.expectedAudience !== undefined && payload.iss !== expectedIssuer) { - throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); - } - if (input.expectedAudience !== undefined && payload.aud !== input.expectedAudience) { + // const expectedIssuer = + // appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); + // if (input.expectedAudience !== undefined && payload.iss !== expectedIssuer) { + // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); + // } + if ( + input.expectedAudience !== undefined && + payload.aud !== input.expectedAudience && + !(payload.aud instanceof Array && payload.aud.includes(input.expectedAudience)) + ) { throw new Error("Audience mismatch: this token doesn't belong to the specified client"); } // TODO: add a check to make sure this is the right token type as they can be signed with the same key diff --git a/lib/ts/combinedRemoteJWKSet.ts b/lib/ts/combinedRemoteJWKSet.ts index 628cd5b8e..aa8fe1417 100644 --- a/lib/ts/combinedRemoteJWKSet.ts +++ b/lib/ts/combinedRemoteJWKSet.ts @@ -14,6 +14,11 @@ export function resetCombinedJWKS() { combinedJWKS = undefined; } +// TODO: remove this after proper core support +const hydraJWKS = createRemoteJWKSet(new URL("http://localhost:4444/.well-known/jwks.json"), { + cooldownDuration: JWKCacheCooldownInMs, + cacheMaxAge: JWKCacheMaxAgeInMs, +}); /** The function returned by this getter fetches all JWKs from the first available core instance. This combines the other JWKS functions to become error resistant. @@ -34,6 +39,11 @@ export function getCombinedJWKS() { combinedJWKS = async (...args) => { let lastError = undefined; + + if (!args[0]?.kid?.startsWith("s-") && !args[0]?.kid?.startsWith("d-")) { + return hydraJWKS(...args); + } + if (JWKS.length === 0) { throw Error( "No SuperTokens core available to query. Please pass supertokens > connectionURI to the init function, or override all the functions of the recipe you are using." diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index 3ff6c05af..269aa2f70 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -221,10 +221,15 @@ export default function getRecipeInterface( userContext: input.userContext, }); - const accessTokenPayload = this.buildAccessTokenPayload({ + const accessTokenPayload = await this.buildAccessTokenPayload({ user, session: input.session, - defaultPayload: input.session.getAccessTokenPayload(input.userContext), // TODO: validate + defaultPayload: { + ...input.session.getAccessTokenPayload(input.userContext), // TODO: validate based on access token structure rfc + iss: appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(), + scope: consentRequest.requestedScope?.join(" ") ?? "", + aud: [consentRequest.client!.clientId], + }, userContext: input.userContext, scopes: consentRequest.requestedScope || [], }); @@ -308,9 +313,9 @@ export default function getRecipeInterface( { ...transformObjectKeys(input, "snake-case"), // TODO: these defaults should be set/enforced on the core side - accessTokenStrategy: "jwt", - skipConsent: true, - subjectType: "public", + access_token_strategy: "jwt", + skip_consent: true, + subject_type: "public", }, userContext ); @@ -392,13 +397,17 @@ export default function getRecipeInterface( const payload = (await jose.jwtVerify(input.token, getCombinedJWKS())).payload; // TODO: make this configurable? - const expectedIssuer = - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); - if (payload.iss !== expectedIssuer) { - throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); - } - - if (input.expectedAudience !== undefined && payload.aud !== input.expectedAudience) { + // const expectedIssuer = + // appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); + // if (payload.iss !== expectedIssuer) { + // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); + // } + + // TODO: Fix this + const aud = + (payload.ext as any)?.aud ?? + (payload.aud instanceof Array ? payload.aud : payload.aud?.split(" ") ?? []); + if (input.expectedAudience !== undefined && !aud.includes(input.expectedAudience)) { throw new Error("Audience mismatch: this token doesn't belong to the specified client"); } @@ -410,13 +419,13 @@ export default function getRecipeInterface( const payload = (await jose.jwtVerify(input.token, getCombinedJWKS())).payload; // TODO: make this configurable? - const expectedIssuer = - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); - if (input.expectedAudience !== undefined && payload.iss !== expectedIssuer) { - throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); - } - - if (input.expectedAudience !== undefined && payload.aud !== input.expectedAudience) { + // const expectedIssuer = + // appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); + // if (input.expectedAudience !== undefined && payload.iss !== expectedIssuer) { + // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); + // } + const aud = payload.aud instanceof Array ? payload.aud : payload.aud?.split(" ") ?? []; + if (input.expectedAudience !== undefined && !aud.includes(input.expectedAudience)) { throw new Error("Audience mismatch: this token doesn't belong to the specified client"); } diff --git a/test/test-server/src/oauth2provider.ts b/test/test-server/src/oauth2provider.ts index 8cd6afcc8..06f732ebe 100644 --- a/test/test-server/src/oauth2provider.ts +++ b/test/test-server/src/oauth2provider.ts @@ -41,6 +41,32 @@ const router = Router() } catch (e) { next(e); } + }) + .post("/validateoauth2accesstoken", async (req, res, next) => { + try { + logDebugMessage("OAuth2Provider:validateOAuth2AccessToken %j", req.body); + const response = await OAuth2Provider.validateOAuth2AccessToken( + req.body.token, + req.body.expectedAudience, + req.body.userContext + ); + res.json(response); + } catch (e) { + next(e); + } + }) + .post("/validateoauth2idtoken", async (req, res, next) => { + try { + logDebugMessage("OAuth2Provider:validateOAuth2IdToken %j", req.body); + const response = await OAuth2Provider.validateOAuth2IdToken( + req.body.token, + req.body.expectedAudience, + req.body.userContext + ); + res.json(response); + } catch (e) { + next(e); + } }); export default router; From a351c0a3b6968dd31663d34ffa67fb0454e9cd91 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Mon, 29 Jul 2024 19:43:03 +0530 Subject: [PATCH 19/35] fix: OAuth2 fixes (#900) --- lib/build/querier.js | 4 +- .../oauth2client/recipeImplementation.js | 11 +++- .../oauth2provider/api/implementation.js | 6 +- .../recipe/oauth2provider/api/userInfo.js | 19 +++--- .../oauth2provider/recipeImplementation.js | 63 +++++++++++-------- lib/build/recipe/oauth2provider/types.d.ts | 8 +-- lib/ts/querier.ts | 4 +- .../oauth2client/recipeImplementation.ts | 4 +- .../oauth2provider/api/implementation.ts | 7 +-- lib/ts/recipe/oauth2provider/api/userInfo.ts | 19 +++--- .../oauth2provider/recipeImplementation.ts | 27 ++++---- lib/ts/recipe/oauth2provider/types.ts | 2 +- 12 files changed, 88 insertions(+), 86 deletions(-) diff --git a/lib/build/querier.js b/lib/build/querier.js index 381491527..f156a8a40 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -97,7 +97,9 @@ class Querier { let headers = { "cdi-version": apiVersion, }; - if (!isForm) { + if (isForm) { + headers["content-type"] = "application/x-www-form-urlencoded"; + } else { headers["content-type"] = "application/json; charset=utf-8"; } if (Querier.apiKey !== undefined) { diff --git a/lib/build/recipe/oauth2client/recipeImplementation.js b/lib/build/recipe/oauth2client/recipeImplementation.js index 012749192..3ea769a72 100644 --- a/lib/build/recipe/oauth2client/recipeImplementation.js +++ b/lib/build/recipe/oauth2client/recipeImplementation.js @@ -106,6 +106,7 @@ function getRecipeImplementation(_querier, config) { return tokenResponse.jsonResponse; }, getUserInfo: async function ({ providerConfig, oAuthTokens }) { + var _a, _b; let jwks; const accessToken = oAuthTokens["access_token"]; const idToken = oAuthTokens["id_token"]; @@ -146,9 +147,15 @@ function getRecipeImplementation(_querier, config) { rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; } let userId = undefined; - if (rawUserInfoFromProvider.fromIdTokenPayload !== undefined) { + if ( + ((_a = rawUserInfoFromProvider.fromIdTokenPayload) === null || _a === void 0 ? void 0 : _a.sub) !== + undefined + ) { userId = rawUserInfoFromProvider.fromIdTokenPayload["sub"]; - } else if (rawUserInfoFromProvider.fromUserInfoAPI !== undefined) { + } else if ( + ((_b = rawUserInfoFromProvider.fromUserInfoAPI) === null || _b === void 0 ? void 0 : _b.sub) !== + undefined + ) { userId = rawUserInfoFromProvider.fromUserInfoAPI["sub"]; } if (userId === undefined) { diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index 4a5f9df54..86d5403ad 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -81,17 +81,13 @@ function getAPIImplementation() { }; }, userInfoGET: async ({ accessTokenPayload, user, scopes, tenantId, options, userContext }) => { - const userInfo = await options.recipeImplementation.buildUserInfo({ + return options.recipeImplementation.buildUserInfo({ user, accessTokenPayload, scopes, tenantId, userContext, }); - return { - status: "OK", - info: userInfo, - }; }, }; } diff --git a/lib/build/recipe/oauth2provider/api/userInfo.js b/lib/build/recipe/oauth2provider/api/userInfo.js index b40716527..cabec7ef8 100644 --- a/lib/build/recipe/oauth2provider/api/userInfo.js +++ b/lib/build/recipe/oauth2provider/api/userInfo.js @@ -36,13 +36,14 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { const accessToken = authHeader.replace(/^Bearer /, "").trim(); let accessTokenPayload; try { - accessTokenPayload = await recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.validateOAuth2AccessToken({ - token: accessToken, - // TODO: expectedAudience? - userContext, - }); + const { + payload, + } = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ + token: accessToken, + // TODO: expectedAudience? + userContext, + }); + accessTokenPayload = payload; } catch (error) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); utils_1.sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 400); @@ -52,7 +53,7 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { accessTokenPayload === null || typeof accessTokenPayload !== "object" || typeof accessTokenPayload.sub !== "string" || - typeof accessTokenPayload.scope !== "string" + !Array.isArray(accessTokenPayload.scp) ) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); utils_1.sendNon200ResponseWithMessage(options.res, "Malformed access token payload", 400); @@ -73,7 +74,7 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { accessTokenPayload, user, tenantId, - scopes: accessTokenPayload.scope.split(" "), + scopes: accessTokenPayload.scp, options, userContext, }); diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 5b95b9bc1..720533812 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -62,6 +62,15 @@ const utils_1 = require("../../utils"); const OAuth2Client_1 = require("./OAuth2Client"); const __1 = require("../.."); const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); +// TODO: Remove this core changes are done +function getUpdatedRedirectTo(appInfo, redirectTo) { + return redirectTo + .replace( + querier_1.hydraPubDomain, + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() + ) + .replace("oauth2", "oauth2provider"); +} function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, getDefaultUserInfoPayload) { return { getLoginRequest: async function (input) { @@ -103,10 +112,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, ); return { // TODO: FIXME!!! - redirectTo: resp.data.redirect_to.replace( - querier_1.hydraPubDomain, - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ), + redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), }; }, rejectLoginRequest: async function (input) { @@ -126,10 +132,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, ); return { // TODO: FIXME!!! - redirectTo: resp.data.redirect_to.replace( - querier_1.hydraPubDomain, - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ), + redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), }; }, getConsentRequest: async function (input) { @@ -172,10 +175,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, ); return { // TODO: FIXME!!! - redirectTo: resp.data.redirect_to.replace( - querier_1.hydraPubDomain, - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ), + redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), }; }, rejectConsentRequest: async function (input) { @@ -195,10 +195,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, ); return { // TODO: FIXME!!! - redirectTo: resp.data.redirect_to.replace( - querier_1.hydraPubDomain, - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ), + redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), }; }, authorization: async function (input) { @@ -237,7 +234,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, scopes: consentRequest.requestedScope || [], userContext: input.userContext, }); - const accessTokenPayload = this.buildAccessTokenPayload({ + const accessTokenPayload = await this.buildAccessTokenPayload({ user, session: input.session, defaultPayload: Object.assign( @@ -251,6 +248,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, : _b.join(" ")) !== null && _c !== void 0 ? _c : "", + aud: [consentRequest.client.clientId], } ), userContext: input.userContext, @@ -405,6 +403,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext); }, validateOAuth2AccessToken: async function (input) { + var _a, _b, _c, _d; const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS())).payload; // TODO: make this configurable? // const expectedIssuer = @@ -412,17 +411,24 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, // if (payload.iss !== expectedIssuer) { // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); // } - if ( - input.expectedAudience !== undefined && - payload.aud !== input.expectedAudience && - !(payload.aud instanceof Array && payload.aud.includes(input.expectedAudience)) - ) { + // TODO: Fix this + const aud = + (_b = (_a = payload.ext) === null || _a === void 0 ? void 0 : _a.aud) !== null && _b !== void 0 + ? _b + : payload.aud instanceof Array + ? payload.aud + : (_d = (_c = payload.aud) === null || _c === void 0 ? void 0 : _c.split(" ")) !== null && + _d !== void 0 + ? _d + : []; + if (input.expectedAudience !== undefined && !aud.includes(input.expectedAudience)) { throw new Error("Audience mismatch: this token doesn't belong to the specified client"); } // TODO: add a check to make sure this is the right token type as they can be signed with the same key return { status: "OK", payload: payload }; }, validateOAuth2IdToken: async function (input) { + var _a, _b; const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS())).payload; // TODO: make this configurable? // const expectedIssuer = @@ -430,11 +436,14 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, // if (input.expectedAudience !== undefined && payload.iss !== expectedIssuer) { // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); // } - if ( - input.expectedAudience !== undefined && - payload.aud !== input.expectedAudience && - !(payload.aud instanceof Array && payload.aud.includes(input.expectedAudience)) - ) { + const aud = + payload.aud instanceof Array + ? payload.aud + : (_b = (_a = payload.aud) === null || _a === void 0 ? void 0 : _a.split(" ")) !== null && + _b !== void 0 + ? _b + : []; + if (input.expectedAudience !== undefined && !aud.includes(input.expectedAudience)) { throw new Error("Audience mismatch: this token doesn't belong to the specified client"); } // TODO: add a check to make sure this is the right token type as they can be signed with the same key diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index 8b10f63a2..e5e56e94d 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -308,13 +308,7 @@ export declare type APIInterface = { tenantId: string; options: APIOptions; userContext: UserContext; - }) => Promise< - | { - status: "OK"; - info: JSONObject; - } - | GeneralErrorResponse - >); + }) => Promise); }; export declare type OAuth2ClientOptions = { clientId: string; diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index 7832e443a..0ad82f47b 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -144,7 +144,9 @@ export class Querier { let headers: any = { "cdi-version": apiVersion, }; - if (!isForm) { + if (isForm) { + headers["content-type"] = "application/x-www-form-urlencoded"; + } else { headers["content-type"] = "application/json; charset=utf-8"; } if (Querier.apiKey !== undefined) { diff --git a/lib/ts/recipe/oauth2client/recipeImplementation.ts b/lib/ts/recipe/oauth2client/recipeImplementation.ts index 68748247b..11b277965 100644 --- a/lib/ts/recipe/oauth2client/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2client/recipeImplementation.ts @@ -201,9 +201,9 @@ export default function getRecipeImplementation(_querier: Querier, config: TypeN let userId: string | undefined = undefined; - if (rawUserInfoFromProvider.fromIdTokenPayload !== undefined) { + if (rawUserInfoFromProvider.fromIdTokenPayload?.sub !== undefined) { userId = rawUserInfoFromProvider.fromIdTokenPayload["sub"]; - } else if (rawUserInfoFromProvider.fromUserInfoAPI !== undefined) { + } else if (rawUserInfoFromProvider.fromUserInfoAPI?.sub !== undefined) { userId = rawUserInfoFromProvider.fromUserInfoAPI["sub"]; } diff --git a/lib/ts/recipe/oauth2provider/api/implementation.ts b/lib/ts/recipe/oauth2provider/api/implementation.ts index 47eb72f24..a3acfdfe1 100644 --- a/lib/ts/recipe/oauth2provider/api/implementation.ts +++ b/lib/ts/recipe/oauth2provider/api/implementation.ts @@ -85,18 +85,13 @@ export default function getAPIImplementation(): APIInterface { }; }, userInfoGET: async ({ accessTokenPayload, user, scopes, tenantId, options, userContext }) => { - const userInfo = await options.recipeImplementation.buildUserInfo({ + return options.recipeImplementation.buildUserInfo({ user, accessTokenPayload, scopes, tenantId, userContext, }); - - return { - status: "OK", - info: userInfo, - }; }, }; } diff --git a/lib/ts/recipe/oauth2provider/api/userInfo.ts b/lib/ts/recipe/oauth2provider/api/userInfo.ts index 6a7a07828..4c9ff043c 100644 --- a/lib/ts/recipe/oauth2provider/api/userInfo.ts +++ b/lib/ts/recipe/oauth2provider/api/userInfo.ts @@ -43,13 +43,14 @@ export default async function userInfoGET( let accessTokenPayload: JSONObject; try { - accessTokenPayload = await OAuth2ProviderRecipe.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken( - { - token: accessToken, - // TODO: expectedAudience? - userContext, - } - ); + const { + payload, + } = await OAuth2ProviderRecipe.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ + token: accessToken, + // TODO: expectedAudience? + userContext, + }); + accessTokenPayload = payload; } catch (error) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 400); @@ -60,7 +61,7 @@ export default async function userInfoGET( accessTokenPayload === null || typeof accessTokenPayload !== "object" || typeof accessTokenPayload.sub !== "string" || - typeof accessTokenPayload.scope !== "string" + !Array.isArray(accessTokenPayload.scp) ) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); sendNon200ResponseWithMessage(options.res, "Malformed access token payload", 400); @@ -81,7 +82,7 @@ export default async function userInfoGET( accessTokenPayload, user, tenantId, - scopes: accessTokenPayload.scope.split(" "), + scopes: accessTokenPayload.scp as string[], options, userContext, }); diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index 269aa2f70..9c6366b99 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -31,6 +31,13 @@ import { getUser } from "../.."; import { getCombinedJWKS } from "../../combinedRemoteJWKSet"; import { JSONObject } from "@loopback/core"; +// TODO: Remove this core changes are done +function getUpdatedRedirectTo(appInfo: NormalisedAppinfo, redirectTo: string) { + return redirectTo + .replace(hydraPubDomain, appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous()) + .replace("oauth2", "oauth2provider"); +} + export default function getRecipeInterface( querier: Querier, _config: TypeNormalisedInput, @@ -80,10 +87,7 @@ export default function getRecipeInterface( return { // TODO: FIXME!!! - redirectTo: resp.data.redirect_to.replace( - hydraPubDomain, - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ), + redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), }; }, rejectLoginRequest: async function (this: RecipeInterface, input): Promise<{ redirectTo: string }> { @@ -104,10 +108,7 @@ export default function getRecipeInterface( return { // TODO: FIXME!!! - redirectTo: resp.data.redirect_to.replace( - hydraPubDomain, - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ), + redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), }; }, getConsentRequest: async function (this: RecipeInterface, input): Promise { @@ -152,10 +153,7 @@ export default function getRecipeInterface( return { // TODO: FIXME!!! - redirectTo: resp.data.redirect_to.replace( - hydraPubDomain, - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ), + redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), }; }, @@ -177,10 +175,7 @@ export default function getRecipeInterface( return { // TODO: FIXME!!! - redirectTo: resp.data.redirect_to.replace( - hydraPubDomain, - appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() - ), + redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), }; }, authorization: async function (this: RecipeInterface, input) { diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index fdfb4e036..dbe02ba1d 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -403,7 +403,7 @@ export type APIInterface = { tenantId: string; options: APIOptions; userContext: UserContext; - }) => Promise<{ status: "OK"; info: JSONObject } | GeneralErrorResponse>); + }) => Promise); }; export type OAuth2ClientOptions = { From 6bab7f533ee43cbbbca8606500a32bc336deb163 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Fri, 2 Aug 2024 00:34:28 +0200 Subject: [PATCH 20/35] feat: review fixes --- lib/build/querier.d.ts | 1 - lib/build/querier.js | 11 +- lib/build/recipe/oauth2provider/api/auth.js | 8 +- .../oauth2provider/api/implementation.js | 20 +-- lib/build/recipe/oauth2provider/api/login.js | 86 ++++------- .../recipe/oauth2provider/api/loginInfo.js | 11 +- lib/build/recipe/oauth2provider/api/token.js | 6 +- .../recipe/oauth2provider/api/userInfo.js | 14 +- lib/build/recipe/oauth2provider/api/utils.js | 33 +++-- .../recipe/oauth2provider/constants.d.ts | 12 +- lib/build/recipe/oauth2provider/constants.js | 12 +- lib/build/recipe/oauth2provider/index.d.ts | 21 ++- lib/build/recipe/oauth2provider/index.js | 25 +++- lib/build/recipe/oauth2provider/recipe.js | 10 +- .../oauth2provider/recipeImplementation.js | 139 ++++++++++++------ lib/build/recipe/oauth2provider/types.d.ts | 55 ++++--- lib/build/recipe/session/accessToken.js | 3 + lib/build/recipe/session/constants.js | 1 + lib/build/recipeModule.d.ts | 2 +- lib/ts/querier.ts | 2 +- lib/ts/recipe/oauth2provider/api/auth.ts | 8 +- .../oauth2provider/api/implementation.ts | 20 +-- lib/ts/recipe/oauth2provider/api/login.ts | 76 ++++------ lib/ts/recipe/oauth2provider/api/loginInfo.ts | 6 +- lib/ts/recipe/oauth2provider/api/token.ts | 9 +- lib/ts/recipe/oauth2provider/api/userInfo.ts | 14 +- lib/ts/recipe/oauth2provider/api/utils.ts | 29 ++-- lib/ts/recipe/oauth2provider/constants.ts | 12 +- lib/ts/recipe/oauth2provider/index.ts | 49 +++++- lib/ts/recipe/oauth2provider/recipe.ts | 10 +- .../oauth2provider/recipeImplementation.ts | 111 +++++++++----- lib/ts/recipe/oauth2provider/types.ts | 57 ++++--- lib/ts/recipe/session/accessToken.ts | 4 + lib/ts/recipe/session/constants.ts | 1 + lib/ts/recipeModule.ts | 2 +- test/test-server/src/oauth2provider.ts | 18 ++- 36 files changed, 521 insertions(+), 377 deletions(-) diff --git a/lib/build/querier.d.ts b/lib/build/querier.d.ts index effa557c9..bc25a61fe 100644 --- a/lib/build/querier.d.ts +++ b/lib/build/querier.d.ts @@ -4,7 +4,6 @@ import NormalisedURLPath from "./normalisedURLPath"; import { UserContext } from "./types"; import { NetworkInterceptor } from "./types"; export declare const hydraPubDomain: string; -export declare const hydraPubPathPrefix = "/recipe/oauth2/pub"; export declare class Querier { private static initCalled; private static hosts; diff --git a/lib/build/querier.js b/lib/build/querier.js index 9ea5e5371..9cc9680bd 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -6,7 +6,7 @@ var __importDefault = }; var _a, _b; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Querier = exports.hydraPubPathPrefix = exports.hydraPubDomain = void 0; +exports.Querier = exports.hydraPubDomain = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the @@ -30,7 +30,7 @@ const logger_1 = require("./logger"); const supertokens_1 = __importDefault(require("./supertokens")); exports.hydraPubDomain = (_a = process.env.HYDRA_PUB) !== null && _a !== void 0 ? _a : "http://localhost:4444"; // This will be used as a domain for paths starting with hydraPubPathPrefix const hydraAdmDomain = (_b = process.env.HYDRA_ADM) !== null && _b !== void 0 ? _b : "http://localhost:4445"; // This will be used as a domain for paths starting with hydraAdmPathPrefix -exports.hydraPubPathPrefix = "/recipe/oauth2/pub"; // Replaced with "/oauth2" when sending the request (/recipe/oauth2/pub/token -> /oauth2/token) +const hydraPubPathPrefix = "/recipe/oauth2/pub"; // Replaced with "/oauth2" when sending the request (/recipe/oauth2/pub/token -> /oauth2/token) const hydraAdmPathPrefix = "/recipe/oauth2/admin"; // Replaced with "/admin" when sending the request (/recipe/oauth2/admin/clients -> /admin/clients) class Querier { // we have rIdToCore so that recipes can force change the rId sent to core. This is a hack until the core is able @@ -466,12 +466,11 @@ class Querier { let currentDomain = this.__hosts[Querier.lastTriedIndex].domain.getAsStringDangerous(); let currentBasePath = this.__hosts[Querier.lastTriedIndex].basePath.getAsStringDangerous(); let strPath = path.getAsStringDangerous(); - const isHydraAPICall = - strPath.startsWith(hydraAdmPathPrefix) || strPath.startsWith(exports.hydraPubPathPrefix); - if (strPath.startsWith(exports.hydraPubPathPrefix)) { + const isHydraAPICall = strPath.startsWith(hydraAdmPathPrefix) || strPath.startsWith(hydraPubPathPrefix); + if (strPath.startsWith(hydraPubPathPrefix)) { currentDomain = exports.hydraPubDomain; currentBasePath = ""; - strPath = strPath.replace(exports.hydraPubPathPrefix, "/oauth2"); + strPath = strPath.replace(hydraPubPathPrefix, "/oauth2"); } if (strPath.startsWith(hydraAdmPathPrefix)) { currentDomain = hydraAdmDomain; diff --git a/lib/build/recipe/oauth2provider/api/auth.js b/lib/build/recipe/oauth2provider/api/auth.js index 30135c712..9e8b5d7a0 100644 --- a/lib/build/recipe/oauth2provider/api/auth.js +++ b/lib/build/recipe/oauth2provider/api/auth.js @@ -33,8 +33,11 @@ async function authGET(apiImplementation, options, userContext) { try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); } catch (_a) { - // TODO: explain - // ignore + // We ignore this here since the authGET is called from the auth endpoint which is used in full-page redirections + // Returning a 401 would break the sign-in flow. + // In theory we could serve some JS that handles refreshing and retrying, but this is not implemented. + // What we do is that the auth endpoint will redirect to the login page, and the login page handles refreshing and + // redirect to the auth endpoint again. This is not optimal, but it works for now. } let response = await apiImplementation.authGET({ options, @@ -44,7 +47,6 @@ async function authGET(apiImplementation, options, userContext) { userContext, }); if ("redirectTo" in response) { - // TODO: if (response.setCookie) { const cookieStr = set_cookie_parser_1.default.splitCookiesString(response.setCookie); const cookies = set_cookie_parser_1.default.parse(cookieStr); diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index 86d5403ad..9739a22ed 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -32,20 +32,6 @@ function getAPIImplementation() { userContext, }); }, - loginPOST: async ({ loginChallenge, accept, options, session, userContext }) => { - const res = accept - ? await options.recipeImplementation.acceptLoginRequest({ - challenge: loginChallenge, - subject: session.getUserId(), - userContext, - }) - : await options.recipeImplementation.rejectLoginRequest({ - challenge: loginChallenge, - error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, - userContext, - }); - return { redirectTo: res.redirectTo }; - }, authGET: async ({ options, params, cookie, session, userContext }) => { const response = await options.recipeImplementation.authorization({ params, @@ -62,7 +48,10 @@ function getAPIImplementation() { }); }, tokenPOST: async (input) => { - return input.options.recipeImplementation.token({ body: input.body, userContext: input.userContext }); + return input.options.recipeImplementation.tokenExchange({ + body: input.body, + userContext: input.userContext, + }); }, loginInfoGET: async ({ loginChallenge, options, userContext }) => { const { client } = await options.recipeImplementation.getLoginRequest({ @@ -76,6 +65,7 @@ function getAPIImplementation() { tosUri: client.tosUri, policyUri: client.policyUri, logoUri: client.logoUri, + clientUri: client.clientUri, metadata: client.metadata, }, }; diff --git a/lib/build/recipe/oauth2provider/api/login.js b/lib/build/recipe/oauth2provider/api/login.js index 878d1a6a8..dc42e7ceb 100644 --- a/lib/build/recipe/oauth2provider/api/login.js +++ b/lib/build/recipe/oauth2provider/api/login.js @@ -21,66 +21,40 @@ var __importDefault = Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const session_1 = __importDefault(require("../../session")); -// TODO: separate post and get? +const error_1 = __importDefault(require("../../../error")); async function login(apiImplementation, options, userContext) { var _a; - if (utils_1.normaliseHttpMethod(options.req.getMethod()) === "post") { - if (apiImplementation.loginPOST === undefined) { - return false; - } - const session = await session_1.default.getSession( - options.req, - options.res, - { sessionRequired: true }, - userContext - ); - const reqBody = await options.req.getJSONBody(); - let response = await apiImplementation.loginPOST({ - options, - accept: reqBody.accept, - loginChallenge: reqBody.loginChallenge, - session, - userContext, + if (apiImplementation.loginGET === undefined) { + return false; + } + let session; + try { + session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); + } catch (_b) { + // We can handle this as if the session is not present, because then we redirect to the frontend, + // which should handle the validation error + session = undefined; + } + const loginChallenge = + (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 + ? _a + : options.req.getKeyValueFromQuery("loginChallenge"); + if (loginChallenge === undefined) { + throw new error_1.default({ + type: error_1.default.BAD_INPUT_ERROR, + message: "Missing input param: loginChallenge", }); - if ("status" in response) { - utils_1.send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } + } + let response = await apiImplementation.loginGET({ + options, + loginChallenge, + session, + userContext, + }); + if ("status" in response) { + utils_1.send200Response(options.res, response); } else { - if (apiImplementation.loginGET === undefined) { - return false; - } - let session; - try { - session = await session_1.default.getSession( - options.req, - options.res, - { sessionRequired: false }, - userContext - ); - } catch (_b) { - // TODO: Claim validation failure - } - // TODO: take only one - const loginChallenge = - (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 - ? _a - : options.req.getKeyValueFromQuery("loginChallenge"); - if (loginChallenge === undefined) { - throw new Error("TODO"); - } - let response = await apiImplementation.loginGET({ - options, - loginChallenge, - session, - userContext, - }); - if ("status" in response) { - utils_1.send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } + options.res.original.redirect(response.redirectTo); } return true; } diff --git a/lib/build/recipe/oauth2provider/api/loginInfo.js b/lib/build/recipe/oauth2provider/api/loginInfo.js index b1d06cd64..15b9da808 100644 --- a/lib/build/recipe/oauth2provider/api/loginInfo.js +++ b/lib/build/recipe/oauth2provider/api/loginInfo.js @@ -13,8 +13,14 @@ * 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 loginInfoGET(apiImplementation, options, userContext) { var _a; if (apiImplementation.loginInfoGET === undefined) { @@ -25,7 +31,10 @@ async function loginInfoGET(apiImplementation, options, userContext) { ? _a : options.req.getKeyValueFromQuery("loginChallenge"); if (loginChallenge === undefined) { - throw new Error("TODO"); + throw new error_1.default({ + type: error_1.default.BAD_INPUT_ERROR, + message: "Missing input param: loginChallenge", + }); } let response = await apiImplementation.loginInfoGET({ options, diff --git a/lib/build/recipe/oauth2provider/api/token.js b/lib/build/recipe/oauth2provider/api/token.js index f8d076426..4f7ef9a77 100644 --- a/lib/build/recipe/oauth2provider/api/token.js +++ b/lib/build/recipe/oauth2provider/api/token.js @@ -24,7 +24,11 @@ async function tokenPOST(apiImplementation, options, userContext) { body: await options.req.getFormData(), userContext, }); - utils_1.send200Response(options.res, response); + if ("statusCode" in response && response.statusCode !== 200) { + utils_1.sendNon200Response(options.res, response.statusCode, response); + } else { + utils_1.send200Response(options.res, response); + } return true; } exports.default = tokenPOST; diff --git a/lib/build/recipe/oauth2provider/api/userInfo.js b/lib/build/recipe/oauth2provider/api/userInfo.js index cabec7ef8..ff38a82b3 100644 --- a/lib/build/recipe/oauth2provider/api/userInfo.js +++ b/lib/build/recipe/oauth2provider/api/userInfo.js @@ -28,9 +28,7 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { } const authHeader = options.req.getHeaderValue("authorization") || options.req.getHeaderValue("Authorization"); if (authHeader === undefined || !authHeader.startsWith("Bearer ")) { - // TODO: Returning a 400 instead of a 401 to prevent a potential refresh loop in the client SDK. - // When addressing this TODO, review other response codes in this function as well. - utils_1.sendNon200ResponseWithMessage(options.res, "Missing or invalid Authorization header", 400); + utils_1.sendNon200ResponseWithMessage(options.res, "Missing or invalid Authorization header", 401); return true; } const accessToken = authHeader.replace(/^Bearer /, "").trim(); @@ -40,13 +38,13 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { payload, } = await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ token: accessToken, - // TODO: expectedAudience? userContext, }); accessTokenPayload = payload; } catch (error) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); - utils_1.sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 400); + options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); + utils_1.sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 401); return true; } if ( @@ -56,17 +54,19 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { !Array.isArray(accessTokenPayload.scp) ) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); - utils_1.sendNon200ResponseWithMessage(options.res, "Malformed access token payload", 400); + options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); + utils_1.sendNon200ResponseWithMessage(options.res, "Malformed access token payload", 401); return true; } const userId = accessTokenPayload.sub; const user = await __1.getUser(userId, userContext); if (user === undefined) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); + options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); utils_1.sendNon200ResponseWithMessage( options.res, "Couldn't find any user associated with the access token", - 400 + 401 ); return true; } diff --git a/lib/build/recipe/oauth2provider/api/utils.js b/lib/build/recipe/oauth2provider/api/utils.js index 2db5d85fe..c65d1dbb2 100644 --- a/lib/build/recipe/oauth2provider/api/utils.js +++ b/lib/build/recipe/oauth2provider/api/utils.js @@ -12,22 +12,26 @@ 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 }) { - var _a, _b; + var _a; const request = 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); + } if (request.skip) { const accept = await recipeImplementation.acceptLoginRequest({ challenge: loginChallenge, + identityProviderSessionId: session === null || session === void 0 ? void 0 : session.getHandle(), subject: request.subject, userContext, }); return { redirectTo: accept.redirectTo, setCookie }; - } else if (session) { - if (session.getUserId() !== request.subject) { - // TODO? - } + } else if (session && (!request.subject || session.getUserId() === request.subject)) { const accept = await recipeImplementation.acceptLoginRequest({ challenge: loginChallenge, subject: session.getUserId(), @@ -45,15 +49,7 @@ async function loginGET({ recipeImplementation, loginChallenge, session, setCook .getAsStringDangerous(); const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); return { - redirectTo: - websiteDomain + - websiteBasePath + - `?hint=${ - (_b = (_a = request.oidcContext) === null || _a === void 0 ? void 0 : _a.login_hint) !== null && - _b !== void 0 - ? _b - : "" - }&loginChallenge=${loginChallenge}`, + redirectTo: websiteDomain + websiteBasePath + `?${queryParams.toString()}`, setCookie, }; } @@ -90,11 +86,16 @@ function mergeSetCookieHeaders(setCookie1, setCookie2) { function isInternalRedirect(redirectTo) { const { apiDomain, apiBasePath } = supertokens_1.default.getInstanceOrThrowError().appInfo; const basePath = `${apiDomain.getAsStringDangerous()}${apiBasePath.getAsStringDangerous()}`; - return [constants_1.LOGIN_PATH, constants_1.AUTH_PATH].some((path) => redirectTo.startsWith(`${basePath}${path}`)); + return [ + constants_1.LOGIN_PATH, + constants_1.AUTH_PATH, + constants_1.LOGIN_PATH.replace("oauth", "oauth2"), + constants_1.AUTH_PATH.replace("oauth", "oauth2"), + ].some((path) => redirectTo.startsWith(`${basePath}${path}`)); } // In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. // If an internal redirect is identified, it's handled directly by this function. -// Currently, we only need to handle redirects to /oauth2provider/login and /oauth2provider/auth endpoints. +// Currently, we only need to handle redirects to /oauth/login and /oauth/auth endpoints. async function handleInternalRedirects({ response, recipeImplementation, session, cookie = "", userContext }) { var _a; if (!isInternalRedirect(response.redirectTo)) { diff --git a/lib/build/recipe/oauth2provider/constants.d.ts b/lib/build/recipe/oauth2provider/constants.d.ts index f10ea72ec..c609db866 100644 --- a/lib/build/recipe/oauth2provider/constants.d.ts +++ b/lib/build/recipe/oauth2provider/constants.d.ts @@ -1,7 +1,7 @@ // @ts-nocheck -export declare const OAUTH2_BASE_PATH = "/oauth2provider/"; -export declare const LOGIN_PATH = "/oauth2provider/login"; -export declare const AUTH_PATH = "/oauth2provider/auth"; -export declare const TOKEN_PATH = "/oauth2provider/token"; -export declare const LOGIN_INFO_PATH = "/oauth2provider/login/info"; -export declare const USER_INFO_PATH = "/oauth2provider/userinfo"; +export declare const OAUTH2_BASE_PATH = "/oauth/"; +export declare const LOGIN_PATH = "/oauth/login"; +export declare const AUTH_PATH = "/oauth/auth"; +export declare const TOKEN_PATH = "/oauth/token"; +export declare const LOGIN_INFO_PATH = "/oauth/login/info"; +export declare const USER_INFO_PATH = "/oauth/userinfo"; diff --git a/lib/build/recipe/oauth2provider/constants.js b/lib/build/recipe/oauth2provider/constants.js index 73a51502d..66872296b 100644 --- a/lib/build/recipe/oauth2provider/constants.js +++ b/lib/build/recipe/oauth2provider/constants.js @@ -15,9 +15,9 @@ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.USER_INFO_PATH = exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; -exports.OAUTH2_BASE_PATH = "/oauth2provider/"; -exports.LOGIN_PATH = "/oauth2provider/login"; -exports.AUTH_PATH = "/oauth2provider/auth"; -exports.TOKEN_PATH = "/oauth2provider/token"; -exports.LOGIN_INFO_PATH = "/oauth2provider/login/info"; -exports.USER_INFO_PATH = "/oauth2provider/userinfo"; +exports.OAUTH2_BASE_PATH = "/oauth/"; +exports.LOGIN_PATH = "/oauth/login"; +exports.AUTH_PATH = "/oauth/auth"; +exports.TOKEN_PATH = "/oauth/token"; +exports.LOGIN_INFO_PATH = "/oauth/login/info"; +exports.USER_INFO_PATH = "/oauth/userinfo"; diff --git a/lib/build/recipe/oauth2provider/index.d.ts b/lib/build/recipe/oauth2provider/index.d.ts index e145e31a3..1bfdc6137 100644 --- a/lib/build/recipe/oauth2provider/index.d.ts +++ b/lib/build/recipe/oauth2provider/index.d.ts @@ -69,7 +69,12 @@ export default class Wrapper { >; static validateOAuth2AccessToken( token: string, - expectedAudience?: string, + requirements?: { + clientId?: string; + scopes?: string[]; + audience?: string; + }, + checkDatabase?: boolean, userContext?: Record ): Promise<{ status: "OK"; @@ -77,12 +82,23 @@ export default class Wrapper { }>; static validateOAuth2IdToken( token: string, - expectedAudience?: string, + requirements?: { + clientId?: string; + scopes?: string[]; + audience?: string; + }, userContext?: Record ): Promise<{ status: "OK"; payload: import("../usermetadata").JSONObject; }>; + static createTokenForClientCredentials( + clientId: string, + clientSecret: string, + scope?: string[], + audience?: string, + userContext?: Record + ): Promise; } export declare let init: typeof Recipe.init; export declare let getOAuth2Clients: typeof Wrapper.getOAuth2Clients; @@ -91,4 +107,5 @@ export declare let updateOAuth2Client: typeof Wrapper.updateOAuth2Client; export declare let deleteOAuth2Client: typeof Wrapper.deleteOAuth2Client; export declare let validateOAuth2AccessToken: typeof Wrapper.validateOAuth2AccessToken; export declare let validateOAuth2IdToken: typeof Wrapper.validateOAuth2IdToken; +export declare let createTokenForClientCredentials: typeof Wrapper.createTokenForClientCredentials; export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/build/recipe/oauth2provider/index.js b/lib/build/recipe/oauth2provider/index.js index fb2c4554a..79f6a2929 100644 --- a/lib/build/recipe/oauth2provider/index.js +++ b/lib/build/recipe/oauth2provider/index.js @@ -19,7 +19,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.validateOAuth2IdToken = exports.validateOAuth2AccessToken = exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.init = void 0; +exports.createTokenForClientCredentials = exports.validateOAuth2IdToken = exports.validateOAuth2AccessToken = exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.init = void 0; const utils_1 = require("../../utils"); const recipe_1 = __importDefault(require("./recipe")); class Wrapper { @@ -43,17 +43,31 @@ class Wrapper { .getInstanceOrThrowError() .recipeInterfaceImpl.deleteOAuth2Client(input, utils_1.getUserContext(userContext)); } - static validateOAuth2AccessToken(token, expectedAudience, userContext) { + static validateOAuth2AccessToken(token, requirements, checkDatabase, userContext) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ token, - expectedAudience, + requirements, + checkDatabase, userContext: utils_1.getUserContext(userContext), }); } - static validateOAuth2IdToken(token, expectedAudience, userContext) { + static validateOAuth2IdToken(token, requirements, userContext) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2IdToken({ token, - expectedAudience, + requirements, + userContext: utils_1.getUserContext(userContext), + }); + } + // TODO: revokeToken + static createTokenForClientCredentials(clientId, clientSecret, scope, audience, userContext) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.tokenExchange({ + body: { + grant_type: "client_credentials", + client_id: clientId, + client_secret: clientSecret, + scope: scope === null || scope === void 0 ? void 0 : scope.join(" "), + audience: audience, + }, userContext: utils_1.getUserContext(userContext), }); } @@ -67,3 +81,4 @@ exports.updateOAuth2Client = Wrapper.updateOAuth2Client; exports.deleteOAuth2Client = Wrapper.deleteOAuth2Client; exports.validateOAuth2AccessToken = Wrapper.validateOAuth2AccessToken; exports.validateOAuth2IdToken = Wrapper.validateOAuth2IdToken; +exports.createTokenForClientCredentials = Wrapper.createTokenForClientCredentials; diff --git a/lib/build/recipe/oauth2provider/recipe.js b/lib/build/recipe/oauth2provider/recipe.js index 8100cc718..95a3de853 100644 --- a/lib/build/recipe/oauth2provider/recipe.js +++ b/lib/build/recipe/oauth2provider/recipe.js @@ -117,12 +117,6 @@ class Recipe extends recipeModule_1.default { /* RecipeModule functions */ getAPIsHandled() { return [ - { - method: "post", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.LOGIN_PATH), - id: constants_1.LOGIN_PATH, - disabled: this.apiImpl.loginPOST === undefined, - }, { method: "get", pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.LOGIN_PATH), @@ -165,7 +159,9 @@ class Recipe extends recipeModule_1.default { return error_1.default.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; } async getDefaultIdTokenPayload(user, scopes, userContext) { - let payload = {}; + let payload = { + iss: this.appInfo.apiDomain.getAsStringDangerous() + this.appInfo.apiBasePath.getAsStringDangerous(), + }; if (scopes.includes("email")) { payload.email = user === null || user === void 0 ? void 0 : user.emails[0]; payload.email_verified = user.loginMethods.some( diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 720533812..1dff06e37 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -62,6 +62,7 @@ const utils_1 = require("../../utils"); const OAuth2Client_1 = require("./OAuth2Client"); const __1 = require("../.."); const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); +const session_1 = require("../session"); // TODO: Remove this core changes are done function getUpdatedRedirectTo(appInfo, redirectTo) { return redirectTo @@ -69,7 +70,7 @@ function getUpdatedRedirectTo(appInfo, redirectTo) { querier_1.hydraPubDomain, appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() ) - .replace("oauth2", "oauth2provider"); + .replace("oauth2/", "oauth/"); } function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, getDefaultUserInfoPayload) { return { @@ -120,9 +121,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/login/reject`), { error: input.error.error, - error_debug: input.error.errorDebug, error_description: input.error.errorDescription, - error_hint: input.error.errorHint, status_code: input.error.statusCode, }, { @@ -183,9 +182,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/consent/reject`), { error: input.error.error, - error_debug: input.error.errorDebug, error_description: input.error.errorDescription, - error_hint: input.error.errorHint, status_code: input.error.statusCode, }, { @@ -199,7 +196,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, }; }, authorization: async function (input) { - var _a, _b, _c, _d, _e; + var _a, _b; const resp = await querier.sendGetRequestWithResponseHeaders( new normalisedURLPath_1.default(`/recipe/oauth2/pub/auth`), input.params, @@ -208,7 +205,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, }, input.userContext ); - const redirectTo = resp.headers.get("Location"); + const redirectTo = getUpdatedRedirectTo(appInfo, resp.headers.get("Location")); if (redirectTo === undefined) { throw new Error(resp.body); } @@ -225,35 +222,22 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, } const idToken = this.buildIdTokenPayload({ user, + client: consentRequest.client, session: input.session, - defaultPayload: await getDefaultIdTokenPayload( - user, - (_a = consentRequest.requestedScope) !== null && _a !== void 0 ? _a : [], - input.userContext - ), scopes: consentRequest.requestedScope || [], userContext: input.userContext, }); const accessTokenPayload = await this.buildAccessTokenPayload({ user, + client: consentRequest.client, session: input.session, - defaultPayload: Object.assign( - Object.assign({}, input.session.getAccessTokenPayload(input.userContext)), - { - iss: appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(), - scope: - (_c = - (_b = consentRequest.requestedScope) === null || _b === void 0 - ? void 0 - : _b.join(" ")) !== null && _c !== void 0 - ? _c - : "", - aud: [consentRequest.client.clientId], - } - ), - userContext: input.userContext, scopes: consentRequest.requestedScope || [], + userContext: input.userContext, }); + const sessionInfo = await session_1.getSessionInformation(input.session.getHandle()); + if (!sessionInfo) { + throw new Error("Session not found"); + } const consentRes = await this.acceptConsentRequest( Object.assign(Object.assign({}, input), { challenge: consentRequest.challenge, @@ -263,19 +247,20 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, id_token: idToken, access_token: accessTokenPayload, }, + handledAt: new Date(sessionInfo.timeCreated).toISOString(), }) ); return { redirectTo: consentRes.redirectTo, - setCookie: (_d = resp.headers.get("set-cookie")) !== null && _d !== void 0 ? _d : undefined, + setCookie: (_a = resp.headers.get("set-cookie")) !== null && _a !== void 0 ? _a : undefined, }; } return { redirectTo, - setCookie: (_e = resp.headers.get("set-cookie")) !== null && _e !== void 0 ? _e : undefined, + setCookie: (_b = resp.headers.get("set-cookie")) !== null && _b !== void 0 ? _b : undefined, }; }, - token: async function (input) { + tokenExchange: async function (input) { const body = { $isFormData: true }; // TODO: we ideally want to avoid using formdata, the core can do the translation for (const key in input.body) { body[key] = input.body[key]; @@ -394,59 +379,117 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, } }, buildAccessTokenPayload: async function (input) { - return input.defaultPayload; + const stAccessTokenPayload = input.session.getAccessTokenPayload(input.userContext); + const sessionInfo = await session_1.getSessionInformation(stAccessTokenPayload.sessionHandle); + if (sessionInfo === undefined) { + throw new Error("Session not found"); + } + return { + tId: stAccessTokenPayload.tId, + rsub: stAccessTokenPayload.rsub, + sessionHandle: stAccessTokenPayload.sessionHandle, + // auth_time: sessionInfo?.timeCreated, + iss: appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(), + }; }, buildIdTokenPayload: async function (input) { - return input.defaultPayload; + return getDefaultIdTokenPayload(input.user, input.scopes, input.userContext); }, buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) { return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext); }, validateOAuth2AccessToken: async function (input) { - var _a, _b, _c, _d; + var _a, _b, _c, _d, _e; const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS())).payload; + // if (payload.stt !== 1) { + // throw new Error("Wrong token type"); + // } + // TODO: we should be able uncomment this after we get proper core support // TODO: make this configurable? // const expectedIssuer = // appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); // if (payload.iss !== expectedIssuer) { // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); // } - // TODO: Fix this + if ( + ((_a = input.requirements) === null || _a === void 0 ? void 0 : _a.clientId) !== undefined && + payload.client_id !== input.requirements.clientId + ) { + throw new Error("The token doesn't belong to the specified client"); + } + if ( + ((_b = input.requirements) === null || _b === void 0 ? void 0 : _b.scopes) !== undefined && + input.requirements.scopes.some((scope) => !payload.scp.includes(scope)) + ) { + throw new Error("The token is missing some required scopes"); + } const aud = - (_b = (_a = payload.ext) === null || _a === void 0 ? void 0 : _a.aud) !== null && _b !== void 0 - ? _b - : payload.aud instanceof Array + payload.aud instanceof Array ? payload.aud : (_d = (_c = payload.aud) === null || _c === void 0 ? void 0 : _c.split(" ")) !== null && _d !== void 0 ? _d : []; - if (input.expectedAudience !== undefined && !aud.includes(input.expectedAudience)) { - throw new Error("Audience mismatch: this token doesn't belong to the specified client"); + if ( + ((_e = input.requirements) === null || _e === void 0 ? void 0 : _e.audience) !== undefined && + !aud.includes(input.requirements.audience) + ) { + throw new Error("The token doesn't belong to the specified audience"); + } + if (input.checkDatabase) { + let response = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/introspect`), + { + $isFormData: true, + token: input.token, + }, + input.userContext + ); + // TODO: fix after the core interface is there + if (response.status !== "OK" || response.data.active !== true) { + throw new Error(response.data.error); + } } - // TODO: add a check to make sure this is the right token type as they can be signed with the same key return { status: "OK", payload: payload }; }, validateOAuth2IdToken: async function (input) { - var _a, _b; + var _a, _b, _c, _d, _e; const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS())).payload; + // TODO: we should be able uncomment this after we get proper core support // TODO: make this configurable? // const expectedIssuer = // appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); - // if (input.expectedAudience !== undefined && payload.iss !== expectedIssuer) { + // if (payload.iss !== expectedIssuer) { // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); // } + // if (payload.stt !== 2) { + // throw new Error("Wrong token type"); + // } + if ( + ((_a = input.requirements) === null || _a === void 0 ? void 0 : _a.clientId) !== undefined && + payload.client_id !== input.requirements.clientId + ) { + throw new Error("The token doesn't belong to the specified client"); + } + if ( + ((_b = input.requirements) === null || _b === void 0 ? void 0 : _b.scopes) !== undefined && + input.requirements.scopes.some((scope) => !payload.scp.includes(scope)) + ) { + throw new Error("The token is missing some required scopes"); + } const aud = payload.aud instanceof Array ? payload.aud - : (_b = (_a = payload.aud) === null || _a === void 0 ? void 0 : _a.split(" ")) !== null && - _b !== void 0 - ? _b + : (_d = (_c = payload.aud) === null || _c === void 0 ? void 0 : _c.split(" ")) !== null && + _d !== void 0 + ? _d : []; - if (input.expectedAudience !== undefined && !aud.includes(input.expectedAudience)) { - throw new Error("Audience mismatch: this token doesn't belong to the specified client"); + if ( + ((_e = input.requirements) === null || _e === void 0 ? void 0 : _e.audience) !== undefined && + !aud.includes(input.requirements.audience) + ) { + throw new Error("The token doesn't belong to the specified audience"); } - // TODO: add a check to make sure this is the right token type as they can be signed with the same key return { status: "OK", payload: payload }; }, }; diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index e5e56e94d..188891403 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -34,8 +34,6 @@ export declare type APIOptions = { export declare type ErrorOAuth2 = { error: string; errorDescription: string; - errorDebug?: string; - errorHint?: string; statusCode?: number; }; export declare type ConsentRequest = { @@ -64,18 +62,19 @@ export declare type LoginRequest = { subject: string; }; export declare type TokenInfo = { - access_token: string; + access_token?: string; expires_in: number; - id_token: string; - refresh_token: string; + id_token?: string; + refresh_token?: string; scope: string; token_type: string; }; export declare type LoginInfo = { clientName: string; - tosUri: string; - policyUri: string; - logoUri: string; + tosUri?: string; + policyUri?: string; + logoUri?: string; + clientUri?: string; metadata?: Record | null; }; export declare type UserInfo = { @@ -88,7 +87,7 @@ export declare type UserInfo = { }; export declare type RecipeInterface = { authorization(input: { - params: any; + params: Record; cookies: string | undefined; session: SessionContainerInterface | undefined; userContext: UserContext; @@ -96,14 +95,17 @@ export declare type RecipeInterface = { redirectTo: string; setCookie: string | undefined; }>; - token(input: { body: any; userContext: UserContext }): Promise; + tokenExchange(input: { + body: Record; + userContext: UserContext; + }): Promise; getConsentRequest(input: { challenge: string; userContext: UserContext }): Promise; acceptConsentRequest(input: { challenge: string; context?: any; grantAccessTokenAudience?: string[]; grantScope?: string[]; - handledAt?: string[]; + handledAt?: string; remember?: boolean; rememberFor?: number; session?: any; @@ -199,7 +201,12 @@ export declare type RecipeInterface = { >; validateOAuth2AccessToken(input: { token: string; - expectedAudience?: string; + requirements?: { + clientId?: string; + scopes?: string[]; + audience?: string; + }; + checkDatabase?: boolean; userContext: UserContext; }): Promise<{ status: "OK"; @@ -207,7 +214,11 @@ export declare type RecipeInterface = { }>; validateOAuth2IdToken(input: { token: string; - expectedAudience?: string; + requirements?: { + clientId?: string; + scopes?: string[]; + audience?: string; + }; userContext: UserContext; }): Promise<{ status: "OK"; @@ -215,16 +226,16 @@ export declare type RecipeInterface = { }>; buildAccessTokenPayload(input: { user: User; + client: OAuth2Client; session: SessionContainerInterface; scopes: string[]; - defaultPayload: JSONObject; userContext: UserContext; }): Promise; buildIdTokenPayload(input: { user: User; + client: OAuth2Client; session: SessionContainerInterface; scopes: string[]; - defaultPayload: JSONObject; userContext: UserContext; }): Promise; buildUserInfo(input: { @@ -249,20 +260,6 @@ export declare type APIInterface = { } | GeneralErrorResponse >); - loginPOST: - | undefined - | ((input: { - loginChallenge: string; - accept: boolean; - session: SessionContainerInterface; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - redirectTo: string; - } - | GeneralErrorResponse - >); authGET: | undefined | ((input: { diff --git a/lib/build/recipe/session/accessToken.js b/lib/build/recipe/session/accessToken.js index 3f0efd2cf..37249db05 100644 --- a/lib/build/recipe/session/accessToken.js +++ b/lib/build/recipe/session/accessToken.js @@ -199,6 +199,9 @@ async function getInfoFromAccessToken(jwtInfo, jwks, doAntiCsrfCheck) { } exports.getInfoFromAccessToken = getInfoFromAccessToken; function validateAccessTokenStructure(payload, version) { + if (payload.stt !== 0 && payload.stt !== undefined) { + throw Error("Wrong token type"); + } if (version >= 5) { if ( typeof payload.sub !== "string" || diff --git a/lib/build/recipe/session/constants.js b/lib/build/recipe/session/constants.js index 4aa6e6b05..84d5b0225 100644 --- a/lib/build/recipe/session/constants.js +++ b/lib/build/recipe/session/constants.js @@ -30,4 +30,5 @@ exports.protectedProps = [ "antiCsrfToken", "rsub", "tId", + "stt", ]; diff --git a/lib/build/recipeModule.d.ts b/lib/build/recipeModule.d.ts index dcf2f48d4..e277192a9 100644 --- a/lib/build/recipeModule.d.ts +++ b/lib/build/recipeModule.d.ts @@ -5,7 +5,7 @@ import NormalisedURLPath from "./normalisedURLPath"; import { BaseRequest, BaseResponse } from "./framework"; export default abstract class RecipeModule { private recipeId; - private appInfo; + protected appInfo: NormalisedAppinfo; constructor(recipeId: string, appInfo: NormalisedAppinfo); getRecipeId: () => string; getAppInfo: () => NormalisedAppinfo; diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index 2b7ac47dd..666eaf6fc 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -25,7 +25,7 @@ import SuperTokens from "./supertokens"; export const hydraPubDomain = process.env.HYDRA_PUB ?? "http://localhost:4444"; // This will be used as a domain for paths starting with hydraPubPathPrefix const hydraAdmDomain = process.env.HYDRA_ADM ?? "http://localhost:4445"; // This will be used as a domain for paths starting with hydraAdmPathPrefix -export const hydraPubPathPrefix = "/recipe/oauth2/pub"; // Replaced with "/oauth2" when sending the request (/recipe/oauth2/pub/token -> /oauth2/token) +const hydraPubPathPrefix = "/recipe/oauth2/pub"; // Replaced with "/oauth2" when sending the request (/recipe/oauth2/pub/token -> /oauth2/token) const hydraAdmPathPrefix = "/recipe/oauth2/admin"; // Replaced with "/admin" when sending the request (/recipe/oauth2/admin/clients -> /admin/clients) export class Querier { diff --git a/lib/ts/recipe/oauth2provider/api/auth.ts b/lib/ts/recipe/oauth2provider/api/auth.ts index 50eba1b00..3a75fd563 100644 --- a/lib/ts/recipe/oauth2provider/api/auth.ts +++ b/lib/ts/recipe/oauth2provider/api/auth.ts @@ -34,8 +34,11 @@ export default async function authGET( try { session = await Session.getSession(options.req, options.res, { sessionRequired: false }, userContext); } catch { - // TODO: explain - // ignore + // We ignore this here since the authGET is called from the auth endpoint which is used in full-page redirections + // Returning a 401 would break the sign-in flow. + // In theory we could serve some JS that handles refreshing and retrying, but this is not implemented. + // What we do is that the auth endpoint will redirect to the login page, and the login page handles refreshing and + // redirect to the auth endpoint again. This is not optimal, but it works for now. } let response = await apiImplementation.authGET({ @@ -46,7 +49,6 @@ export default async function authGET( userContext, }); if ("redirectTo" in response) { - // TODO: if (response.setCookie) { const cookieStr = setCookieParser.splitCookiesString(response.setCookie); const cookies = setCookieParser.parse(cookieStr); diff --git a/lib/ts/recipe/oauth2provider/api/implementation.ts b/lib/ts/recipe/oauth2provider/api/implementation.ts index a3acfdfe1..2ba89b59d 100644 --- a/lib/ts/recipe/oauth2provider/api/implementation.ts +++ b/lib/ts/recipe/oauth2provider/api/implementation.ts @@ -33,20 +33,6 @@ export default function getAPIImplementation(): APIInterface { userContext, }); }, - loginPOST: async ({ loginChallenge, accept, options, session, userContext }) => { - const res = accept - ? await options.recipeImplementation.acceptLoginRequest({ - challenge: loginChallenge, - subject: session.getUserId(), - userContext, - }) - : await options.recipeImplementation.rejectLoginRequest({ - challenge: loginChallenge, - error: { error: "access_denied", errorDescription: "The resource owner denied the request" }, - userContext, - }); - return { redirectTo: res.redirectTo }; - }, authGET: async ({ options, params, cookie, session, userContext }) => { const response = await options.recipeImplementation.authorization({ @@ -65,7 +51,10 @@ export default function getAPIImplementation(): APIInterface { }); }, tokenPOST: async (input) => { - return input.options.recipeImplementation.token({ body: input.body, userContext: input.userContext }); + return input.options.recipeImplementation.tokenExchange({ + body: input.body, + userContext: input.userContext, + }); }, loginInfoGET: async ({ loginChallenge, options, userContext }) => { const { client } = await options.recipeImplementation.getLoginRequest({ @@ -80,6 +69,7 @@ export default function getAPIImplementation(): APIInterface { tosUri: client.tosUri, policyUri: client.policyUri, logoUri: client.logoUri, + clientUri: client.clientUri, metadata: client.metadata, }, }; diff --git a/lib/ts/recipe/oauth2provider/api/login.ts b/lib/ts/recipe/oauth2provider/api/login.ts index f1912059a..ef0c2cb9f 100644 --- a/lib/ts/recipe/oauth2provider/api/login.ts +++ b/lib/ts/recipe/oauth2provider/api/login.ts @@ -13,64 +13,48 @@ * under the License. */ -import { normaliseHttpMethod, send200Response } from "../../../utils"; +import { send200Response } from "../../../utils"; import { APIInterface, APIOptions } from ".."; import Session from "../../session"; import { UserContext } from "../../../types"; +import SuperTokensError from "../../../error"; -// TODO: separate post and get? export default async function login( apiImplementation: APIInterface, options: APIOptions, userContext: UserContext ): Promise { - if (normaliseHttpMethod(options.req.getMethod()) === "post") { - if (apiImplementation.loginPOST === undefined) { - return false; - } - const session = await Session.getSession(options.req, options.res, { sessionRequired: true }, userContext); - const reqBody = await options.req.getJSONBody(); - let response = await apiImplementation.loginPOST({ - options, - accept: reqBody.accept, - loginChallenge: reqBody.loginChallenge, - session, - userContext, - }); - if ("status" in response) { - send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } - } else { - if (apiImplementation.loginGET === undefined) { - return false; - } + if (apiImplementation.loginGET === undefined) { + return false; + } - let session; - try { - session = await Session.getSession(options.req, options.res, { sessionRequired: false }, userContext); - } catch { - // TODO: Claim validation failure - } + let session; + try { + session = await Session.getSession(options.req, options.res, { sessionRequired: false }, userContext); + } catch { + // We can handle this as if the session is not present, because then we redirect to the frontend, + // which should handle the validation error + session = undefined; + } - // TODO: take only one - const loginChallenge = - options.req.getKeyValueFromQuery("login_challenge") ?? options.req.getKeyValueFromQuery("loginChallenge"); - if (loginChallenge === undefined) { - throw new Error("TODO"); - } - let response = await apiImplementation.loginGET({ - options, - loginChallenge, - session, - userContext, + const loginChallenge = + options.req.getKeyValueFromQuery("login_challenge") ?? options.req.getKeyValueFromQuery("loginChallenge"); + if (loginChallenge === undefined) { + throw new SuperTokensError({ + type: SuperTokensError.BAD_INPUT_ERROR, + message: "Missing input param: loginChallenge", }); - if ("status" in response) { - send200Response(options.res, response); - } else { - options.res.original.redirect(response.redirectTo); - } + } + let response = await apiImplementation.loginGET({ + options, + loginChallenge, + session, + userContext, + }); + if ("status" in response) { + send200Response(options.res, response); + } else { + options.res.original.redirect(response.redirectTo); } return true; } diff --git a/lib/ts/recipe/oauth2provider/api/loginInfo.ts b/lib/ts/recipe/oauth2provider/api/loginInfo.ts index b0d6241be..2c13ddad0 100644 --- a/lib/ts/recipe/oauth2provider/api/loginInfo.ts +++ b/lib/ts/recipe/oauth2provider/api/loginInfo.ts @@ -16,6 +16,7 @@ import { send200Response } from "../../../utils"; import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; +import SuperTokensError from "../../../error"; export default async function loginInfoGET( apiImplementation: APIInterface, @@ -30,7 +31,10 @@ export default async function loginInfoGET( options.req.getKeyValueFromQuery("login_challenge") ?? options.req.getKeyValueFromQuery("loginChallenge"); if (loginChallenge === undefined) { - throw new Error("TODO"); + throw new SuperTokensError({ + type: SuperTokensError.BAD_INPUT_ERROR, + message: "Missing input param: loginChallenge", + }); } let response = await apiImplementation.loginInfoGET({ diff --git a/lib/ts/recipe/oauth2provider/api/token.ts b/lib/ts/recipe/oauth2provider/api/token.ts index 91f2d3cc6..62bfd5739 100644 --- a/lib/ts/recipe/oauth2provider/api/token.ts +++ b/lib/ts/recipe/oauth2provider/api/token.ts @@ -13,7 +13,7 @@ * under the License. */ -import { send200Response } from "../../../utils"; +import { send200Response, sendNon200Response } from "../../../utils"; import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; @@ -32,6 +32,11 @@ export default async function tokenPOST( userContext, }); - send200Response(options.res, response); + if ("statusCode" in response && response.statusCode !== 200) { + sendNon200Response(options.res, response.statusCode!, response); + } else { + send200Response(options.res, response); + } + return true; } diff --git a/lib/ts/recipe/oauth2provider/api/userInfo.ts b/lib/ts/recipe/oauth2provider/api/userInfo.ts index 4c9ff043c..897d57672 100644 --- a/lib/ts/recipe/oauth2provider/api/userInfo.ts +++ b/lib/ts/recipe/oauth2provider/api/userInfo.ts @@ -32,9 +32,7 @@ export default async function userInfoGET( const authHeader = options.req.getHeaderValue("authorization") || options.req.getHeaderValue("Authorization"); if (authHeader === undefined || !authHeader.startsWith("Bearer ")) { - // TODO: Returning a 400 instead of a 401 to prevent a potential refresh loop in the client SDK. - // When addressing this TODO, review other response codes in this function as well. - sendNon200ResponseWithMessage(options.res, "Missing or invalid Authorization header", 400); + sendNon200ResponseWithMessage(options.res, "Missing or invalid Authorization header", 401); return true; } @@ -47,13 +45,13 @@ export default async function userInfoGET( payload, } = await OAuth2ProviderRecipe.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ token: accessToken, - // TODO: expectedAudience? userContext, }); accessTokenPayload = payload; } catch (error) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); - sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 400); + options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); + sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 401); return true; } @@ -64,7 +62,8 @@ export default async function userInfoGET( !Array.isArray(accessTokenPayload.scp) ) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); - sendNon200ResponseWithMessage(options.res, "Malformed access token payload", 400); + options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); + sendNon200ResponseWithMessage(options.res, "Malformed access token payload", 401); return true; } @@ -74,7 +73,8 @@ export default async function userInfoGET( if (user === undefined) { options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false); - sendNon200ResponseWithMessage(options.res, "Couldn't find any user associated with the access token", 400); + options.res.setHeader("Access-Control-Expose-Headers", "WWW-Authenticate", true); + sendNon200ResponseWithMessage(options.res, "Couldn't find any user associated with the access token", 401); return true; } diff --git a/lib/ts/recipe/oauth2provider/api/utils.ts b/lib/ts/recipe/oauth2provider/api/utils.ts index 743fec939..3eba3cd15 100644 --- a/lib/ts/recipe/oauth2provider/api/utils.ts +++ b/lib/ts/recipe/oauth2provider/api/utils.ts @@ -25,18 +25,24 @@ export async function loginGET({ userContext, }); + const queryParams = new URLSearchParams({ + loginChallenge, + }); + + if (request.oidcContext?.login_hint) { + queryParams.set("hint", request.oidcContext.login_hint); + } + if (request.skip) { const accept = await recipeImplementation.acceptLoginRequest({ challenge: loginChallenge, + identityProviderSessionId: session?.getHandle(), subject: request.subject, userContext, }); return { redirectTo: accept.redirectTo, setCookie }; - } else if (session) { - if (session.getUserId() !== request.subject) { - // TODO? - } + } else if (session && (!request.subject || session.getUserId() === request.subject)) { const accept = await recipeImplementation.acceptLoginRequest({ challenge: loginChallenge, subject: session.getUserId(), @@ -55,10 +61,7 @@ export async function loginGET({ const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); return { - redirectTo: - websiteDomain + - websiteBasePath + - `?hint=${request.oidcContext?.login_hint ?? ""}&loginChallenge=${loginChallenge}`, + redirectTo: websiteDomain + websiteBasePath + `?${queryParams.toString()}`, setCookie, }; } @@ -101,13 +104,17 @@ function mergeSetCookieHeaders(setCookie1?: string, setCookie2?: string): string function isInternalRedirect(redirectTo: string): boolean { const { apiDomain, apiBasePath } = SuperTokens.getInstanceOrThrowError().appInfo; const basePath = `${apiDomain.getAsStringDangerous()}${apiBasePath.getAsStringDangerous()}`; - - return [LOGIN_PATH, AUTH_PATH].some((path) => redirectTo.startsWith(`${basePath}${path}`)); + return [ + LOGIN_PATH, + AUTH_PATH, + LOGIN_PATH.replace("oauth", "oauth2"), + AUTH_PATH.replace("oauth", "oauth2"), + ].some((path) => redirectTo.startsWith(`${basePath}${path}`)); } // In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. // If an internal redirect is identified, it's handled directly by this function. -// Currently, we only need to handle redirects to /oauth2provider/login and /oauth2provider/auth endpoints. +// Currently, we only need to handle redirects to /oauth/login and /oauth/auth endpoints. export async function handleInternalRedirects({ response, recipeImplementation, diff --git a/lib/ts/recipe/oauth2provider/constants.ts b/lib/ts/recipe/oauth2provider/constants.ts index eb30a951e..a4e3ec85b 100644 --- a/lib/ts/recipe/oauth2provider/constants.ts +++ b/lib/ts/recipe/oauth2provider/constants.ts @@ -13,10 +13,10 @@ * under the License. */ -export const OAUTH2_BASE_PATH = "/oauth2provider/"; +export const OAUTH2_BASE_PATH = "/oauth/"; -export const LOGIN_PATH = "/oauth2provider/login"; -export const AUTH_PATH = "/oauth2provider/auth"; -export const TOKEN_PATH = "/oauth2provider/token"; -export const LOGIN_INFO_PATH = "/oauth2provider/login/info"; -export const USER_INFO_PATH = "/oauth2provider/userinfo"; +export const LOGIN_PATH = "/oauth/login"; +export const AUTH_PATH = "/oauth/auth"; +export const TOKEN_PATH = "/oauth/token"; +export const LOGIN_INFO_PATH = "/oauth/login/info"; +export const USER_INFO_PATH = "/oauth/userinfo"; diff --git a/lib/ts/recipe/oauth2provider/index.ts b/lib/ts/recipe/oauth2provider/index.ts index 32f6ac012..4147ac072 100644 --- a/lib/ts/recipe/oauth2provider/index.ts +++ b/lib/ts/recipe/oauth2provider/index.ts @@ -53,18 +53,57 @@ export default class Wrapper { ); } - static validateOAuth2AccessToken(token: string, expectedAudience?: string, userContext?: Record) { + static validateOAuth2AccessToken( + token: string, + requirements?: { + clientId?: string; + scopes?: string[]; + audience?: string; + }, + checkDatabase?: boolean, + userContext?: Record + ) { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ token, - expectedAudience, + requirements, + checkDatabase, userContext: getUserContext(userContext), }); } - static validateOAuth2IdToken(token: string, expectedAudience?: string, userContext?: Record) { + static validateOAuth2IdToken( + token: string, + requirements?: { + clientId?: string; + scopes?: string[]; + audience?: string; + }, + userContext?: Record + ) { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2IdToken({ token, - expectedAudience, + requirements, + userContext: getUserContext(userContext), + }); + } + + // TODO: revokeToken + + static createTokenForClientCredentials( + clientId: string, + clientSecret: string, + scope?: string[], + audience?: string, + userContext?: Record + ) { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.tokenExchange({ + body: { + grant_type: "client_credentials", + client_id: clientId, + client_secret: clientSecret, + scope: scope?.join(" "), + audience: audience, + }, userContext: getUserContext(userContext), }); } @@ -84,4 +123,6 @@ export let validateOAuth2AccessToken = Wrapper.validateOAuth2AccessToken; export let validateOAuth2IdToken = Wrapper.validateOAuth2IdToken; +export let createTokenForClientCredentials = Wrapper.createTokenForClientCredentials; + export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/ts/recipe/oauth2provider/recipe.ts b/lib/ts/recipe/oauth2provider/recipe.ts index fac080143..2c3b8e9cc 100644 --- a/lib/ts/recipe/oauth2provider/recipe.ts +++ b/lib/ts/recipe/oauth2provider/recipe.ts @@ -115,12 +115,6 @@ export default class Recipe extends RecipeModule { getAPIsHandled(): APIHandled[] { return [ - { - method: "post", - pathWithoutApiBasePath: new NormalisedURLPath(LOGIN_PATH), - id: LOGIN_PATH, - disabled: this.apiImpl.loginPOST === undefined, - }, { method: "get", pathWithoutApiBasePath: new NormalisedURLPath(LOGIN_PATH), @@ -203,7 +197,9 @@ export default class Recipe extends RecipeModule { } async getDefaultIdTokenPayload(user: User, scopes: string[], userContext: UserContext) { - let payload: JSONObject = {}; + let payload: JSONObject = { + iss: this.appInfo.apiDomain.getAsStringDangerous() + this.appInfo.apiBasePath.getAsStringDangerous(), + }; if (scopes.includes("email")) { payload.email = user?.emails[0]; payload.email_verified = user.loginMethods.some((lm) => lm.hasSameEmailAs(user?.emails[0]) && lm.verified); diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index 9c6366b99..e9238fbc5 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -30,12 +30,13 @@ import { OAuth2Client } from "./OAuth2Client"; import { getUser } from "../.."; import { getCombinedJWKS } from "../../combinedRemoteJWKSet"; import { JSONObject } from "@loopback/core"; +import { getSessionInformation } from "../session"; // TODO: Remove this core changes are done function getUpdatedRedirectTo(appInfo: NormalisedAppinfo, redirectTo: string) { return redirectTo .replace(hydraPubDomain, appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous()) - .replace("oauth2", "oauth2provider"); + .replace("oauth2/", "oauth/"); } export default function getRecipeInterface( @@ -95,9 +96,7 @@ export default function getRecipeInterface( new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/login/reject`), { error: input.error.error, - error_debug: input.error.errorDebug, error_description: input.error.errorDescription, - error_hint: input.error.errorHint, status_code: input.error.statusCode, }, { @@ -162,9 +161,7 @@ export default function getRecipeInterface( new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/consent/reject`), { error: input.error.error, - error_debug: input.error.errorDebug, error_description: input.error.errorDescription, - error_hint: input.error.errorHint, status_code: input.error.statusCode, }, { @@ -188,7 +185,7 @@ export default function getRecipeInterface( input.userContext ); - const redirectTo = resp.headers.get("Location")!; + const redirectTo = getUpdatedRedirectTo(appInfo, resp.headers.get("Location")!); if (redirectTo === undefined) { throw new Error(resp.body); } @@ -206,29 +203,25 @@ export default function getRecipeInterface( } const idToken = this.buildIdTokenPayload({ user, + client: consentRequest.client!, session: input.session, - defaultPayload: await getDefaultIdTokenPayload( - user, - consentRequest.requestedScope ?? [], - input.userContext - ), scopes: consentRequest.requestedScope || [], userContext: input.userContext, }); const accessTokenPayload = await this.buildAccessTokenPayload({ user, + client: consentRequest.client!, session: input.session, - defaultPayload: { - ...input.session.getAccessTokenPayload(input.userContext), // TODO: validate based on access token structure rfc - iss: appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(), - scope: consentRequest.requestedScope?.join(" ") ?? "", - aud: [consentRequest.client!.clientId], - }, - userContext: input.userContext, scopes: consentRequest.requestedScope || [], + userContext: input.userContext, }); + const sessionInfo = await getSessionInformation(input.session.getHandle()); + if (!sessionInfo) { + throw new Error("Session not found"); + } + const consentRes = await this.acceptConsentRequest({ ...input, challenge: consentRequest.challenge, @@ -238,6 +231,7 @@ export default function getRecipeInterface( id_token: idToken, access_token: accessTokenPayload, }, + handledAt: new Date(sessionInfo.timeCreated).toISOString(), }); return { @@ -248,7 +242,7 @@ export default function getRecipeInterface( return { redirectTo, setCookie: resp.headers.get("set-cookie") ?? undefined }; }, - token: async function (this: RecipeInterface, input) { + tokenExchange: async function (this: RecipeInterface, input) { const body: any = { $isFormData: true }; // TODO: we ideally want to avoid using formdata, the core can do the translation for (const key in input.body) { body[key] = input.body[key]; @@ -380,10 +374,21 @@ export default function getRecipeInterface( } }, buildAccessTokenPayload: async function (input) { - return input.defaultPayload; + const stAccessTokenPayload = input.session.getAccessTokenPayload(input.userContext); + const sessionInfo = await getSessionInformation(stAccessTokenPayload.sessionHandle); + if (sessionInfo === undefined) { + throw new Error("Session not found"); + } + return { + tId: stAccessTokenPayload.tId, + rsub: stAccessTokenPayload.rsub, + sessionHandle: stAccessTokenPayload.sessionHandle, + // auth_time: sessionInfo?.timeCreated, + iss: appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(), + }; }, buildIdTokenPayload: async function (input) { - return input.defaultPayload; + return getDefaultIdTokenPayload(input.user, input.scopes, input.userContext); }, buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) { return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext); @@ -391,6 +396,11 @@ export default function getRecipeInterface( validateOAuth2AccessToken: async function (input) { const payload = (await jose.jwtVerify(input.token, getCombinedJWKS())).payload; + // if (payload.stt !== 1) { + // throw new Error("Wrong token type"); + // } + + // TODO: we should be able uncomment this after we get proper core support // TODO: make this configurable? // const expectedIssuer = // appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); @@ -398,33 +408,68 @@ export default function getRecipeInterface( // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); // } - // TODO: Fix this - const aud = - (payload.ext as any)?.aud ?? - (payload.aud instanceof Array ? payload.aud : payload.aud?.split(" ") ?? []); - if (input.expectedAudience !== undefined && !aud.includes(input.expectedAudience)) { - throw new Error("Audience mismatch: this token doesn't belong to the specified client"); + if (input.requirements?.clientId !== undefined && payload.client_id !== input.requirements.clientId) { + throw new Error("The token doesn't belong to the specified client"); + } + + if ( + input.requirements?.scopes !== undefined && + input.requirements.scopes.some((scope) => !(payload.scp as string[]).includes(scope)) + ) { + throw new Error("The token is missing some required scopes"); + } + + const aud = payload.aud instanceof Array ? payload.aud : payload.aud?.split(" ") ?? []; + if (input.requirements?.audience !== undefined && !aud.includes(input.requirements.audience)) { + throw new Error("The token doesn't belong to the specified audience"); } - // TODO: add a check to make sure this is the right token type as they can be signed with the same key + if (input.checkDatabase) { + let response = await querier.sendPostRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/introspect`), + { + $isFormData: true, + token: input.token, + }, + input.userContext + ); + // TODO: fix after the core interface is there + if (response.status !== "OK" || response.data.active !== true) { + throw new Error(response.data.error); + } + } return { status: "OK", payload: payload as JSONObject }; }, validateOAuth2IdToken: async function (input) { const payload = (await jose.jwtVerify(input.token, getCombinedJWKS())).payload; + // TODO: we should be able uncomment this after we get proper core support // TODO: make this configurable? // const expectedIssuer = // appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); - // if (input.expectedAudience !== undefined && payload.iss !== expectedIssuer) { + // if (payload.iss !== expectedIssuer) { // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); // } - const aud = payload.aud instanceof Array ? payload.aud : payload.aud?.split(" ") ?? []; - if (input.expectedAudience !== undefined && !aud.includes(input.expectedAudience)) { - throw new Error("Audience mismatch: this token doesn't belong to the specified client"); + // if (payload.stt !== 2) { + // throw new Error("Wrong token type"); + // } + + if (input.requirements?.clientId !== undefined && payload.client_id !== input.requirements.clientId) { + throw new Error("The token doesn't belong to the specified client"); + } + + if ( + input.requirements?.scopes !== undefined && + input.requirements.scopes.some((scope) => !(payload.scp as string[]).includes(scope)) + ) { + throw new Error("The token is missing some required scopes"); } - // TODO: add a check to make sure this is the right token type as they can be signed with the same key + const aud = payload.aud instanceof Array ? payload.aud : payload.aud?.split(" ") ?? []; + if (input.requirements?.audience !== undefined && !aud.includes(input.requirements.audience)) { + throw new Error("The token doesn't belong to the specified audience"); + } return { status: "OK", payload: payload as JSONObject }; }, diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index dbe02ba1d..353c7ac21 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -21,6 +21,7 @@ import { OAuth2Client } from "./OAuth2Client"; import { User } from "../../user"; export type TypeInput = { + // TODO: issuer? override?: { functions?: ( originalImplementation: RecipeInterface, @@ -57,12 +58,6 @@ export type ErrorOAuth2 = { // Description of the error in a human readable format. errorDescription: string; - // Debug contains information to help resolve the problem as a developer. Usually not exposed to the public but only in the server logs. - errorDebug?: string; - - // Hint to help resolve the error. - errorHint?: string; - // Represents the HTTP status code of the error (e.g. 401 or 403) // Defaults to 400 statusCode?: number; @@ -138,14 +133,14 @@ export type LoginRequest = { export type TokenInfo = { // The access token issued by the authorization server. - access_token: string; + access_token?: string; // The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will expire in one hour from the time the response was generated. // integer expires_in: number; // To retrieve a refresh token request the id_token scope. - id_token: string; + id_token?: string; // The refresh token, which can be used to obtain new access tokens. To retrieve it add the scope "offline" to your access token request. - refresh_token: string; + refresh_token?: string; // The scope of the access token scope: string; // The type of the token issued @@ -156,11 +151,13 @@ export type LoginInfo = { // The name of the client. clientName: string; // The URI of the client's terms of service. - tosUri: string; + tosUri?: string; // The URI of the client's privacy policy. - policyUri: string; + policyUri?: string; // The URI of the client's logo. - logoUri: string; + logoUri?: string; + // The URI of the client + clientUri?: string; // The metadata associated with the client. metadata?: Record | null; }; @@ -176,12 +173,15 @@ export type UserInfo = { export type RecipeInterface = { authorization(input: { - params: any; + params: Record; cookies: string | undefined; session: SessionContainerInterface | undefined; userContext: UserContext; }): Promise<{ redirectTo: string; setCookie: string | undefined }>; - token(input: { body: any; userContext: UserContext }): Promise; + tokenExchange(input: { + body: Record; + userContext: UserContext; + }): Promise; getConsentRequest(input: { challenge: string; userContext: UserContext }): Promise; acceptConsentRequest(input: { challenge: string; @@ -193,7 +193,7 @@ export type RecipeInterface = { // Array of strings (StringSliceJSONFormat represents []string{} which is encoded to/from JSON for SQL storage.) grantScope?: string[]; // string (NullTime implements sql.NullTime functionality.) - handledAt?: string[]; + handledAt?: string; // Remember, if set to true, tells ORY Hydra to remember this consent authorization and reuse it if the same client asks the same user for the same, or a subset of, scope. remember?: boolean; @@ -319,27 +319,36 @@ export type RecipeInterface = { validateOAuth2AccessToken(input: { token: string; - expectedAudience?: string; + requirements?: { + clientId?: string; + scopes?: string[]; + audience?: string; + }; + checkDatabase?: boolean; userContext: UserContext; }): Promise<{ status: "OK"; payload: JSONObject }>; validateOAuth2IdToken(input: { token: string; - expectedAudience?: string; + requirements?: { + clientId?: string; + scopes?: string[]; + audience?: string; + }; userContext: UserContext; }): Promise<{ status: "OK"; payload: JSONObject }>; buildAccessTokenPayload(input: { user: User; + client: OAuth2Client; session: SessionContainerInterface; scopes: string[]; - defaultPayload: JSONObject; userContext: UserContext; }): Promise; buildIdTokenPayload(input: { user: User; + client: OAuth2Client; session: SessionContainerInterface; scopes: string[]; - defaultPayload: JSONObject; userContext: UserContext; }): Promise; buildUserInfo(input: { @@ -361,16 +370,6 @@ export type APIInterface = { userContext: UserContext; }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); - loginPOST: - | undefined - | ((input: { - loginChallenge: string; - accept: boolean; - session: SessionContainerInterface; - options: APIOptions; - userContext: UserContext; - }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); - authGET: | undefined | ((input: { diff --git a/lib/ts/recipe/session/accessToken.ts b/lib/ts/recipe/session/accessToken.ts index c9f5bc637..8c47ef6f6 100644 --- a/lib/ts/recipe/session/accessToken.ts +++ b/lib/ts/recipe/session/accessToken.ts @@ -124,6 +124,10 @@ export async function getInfoFromAccessToken( } export function validateAccessTokenStructure(payload: any, version: number) { + if (payload.stt !== 0 && payload.stt !== undefined) { + throw Error("Wrong token type"); + } + if (version >= 5) { if ( typeof payload.sub !== "string" || diff --git a/lib/ts/recipe/session/constants.ts b/lib/ts/recipe/session/constants.ts index 7a2a78498..e7a5c296a 100644 --- a/lib/ts/recipe/session/constants.ts +++ b/lib/ts/recipe/session/constants.ts @@ -34,4 +34,5 @@ export const protectedProps = [ "antiCsrfToken", "rsub", "tId", + "stt", ]; diff --git a/lib/ts/recipeModule.ts b/lib/ts/recipeModule.ts index e800c7f00..1f3c14b45 100644 --- a/lib/ts/recipeModule.ts +++ b/lib/ts/recipeModule.ts @@ -22,7 +22,7 @@ import { DEFAULT_TENANT_ID } from "./recipe/multitenancy/constants"; export default abstract class RecipeModule { private recipeId: string; - private appInfo: NormalisedAppinfo; + protected appInfo: NormalisedAppinfo; constructor(recipeId: string, appInfo: NormalisedAppinfo) { this.recipeId = recipeId; diff --git a/test/test-server/src/oauth2provider.ts b/test/test-server/src/oauth2provider.ts index 06f732ebe..ce5b4beb1 100644 --- a/test/test-server/src/oauth2provider.ts +++ b/test/test-server/src/oauth2provider.ts @@ -47,7 +47,8 @@ const router = Router() logDebugMessage("OAuth2Provider:validateOAuth2AccessToken %j", req.body); const response = await OAuth2Provider.validateOAuth2AccessToken( req.body.token, - req.body.expectedAudience, + req.body.requirements, + req.body.checkDatabase, req.body.userContext ); res.json(response); @@ -67,6 +68,21 @@ const router = Router() } catch (e) { next(e); } + }) + .post("/createtokenforclientcredentials", async (req, res, next) => { + try { + logDebugMessage("OAuth2Provider:createTokenForClientCredentials %j", req.body); + const response = await OAuth2Provider.createTokenForClientCredentials( + req.body.clientId, + req.body.clientSecret, + req.body.scope, + req.body.audience, + req.body.userContext + ); + res.json(response); + } catch (e) { + next(e); + } }); export default router; From be263bd7489206aa3809d1cbbfebb3be44a8c8f4 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Fri, 2 Aug 2024 01:35:52 +0200 Subject: [PATCH 21/35] feat: remove accessTokenStrategy --- lib/build/recipe/oauth2provider/OAuth2Client.d.ts | 7 ------- lib/build/recipe/oauth2provider/OAuth2Client.js | 2 -- lib/build/recipe/oauth2provider/types.d.ts | 1 - lib/ts/recipe/oauth2provider/OAuth2Client.ts | 9 --------- lib/ts/recipe/oauth2provider/types.ts | 1 - 5 files changed, 20 deletions(-) diff --git a/lib/build/recipe/oauth2provider/OAuth2Client.d.ts b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts index 71cb89757..cef31eb4a 100644 --- a/lib/build/recipe/oauth2provider/OAuth2Client.d.ts +++ b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts @@ -78,12 +78,6 @@ export declare class OAuth2Client { * Requested Client Authentication method for the Token Endpoint. */ tokenEndpointAuthMethod: string; - /** - * OAuth 2.0 Access Token Strategy - * AccessTokenStrategy is the strategy used to generate access tokens. - * Valid options are jwt and opaque. - */ - accessTokenStrategy?: "jwt" | "opaque"; /** * OAuth 2.0 Client URI * ClientURI is a URL string of a web page providing information about the client. @@ -160,7 +154,6 @@ export declare class OAuth2Client { refreshTokenGrantIdTokenLifespan, refreshTokenGrantRefreshTokenLifespan, tokenEndpointAuthMethod, - accessTokenStrategy, clientUri, allowedCorsOrigins, audience, diff --git a/lib/build/recipe/oauth2provider/OAuth2Client.js b/lib/build/recipe/oauth2provider/OAuth2Client.js index 6876c76a0..4e9c91e41 100644 --- a/lib/build/recipe/oauth2provider/OAuth2Client.js +++ b/lib/build/recipe/oauth2provider/OAuth2Client.js @@ -33,7 +33,6 @@ class OAuth2Client { refreshTokenGrantIdTokenLifespan = null, refreshTokenGrantRefreshTokenLifespan = null, tokenEndpointAuthMethod, - accessTokenStrategy, clientUri = "", allowedCorsOrigins = [], audience = [], @@ -66,7 +65,6 @@ class OAuth2Client { this.refreshTokenGrantIdTokenLifespan = refreshTokenGrantIdTokenLifespan; this.refreshTokenGrantRefreshTokenLifespan = refreshTokenGrantRefreshTokenLifespan; this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; - this.accessTokenStrategy = accessTokenStrategy; this.clientUri = clientUri; this.allowedCorsOrigins = allowedCorsOrigins; this.audience = audience; diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index 188891403..de87ea508 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -326,7 +326,6 @@ export declare type OAuth2ClientOptions = { refreshTokenGrantIdTokenLifespan?: string | null; refreshTokenGrantRefreshTokenLifespan?: string | null; tokenEndpointAuthMethod: string; - accessTokenStrategy?: "jwt" | "opaque"; audience?: string[]; grantTypes?: string[] | null; responseTypes?: string[] | null; diff --git a/lib/ts/recipe/oauth2provider/OAuth2Client.ts b/lib/ts/recipe/oauth2provider/OAuth2Client.ts index caa8518a5..aa2e7ccac 100644 --- a/lib/ts/recipe/oauth2provider/OAuth2Client.ts +++ b/lib/ts/recipe/oauth2provider/OAuth2Client.ts @@ -109,13 +109,6 @@ export class OAuth2Client { */ tokenEndpointAuthMethod: string; - /** - * OAuth 2.0 Access Token Strategy - * AccessTokenStrategy is the strategy used to generate access tokens. - * Valid options are jwt and opaque. - */ - accessTokenStrategy?: "jwt" | "opaque"; - /** * OAuth 2.0 Client URI * ClientURI is a URL string of a web page providing information about the client. @@ -203,7 +196,6 @@ export class OAuth2Client { refreshTokenGrantIdTokenLifespan = null, refreshTokenGrantRefreshTokenLifespan = null, tokenEndpointAuthMethod, - accessTokenStrategy, clientUri = "", allowedCorsOrigins = [], audience = [], @@ -231,7 +223,6 @@ export class OAuth2Client { this.refreshTokenGrantIdTokenLifespan = refreshTokenGrantIdTokenLifespan; this.refreshTokenGrantRefreshTokenLifespan = refreshTokenGrantRefreshTokenLifespan; this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; - this.accessTokenStrategy = accessTokenStrategy; this.clientUri = clientUri; this.allowedCorsOrigins = allowedCorsOrigins; this.audience = audience; diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index 353c7ac21..5eeca4817 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -428,7 +428,6 @@ export type OAuth2ClientOptions = { refreshTokenGrantRefreshTokenLifespan?: string | null; tokenEndpointAuthMethod: string; - accessTokenStrategy?: "jwt" | "opaque"; audience?: string[]; grantTypes?: string[] | null; From f53853c763bbb4a61162e739fee7b7138e3b7d01 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Fri, 2 Aug 2024 01:45:52 +0200 Subject: [PATCH 22/35] test: update tests --- test/oauth2/oauth2client.test.js | 48 ++++++++------------------------ 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/test/oauth2/oauth2client.test.js b/test/oauth2/oauth2client.test.js index 36d4008b6..cc2b701f4 100644 --- a/test/oauth2/oauth2client.test.js +++ b/test/oauth2/oauth2client.test.js @@ -31,7 +31,7 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` recipeList: [OAuth2Recipe.init()], }); - const { client } = await OAuth2Recipe.createOAuth2Client({}, {}); + const { client } = await OAuth2Recipe.createOAuth2Client({}); assert(client.clientId !== undefined); assert(client.clientSecret !== undefined); @@ -54,14 +54,12 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` const { client } = await OAuth2Recipe.createOAuth2Client( { - client_id: "client_id", - client_secret: "client_secret", + clientName: "client_name", }, {} ); - assert.strictEqual(client.clientId, "client_id"); - assert.strictEqual(client.clientSecret, "client_secret"); + assert.strictEqual(client.clientName, "client_name"); }); it("should update the OAuth2Client", async function () { @@ -81,16 +79,12 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` // Create a client const { client } = await OAuth2Recipe.createOAuth2Client( { - client_id: "client_id", - client_secret: "client_secret", scope: "offline_access offline", redirectUris: ["http://localhost:3000"], }, {} ); - assert.strictEqual(client.clientId, "client_id"); - assert.strictEqual(client.clientSecret, "client_secret"); assert.strictEqual(client.scope, "offline_access offline"); assert.strictEqual(JSON.stringify(client.redirectUris), JSON.stringify(["http://localhost:3000"])); assert.strictEqual(JSON.stringify(client.metadata), JSON.stringify({})); @@ -128,16 +122,9 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` }); // Create a client - const { client } = await OAuth2Recipe.createOAuth2Client( - { - client_id: "client_id", - client_secret: "client_secret", - }, - {} - ); + const { client } = await OAuth2Recipe.createOAuth2Client({}); - assert.strictEqual(client.clientId, "client_id"); - assert.strictEqual(client.clientSecret, "client_secret"); + assert.strictEqual(client.scope, "offline_access offline openid"); // Delete the client const { status } = await OAuth2Recipe.deleteOAuth2Client( @@ -164,14 +151,11 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` recipeList: [OAuth2Recipe.init()], }); + let clientIds = new Set(); // Create 10 clients for (let i = 0; i < 10; i++) { - await OAuth2Recipe.createOAuth2Client( - { - client_id: `client_id_${i}`, - }, - {} - ); + const client = await OAuth2Recipe.createOAuth2Client({}); + clientIds.add(client.clientId); } let allClients = []; @@ -188,9 +172,10 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` allClients.push(...result.clients); } while (nextPaginationToken); + assert.strictEqual(allClients.length, 10); // Check the client IDs for (let i = 0; i < 10; i++) { - assert.strictEqual(allClients[i].clientId, `client_id_${i}`); + assert(clientIds.has(allClients[i].clientId)); } }); @@ -210,19 +195,10 @@ describe(`OAuth2ClientTests: ${printPath("[test/oauth2/oauth2client.test.js]")}` // Create 5 clients with clientName = "customClientName" for (let i = 0; i < 5; i++) { - await OAuth2Recipe.createOAuth2Client({ clientName: "customClientName" }, {}); - } - - // Create 5 clients with owner = "test" - for (let i = 0; i < 5; i++) { - await OAuth2Recipe.createOAuth2Client({ owner: "test" }, {}); + await OAuth2Recipe.createOAuth2Client({ clientName: "customClientName" }); } - let result = await OAuth2Recipe.getOAuth2Clients({ clientName: "customClientName" }, {}); - assert.strictEqual(result.status, "OK"); - assert.strictEqual(result.clients.length, 5); - - result = await OAuth2Recipe.getOAuth2Clients({ owner: "test" }, {}); + let result = await OAuth2Recipe.getOAuth2Clients({ clientName: "customClientName" }); assert.strictEqual(result.status, "OK"); assert.strictEqual(result.clients.length, 5); }); From 1271be1f35b63f3f599fc2ea4f8e2229e5f559a6 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 7 Aug 2024 03:57:38 +0530 Subject: [PATCH 23/35] feat: OAuth2Client interface changes (#904) --- .../oauth2client/api/authorisationUrl.d.ts | 9 --- .../oauth2client/api/authorisationUrl.js | 44 ------------ .../recipe/oauth2client/api/implementation.js | 15 +---- lib/build/recipe/oauth2client/api/signin.js | 5 +- lib/build/recipe/oauth2client/constants.d.ts | 3 +- lib/build/recipe/oauth2client/constants.js | 5 +- lib/build/recipe/oauth2client/index.d.ts | 10 +-- lib/build/recipe/oauth2client/index.js | 15 +---- lib/build/recipe/oauth2client/recipe.js | 9 --- .../oauth2client/recipeImplementation.js | 54 +++------------ lib/build/recipe/oauth2client/types.d.ts | 40 ++--------- .../oauth2client/api/authorisationUrl.ts | 49 -------------- .../recipe/oauth2client/api/implementation.ts | 19 +----- lib/ts/recipe/oauth2client/api/signin.ts | 7 +- lib/ts/recipe/oauth2client/constants.ts | 4 +- lib/ts/recipe/oauth2client/index.ts | 20 +----- lib/ts/recipe/oauth2client/recipe.ts | 11 +-- .../oauth2client/recipeImplementation.ts | 67 ++++--------------- lib/ts/recipe/oauth2client/types.ts | 39 ++--------- 19 files changed, 56 insertions(+), 369 deletions(-) delete mode 100644 lib/build/recipe/oauth2client/api/authorisationUrl.d.ts delete mode 100644 lib/build/recipe/oauth2client/api/authorisationUrl.js delete mode 100644 lib/ts/recipe/oauth2client/api/authorisationUrl.ts diff --git a/lib/build/recipe/oauth2client/api/authorisationUrl.d.ts b/lib/build/recipe/oauth2client/api/authorisationUrl.d.ts deleted file mode 100644 index 61b231e1a..000000000 --- a/lib/build/recipe/oauth2client/api/authorisationUrl.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// @ts-nocheck -import { APIInterface, APIOptions } from "../"; -import { UserContext } from "../../../types"; -export default function authorisationUrlAPI( - apiImplementation: APIInterface, - _tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise; diff --git a/lib/build/recipe/oauth2client/api/authorisationUrl.js b/lib/build/recipe/oauth2client/api/authorisationUrl.js deleted file mode 100644 index e4544b31c..000000000 --- a/lib/build/recipe/oauth2client/api/authorisationUrl.js +++ /dev/null @@ -1,44 +0,0 @@ -"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; diff --git a/lib/build/recipe/oauth2client/api/implementation.js b/lib/build/recipe/oauth2client/api/implementation.js index 5b642629a..159016c57 100644 --- a/lib/build/recipe/oauth2client/api/implementation.js +++ b/lib/build/recipe/oauth2client/api/implementation.js @@ -8,15 +8,6 @@ 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 }); @@ -32,7 +23,7 @@ function getAPIInterface() { } else { throw Error("should never come here"); } - const { userId, rawUserInfoFromProvider } = await options.recipeImplementation.getUserInfo({ + const { userId, rawUserInfo } = await options.recipeImplementation.getUserInfo({ providerConfig, oAuthTokens: oAuthTokensToUse, userContext, @@ -40,7 +31,7 @@ function getAPIInterface() { const { user, recipeUserId } = await options.recipeImplementation.signIn({ userId, tenantId, - rawUserInfoFromProvider, + rawUserInfo, oAuthTokens: oAuthTokensToUse, userContext, }); @@ -58,7 +49,7 @@ function getAPIInterface() { user, session, oAuthTokens: oAuthTokensToUse, - rawUserInfoFromProvider, + rawUserInfo, }; }, }; diff --git a/lib/build/recipe/oauth2client/api/signin.js b/lib/build/recipe/oauth2client/api/signin.js index 0a0fadc45..52631ac60 100644 --- a/lib/build/recipe/oauth2client/api/signin.js +++ b/lib/build/recipe/oauth2client/api/signin.js @@ -30,10 +30,10 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { let redirectURIInfo; let oAuthTokens; if (bodyParams.redirectURIInfo !== undefined) { - if (bodyParams.redirectURIInfo.redirectURIOnProviderDashboard === undefined) { + if (bodyParams.redirectURIInfo.redirectURI === undefined) { throw new error_1.default({ type: error_1.default.BAD_INPUT_ERROR, - message: "Please provide the redirectURIOnProviderDashboard in request body", + message: "Please provide the redirectURI in request body", }); } redirectURIInfo = bodyParams.redirectURIInfo; @@ -61,7 +61,6 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { tenantId, redirectURIInfo, oAuthTokens, - session, options, userContext, }); diff --git a/lib/build/recipe/oauth2client/constants.d.ts b/lib/build/recipe/oauth2client/constants.d.ts index fd2bef07d..1fb91e760 100644 --- a/lib/build/recipe/oauth2client/constants.d.ts +++ b/lib/build/recipe/oauth2client/constants.d.ts @@ -1,3 +1,2 @@ // @ts-nocheck -export declare const AUTHORISATION_API = "/oauth2client/authorisationurl"; -export declare const SIGN_IN_API = "/oauth2client/signin"; +export declare const SIGN_IN_API = "/oauth/client/signin"; diff --git a/lib/build/recipe/oauth2client/constants.js b/lib/build/recipe/oauth2client/constants.js index e8156ec89..4f42a6cb4 100644 --- a/lib/build/recipe/oauth2client/constants.js +++ b/lib/build/recipe/oauth2client/constants.js @@ -14,6 +14,5 @@ * 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"; +exports.SIGN_IN_API = void 0; +exports.SIGN_IN_API = "/oauth/client/signin"; diff --git a/lib/build/recipe/oauth2client/index.d.ts b/lib/build/recipe/oauth2client/index.d.ts index a16f0bb05..fd09feb41 100644 --- a/lib/build/recipe/oauth2client/index.d.ts +++ b/lib/build/recipe/oauth2client/index.d.ts @@ -3,16 +3,9 @@ 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 - ): Promise<{ - urlWithQueryParams: string; - pkceCodeVerifier?: string | undefined; - }>; static exchangeAuthCodeForOAuthTokens( redirectURIInfo: { - redirectURIOnProviderDashboard: string; + redirectURI: string; redirectURIQueryParams: any; pkceCodeVerifier?: string | undefined; }, @@ -24,7 +17,6 @@ export default class Wrapper { ): Promise; } 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 }; diff --git a/lib/build/recipe/oauth2client/index.js b/lib/build/recipe/oauth2client/index.js index cdf968aaf..c70a69f41 100644 --- a/lib/build/recipe/oauth2client/index.js +++ b/lib/build/recipe/oauth2client/index.js @@ -19,22 +19,10 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getUserInfo = exports.exchangeAuthCodeForOAuthTokens = exports.getAuthorisationRedirectURL = exports.init = void 0; +exports.getUserInfo = exports.exchangeAuthCodeForOAuthTokens = 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 normalisedUserContext = utils_1.getUserContext(userContext); - const providerConfig = await recipeInterfaceImpl.getProviderConfig({ - userContext: normalisedUserContext, - }); - return await recipeInterfaceImpl.getAuthorisationRedirectURL({ - providerConfig, - redirectURIOnProviderDashboard, - userContext: normalisedUserContext, - }); - } static async exchangeAuthCodeForOAuthTokens(redirectURIInfo, userContext) { const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl; const normalisedUserContext = utils_1.getUserContext(userContext); @@ -63,6 +51,5 @@ class Wrapper { 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; diff --git a/lib/build/recipe/oauth2client/recipe.js b/lib/build/recipe/oauth2client/recipe.js index d3ad7c29a..daf6b07f3 100644 --- a/lib/build/recipe/oauth2client/recipe.js +++ b/lib/build/recipe/oauth2client/recipe.js @@ -25,7 +25,6 @@ const error_1 = __importDefault(require("../../error")); const constants_1 = require("./constants"); const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath")); const signin_1 = __importDefault(require("./api/signin")); -const authorisationUrl_1 = __importDefault(require("./api/authorisationUrl")); const recipeImplementation_1 = __importDefault(require("./recipeImplementation")); const implementation_1 = __importDefault(require("./api/implementation")); const querier_1 = require("../../querier"); @@ -41,12 +40,6 @@ class Recipe extends recipeModule_1.default { id: constants_1.SIGN_IN_API, disabled: this.apiImpl.signInPOST === undefined, }, - { - method: "get", - pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.AUTHORISATION_API), - id: constants_1.AUTHORISATION_API, - disabled: this.apiImpl.authorisationUrlGET === undefined, - }, ]; }; this.handleAPIRequest = async (id, tenantId, req, res, _path, _method, userContext) => { @@ -61,8 +54,6 @@ class Recipe extends recipeModule_1.default { }; if (id === constants_1.SIGN_IN_API) { return await signin_1.default(this.apiImpl, tenantId, options, userContext); - } else if (id === constants_1.AUTHORISATION_API) { - return await authorisationUrl_1.default(this.apiImpl, tenantId, options, userContext); } return false; }; diff --git a/lib/build/recipe/oauth2client/recipeImplementation.js b/lib/build/recipe/oauth2client/recipeImplementation.js index 3ea769a72..e935c9e60 100644 --- a/lib/build/recipe/oauth2client/recipeImplementation.js +++ b/lib/build/recipe/oauth2client/recipeImplementation.js @@ -7,39 +7,13 @@ var __importDefault = Object.defineProperty(exports, "__esModule", { value: true }); const recipeUserId_1 = __importDefault(require("../../recipeUserId")); const thirdpartyUtils_1 = require("../../thirdpartyUtils"); -const pkce_challenge_1 = __importDefault(require("pkce-challenge")); const __1 = require("../.."); const logger_1 = require("../../logger"); const jose_1 = require("jose"); function getRecipeImplementation(_querier, config) { let providerConfigWithOIDCInfo = null; return { - getAuthorisationRedirectURL: async function ({ providerConfig, redirectURIOnProviderDashboard }) { - const queryParams = { - client_id: providerConfig.clientId, - redirect_uri: redirectURIOnProviderDashboard, - response_type: "code", - }; - if (providerConfig.scope !== undefined) { - queryParams.scope = providerConfig.scope.join(" "); - } - let pkceCodeVerifier = undefined; - if (providerConfig.clientSecret === undefined || providerConfig.forcePKCE) { - const { code_challenge, code_verifier } = pkce_challenge_1.default(64); // According to https://www.rfc-editor.org/rfc/rfc7636, length must be between 43 and 128 - queryParams["code_challenge"] = code_challenge; - queryParams["code_challenge_method"] = "S256"; - pkceCodeVerifier = code_verifier; - } - const urlObj = new URL(providerConfig.authorizationEndpoint); - for (const [key, value] of Object.entries(queryParams)) { - urlObj.searchParams.set(key, value); - } - return { - urlWithQueryParams: urlObj.toString(), - pkceCodeVerifier: pkceCodeVerifier, - }; - }, - signIn: async function ({ userId, tenantId, userContext, oAuthTokens, rawUserInfoFromProvider }) { + signIn: async function ({ userId, tenantId, userContext, oAuthTokens, rawUserInfo }) { const user = await __1.getUser(userId, userContext); if (user === undefined) { throw new Error(`Failed to getUser from the userId ${userId} in the ${tenantId} tenant`); @@ -49,7 +23,7 @@ function getRecipeImplementation(_querier, config) { user, recipeUserId: new recipeUserId_1.default(userId), oAuthTokens, - rawUserInfoFromProvider, + rawUserInfo, }; }, getProviderConfig: async function () { @@ -84,7 +58,7 @@ function getRecipeImplementation(_querier, config) { const tokenAPIURL = providerConfig.tokenEndpoint; const accessTokenAPIParams = { client_id: providerConfig.clientId, - redirect_uri: redirectURIInfo.redirectURIOnProviderDashboard, + redirect_uri: redirectURIInfo.redirectURI, code: redirectURIInfo.redirectURIQueryParams["code"], grant_type: "authorization_code", }; @@ -110,7 +84,7 @@ function getRecipeImplementation(_querier, config) { let jwks; const accessToken = oAuthTokens["access_token"]; const idToken = oAuthTokens["id_token"]; - let rawUserInfoFromProvider = { + let rawUserInfo = { fromUserInfoAPI: {}, fromIdTokenPayload: {}, }; @@ -118,7 +92,7 @@ function getRecipeImplementation(_querier, config) { if (jwks === undefined) { jwks = jose_1.createRemoteJWKSet(new URL(providerConfig.jwksURI)); } - rawUserInfoFromProvider.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload( + rawUserInfo.fromIdTokenPayload = await thirdpartyUtils_1.verifyIdTokenFromJWKSEndpointAndGetPayload( idToken, jwks, { @@ -144,26 +118,20 @@ function getRecipeImplementation(_querier, config) { `Received response with status ${userInfoFromAccessToken.status} and body ${userInfoFromAccessToken.stringResponse}` ); } - rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; + rawUserInfo.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; } let userId = undefined; - if ( - ((_a = rawUserInfoFromProvider.fromIdTokenPayload) === null || _a === void 0 ? void 0 : _a.sub) !== - undefined - ) { - userId = rawUserInfoFromProvider.fromIdTokenPayload["sub"]; - } else if ( - ((_b = rawUserInfoFromProvider.fromUserInfoAPI) === null || _b === void 0 ? void 0 : _b.sub) !== - undefined - ) { - userId = rawUserInfoFromProvider.fromUserInfoAPI["sub"]; + if (((_a = rawUserInfo.fromIdTokenPayload) === null || _a === void 0 ? void 0 : _a.sub) !== undefined) { + userId = rawUserInfo.fromIdTokenPayload["sub"]; + } else if (((_b = rawUserInfo.fromUserInfoAPI) === null || _b === void 0 ? void 0 : _b.sub) !== undefined) { + userId = rawUserInfo.fromUserInfoAPI["sub"]; } if (userId === undefined) { throw new Error(`Failed to get userId from both the idToken and userInfo endpoint.`); } return { userId, - rawUserInfoFromProvider, + rawUserInfo, }; }, }; diff --git a/lib/build/recipe/oauth2client/types.d.ts b/lib/build/recipe/oauth2client/types.d.ts index d00a4a069..c7b43e2f3 100644 --- a/lib/build/recipe/oauth2client/types.d.ts +++ b/lib/build/recipe/oauth2client/types.d.ts @@ -7,7 +7,7 @@ import { GeneralErrorResponse, User } from "../../types"; import RecipeUserId from "../../recipeUserId"; export declare type UserInfo = { userId: string; - rawUserInfoFromProvider: { + rawUserInfo: { fromIdTokenPayload?: { [key: string]: any; }; @@ -19,12 +19,7 @@ export declare type UserInfo = { export declare type ProviderConfigInput = { clientId: string; clientSecret: string; - authorizationEndpointQueryParams?: { - [key: string]: string | null; - }; oidcDiscoveryEndpoint: string; - scope?: string[]; - forcePKCE?: boolean; }; export declare type ProviderConfigWithOIDCInfo = ProviderConfigInput & { authorizationEndpoint: string; @@ -65,19 +60,11 @@ export declare type TypeNormalisedInput = { }; }; export declare type RecipeInterface = { - getAuthorisationRedirectURL(input: { - providerConfig: ProviderConfigWithOIDCInfo; - redirectURIOnProviderDashboard: string; - userContext: UserContext; - }): Promise<{ - urlWithQueryParams: string; - pkceCodeVerifier?: string; - }>; getProviderConfig(input: { userContext: UserContext }): Promise; signIn(input: { userId: string; oAuthTokens: OAuthTokens; - rawUserInfoFromProvider: { + rawUserInfo: { fromIdTokenPayload?: { [key: string]: any; }; @@ -92,7 +79,7 @@ export declare type RecipeInterface = { recipeUserId: RecipeUserId; user: User; oAuthTokens: OAuthTokens; - rawUserInfoFromProvider: { + rawUserInfo: { fromIdTokenPayload?: { [key: string]: any; }; @@ -104,7 +91,7 @@ export declare type RecipeInterface = { exchangeAuthCodeForOAuthTokens(input: { providerConfig: ProviderConfigWithOIDCInfo; redirectURIInfo: { - redirectURIOnProviderDashboard: string; + redirectURI: string; redirectURIQueryParams: any; pkceCodeVerifier?: string | undefined; }; @@ -126,30 +113,15 @@ export declare type APIOptions = { appInfo: NormalisedAppinfo; }; export declare type APIInterface = { - authorisationUrlGET: - | undefined - | ((input: { - redirectURIOnProviderDashboard: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - urlWithQueryParams: string; - pkceCodeVerifier?: string; - } - | GeneralErrorResponse - >); signInPOST: ( input: { tenantId: string; - session: SessionContainerInterface | undefined; options: APIOptions; userContext: UserContext; } & ( | { redirectURIInfo: { - redirectURIOnProviderDashboard: string; + redirectURI: string; redirectURIQueryParams: any; pkceCodeVerifier?: string; }; @@ -168,7 +140,7 @@ export declare type APIInterface = { oAuthTokens: { [key: string]: any; }; - rawUserInfoFromProvider: { + rawUserInfo: { fromIdTokenPayload?: { [key: string]: any; }; diff --git a/lib/ts/recipe/oauth2client/api/authorisationUrl.ts b/lib/ts/recipe/oauth2client/api/authorisationUrl.ts deleted file mode 100644 index 80d43138c..000000000 --- a/lib/ts/recipe/oauth2client/api/authorisationUrl.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* 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. - */ - -import { send200Response } from "../../../utils"; -import STError from "../../../error"; -import { APIInterface, APIOptions } from "../"; -import { UserContext } from "../../../types"; - -export default async function authorisationUrlAPI( - apiImplementation: APIInterface, - _tenantId: string, - options: APIOptions, - userContext: UserContext -): Promise { - 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 STError({ - type: STError.BAD_INPUT_ERROR, - message: "Please provide the redirectURIOnProviderDashboard as a GET param", - }); - } - - let result = await apiImplementation.authorisationUrlGET({ - redirectURIOnProviderDashboard, - options, - userContext, - }); - - send200Response(options.res, result); - return true; -} diff --git a/lib/ts/recipe/oauth2client/api/implementation.ts b/lib/ts/recipe/oauth2client/api/implementation.ts index a6f14e0cb..7e18629e0 100644 --- a/lib/ts/recipe/oauth2client/api/implementation.ts +++ b/lib/ts/recipe/oauth2client/api/implementation.ts @@ -4,19 +4,6 @@ import { OAuthTokens } from "../types"; export default function getAPIInterface(): APIInterface { return { - authorisationUrlGET: async function ({ options, redirectURIOnProviderDashboard, userContext }) { - const providerConfig = await options.recipeImplementation.getProviderConfig({ userContext }); - - const authUrl = await options.recipeImplementation.getAuthorisationRedirectURL({ - providerConfig, - redirectURIOnProviderDashboard, - userContext, - }); - return { - status: "OK", - ...authUrl, - }; - }, signInPOST: async function (input) { const { options, tenantId, userContext } = input; @@ -36,7 +23,7 @@ export default function getAPIInterface(): APIInterface { throw Error("should never come here"); } - const { userId, rawUserInfoFromProvider } = await options.recipeImplementation.getUserInfo({ + const { userId, rawUserInfo } = await options.recipeImplementation.getUserInfo({ providerConfig, oAuthTokens: oAuthTokensToUse, userContext, @@ -45,7 +32,7 @@ export default function getAPIInterface(): APIInterface { const { user, recipeUserId } = await options.recipeImplementation.signIn({ userId, tenantId, - rawUserInfoFromProvider, + rawUserInfo, oAuthTokens: oAuthTokensToUse, userContext, }); @@ -65,7 +52,7 @@ export default function getAPIInterface(): APIInterface { user, session, oAuthTokens: oAuthTokensToUse, - rawUserInfoFromProvider, + rawUserInfo, }; }, }; diff --git a/lib/ts/recipe/oauth2client/api/signin.ts b/lib/ts/recipe/oauth2client/api/signin.ts index 663af60e4..6e89436f9 100644 --- a/lib/ts/recipe/oauth2client/api/signin.ts +++ b/lib/ts/recipe/oauth2client/api/signin.ts @@ -34,17 +34,17 @@ export default async function signInAPI( let redirectURIInfo: | undefined | { - redirectURIOnProviderDashboard: string; + redirectURI: string; redirectURIQueryParams: any; pkceCodeVerifier?: string; }; let oAuthTokens: any; if (bodyParams.redirectURIInfo !== undefined) { - if (bodyParams.redirectURIInfo.redirectURIOnProviderDashboard === undefined) { + if (bodyParams.redirectURIInfo.redirectURI === undefined) { throw new STError({ type: STError.BAD_INPUT_ERROR, - message: "Please provide the redirectURIOnProviderDashboard in request body", + message: "Please provide the redirectURI in request body", }); } redirectURIInfo = bodyParams.redirectURIInfo; @@ -75,7 +75,6 @@ export default async function signInAPI( tenantId, redirectURIInfo, oAuthTokens, - session, options, userContext, }); diff --git a/lib/ts/recipe/oauth2client/constants.ts b/lib/ts/recipe/oauth2client/constants.ts index 545ef08f1..8e45f0567 100644 --- a/lib/ts/recipe/oauth2client/constants.ts +++ b/lib/ts/recipe/oauth2client/constants.ts @@ -13,6 +13,4 @@ * under the License. */ -export const AUTHORISATION_API = "/oauth2client/authorisationurl"; - -export const SIGN_IN_API = "/oauth2client/signin"; +export const SIGN_IN_API = "/oauth/client/signin"; diff --git a/lib/ts/recipe/oauth2client/index.ts b/lib/ts/recipe/oauth2client/index.ts index 0ae16a19d..d2b2e2a02 100644 --- a/lib/ts/recipe/oauth2client/index.ts +++ b/lib/ts/recipe/oauth2client/index.ts @@ -20,25 +20,9 @@ import { RecipeInterface, APIInterface, APIOptions, OAuthTokens } from "./types" export default class Wrapper { static init = Recipe.init; - static async getAuthorisationRedirectURL( - redirectURIOnProviderDashboard: string, - userContext?: Record - ) { - const recipeInterfaceImpl = Recipe.getInstanceOrThrowError().recipeInterfaceImpl; - const normalisedUserContext = getUserContext(userContext); - const providerConfig = await recipeInterfaceImpl.getProviderConfig({ - userContext: normalisedUserContext, - }); - return await recipeInterfaceImpl.getAuthorisationRedirectURL({ - providerConfig, - redirectURIOnProviderDashboard, - userContext: normalisedUserContext, - }); - } - static async exchangeAuthCodeForOAuthTokens( redirectURIInfo: { - redirectURIOnProviderDashboard: string; + redirectURI: string; redirectURIQueryParams: any; pkceCodeVerifier?: string | undefined; }, @@ -72,8 +56,6 @@ export default class Wrapper { export let init = Wrapper.init; -export let getAuthorisationRedirectURL = Wrapper.getAuthorisationRedirectURL; - export let exchangeAuthCodeForOAuthTokens = Wrapper.exchangeAuthCodeForOAuthTokens; export let getUserInfo = Wrapper.getUserInfo; diff --git a/lib/ts/recipe/oauth2client/recipe.ts b/lib/ts/recipe/oauth2client/recipe.ts index 9dfc98a90..7da475c5c 100644 --- a/lib/ts/recipe/oauth2client/recipe.ts +++ b/lib/ts/recipe/oauth2client/recipe.ts @@ -18,10 +18,9 @@ import { NormalisedAppinfo, APIHandled, RecipeListFunction, HTTPMethod, UserCont import { TypeInput, TypeNormalisedInput, RecipeInterface, APIInterface } from "./types"; import { validateAndNormaliseUserInput } from "./utils"; import STError from "../../error"; -import { SIGN_IN_API, AUTHORISATION_API } from "./constants"; +import { SIGN_IN_API } from "./constants"; import NormalisedURLPath from "../../normalisedURLPath"; import signInAPI from "./api/signin"; -import authorisationUrlAPI from "./api/authorisationUrl"; import RecipeImplementation from "./recipeImplementation"; import APIImplementation from "./api/implementation"; import { Querier } from "../../querier"; @@ -97,12 +96,6 @@ export default class Recipe extends RecipeModule { id: SIGN_IN_API, disabled: this.apiImpl.signInPOST === undefined, }, - { - method: "get", - pathWithoutApiBasePath: new NormalisedURLPath(AUTHORISATION_API), - id: AUTHORISATION_API, - disabled: this.apiImpl.authorisationUrlGET === undefined, - }, ]; }; @@ -126,8 +119,6 @@ export default class Recipe extends RecipeModule { }; if (id === SIGN_IN_API) { return await signInAPI(this.apiImpl, tenantId, options, userContext); - } else if (id === AUTHORISATION_API) { - return await authorisationUrlAPI(this.apiImpl, tenantId, options, userContext); } return false; }; diff --git a/lib/ts/recipe/oauth2client/recipeImplementation.ts b/lib/ts/recipe/oauth2client/recipeImplementation.ts index 11b277965..a5115c815 100644 --- a/lib/ts/recipe/oauth2client/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2client/recipeImplementation.ts @@ -15,7 +15,6 @@ import { getOIDCDiscoveryInfo, verifyIdTokenFromJWKSEndpointAndGetPayload, } from "../../thirdpartyUtils"; -import pkceChallenge from "pkce-challenge"; import { getUser } from "../.."; import { logDebugMessage } from "../../logger"; import { JWTVerifyGetKey, createRemoteJWKSet } from "jose"; @@ -24,52 +23,18 @@ export default function getRecipeImplementation(_querier: Querier, config: TypeN let providerConfigWithOIDCInfo: ProviderConfigWithOIDCInfo | null = null; return { - getAuthorisationRedirectURL: async function ( - this: RecipeInterface, - { providerConfig, redirectURIOnProviderDashboard } - ) { - const queryParams: { [key: string]: string } = { - client_id: providerConfig.clientId, - redirect_uri: redirectURIOnProviderDashboard, - response_type: "code", - }; - - if (providerConfig.scope !== undefined) { - queryParams.scope = providerConfig.scope.join(" "); - } - - let pkceCodeVerifier: string | undefined = undefined; - - if (providerConfig.clientSecret === undefined || providerConfig.forcePKCE) { - const { code_challenge, code_verifier } = pkceChallenge(64); // According to https://www.rfc-editor.org/rfc/rfc7636, length must be between 43 and 128 - queryParams["code_challenge"] = code_challenge; - queryParams["code_challenge_method"] = "S256"; - pkceCodeVerifier = code_verifier; - } - - const urlObj = new URL(providerConfig.authorizationEndpoint); - - for (const [key, value] of Object.entries(queryParams)) { - urlObj.searchParams.set(key, value); - } - - return { - urlWithQueryParams: urlObj.toString(), - pkceCodeVerifier: pkceCodeVerifier, - }; - }, signIn: async function ({ userId, tenantId, userContext, oAuthTokens, - rawUserInfoFromProvider, + rawUserInfo, }): Promise<{ status: "OK"; user: UserType; recipeUserId: RecipeUserId; oAuthTokens: OAuthTokens; - rawUserInfoFromProvider: { + rawUserInfo: { fromIdTokenPayload?: { [key: string]: any }; fromUserInfoAPI?: { [key: string]: any }; }; @@ -85,7 +50,7 @@ export default function getRecipeImplementation(_querier: Querier, config: TypeN user, recipeUserId: new RecipeUserId(userId), oAuthTokens, - rawUserInfoFromProvider, + rawUserInfo, }; }, getProviderConfig: async function () { @@ -123,7 +88,7 @@ export default function getRecipeImplementation(_querier: Querier, config: TypeN const tokenAPIURL = providerConfig.tokenEndpoint; const accessTokenAPIParams: { [key: string]: string } = { client_id: providerConfig.clientId, - redirect_uri: redirectURIInfo.redirectURIOnProviderDashboard, + redirect_uri: redirectURIInfo.redirectURI, code: redirectURIInfo.redirectURIQueryParams["code"], grant_type: "authorization_code", }; @@ -153,7 +118,7 @@ export default function getRecipeImplementation(_querier: Querier, config: TypeN const accessToken = oAuthTokens["access_token"]; const idToken = oAuthTokens["id_token"]; - let rawUserInfoFromProvider: { + let rawUserInfo: { fromUserInfoAPI: any; fromIdTokenPayload: any; } = { @@ -166,13 +131,9 @@ export default function getRecipeImplementation(_querier: Querier, config: TypeN jwks = createRemoteJWKSet(new URL(providerConfig.jwksURI)); } - rawUserInfoFromProvider.fromIdTokenPayload = await verifyIdTokenFromJWKSEndpointAndGetPayload( - idToken, - jwks, - { - audience: providerConfig.clientId, - } - ); + rawUserInfo.fromIdTokenPayload = await verifyIdTokenFromJWKSEndpointAndGetPayload(idToken, jwks, { + audience: providerConfig.clientId, + }); } if (accessToken && providerConfig.userInfoEndpoint !== undefined) { @@ -196,15 +157,15 @@ export default function getRecipeImplementation(_querier: Querier, config: TypeN ); } - rawUserInfoFromProvider.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; + rawUserInfo.fromUserInfoAPI = userInfoFromAccessToken.jsonResponse; } let userId: string | undefined = undefined; - if (rawUserInfoFromProvider.fromIdTokenPayload?.sub !== undefined) { - userId = rawUserInfoFromProvider.fromIdTokenPayload["sub"]; - } else if (rawUserInfoFromProvider.fromUserInfoAPI?.sub !== undefined) { - userId = rawUserInfoFromProvider.fromUserInfoAPI["sub"]; + if (rawUserInfo.fromIdTokenPayload?.sub !== undefined) { + userId = rawUserInfo.fromIdTokenPayload["sub"]; + } else if (rawUserInfo.fromUserInfoAPI?.sub !== undefined) { + userId = rawUserInfo.fromUserInfoAPI["sub"]; } if (userId === undefined) { @@ -213,7 +174,7 @@ export default function getRecipeImplementation(_querier: Querier, config: TypeN return { userId, - rawUserInfoFromProvider, + rawUserInfo, }; }, }; diff --git a/lib/ts/recipe/oauth2client/types.ts b/lib/ts/recipe/oauth2client/types.ts index 75d3a55c2..0c40c9bdc 100644 --- a/lib/ts/recipe/oauth2client/types.ts +++ b/lib/ts/recipe/oauth2client/types.ts @@ -22,16 +22,13 @@ import RecipeUserId from "../../recipeUserId"; export type UserInfo = { userId: string; - rawUserInfoFromProvider: { fromIdTokenPayload?: { [key: string]: any }; fromUserInfoAPI?: { [key: string]: any } }; + rawUserInfo: { fromIdTokenPayload?: { [key: string]: any }; fromUserInfoAPI?: { [key: string]: any } }; }; export type ProviderConfigInput = { clientId: string; clientSecret: string; - authorizationEndpointQueryParams?: { [key: string]: string | null }; oidcDiscoveryEndpoint: string; - scope?: string[]; - forcePKCE?: boolean; }; export type ProviderConfigWithOIDCInfo = ProviderConfigInput & { @@ -78,20 +75,12 @@ export type TypeNormalisedInput = { }; export type RecipeInterface = { - getAuthorisationRedirectURL(input: { - providerConfig: ProviderConfigWithOIDCInfo; - redirectURIOnProviderDashboard: string; - userContext: UserContext; - }): Promise<{ - urlWithQueryParams: string; - pkceCodeVerifier?: string; - }>; getProviderConfig(input: { userContext: UserContext }): Promise; signIn(input: { userId: string; oAuthTokens: OAuthTokens; - rawUserInfoFromProvider: { + rawUserInfo: { fromIdTokenPayload?: { [key: string]: any }; fromUserInfoAPI?: { [key: string]: any }; }; @@ -102,7 +91,7 @@ export type RecipeInterface = { recipeUserId: RecipeUserId; user: User; oAuthTokens: OAuthTokens; - rawUserInfoFromProvider: { + rawUserInfo: { fromIdTokenPayload?: { [key: string]: any }; fromUserInfoAPI?: { [key: string]: any }; }; @@ -110,7 +99,7 @@ export type RecipeInterface = { exchangeAuthCodeForOAuthTokens(input: { providerConfig: ProviderConfigWithOIDCInfo; redirectURIInfo: { - redirectURIOnProviderDashboard: string; + redirectURI: string; redirectURIQueryParams: any; pkceCodeVerifier?: string | undefined; }; @@ -134,31 +123,15 @@ export type APIOptions = { }; export type APIInterface = { - authorisationUrlGET: - | undefined - | ((input: { - redirectURIOnProviderDashboard: string; - options: APIOptions; - userContext: UserContext; - }) => Promise< - | { - status: "OK"; - urlWithQueryParams: string; - pkceCodeVerifier?: string; - } - | GeneralErrorResponse - >); - signInPOST: ( input: { tenantId: string; - session: SessionContainerInterface | undefined; options: APIOptions; userContext: UserContext; } & ( | { redirectURIInfo: { - redirectURIOnProviderDashboard: string; + redirectURI: string; redirectURIQueryParams: any; pkceCodeVerifier?: string; }; @@ -173,7 +146,7 @@ export type APIInterface = { user: User; session: SessionContainerInterface; oAuthTokens: { [key: string]: any }; - rawUserInfoFromProvider: { + rawUserInfo: { fromIdTokenPayload?: { [key: string]: any }; fromUserInfoAPI?: { [key: string]: any }; }; From a7a2b87a165d552fa113c229e2d86bfc595b8195 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Thu, 8 Aug 2024 19:23:56 +0530 Subject: [PATCH 24/35] feat: Add token revocation endpoint (#902) * feat: Add token revocation endpoint * fix: PR changes * fix: PR changes * fix: PR changes * fix: PR changes * fix: PR changes * fix: Add revocation_endpoint --- lib/build/querier.js | 14 ++++- .../oauth2provider/api/implementation.js | 16 +++++ .../oauth2provider/api/revokeToken.d.ts | 8 +++ .../recipe/oauth2provider/api/revokeToken.js | 51 +++++++++++++++ .../recipe/oauth2provider/api/userInfo.js | 2 +- .../recipe/oauth2provider/constants.d.ts | 1 + lib/build/recipe/oauth2provider/constants.js | 3 +- lib/build/recipe/oauth2provider/index.d.ts | 26 ++++++++ lib/build/recipe/oauth2provider/index.js | 36 ++++++++++- lib/build/recipe/oauth2provider/recipe.js | 10 +++ .../oauth2provider/recipeImplementation.js | 56 +++++++++++++++++ lib/build/recipe/oauth2provider/types.d.ts | 55 ++++++++++++++++ .../api/getOpenIdDiscoveryConfiguration.js | 1 + lib/build/recipe/openid/index.d.ts | 1 + .../recipe/openid/recipeImplementation.js | 1 + lib/build/recipe/openid/types.d.ts | 2 + lib/build/recipe/session/index.d.ts | 1 + lib/ts/querier.ts | 16 ++++- .../oauth2provider/api/implementation.ts | 16 +++++ .../recipe/oauth2provider/api/revokeToken.ts | 62 +++++++++++++++++++ lib/ts/recipe/oauth2provider/api/userInfo.ts | 2 +- lib/ts/recipe/oauth2provider/constants.ts | 1 + lib/ts/recipe/oauth2provider/index.ts | 49 +++++++++++++++ lib/ts/recipe/oauth2provider/recipe.ts | 12 +++- .../oauth2provider/recipeImplementation.ts | 62 +++++++++++++++++++ lib/ts/recipe/oauth2provider/types.ts | 35 +++++++++++ .../api/getOpenIdDiscoveryConfiguration.ts | 1 + lib/ts/recipe/openid/recipeImplementation.ts | 3 +- lib/ts/recipe/openid/types.ts | 2 + test/with-typescript/index.ts | 1 + 30 files changed, 536 insertions(+), 10 deletions(-) create mode 100644 lib/build/recipe/oauth2provider/api/revokeToken.d.ts create mode 100644 lib/build/recipe/oauth2provider/api/revokeToken.js create mode 100644 lib/ts/recipe/oauth2provider/api/revokeToken.ts diff --git a/lib/build/querier.js b/lib/build/querier.js index 9cc9680bd..ee366ca91 100644 --- a/lib/build/querier.js +++ b/lib/build/querier.js @@ -124,6 +124,11 @@ class Querier { } else { headers["content-type"] = "application/json; charset=utf-8"; } + // TODO: Remove this after core changes are done + if (body !== undefined && body["authorizationHeader"]) { + headers["authorization"] = body["authorizationHeader"]; + delete body["authorizationHeader"]; + } if (Querier.apiKey !== undefined) { headers = Object.assign(Object.assign({}, headers), { "api-key": Querier.apiKey }); } @@ -603,15 +608,20 @@ async function handleHydraAPICall(response) { return { body: { status: response.ok ? "OK" : "ERROR", + statusCode: response.status, data: await response.clone().json(), }, headers: response.headers, }; } else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("text/plain")) { return { - body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().text() }, + body: { + status: response.ok ? "OK" : "ERROR", + statusCode: response.status, + data: await response.clone().text(), + }, headers: response.headers, }; } - return { body: { status: response.ok ? "OK" : "ERROR" }, headers: response.headers }; + return { body: { status: response.ok ? "OK" : "ERROR", statusCode: response.status }, headers: response.headers }; } diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index 9739a22ed..bc5dddac4 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -79,6 +79,22 @@ function getAPIImplementation() { userContext, }); }, + revokeTokenPOST: async (input) => { + if ("authorizationHeader" in input) { + return input.options.recipeImplementation.revokeToken({ + token: input.token, + authorizationHeader: input.authorizationHeader, + userContext: input.userContext, + }); + } else { + return input.options.recipeImplementation.revokeToken({ + token: input.token, + clientId: input.clientId, + clientSecret: input.clientSecret, + userContext: input.userContext, + }); + } + }, }; } exports.default = getAPIImplementation; diff --git a/lib/build/recipe/oauth2provider/api/revokeToken.d.ts b/lib/build/recipe/oauth2provider/api/revokeToken.d.ts new file mode 100644 index 000000000..902e734d5 --- /dev/null +++ b/lib/build/recipe/oauth2provider/api/revokeToken.d.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function revokeTokenPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/revokeToken.js b/lib/build/recipe/oauth2provider/api/revokeToken.js new file mode 100644 index 000000000..c36975c41 --- /dev/null +++ b/lib/build/recipe/oauth2provider/api/revokeToken.js @@ -0,0 +1,51 @@ +"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 }); +const utils_1 = require("../../../utils"); +async function revokeTokenPOST(apiImplementation, options, userContext) { + if (apiImplementation.revokeTokenPOST === undefined) { + return false; + } + const body = await options.req.getFormData(); + if (body.token === undefined) { + utils_1.sendNon200ResponseWithMessage(options.res, "token is required in the request body", 400); + return true; + } + const authorizationHeader = options.req.getHeaderValue("authorization"); + if (authorizationHeader !== undefined && (body.client_id !== undefined || body.client_secret !== undefined)) { + utils_1.sendNon200ResponseWithMessage( + options.res, + "Only one of authorization header or client_id and client_secret can be provided", + 400 + ); + return true; + } + let response = await apiImplementation.revokeTokenPOST({ + options, + authorizationHeader, + token: body.token, + clientId: body.client_id, + clientSecret: body.client_secret, + userContext, + }); + if ("statusCode" in response && response.statusCode !== 200) { + utils_1.sendNon200Response(options.res, response.statusCode, response); + } else { + utils_1.send200Response(options.res, response); + } + return true; +} +exports.default = revokeTokenPOST; diff --git a/lib/build/recipe/oauth2provider/api/userInfo.js b/lib/build/recipe/oauth2provider/api/userInfo.js index ff38a82b3..cfa8f704b 100644 --- a/lib/build/recipe/oauth2provider/api/userInfo.js +++ b/lib/build/recipe/oauth2provider/api/userInfo.js @@ -26,7 +26,7 @@ async function userInfoGET(apiImplementation, tenantId, options, userContext) { if (apiImplementation.userInfoGET === undefined) { return false; } - const authHeader = options.req.getHeaderValue("authorization") || options.req.getHeaderValue("Authorization"); + const authHeader = options.req.getHeaderValue("authorization"); if (authHeader === undefined || !authHeader.startsWith("Bearer ")) { utils_1.sendNon200ResponseWithMessage(options.res, "Missing or invalid Authorization header", 401); return true; diff --git a/lib/build/recipe/oauth2provider/constants.d.ts b/lib/build/recipe/oauth2provider/constants.d.ts index c609db866..0ddf7e65f 100644 --- a/lib/build/recipe/oauth2provider/constants.d.ts +++ b/lib/build/recipe/oauth2provider/constants.d.ts @@ -5,3 +5,4 @@ export declare const AUTH_PATH = "/oauth/auth"; export declare const TOKEN_PATH = "/oauth/token"; export declare const LOGIN_INFO_PATH = "/oauth/login/info"; export declare const USER_INFO_PATH = "/oauth/userinfo"; +export declare const REVOKE_TOKEN_PATH = "/oauth/revoke"; diff --git a/lib/build/recipe/oauth2provider/constants.js b/lib/build/recipe/oauth2provider/constants.js index 66872296b..be31a261b 100644 --- a/lib/build/recipe/oauth2provider/constants.js +++ b/lib/build/recipe/oauth2provider/constants.js @@ -14,10 +14,11 @@ * under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.USER_INFO_PATH = exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; +exports.REVOKE_TOKEN_PATH = exports.USER_INFO_PATH = exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; exports.OAUTH2_BASE_PATH = "/oauth/"; exports.LOGIN_PATH = "/oauth/login"; exports.AUTH_PATH = "/oauth/auth"; exports.TOKEN_PATH = "/oauth/token"; exports.LOGIN_INFO_PATH = "/oauth/login/info"; exports.USER_INFO_PATH = "/oauth/userinfo"; +exports.REVOKE_TOKEN_PATH = "/oauth/revoke"; diff --git a/lib/build/recipe/oauth2provider/index.d.ts b/lib/build/recipe/oauth2provider/index.d.ts index 1bfdc6137..a0e9741f1 100644 --- a/lib/build/recipe/oauth2provider/index.d.ts +++ b/lib/build/recipe/oauth2provider/index.d.ts @@ -11,6 +11,20 @@ import { } from "./types"; export default class Wrapper { static init: typeof Recipe.init; + static getOAuth2Client( + clientId: string, + userContext?: Record + ): Promise< + | { + status: "OK"; + client: import("./OAuth2Client").OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; static getOAuth2Clients( input: GetOAuth2ClientsInput, userContext?: Record @@ -99,6 +113,17 @@ export default class Wrapper { audience?: string, userContext?: Record ): Promise; + static revokeToken( + token: string, + clientId: string, + clientSecret?: string, + userContext?: Record + ): Promise< + | import("./types").ErrorOAuth2 + | { + status: "OK"; + } + >; } export declare let init: typeof Recipe.init; export declare let getOAuth2Clients: typeof Wrapper.getOAuth2Clients; @@ -108,4 +133,5 @@ export declare let deleteOAuth2Client: typeof Wrapper.deleteOAuth2Client; export declare let validateOAuth2AccessToken: typeof Wrapper.validateOAuth2AccessToken; export declare let validateOAuth2IdToken: typeof Wrapper.validateOAuth2IdToken; export declare let createTokenForClientCredentials: typeof Wrapper.createTokenForClientCredentials; +export declare let revokeToken: typeof Wrapper.revokeToken; export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/build/recipe/oauth2provider/index.js b/lib/build/recipe/oauth2provider/index.js index 79f6a2929..96bbf2939 100644 --- a/lib/build/recipe/oauth2provider/index.js +++ b/lib/build/recipe/oauth2provider/index.js @@ -19,10 +19,15 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.createTokenForClientCredentials = exports.validateOAuth2IdToken = exports.validateOAuth2AccessToken = exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.init = void 0; +exports.revokeToken = exports.createTokenForClientCredentials = exports.validateOAuth2IdToken = exports.validateOAuth2AccessToken = exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.init = void 0; const utils_1 = require("../../utils"); const recipe_1 = __importDefault(require("./recipe")); class Wrapper { + static async getOAuth2Client(clientId, userContext) { + return await recipe_1.default + .getInstanceOrThrowError() + .recipeInterfaceImpl.getOAuth2Client({ clientId }, utils_1.getUserContext(userContext)); + } static async getOAuth2Clients(input, userContext) { return await recipe_1.default .getInstanceOrThrowError() @@ -71,6 +76,34 @@ class Wrapper { userContext: utils_1.getUserContext(userContext), }); } + static async revokeToken(token, clientId, clientSecret, userContext) { + let authorizationHeader = undefined; + const normalisedUserContext = utils_1.getUserContext(userContext); + const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl; + const res = await recipeInterfaceImpl.getOAuth2Client({ clientId }, normalisedUserContext); + if (res.status !== "OK") { + throw new Error(`Failed to get OAuth2 client with id ${clientId}: ${res.error}`); + } + const { tokenEndpointAuthMethod } = res.client; + if (tokenEndpointAuthMethod === "none") { + authorizationHeader = "Basic " + Buffer.from(clientId + ":").toString("base64"); + } else if (tokenEndpointAuthMethod === "client_secret_basic") { + authorizationHeader = "Basic " + Buffer.from(clientId + ":" + clientSecret).toString("base64"); + } + if (authorizationHeader !== undefined) { + return await recipeInterfaceImpl.revokeToken({ + token, + authorizationHeader, + userContext: normalisedUserContext, + }); + } + return await recipeInterfaceImpl.revokeToken({ + token, + clientId, + clientSecret, + userContext: normalisedUserContext, + }); + } } exports.default = Wrapper; Wrapper.init = recipe_1.default.init; @@ -82,3 +115,4 @@ exports.deleteOAuth2Client = Wrapper.deleteOAuth2Client; exports.validateOAuth2AccessToken = Wrapper.validateOAuth2AccessToken; exports.validateOAuth2IdToken = Wrapper.validateOAuth2IdToken; exports.createTokenForClientCredentials = Wrapper.createTokenForClientCredentials; +exports.revokeToken = Wrapper.revokeToken; diff --git a/lib/build/recipe/oauth2provider/recipe.js b/lib/build/recipe/oauth2provider/recipe.js index 95a3de853..d523000c0 100644 --- a/lib/build/recipe/oauth2provider/recipe.js +++ b/lib/build/recipe/oauth2provider/recipe.js @@ -34,6 +34,7 @@ const utils_1 = require("./utils"); const supertokens_js_override_1 = __importDefault(require("supertokens-js-override")); const userInfo_1 = __importDefault(require("./api/userInfo")); const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); +const revokeToken_1 = __importDefault(require("./api/revokeToken")); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config) { super(recipeId, appInfo); @@ -66,6 +67,9 @@ class Recipe extends recipeModule_1.default { if (id === constants_1.USER_INFO_PATH) { return userInfo_1.default(this.apiImpl, tenantId, options, userContext); } + if (id === constants_1.REVOKE_TOKEN_PATH) { + return revokeToken_1.default(this.apiImpl, options, userContext); + } throw new Error("Should never come here: handleAPIRequest called with unknown id"); }; this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); @@ -147,6 +151,12 @@ class Recipe extends recipeModule_1.default { id: constants_1.USER_INFO_PATH, disabled: this.apiImpl.userInfoGET === undefined, }, + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.REVOKE_TOKEN_PATH), + id: constants_1.REVOKE_TOKEN_PATH, + disabled: this.apiImpl.revokeTokenPOST === undefined, + }, ]; } handleError(error, _, __, _userContext) { diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 1dff06e37..caad2a357 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -270,6 +270,13 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, body, input.userContext ); + if (res.status !== "OK") { + return { + statusCode: res.statusCode, + error: res.data.error, + errorDescription: res.data.error_description, + }; + } return res.data; }, getOAuth2Clients: async function (input, userContext) { @@ -308,6 +315,26 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, }; } }, + getOAuth2Client: async function (input, userContext) { + let response = await querier.sendGetRequestWithResponseHeaders( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients/${input.clientId}`), + {}, + {}, + userContext + ); + if (response.body.status === "OK") { + return { + status: "OK", + client: OAuth2Client_1.OAuth2Client.fromAPIResponse(response.body.data), + }; + } else { + return { + status: "ERROR", + error: response.body.data.error, + errorHint: response.body.data.errorHint, + }; + } + }, createOAuth2Client: async function (input, userContext) { let response = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients`), @@ -492,6 +519,35 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, } return { status: "OK", payload: payload }; }, + revokeToken: async function (input) { + const requestBody = { + $isFormData: true, + token: input.token, + }; + if ("authorizationHeader" in input) { + requestBody.authorizationHeader = input.authorizationHeader; + } else { + if ("clientId" in input) { + requestBody.client_id = input.clientId; + } + if ("clientSecret" in input) { + requestBody.client_secret = input.clientSecret; + } + } + const res = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/pub/revoke`), + requestBody, + input.userContext + ); + if (res.status !== "OK") { + return { + statusCode: res.statusCode, + error: res.data.error, + errorDescription: res.data.error_description, + }; + } + return { status: "OK" }; + }, }; } exports.default = getRecipeInterface; diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index de87ea508..f0b943bb7 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -143,6 +143,20 @@ export declare type RecipeInterface = { }): Promise<{ redirectTo: string; }>; + getOAuth2Client( + input: Pick, + userContext: UserContext + ): Promise< + | { + status: "OK"; + client: OAuth2Client; + } + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; getOAuth2Clients( input: GetOAuth2ClientsInput, userContext: UserContext @@ -245,6 +259,25 @@ export declare type RecipeInterface = { tenantId: string; userContext: UserContext; }): Promise; + revokeToken( + input: { + token: string; + userContext: UserContext; + } & ( + | { + authorizationHeader: string; + } + | { + clientId: string; + clientSecret?: string; + } + ) + ): Promise< + | { + status: "OK"; + } + | ErrorOAuth2 + >; }; export declare type APIInterface = { loginGET: @@ -306,6 +339,28 @@ export declare type APIInterface = { options: APIOptions; userContext: UserContext; }) => Promise); + revokeTokenPOST: + | undefined + | (( + input: { + token: string; + options: APIOptions; + userContext: UserContext; + } & ( + | { + authorizationHeader: string; + } + | { + clientId: string; + clientSecret?: string; + } + ) + ) => Promise< + | { + status: "OK"; + } + | ErrorOAuth2 + >); }; export declare type OAuth2ClientOptions = { clientId: string; diff --git a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js index 6cd6f8ec5..227295247 100644 --- a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js +++ b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js @@ -17,6 +17,7 @@ async function getOpenIdDiscoveryConfiguration(apiImplementation, options, userC authorization_endpoint: result.authorization_endpoint, token_endpoint: result.token_endpoint, userinfo_endpoint: result.userinfo_endpoint, + revocation_endpoint: result.revocation_endpoint, subject_types_supported: result.subject_types_supported, id_token_signing_alg_values_supported: result.id_token_signing_alg_values_supported, response_types_supported: result.response_types_supported, diff --git a/lib/build/recipe/openid/index.d.ts b/lib/build/recipe/openid/index.d.ts index 6c78c266e..4b3c30cea 100644 --- a/lib/build/recipe/openid/index.d.ts +++ b/lib/build/recipe/openid/index.d.ts @@ -11,6 +11,7 @@ export default class OpenIdRecipeWrapper { authorization_endpoint: string; token_endpoint: string; userinfo_endpoint: string; + revocation_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/lib/build/recipe/openid/recipeImplementation.js b/lib/build/recipe/openid/recipeImplementation.js index 61726ce5d..70de44644 100644 --- a/lib/build/recipe/openid/recipeImplementation.js +++ b/lib/build/recipe/openid/recipeImplementation.js @@ -25,6 +25,7 @@ function getRecipeInterface(config, jwtRecipeImplementation, appInfo) { authorization_endpoint: apiBasePath + constants_2.AUTH_PATH, token_endpoint: apiBasePath + constants_2.TOKEN_PATH, userinfo_endpoint: apiBasePath + constants_2.USER_INFO_PATH, + revocation_endpoint: apiBasePath + constants_2.REVOKE_TOKEN_PATH, subject_types_supported: ["public"], id_token_signing_alg_values_supported: ["RS256"], response_types_supported: ["code", "id_token", "id_token token"], diff --git a/lib/build/recipe/openid/types.d.ts b/lib/build/recipe/openid/types.d.ts index 246c57ac5..24dc22887 100644 --- a/lib/build/recipe/openid/types.d.ts +++ b/lib/build/recipe/openid/types.d.ts @@ -69,6 +69,7 @@ export declare type APIInterface = { authorization_endpoint: string; token_endpoint: string; userinfo_endpoint: string; + revocation_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; @@ -86,6 +87,7 @@ export declare type RecipeInterface = { authorization_endpoint: string; token_endpoint: string; userinfo_endpoint: string; + revocation_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/lib/build/recipe/session/index.d.ts b/lib/build/recipe/session/index.d.ts index 73a99d24e..7c397dc28 100644 --- a/lib/build/recipe/session/index.d.ts +++ b/lib/build/recipe/session/index.d.ts @@ -180,6 +180,7 @@ export default class SessionWrapper { authorization_endpoint: string; token_endpoint: string; userinfo_endpoint: string; + revocation_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/lib/ts/querier.ts b/lib/ts/querier.ts index 666eaf6fc..895239ca9 100644 --- a/lib/ts/querier.ts +++ b/lib/ts/querier.ts @@ -179,6 +179,13 @@ export class Querier { } else { headers["content-type"] = "application/json; charset=utf-8"; } + + // TODO: Remove this after core changes are done + if (body !== undefined && body["authorizationHeader"]) { + headers["authorization"] = body["authorizationHeader"]; + delete body["authorizationHeader"]; + } + if (Querier.apiKey !== undefined) { headers = { ...headers, @@ -704,16 +711,21 @@ async function handleHydraAPICall(response: Response) { return { body: { status: response.ok ? "OK" : "ERROR", + statusCode: response.status, data: await response.clone().json(), }, headers: response.headers, }; } else if (contentType?.startsWith("text/plain")) { return { - body: { status: response.ok ? "OK" : "ERROR", data: await response.clone().text() }, + body: { + status: response.ok ? "OK" : "ERROR", + statusCode: response.status, + data: await response.clone().text(), + }, headers: response.headers, }; } - return { body: { status: response.ok ? "OK" : "ERROR" }, headers: response.headers }; + return { body: { status: response.ok ? "OK" : "ERROR", statusCode: response.status }, headers: response.headers }; } diff --git a/lib/ts/recipe/oauth2provider/api/implementation.ts b/lib/ts/recipe/oauth2provider/api/implementation.ts index 2ba89b59d..8af408488 100644 --- a/lib/ts/recipe/oauth2provider/api/implementation.ts +++ b/lib/ts/recipe/oauth2provider/api/implementation.ts @@ -83,5 +83,21 @@ export default function getAPIImplementation(): APIInterface { userContext, }); }, + revokeTokenPOST: async (input) => { + if ("authorizationHeader" in input) { + return input.options.recipeImplementation.revokeToken({ + token: input.token, + authorizationHeader: input.authorizationHeader, + userContext: input.userContext, + }); + } else { + return input.options.recipeImplementation.revokeToken({ + token: input.token, + clientId: input.clientId, + clientSecret: input.clientSecret, + userContext: input.userContext, + }); + } + }, }; } diff --git a/lib/ts/recipe/oauth2provider/api/revokeToken.ts b/lib/ts/recipe/oauth2provider/api/revokeToken.ts new file mode 100644 index 000000000..c81eb21c5 --- /dev/null +++ b/lib/ts/recipe/oauth2provider/api/revokeToken.ts @@ -0,0 +1,62 @@ +/* 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. + */ + +import { send200Response, sendNon200Response, sendNon200ResponseWithMessage } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; + +export default async function revokeTokenPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.revokeTokenPOST === undefined) { + return false; + } + + const body = await options.req.getFormData(); + + if (body.token === undefined) { + sendNon200ResponseWithMessage(options.res, "token is required in the request body", 400); + return true; + } + + const authorizationHeader = options.req.getHeaderValue("authorization"); + + if (authorizationHeader !== undefined && (body.client_id !== undefined || body.client_secret !== undefined)) { + sendNon200ResponseWithMessage( + options.res, + "Only one of authorization header or client_id and client_secret can be provided", + 400 + ); + return true; + } + + let response = await apiImplementation.revokeTokenPOST({ + options, + authorizationHeader, + token: body.token, + clientId: body.client_id, + clientSecret: body.client_secret, + userContext, + }); + + if ("statusCode" in response && response.statusCode !== 200) { + sendNon200Response(options.res, response.statusCode!, response); + } else { + send200Response(options.res, response); + } + return true; +} diff --git a/lib/ts/recipe/oauth2provider/api/userInfo.ts b/lib/ts/recipe/oauth2provider/api/userInfo.ts index 897d57672..3e661068d 100644 --- a/lib/ts/recipe/oauth2provider/api/userInfo.ts +++ b/lib/ts/recipe/oauth2provider/api/userInfo.ts @@ -29,7 +29,7 @@ export default async function userInfoGET( return false; } - const authHeader = options.req.getHeaderValue("authorization") || options.req.getHeaderValue("Authorization"); + const authHeader = options.req.getHeaderValue("authorization"); if (authHeader === undefined || !authHeader.startsWith("Bearer ")) { sendNon200ResponseWithMessage(options.res, "Missing or invalid Authorization header", 401); diff --git a/lib/ts/recipe/oauth2provider/constants.ts b/lib/ts/recipe/oauth2provider/constants.ts index a4e3ec85b..4a58eb3b4 100644 --- a/lib/ts/recipe/oauth2provider/constants.ts +++ b/lib/ts/recipe/oauth2provider/constants.ts @@ -20,3 +20,4 @@ export const AUTH_PATH = "/oauth/auth"; export const TOKEN_PATH = "/oauth/token"; export const LOGIN_INFO_PATH = "/oauth/login/info"; export const USER_INFO_PATH = "/oauth/userinfo"; +export const REVOKE_TOKEN_PATH = "/oauth/revoke"; diff --git a/lib/ts/recipe/oauth2provider/index.ts b/lib/ts/recipe/oauth2provider/index.ts index 4147ac072..55cfe2657 100644 --- a/lib/ts/recipe/oauth2provider/index.ts +++ b/lib/ts/recipe/oauth2provider/index.ts @@ -28,6 +28,12 @@ import { export default class Wrapper { static init = Recipe.init; + static async getOAuth2Client(clientId: string, userContext?: Record) { + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Client( + { clientId }, + getUserContext(userContext) + ); + } static async getOAuth2Clients(input: GetOAuth2ClientsInput, userContext?: Record) { return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Clients( input, @@ -107,6 +113,47 @@ export default class Wrapper { userContext: getUserContext(userContext), }); } + + static async revokeToken( + token: string, + clientId: string, + clientSecret?: string, + userContext?: Record + ) { + let authorizationHeader: string | undefined = undefined; + + const normalisedUserContext = getUserContext(userContext); + const recipeInterfaceImpl = Recipe.getInstanceOrThrowError().recipeInterfaceImpl; + + const res = await recipeInterfaceImpl.getOAuth2Client({ clientId }, normalisedUserContext); + + if (res.status !== "OK") { + throw new Error(`Failed to get OAuth2 client with id ${clientId}: ${res.error}`); + } + + const { tokenEndpointAuthMethod } = res.client; + + if (tokenEndpointAuthMethod === "none") { + authorizationHeader = "Basic " + Buffer.from(clientId + ":").toString("base64"); + } else if (tokenEndpointAuthMethod === "client_secret_basic") { + authorizationHeader = "Basic " + Buffer.from(clientId + ":" + clientSecret).toString("base64"); + } + + if (authorizationHeader !== undefined) { + return await recipeInterfaceImpl.revokeToken({ + token, + authorizationHeader, + userContext: normalisedUserContext, + }); + } + + return await recipeInterfaceImpl.revokeToken({ + token, + clientId, + clientSecret, + userContext: normalisedUserContext, + }); + } } export let init = Wrapper.init; @@ -125,4 +172,6 @@ export let validateOAuth2IdToken = Wrapper.validateOAuth2IdToken; export let createTokenForClientCredentials = Wrapper.createTokenForClientCredentials; +export let revokeToken = Wrapper.revokeToken; + export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/ts/recipe/oauth2provider/recipe.ts b/lib/ts/recipe/oauth2provider/recipe.ts index 2c3b8e9cc..e60665bc6 100644 --- a/lib/ts/recipe/oauth2provider/recipe.ts +++ b/lib/ts/recipe/oauth2provider/recipe.ts @@ -25,7 +25,7 @@ import APIImplementation from "./api/implementation"; import loginAPI from "./api/login"; import tokenPOST from "./api/token"; import loginInfoGET from "./api/loginInfo"; -import { AUTH_PATH, LOGIN_INFO_PATH, LOGIN_PATH, TOKEN_PATH, USER_INFO_PATH } from "./constants"; +import { AUTH_PATH, LOGIN_INFO_PATH, LOGIN_PATH, REVOKE_TOKEN_PATH, TOKEN_PATH, USER_INFO_PATH } from "./constants"; import RecipeImplementation from "./recipeImplementation"; import { APIInterface, @@ -41,6 +41,7 @@ import OverrideableBuilder from "supertokens-js-override"; import { User } from "../../user"; import userInfoGET from "./api/userInfo"; import { resetCombinedJWKS } from "../../combinedRemoteJWKSet"; +import revokeTokenPOST from "./api/revokeToken"; export default class Recipe extends RecipeModule { static RECIPE_ID = "oauth2provider"; @@ -145,6 +146,12 @@ export default class Recipe extends RecipeModule { id: USER_INFO_PATH, disabled: this.apiImpl.userInfoGET === undefined, }, + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(REVOKE_TOKEN_PATH), + id: REVOKE_TOKEN_PATH, + disabled: this.apiImpl.revokeTokenPOST === undefined, + }, ]; } @@ -181,6 +188,9 @@ export default class Recipe extends RecipeModule { if (id === USER_INFO_PATH) { return userInfoGET(this.apiImpl, tenantId, options, userContext); } + if (id === REVOKE_TOKEN_PATH) { + return revokeTokenPOST(this.apiImpl, options, userContext); + } throw new Error("Should never come here: handleAPIRequest called with unknown id"); }; diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index e9238fbc5..5c68e01dd 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -253,6 +253,13 @@ export default function getRecipeInterface( input.userContext ); + if (res.status !== "OK") { + return { + statusCode: res.statusCode, + error: res.data.error, + errorDescription: res.data.error_description, + }; + } return res.data; }, @@ -296,6 +303,27 @@ export default function getRecipeInterface( }; } }, + getOAuth2Client: async function (input, userContext) { + let response = await querier.sendGetRequestWithResponseHeaders( + new NormalisedURLPath(`/recipe/oauth2/admin/clients/${input.clientId}`), + {}, + {}, + userContext + ); + + if (response.body.status === "OK") { + return { + status: "OK", + client: OAuth2Client.fromAPIResponse(response.body.data), + }; + } else { + return { + status: "ERROR", + error: response.body.data.error, + errorHint: response.body.data.errorHint, + }; + } + }, createOAuth2Client: async function (input, userContext) { let response = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth2/admin/clients`), @@ -473,5 +501,39 @@ export default function getRecipeInterface( return { status: "OK", payload: payload as JSONObject }; }, + + revokeToken: async function (this: RecipeInterface, input) { + const requestBody: Record = { + $isFormData: true, + token: input.token, + }; + + if ("authorizationHeader" in input) { + requestBody.authorizationHeader = input.authorizationHeader; + } else { + if ("clientId" in input) { + requestBody.client_id = input.clientId; + } + if ("clientSecret" in input) { + requestBody.client_secret = input.clientSecret; + } + } + + const res = await querier.sendPostRequest( + new NormalisedURLPath(`/recipe/oauth2/pub/revoke`), + requestBody, + input.userContext + ); + + if (res.status !== "OK") { + return { + statusCode: res.statusCode, + error: res.data.error, + errorDescription: res.data.error_description, + }; + } + + return { status: "OK" }; + }, }; } diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index 5eeca4817..9e35fbff5 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -256,6 +256,21 @@ export type RecipeInterface = { userContext: UserContext; }): Promise<{ redirectTo: string }>; + getOAuth2Client( + input: Pick, + userContext: UserContext + ): Promise< + | { + status: "OK"; + client: OAuth2Client; + } + // TODO: Define specific error types once requirements are clearer + | { + status: "ERROR"; + error: string; + errorHint: string; + } + >; getOAuth2Clients( input: GetOAuth2ClientsInput, userContext: UserContext @@ -358,6 +373,17 @@ export type RecipeInterface = { tenantId: string; userContext: UserContext; }): Promise; + revokeToken( + input: { + token: string; + userContext: UserContext; + } & ( + | { + authorizationHeader: string; + } + | { clientId: string; clientSecret?: string } + ) + ): Promise<{ status: "OK" } | ErrorOAuth2>; }; export type APIInterface = { @@ -403,6 +429,15 @@ export type APIInterface = { options: APIOptions; userContext: UserContext; }) => Promise); + revokeTokenPOST: + | undefined + | (( + input: { + token: string; + options: APIOptions; + userContext: UserContext; + } & ({ authorizationHeader: string } | { clientId: string; clientSecret?: string }) + ) => Promise<{ status: "OK" } | ErrorOAuth2>); }; export type OAuth2ClientOptions = { diff --git a/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts b/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts index 2dfe7a2bd..8bd134a78 100644 --- a/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts +++ b/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts @@ -37,6 +37,7 @@ export default async function getOpenIdDiscoveryConfiguration( authorization_endpoint: result.authorization_endpoint, token_endpoint: result.token_endpoint, userinfo_endpoint: result.userinfo_endpoint, + revocation_endpoint: result.revocation_endpoint, subject_types_supported: result.subject_types_supported, id_token_signing_alg_values_supported: result.id_token_signing_alg_values_supported, response_types_supported: result.response_types_supported, diff --git a/lib/ts/recipe/openid/recipeImplementation.ts b/lib/ts/recipe/openid/recipeImplementation.ts index a01377cbc..c8b302788 100644 --- a/lib/ts/recipe/openid/recipeImplementation.ts +++ b/lib/ts/recipe/openid/recipeImplementation.ts @@ -17,7 +17,7 @@ import { RecipeInterface as JWTRecipeInterface, JsonWebKey } from "../jwt/types" import NormalisedURLPath from "../../normalisedURLPath"; import { GET_JWKS_API } from "../jwt/constants"; import { NormalisedAppinfo, UserContext } from "../../types"; -import { AUTH_PATH, TOKEN_PATH, USER_INFO_PATH } from "../oauth2provider/constants"; +import { AUTH_PATH, REVOKE_TOKEN_PATH, TOKEN_PATH, USER_INFO_PATH } from "../oauth2provider/constants"; export default function getRecipeInterface( config: TypeNormalisedInput, @@ -39,6 +39,7 @@ export default function getRecipeInterface( authorization_endpoint: apiBasePath + AUTH_PATH, token_endpoint: apiBasePath + TOKEN_PATH, userinfo_endpoint: apiBasePath + USER_INFO_PATH, + revocation_endpoint: apiBasePath + REVOKE_TOKEN_PATH, subject_types_supported: ["public"], id_token_signing_alg_values_supported: ["RS256"], response_types_supported: ["code", "id_token", "id_token token"], diff --git a/lib/ts/recipe/openid/types.ts b/lib/ts/recipe/openid/types.ts index 32f3bc958..a798e1eaa 100644 --- a/lib/ts/recipe/openid/types.ts +++ b/lib/ts/recipe/openid/types.ts @@ -86,6 +86,7 @@ export type APIInterface = { authorization_endpoint: string; token_endpoint: string; userinfo_endpoint: string; + revocation_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; @@ -104,6 +105,7 @@ export type RecipeInterface = { authorization_endpoint: string; token_endpoint: string; userinfo_endpoint: string; + revocation_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/test/with-typescript/index.ts b/test/with-typescript/index.ts index d67684458..40fc4c6f6 100644 --- a/test/with-typescript/index.ts +++ b/test/with-typescript/index.ts @@ -1596,6 +1596,7 @@ Session.init({ token_endpoint: "http://localhost:3000/auth/oauth2/token", authorization_endpoint: "http://localhost:3000/auth/oauth2/auth", userinfo_endpoint: "http://localhost:3000/auth/oauth2/userinfo", + revocation_endpoint: "http://localhost:3000/auth/oauth2/revoke", id_token_signing_alg_values_supported: [], response_types_supported: [], subject_types_supported: [], From 611d8604b03978b95cb881be4750ec0218c5bfba Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 9 Aug 2024 01:00:23 +0530 Subject: [PATCH 25/35] feat: Add token introspection endpoint (#906) * feat: Add token revocation endpoint * fix: PR changes * fix: PR changes * fix: PR changes * fix: PR changes * feat: Add token introspection endpoint * fix: PR changes * fix: Add revocation_endpoint * fix: PR changes * fix: merge issue --------- Co-authored-by: Mihaly Lengyel --- lib/build/framework/request.d.ts | 1 + lib/build/framework/request.js | 20 ++++++++ .../oauth2provider/api/implementation.js | 8 ++++ .../oauth2provider/api/introspectToken.d.ts | 8 ++++ .../oauth2provider/api/introspectToken.js | 37 +++++++++++++++ .../recipe/oauth2provider/api/revokeToken.js | 2 +- lib/build/recipe/oauth2provider/api/token.js | 4 +- .../recipe/oauth2provider/constants.d.ts | 1 + lib/build/recipe/oauth2provider/constants.js | 3 +- lib/build/recipe/oauth2provider/index.d.ts | 5 ++ lib/build/recipe/oauth2provider/index.js | 7 +++ lib/build/recipe/oauth2provider/recipe.js | 10 ++++ .../oauth2provider/recipeImplementation.js | 33 +++++++++++++ lib/build/recipe/oauth2provider/types.d.ts | 22 +++++++++ .../api/getOpenIdDiscoveryConfiguration.js | 1 + lib/build/recipe/openid/index.d.ts | 1 + .../recipe/openid/recipeImplementation.js | 1 + lib/build/recipe/openid/types.d.ts | 2 + lib/build/recipe/session/index.d.ts | 1 + lib/ts/framework/request.ts | 22 +++++++++ .../oauth2provider/api/implementation.ts | 8 ++++ .../oauth2provider/api/introspectToken.ts | 47 +++++++++++++++++++ .../recipe/oauth2provider/api/revokeToken.ts | 2 +- lib/ts/recipe/oauth2provider/api/token.ts | 5 +- lib/ts/recipe/oauth2provider/constants.ts | 1 + lib/ts/recipe/oauth2provider/index.ts | 8 ++++ lib/ts/recipe/oauth2provider/recipe.ts | 20 +++++++- .../oauth2provider/recipeImplementation.ts | 39 +++++++++++++++ lib/ts/recipe/oauth2provider/types.ts | 17 +++++++ .../api/getOpenIdDiscoveryConfiguration.ts | 1 + lib/ts/recipe/openid/recipeImplementation.ts | 9 +++- lib/ts/recipe/openid/types.ts | 2 + test/with-typescript/index.ts | 1 + 33 files changed, 342 insertions(+), 7 deletions(-) create mode 100644 lib/build/recipe/oauth2provider/api/introspectToken.d.ts create mode 100644 lib/build/recipe/oauth2provider/api/introspectToken.js create mode 100644 lib/ts/recipe/oauth2provider/api/introspectToken.ts diff --git a/lib/build/framework/request.d.ts b/lib/build/framework/request.d.ts index fafdcdb20..00c5fe6c3 100644 --- a/lib/build/framework/request.d.ts +++ b/lib/build/framework/request.d.ts @@ -15,4 +15,5 @@ export declare abstract class BaseRequest { abstract getOriginalURL: () => string; getFormData: () => Promise; getJSONBody: () => Promise; + getBodyAsJSONOrFormData: () => Promise; } diff --git a/lib/build/framework/request.js b/lib/build/framework/request.js index afc940e7a..edaa6e780 100644 --- a/lib/build/framework/request.js +++ b/lib/build/framework/request.js @@ -41,6 +41,26 @@ class BaseRequest { } return this.parsedJSONBody; }; + this.getBodyAsJSONOrFormData = async () => { + const contentType = this.getHeaderValue("content-type"); + if (contentType) { + if (contentType.startsWith("application/json")) { + return await this.getJSONBody(); + } else if (contentType.startsWith("application/x-www-form-urlencoded")) { + return await this.getFormData(); + } + } else { + try { + return await this.getJSONBody(); + } catch (_a) { + try { + return await this.getFormData(); + } catch (_b) { + throw new Error("Unable to parse body as JSON or Form Data."); + } + } + } + }; this.wrapperUsed = true; this.parsedJSONBody = undefined; this.parsedUrlEncodedFormData = undefined; diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index bc5dddac4..48566f5ee 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -49,6 +49,7 @@ function getAPIImplementation() { }, tokenPOST: async (input) => { return input.options.recipeImplementation.tokenExchange({ + authorizationHeader: input.authorizationHeader, body: input.body, userContext: input.userContext, }); @@ -95,6 +96,13 @@ function getAPIImplementation() { }); } }, + introspectTokenPOST: async (input) => { + return input.options.recipeImplementation.introspectToken({ + token: input.token, + scopes: input.scopes, + userContext: input.userContext, + }); + }, }; } exports.default = getAPIImplementation; diff --git a/lib/build/recipe/oauth2provider/api/introspectToken.d.ts b/lib/build/recipe/oauth2provider/api/introspectToken.d.ts new file mode 100644 index 000000000..3d2972c0d --- /dev/null +++ b/lib/build/recipe/oauth2provider/api/introspectToken.d.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; +export default function introspectTokenPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise; diff --git a/lib/build/recipe/oauth2provider/api/introspectToken.js b/lib/build/recipe/oauth2provider/api/introspectToken.js new file mode 100644 index 000000000..6e36c9c68 --- /dev/null +++ b/lib/build/recipe/oauth2provider/api/introspectToken.js @@ -0,0 +1,37 @@ +"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 }); +const utils_1 = require("../../../utils"); +async function introspectTokenPOST(apiImplementation, options, userContext) { + if (apiImplementation.introspectTokenPOST === undefined) { + return false; + } + const body = await options.req.getBodyAsJSONOrFormData(); + if (body.token === undefined) { + utils_1.sendNon200ResponseWithMessage(options.res, "token is required in the request body", 400); + return true; + } + const scopes = body.scope ? body.scope.split(" ") : []; + let response = await apiImplementation.introspectTokenPOST({ + options, + token: body.token, + scopes, + userContext, + }); + utils_1.send200Response(options.res, response); + return true; +} +exports.default = introspectTokenPOST; diff --git a/lib/build/recipe/oauth2provider/api/revokeToken.js b/lib/build/recipe/oauth2provider/api/revokeToken.js index c36975c41..46fadd8aa 100644 --- a/lib/build/recipe/oauth2provider/api/revokeToken.js +++ b/lib/build/recipe/oauth2provider/api/revokeToken.js @@ -19,7 +19,7 @@ async function revokeTokenPOST(apiImplementation, options, userContext) { if (apiImplementation.revokeTokenPOST === undefined) { return false; } - const body = await options.req.getFormData(); + const body = await options.req.getBodyAsJSONOrFormData(); if (body.token === undefined) { utils_1.sendNon200ResponseWithMessage(options.res, "token is required in the request body", 400); return true; diff --git a/lib/build/recipe/oauth2provider/api/token.js b/lib/build/recipe/oauth2provider/api/token.js index 4f7ef9a77..4d0d9e873 100644 --- a/lib/build/recipe/oauth2provider/api/token.js +++ b/lib/build/recipe/oauth2provider/api/token.js @@ -19,9 +19,11 @@ async function tokenPOST(apiImplementation, options, userContext) { if (apiImplementation.tokenPOST === undefined) { return false; } + const authorizationHeader = options.req.getHeaderValue("authorization"); let response = await apiImplementation.tokenPOST({ + authorizationHeader, options, - body: await options.req.getFormData(), + body: await options.req.getBodyAsJSONOrFormData(), userContext, }); if ("statusCode" in response && response.statusCode !== 200) { diff --git a/lib/build/recipe/oauth2provider/constants.d.ts b/lib/build/recipe/oauth2provider/constants.d.ts index 0ddf7e65f..10dd83851 100644 --- a/lib/build/recipe/oauth2provider/constants.d.ts +++ b/lib/build/recipe/oauth2provider/constants.d.ts @@ -6,3 +6,4 @@ export declare const TOKEN_PATH = "/oauth/token"; export declare const LOGIN_INFO_PATH = "/oauth/login/info"; export declare const USER_INFO_PATH = "/oauth/userinfo"; export declare const REVOKE_TOKEN_PATH = "/oauth/revoke"; +export declare const INTROSPECT_TOKEN_PATH = "/oauth/introspect"; diff --git a/lib/build/recipe/oauth2provider/constants.js b/lib/build/recipe/oauth2provider/constants.js index be31a261b..df8d4441c 100644 --- a/lib/build/recipe/oauth2provider/constants.js +++ b/lib/build/recipe/oauth2provider/constants.js @@ -14,7 +14,7 @@ * under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.REVOKE_TOKEN_PATH = exports.USER_INFO_PATH = exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; +exports.INTROSPECT_TOKEN_PATH = exports.REVOKE_TOKEN_PATH = exports.USER_INFO_PATH = exports.LOGIN_INFO_PATH = exports.TOKEN_PATH = exports.AUTH_PATH = exports.LOGIN_PATH = exports.OAUTH2_BASE_PATH = void 0; exports.OAUTH2_BASE_PATH = "/oauth/"; exports.LOGIN_PATH = "/oauth/login"; exports.AUTH_PATH = "/oauth/auth"; @@ -22,3 +22,4 @@ exports.TOKEN_PATH = "/oauth/token"; exports.LOGIN_INFO_PATH = "/oauth/login/info"; exports.USER_INFO_PATH = "/oauth/userinfo"; exports.REVOKE_TOKEN_PATH = "/oauth/revoke"; +exports.INTROSPECT_TOKEN_PATH = "/oauth/introspect"; diff --git a/lib/build/recipe/oauth2provider/index.d.ts b/lib/build/recipe/oauth2provider/index.d.ts index a0e9741f1..b3f45edd7 100644 --- a/lib/build/recipe/oauth2provider/index.d.ts +++ b/lib/build/recipe/oauth2provider/index.d.ts @@ -124,6 +124,11 @@ export default class Wrapper { status: "OK"; } >; + static validateOAuth2RefreshToken( + token: string, + scopes?: string[], + userContext?: Record + ): Promise; } export declare let init: typeof Recipe.init; export declare let getOAuth2Clients: typeof Wrapper.getOAuth2Clients; diff --git a/lib/build/recipe/oauth2provider/index.js b/lib/build/recipe/oauth2provider/index.js index 96bbf2939..de80a07d9 100644 --- a/lib/build/recipe/oauth2provider/index.js +++ b/lib/build/recipe/oauth2provider/index.js @@ -104,6 +104,13 @@ class Wrapper { userContext: normalisedUserContext, }); } + static validateOAuth2RefreshToken(token, scopes, userContext) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.introspectToken({ + token, + scopes, + userContext: utils_1.getUserContext(userContext), + }); + } } exports.default = Wrapper; Wrapper.init = recipe_1.default.init; diff --git a/lib/build/recipe/oauth2provider/recipe.js b/lib/build/recipe/oauth2provider/recipe.js index d523000c0..7f0965f78 100644 --- a/lib/build/recipe/oauth2provider/recipe.js +++ b/lib/build/recipe/oauth2provider/recipe.js @@ -35,6 +35,7 @@ const supertokens_js_override_1 = __importDefault(require("supertokens-js-overri const userInfo_1 = __importDefault(require("./api/userInfo")); const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); const revokeToken_1 = __importDefault(require("./api/revokeToken")); +const introspectToken_1 = __importDefault(require("./api/introspectToken")); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config) { super(recipeId, appInfo); @@ -70,6 +71,9 @@ class Recipe extends recipeModule_1.default { if (id === constants_1.REVOKE_TOKEN_PATH) { return revokeToken_1.default(this.apiImpl, options, userContext); } + if (id === constants_1.INTROSPECT_TOKEN_PATH) { + return introspectToken_1.default(this.apiImpl, options, userContext); + } throw new Error("Should never come here: handleAPIRequest called with unknown id"); }; this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); @@ -157,6 +161,12 @@ class Recipe extends recipeModule_1.default { id: constants_1.REVOKE_TOKEN_PATH, disabled: this.apiImpl.revokeTokenPOST === undefined, }, + { + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default(constants_1.INTROSPECT_TOKEN_PATH), + id: constants_1.INTROSPECT_TOKEN_PATH, + disabled: this.apiImpl.introspectTokenPOST === undefined, + }, ]; } handleError(error, _, __, _userContext) { diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index caad2a357..2f381a69f 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -265,6 +265,9 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, for (const key in input.body) { body[key] = input.body[key]; } + if (input.authorizationHeader) { + body["authorizationHeader"] = input.authorizationHeader; + } const res = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth2/pub/token`), body, @@ -548,6 +551,36 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, } return { status: "OK" }; }, + introspectToken: async function ({ token, scopes, userContext }) { + // Determine if the token is an access token by checking if it doesn't start with "ory_rt" + const isAccessToken = !token.startsWith("ory_rt"); + // Attempt to validate the access token locally + // If it fails, the token is not active, and we return early + if (isAccessToken) { + try { + await this.validateOAuth2AccessToken({ + token, + requirements: { scopes }, + checkDatabase: false, + userContext, + }); + } catch (error) { + return { active: false }; + } + } + // For tokens that passed local validation or if it's a refresh token, + // validate the token with the database by calling the core introspection endpoint + const res = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/introspect`), + { + $isFormData: true, + token, + scope: scopes ? scopes.join(" ") : undefined, + }, + userContext + ); + return res.data; + }, }; } exports.default = getRecipeInterface; diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index f0b943bb7..32442289a 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -85,6 +85,13 @@ export declare type UserInfo = { phoneNumber_verified?: boolean; [key: string]: JSONValue; }; +export declare type InstrospectTokenResponse = + | { + active: false; + } + | ({ + active: true; + } & JSONObject); export declare type RecipeInterface = { authorization(input: { params: Record; @@ -96,6 +103,7 @@ export declare type RecipeInterface = { setCookie: string | undefined; }>; tokenExchange(input: { + authorizationHeader?: string; body: Record; userContext: UserContext; }): Promise; @@ -278,6 +286,11 @@ export declare type RecipeInterface = { } | ErrorOAuth2 >; + introspectToken(input: { + token: string; + scopes?: string[]; + userContext: UserContext; + }): Promise; }; export declare type APIInterface = { loginGET: @@ -312,6 +325,7 @@ export declare type APIInterface = { tokenPOST: | undefined | ((input: { + authorizationHeader?: string; body: any; options: APIOptions; userContext: UserContext; @@ -361,6 +375,14 @@ export declare type APIInterface = { } | ErrorOAuth2 >); + introspectTokenPOST: + | undefined + | ((input: { + token: string; + scopes?: string[]; + options: APIOptions; + userContext: UserContext; + }) => Promise); }; export declare type OAuth2ClientOptions = { clientId: string; diff --git a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js index 227295247..025542687 100644 --- a/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js +++ b/lib/build/recipe/openid/api/getOpenIdDiscoveryConfiguration.js @@ -18,6 +18,7 @@ async function getOpenIdDiscoveryConfiguration(apiImplementation, options, userC token_endpoint: result.token_endpoint, userinfo_endpoint: result.userinfo_endpoint, revocation_endpoint: result.revocation_endpoint, + token_introspection_endpoint: result.token_introspection_endpoint, subject_types_supported: result.subject_types_supported, id_token_signing_alg_values_supported: result.id_token_signing_alg_values_supported, response_types_supported: result.response_types_supported, diff --git a/lib/build/recipe/openid/index.d.ts b/lib/build/recipe/openid/index.d.ts index 4b3c30cea..e32644715 100644 --- a/lib/build/recipe/openid/index.d.ts +++ b/lib/build/recipe/openid/index.d.ts @@ -12,6 +12,7 @@ export default class OpenIdRecipeWrapper { token_endpoint: string; userinfo_endpoint: string; revocation_endpoint: string; + token_introspection_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/lib/build/recipe/openid/recipeImplementation.js b/lib/build/recipe/openid/recipeImplementation.js index 70de44644..58c278f4f 100644 --- a/lib/build/recipe/openid/recipeImplementation.js +++ b/lib/build/recipe/openid/recipeImplementation.js @@ -26,6 +26,7 @@ function getRecipeInterface(config, jwtRecipeImplementation, appInfo) { token_endpoint: apiBasePath + constants_2.TOKEN_PATH, userinfo_endpoint: apiBasePath + constants_2.USER_INFO_PATH, revocation_endpoint: apiBasePath + constants_2.REVOKE_TOKEN_PATH, + token_introspection_endpoint: apiBasePath + constants_2.INTROSPECT_TOKEN_PATH, subject_types_supported: ["public"], id_token_signing_alg_values_supported: ["RS256"], response_types_supported: ["code", "id_token", "id_token token"], diff --git a/lib/build/recipe/openid/types.d.ts b/lib/build/recipe/openid/types.d.ts index 24dc22887..0908e5b2c 100644 --- a/lib/build/recipe/openid/types.d.ts +++ b/lib/build/recipe/openid/types.d.ts @@ -70,6 +70,7 @@ export declare type APIInterface = { token_endpoint: string; userinfo_endpoint: string; revocation_endpoint: string; + token_introspection_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; @@ -88,6 +89,7 @@ export declare type RecipeInterface = { token_endpoint: string; userinfo_endpoint: string; revocation_endpoint: string; + token_introspection_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/lib/build/recipe/session/index.d.ts b/lib/build/recipe/session/index.d.ts index 7c397dc28..fb87a07d1 100644 --- a/lib/build/recipe/session/index.d.ts +++ b/lib/build/recipe/session/index.d.ts @@ -181,6 +181,7 @@ export default class SessionWrapper { token_endpoint: string; userinfo_endpoint: string; revocation_endpoint: string; + token_introspection_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/lib/ts/framework/request.ts b/lib/ts/framework/request.ts index 7e24b9f3b..5bac2548f 100644 --- a/lib/ts/framework/request.ts +++ b/lib/ts/framework/request.ts @@ -63,4 +63,26 @@ export abstract class BaseRequest { } return this.parsedJSONBody; }; + + getBodyAsJSONOrFormData = async (): Promise => { + const contentType = this.getHeaderValue("content-type"); + + if (contentType) { + if (contentType.startsWith("application/json")) { + return await this.getJSONBody(); + } else if (contentType.startsWith("application/x-www-form-urlencoded")) { + return await this.getFormData(); + } + } else { + try { + return await this.getJSONBody(); + } catch { + try { + return await this.getFormData(); + } catch { + throw new Error("Unable to parse body as JSON or Form Data."); + } + } + } + }; } diff --git a/lib/ts/recipe/oauth2provider/api/implementation.ts b/lib/ts/recipe/oauth2provider/api/implementation.ts index 8af408488..e58f8c30d 100644 --- a/lib/ts/recipe/oauth2provider/api/implementation.ts +++ b/lib/ts/recipe/oauth2provider/api/implementation.ts @@ -52,6 +52,7 @@ export default function getAPIImplementation(): APIInterface { }, tokenPOST: async (input) => { return input.options.recipeImplementation.tokenExchange({ + authorizationHeader: input.authorizationHeader, body: input.body, userContext: input.userContext, }); @@ -99,5 +100,12 @@ export default function getAPIImplementation(): APIInterface { }); } }, + introspectTokenPOST: async (input) => { + return input.options.recipeImplementation.introspectToken({ + token: input.token, + scopes: input.scopes, + userContext: input.userContext, + }); + }, }; } diff --git a/lib/ts/recipe/oauth2provider/api/introspectToken.ts b/lib/ts/recipe/oauth2provider/api/introspectToken.ts new file mode 100644 index 000000000..47b4be69c --- /dev/null +++ b/lib/ts/recipe/oauth2provider/api/introspectToken.ts @@ -0,0 +1,47 @@ +/* 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. + */ + +import { send200Response, sendNon200ResponseWithMessage } from "../../../utils"; +import { APIInterface, APIOptions } from ".."; +import { UserContext } from "../../../types"; + +export default async function introspectTokenPOST( + apiImplementation: APIInterface, + options: APIOptions, + userContext: UserContext +): Promise { + if (apiImplementation.introspectTokenPOST === undefined) { + return false; + } + + const body = await options.req.getBodyAsJSONOrFormData(); + + if (body.token === undefined) { + sendNon200ResponseWithMessage(options.res, "token is required in the request body", 400); + return true; + } + + const scopes: string[] = body.scope ? body.scope.split(" ") : []; + + let response = await apiImplementation.introspectTokenPOST({ + options, + token: body.token, + scopes, + userContext, + }); + + send200Response(options.res, response); + return true; +} diff --git a/lib/ts/recipe/oauth2provider/api/revokeToken.ts b/lib/ts/recipe/oauth2provider/api/revokeToken.ts index c81eb21c5..9974f7795 100644 --- a/lib/ts/recipe/oauth2provider/api/revokeToken.ts +++ b/lib/ts/recipe/oauth2provider/api/revokeToken.ts @@ -26,7 +26,7 @@ export default async function revokeTokenPOST( return false; } - const body = await options.req.getFormData(); + const body = await options.req.getBodyAsJSONOrFormData(); if (body.token === undefined) { sendNon200ResponseWithMessage(options.res, "token is required in the request body", 400); diff --git a/lib/ts/recipe/oauth2provider/api/token.ts b/lib/ts/recipe/oauth2provider/api/token.ts index 62bfd5739..2414aea33 100644 --- a/lib/ts/recipe/oauth2provider/api/token.ts +++ b/lib/ts/recipe/oauth2provider/api/token.ts @@ -26,9 +26,12 @@ export default async function tokenPOST( return false; } + const authorizationHeader = options.req.getHeaderValue("authorization"); + let response = await apiImplementation.tokenPOST({ + authorizationHeader, options, - body: await options.req.getFormData(), + body: await options.req.getBodyAsJSONOrFormData(), userContext, }); diff --git a/lib/ts/recipe/oauth2provider/constants.ts b/lib/ts/recipe/oauth2provider/constants.ts index 4a58eb3b4..19c7173e3 100644 --- a/lib/ts/recipe/oauth2provider/constants.ts +++ b/lib/ts/recipe/oauth2provider/constants.ts @@ -21,3 +21,4 @@ export const TOKEN_PATH = "/oauth/token"; export const LOGIN_INFO_PATH = "/oauth/login/info"; export const USER_INFO_PATH = "/oauth/userinfo"; export const REVOKE_TOKEN_PATH = "/oauth/revoke"; +export const INTROSPECT_TOKEN_PATH = "/oauth/introspect"; diff --git a/lib/ts/recipe/oauth2provider/index.ts b/lib/ts/recipe/oauth2provider/index.ts index 55cfe2657..88836efee 100644 --- a/lib/ts/recipe/oauth2provider/index.ts +++ b/lib/ts/recipe/oauth2provider/index.ts @@ -154,6 +154,14 @@ export default class Wrapper { userContext: normalisedUserContext, }); } + + static validateOAuth2RefreshToken(token: string, scopes?: string[], userContext?: Record) { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.introspectToken({ + token, + scopes, + userContext: getUserContext(userContext), + }); + } } export let init = Wrapper.init; diff --git a/lib/ts/recipe/oauth2provider/recipe.ts b/lib/ts/recipe/oauth2provider/recipe.ts index e60665bc6..f671b7b9b 100644 --- a/lib/ts/recipe/oauth2provider/recipe.ts +++ b/lib/ts/recipe/oauth2provider/recipe.ts @@ -25,7 +25,15 @@ import APIImplementation from "./api/implementation"; import loginAPI from "./api/login"; import tokenPOST from "./api/token"; import loginInfoGET from "./api/loginInfo"; -import { AUTH_PATH, LOGIN_INFO_PATH, LOGIN_PATH, REVOKE_TOKEN_PATH, TOKEN_PATH, USER_INFO_PATH } from "./constants"; +import { + AUTH_PATH, + INTROSPECT_TOKEN_PATH, + LOGIN_INFO_PATH, + LOGIN_PATH, + REVOKE_TOKEN_PATH, + TOKEN_PATH, + USER_INFO_PATH, +} from "./constants"; import RecipeImplementation from "./recipeImplementation"; import { APIInterface, @@ -42,6 +50,7 @@ import { User } from "../../user"; import userInfoGET from "./api/userInfo"; import { resetCombinedJWKS } from "../../combinedRemoteJWKSet"; import revokeTokenPOST from "./api/revokeToken"; +import introspectTokenPOST from "./api/introspectToken"; export default class Recipe extends RecipeModule { static RECIPE_ID = "oauth2provider"; @@ -152,6 +161,12 @@ export default class Recipe extends RecipeModule { id: REVOKE_TOKEN_PATH, disabled: this.apiImpl.revokeTokenPOST === undefined, }, + { + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath(INTROSPECT_TOKEN_PATH), + id: INTROSPECT_TOKEN_PATH, + disabled: this.apiImpl.introspectTokenPOST === undefined, + }, ]; } @@ -191,6 +206,9 @@ export default class Recipe extends RecipeModule { if (id === REVOKE_TOKEN_PATH) { return revokeTokenPOST(this.apiImpl, options, userContext); } + if (id === INTROSPECT_TOKEN_PATH) { + return introspectTokenPOST(this.apiImpl, options, userContext); + } throw new Error("Should never come here: handleAPIRequest called with unknown id"); }; diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index 5c68e01dd..6854f389d 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -247,6 +247,11 @@ export default function getRecipeInterface( for (const key in input.body) { body[key] = input.body[key]; } + + if (input.authorizationHeader) { + body["authorizationHeader"] = input.authorizationHeader; + } + const res = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth2/pub/token`), body, @@ -535,5 +540,39 @@ export default function getRecipeInterface( return { status: "OK" }; }, + + introspectToken: async function (this: RecipeInterface, { token, scopes, userContext }) { + // Determine if the token is an access token by checking if it doesn't start with "ory_rt" + const isAccessToken = !token.startsWith("ory_rt"); + + // Attempt to validate the access token locally + // If it fails, the token is not active, and we return early + if (isAccessToken) { + try { + await this.validateOAuth2AccessToken({ + token, + requirements: { scopes }, + checkDatabase: false, + userContext, + }); + } catch (error) { + return { active: false }; + } + } + + // For tokens that passed local validation or if it's a refresh token, + // validate the token with the database by calling the core introspection endpoint + const res = await querier.sendPostRequest( + new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/introspect`), + { + $isFormData: true, + token, + scope: scopes ? scopes.join(" ") : undefined, + }, + userContext + ); + + return res.data; + }, }; } diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index 9e35fbff5..36dd1ce6b 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -171,6 +171,8 @@ export type UserInfo = { [key: string]: JSONValue; }; +export type InstrospectTokenResponse = { active: false } | ({ active: true } & JSONObject); + export type RecipeInterface = { authorization(input: { params: Record; @@ -179,6 +181,7 @@ export type RecipeInterface = { userContext: UserContext; }): Promise<{ redirectTo: string; setCookie: string | undefined }>; tokenExchange(input: { + authorizationHeader?: string; body: Record; userContext: UserContext; }): Promise; @@ -384,6 +387,11 @@ export type RecipeInterface = { | { clientId: string; clientSecret?: string } ) ): Promise<{ status: "OK" } | ErrorOAuth2>; + introspectToken(input: { + token: string; + scopes?: string[]; + userContext: UserContext; + }): Promise; }; export type APIInterface = { @@ -408,6 +416,7 @@ export type APIInterface = { tokenPOST: | undefined | ((input: { + authorizationHeader?: string; body: any; options: APIOptions; userContext: UserContext; @@ -438,6 +447,14 @@ export type APIInterface = { userContext: UserContext; } & ({ authorizationHeader: string } | { clientId: string; clientSecret?: string }) ) => Promise<{ status: "OK" } | ErrorOAuth2>); + introspectTokenPOST: + | undefined + | ((input: { + token: string; + scopes?: string[]; + options: APIOptions; + userContext: UserContext; + }) => Promise); }; export type OAuth2ClientOptions = { diff --git a/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts b/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts index 8bd134a78..169589677 100644 --- a/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts +++ b/lib/ts/recipe/openid/api/getOpenIdDiscoveryConfiguration.ts @@ -38,6 +38,7 @@ export default async function getOpenIdDiscoveryConfiguration( token_endpoint: result.token_endpoint, userinfo_endpoint: result.userinfo_endpoint, revocation_endpoint: result.revocation_endpoint, + token_introspection_endpoint: result.token_introspection_endpoint, subject_types_supported: result.subject_types_supported, id_token_signing_alg_values_supported: result.id_token_signing_alg_values_supported, response_types_supported: result.response_types_supported, diff --git a/lib/ts/recipe/openid/recipeImplementation.ts b/lib/ts/recipe/openid/recipeImplementation.ts index c8b302788..ce91ede5e 100644 --- a/lib/ts/recipe/openid/recipeImplementation.ts +++ b/lib/ts/recipe/openid/recipeImplementation.ts @@ -17,7 +17,13 @@ import { RecipeInterface as JWTRecipeInterface, JsonWebKey } from "../jwt/types" import NormalisedURLPath from "../../normalisedURLPath"; import { GET_JWKS_API } from "../jwt/constants"; import { NormalisedAppinfo, UserContext } from "../../types"; -import { AUTH_PATH, REVOKE_TOKEN_PATH, TOKEN_PATH, USER_INFO_PATH } from "../oauth2provider/constants"; +import { + AUTH_PATH, + INTROSPECT_TOKEN_PATH, + REVOKE_TOKEN_PATH, + TOKEN_PATH, + USER_INFO_PATH, +} from "../oauth2provider/constants"; export default function getRecipeInterface( config: TypeNormalisedInput, @@ -40,6 +46,7 @@ export default function getRecipeInterface( token_endpoint: apiBasePath + TOKEN_PATH, userinfo_endpoint: apiBasePath + USER_INFO_PATH, revocation_endpoint: apiBasePath + REVOKE_TOKEN_PATH, + token_introspection_endpoint: apiBasePath + INTROSPECT_TOKEN_PATH, subject_types_supported: ["public"], id_token_signing_alg_values_supported: ["RS256"], response_types_supported: ["code", "id_token", "id_token token"], diff --git a/lib/ts/recipe/openid/types.ts b/lib/ts/recipe/openid/types.ts index a798e1eaa..51702f454 100644 --- a/lib/ts/recipe/openid/types.ts +++ b/lib/ts/recipe/openid/types.ts @@ -87,6 +87,7 @@ export type APIInterface = { token_endpoint: string; userinfo_endpoint: string; revocation_endpoint: string; + token_introspection_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; @@ -106,6 +107,7 @@ export type RecipeInterface = { token_endpoint: string; userinfo_endpoint: string; revocation_endpoint: string; + token_introspection_endpoint: string; subject_types_supported: string[]; id_token_signing_alg_values_supported: string[]; response_types_supported: string[]; diff --git a/test/with-typescript/index.ts b/test/with-typescript/index.ts index 40fc4c6f6..5edd52ccb 100644 --- a/test/with-typescript/index.ts +++ b/test/with-typescript/index.ts @@ -1597,6 +1597,7 @@ Session.init({ authorization_endpoint: "http://localhost:3000/auth/oauth2/auth", userinfo_endpoint: "http://localhost:3000/auth/oauth2/userinfo", revocation_endpoint: "http://localhost:3000/auth/oauth2/revoke", + token_introspection_endpoint: "http://localhost:3000/auth/oauth2/introspect", id_token_signing_alg_values_supported: [], response_types_supported: [], subject_types_supported: [], From 4830f0abf2cfd59f30cd12ea792dfff9d6ce4d8d Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 9 Aug 2024 13:32:12 +0530 Subject: [PATCH 26/35] fix: make clientSecret optional (#908) --- lib/build/recipe/oauth2client/types.d.ts | 2 +- lib/build/recipe/oauth2client/utils.js | 3 --- lib/build/recipe/oauth2provider/OAuth2Client.d.ts | 2 +- lib/build/recipe/oauth2provider/index.js | 1 - lib/build/recipe/oauth2provider/types.d.ts | 2 +- lib/ts/recipe/oauth2client/types.ts | 2 +- lib/ts/recipe/oauth2client/utils.ts | 4 ---- lib/ts/recipe/oauth2provider/OAuth2Client.ts | 2 +- lib/ts/recipe/oauth2provider/index.ts | 2 -- lib/ts/recipe/oauth2provider/types.ts | 2 +- 10 files changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/build/recipe/oauth2client/types.d.ts b/lib/build/recipe/oauth2client/types.d.ts index c7b43e2f3..36b685837 100644 --- a/lib/build/recipe/oauth2client/types.d.ts +++ b/lib/build/recipe/oauth2client/types.d.ts @@ -18,7 +18,7 @@ export declare type UserInfo = { }; export declare type ProviderConfigInput = { clientId: string; - clientSecret: string; + clientSecret?: string; oidcDiscoveryEndpoint: string; }; export declare type ProviderConfigWithOIDCInfo = ProviderConfigInput & { diff --git a/lib/build/recipe/oauth2client/utils.js b/lib/build/recipe/oauth2client/utils.js index cc35b347f..0c5e2f39b 100644 --- a/lib/build/recipe/oauth2client/utils.js +++ b/lib/build/recipe/oauth2client/utils.js @@ -28,9 +28,6 @@ function validateAndNormaliseUserInput(_appInfo, config) { // `Only Supertokens OAuth ClientIds are supported in the OAuth2Client recipe. For any other OAuth Clients use the thirdparty recipe.` // ); // } - if (config.providerConfig.clientSecret === undefined) { - throw new Error("Please pass clientSecret argument in the OAuth2Client providerConfig."); - } if (config.providerConfig.oidcDiscoveryEndpoint === undefined) { throw new Error("Please pass oidcDiscoveryEndpoint argument in the OAuth2Client providerConfig."); } diff --git a/lib/build/recipe/oauth2provider/OAuth2Client.d.ts b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts index cef31eb4a..f61f7c4f8 100644 --- a/lib/build/recipe/oauth2provider/OAuth2Client.d.ts +++ b/lib/build/recipe/oauth2provider/OAuth2Client.d.ts @@ -11,7 +11,7 @@ export declare class OAuth2Client { * The secret will be included in the create request as cleartext, and then * never again. The secret is kept in hashed format and is not recoverable once lost. */ - clientSecret: string; + clientSecret?: string; /** * OAuth 2.0 Client Name * The human-readable name of the client to be presented to the end-user during authorization. diff --git a/lib/build/recipe/oauth2provider/index.js b/lib/build/recipe/oauth2provider/index.js index de80a07d9..3a0d0166c 100644 --- a/lib/build/recipe/oauth2provider/index.js +++ b/lib/build/recipe/oauth2provider/index.js @@ -63,7 +63,6 @@ class Wrapper { userContext: utils_1.getUserContext(userContext), }); } - // TODO: revokeToken static createTokenForClientCredentials(clientId, clientSecret, scope, audience, userContext) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.tokenExchange({ body: { diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index 32442289a..b57e00a2c 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -386,7 +386,7 @@ export declare type APIInterface = { }; export declare type OAuth2ClientOptions = { clientId: string; - clientSecret: string; + clientSecret?: string; createdAt: string; updatedAt: string; clientName: string; diff --git a/lib/ts/recipe/oauth2client/types.ts b/lib/ts/recipe/oauth2client/types.ts index 0c40c9bdc..740d00d16 100644 --- a/lib/ts/recipe/oauth2client/types.ts +++ b/lib/ts/recipe/oauth2client/types.ts @@ -27,7 +27,7 @@ export type UserInfo = { export type ProviderConfigInput = { clientId: string; - clientSecret: string; + clientSecret?: string; oidcDiscoveryEndpoint: string; }; diff --git a/lib/ts/recipe/oauth2client/utils.ts b/lib/ts/recipe/oauth2client/utils.ts index 925d67d66..5996c2490 100644 --- a/lib/ts/recipe/oauth2client/utils.ts +++ b/lib/ts/recipe/oauth2client/utils.ts @@ -32,10 +32,6 @@ export function validateAndNormaliseUserInput(_appInfo: NormalisedAppinfo, confi // ); // } - if (config.providerConfig.clientSecret === undefined) { - throw new Error("Please pass clientSecret argument in the OAuth2Client providerConfig."); - } - if (config.providerConfig.oidcDiscoveryEndpoint === undefined) { throw new Error("Please pass oidcDiscoveryEndpoint argument in the OAuth2Client providerConfig."); } diff --git a/lib/ts/recipe/oauth2provider/OAuth2Client.ts b/lib/ts/recipe/oauth2provider/OAuth2Client.ts index aa2e7ccac..7105a9262 100644 --- a/lib/ts/recipe/oauth2provider/OAuth2Client.ts +++ b/lib/ts/recipe/oauth2provider/OAuth2Client.ts @@ -28,7 +28,7 @@ export class OAuth2Client { * The secret will be included in the create request as cleartext, and then * never again. The secret is kept in hashed format and is not recoverable once lost. */ - clientSecret: string; + clientSecret?: string; /** * OAuth 2.0 Client Name diff --git a/lib/ts/recipe/oauth2provider/index.ts b/lib/ts/recipe/oauth2provider/index.ts index 88836efee..6bc70f067 100644 --- a/lib/ts/recipe/oauth2provider/index.ts +++ b/lib/ts/recipe/oauth2provider/index.ts @@ -93,8 +93,6 @@ export default class Wrapper { }); } - // TODO: revokeToken - static createTokenForClientCredentials( clientId: string, clientSecret: string, diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index 36dd1ce6b..accaad4a2 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -459,7 +459,7 @@ export type APIInterface = { export type OAuth2ClientOptions = { clientId: string; - clientSecret: string; + clientSecret?: string; createdAt: string; updatedAt: string; From 6f45c5f82879085f2bef1e55c12aa8dce73682b3 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 9 Aug 2024 16:29:43 +0530 Subject: [PATCH 27/35] fix: revokeToken input check --- lib/build/recipe/oauth2provider/api/implementation.js | 6 ++++-- lib/build/recipe/oauth2provider/recipeImplementation.js | 6 +++--- lib/ts/recipe/oauth2provider/api/implementation.ts | 6 ++++-- lib/ts/recipe/oauth2provider/recipeImplementation.ts | 6 +++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index 48566f5ee..472fff58a 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -81,19 +81,21 @@ function getAPIImplementation() { }); }, revokeTokenPOST: async (input) => { - if ("authorizationHeader" in input) { + if ("authorizationHeader" in input && input.authorizationHeader !== undefined) { return input.options.recipeImplementation.revokeToken({ token: input.token, authorizationHeader: input.authorizationHeader, userContext: input.userContext, }); - } else { + } else if ("clientId" in input && input.clientId !== undefined) { return input.options.recipeImplementation.revokeToken({ token: input.token, clientId: input.clientId, clientSecret: input.clientSecret, userContext: input.userContext, }); + } else { + throw new Error(`Either of 'authorizationHeader' or 'clientId' must be provided`); } }, introspectTokenPOST: async (input) => { diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 2f381a69f..2a6d65129 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -527,13 +527,13 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, $isFormData: true, token: input.token, }; - if ("authorizationHeader" in input) { + if ("authorizationHeader" in input && input.authorizationHeader !== undefined) { requestBody.authorizationHeader = input.authorizationHeader; } else { - if ("clientId" in input) { + if ("clientId" in input && input.clientId !== undefined) { requestBody.client_id = input.clientId; } - if ("clientSecret" in input) { + if ("clientSecret" in input && input.clientSecret !== undefined) { requestBody.client_secret = input.clientSecret; } } diff --git a/lib/ts/recipe/oauth2provider/api/implementation.ts b/lib/ts/recipe/oauth2provider/api/implementation.ts index e58f8c30d..8ca86eb63 100644 --- a/lib/ts/recipe/oauth2provider/api/implementation.ts +++ b/lib/ts/recipe/oauth2provider/api/implementation.ts @@ -85,19 +85,21 @@ export default function getAPIImplementation(): APIInterface { }); }, revokeTokenPOST: async (input) => { - if ("authorizationHeader" in input) { + if ("authorizationHeader" in input && input.authorizationHeader !== undefined) { return input.options.recipeImplementation.revokeToken({ token: input.token, authorizationHeader: input.authorizationHeader, userContext: input.userContext, }); - } else { + } else if ("clientId" in input && input.clientId !== undefined) { return input.options.recipeImplementation.revokeToken({ token: input.token, clientId: input.clientId, clientSecret: input.clientSecret, userContext: input.userContext, }); + } else { + throw new Error(`Either of 'authorizationHeader' or 'clientId' must be provided`); } }, introspectTokenPOST: async (input) => { diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index 6854f389d..21f2dd1d3 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -513,13 +513,13 @@ export default function getRecipeInterface( token: input.token, }; - if ("authorizationHeader" in input) { + if ("authorizationHeader" in input && input.authorizationHeader !== undefined) { requestBody.authorizationHeader = input.authorizationHeader; } else { - if ("clientId" in input) { + if ("clientId" in input && input.clientId !== undefined) { requestBody.client_id = input.clientId; } - if ("clientSecret" in input) { + if ("clientSecret" in input && input.clientSecret !== undefined) { requestBody.client_secret = input.clientSecret; } } From 905b5cd6e9babe50488318e32295a353523b8069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mih=C3=A1ly=20Lengyel?= Date: Sun, 11 Aug 2024 22:04:04 +0200 Subject: [PATCH 28/35] feat: add shouldTryLinkingWithSessionUser flag to auth apis and make overwriteSessionDuringSignInUp deafult to true (#909) * feat: add shouldTryLinkingWithSessionUser flag * feat: add tryLinkingWithSessionUser, forceFreshAuth and small test fixes * fix: test server compatible with 1.17/2.0 (#897) * fix: test server compatible with 1.17 * fix: pr comments * fix: mfa claim * fix: version and changelog * fix: using version function for comparision * fix: circle ci scripts * fix: circle ci testing * fix: circle ci testing * fix: circle ci testing * fix: test server * fix: circle ci restore * adding dev-v20.0.1 tag to this commit to ensure building * fix: config (#905) * adding dev-v20.0.1 tag to this commit to ensure building * feat: prompt param fixing * refactors an exception case * feat: validate max_age * fix: make shouldDoAutomaticAccountLinking properly get the primary user when linking to oldest user (#907) * adding dev-v20.0.2 tag to this commit to ensure building * feat: make shouldTryLinkingWithSessionUser optional in FDI3.1 * feat: fix tryLinkingImplementation and change degault for overwriteSessionDuringSignInUp --------- Co-authored-by: Sattvik Chakravarthy Co-authored-by: Sattvik Chakravarthy Co-authored-by: rishabhpoddar --- .circleci/config_continue.yml | 33 +++++ .circleci/doBackendSDKTests.sh | 38 ++++++ .../setupAndTestBackendSDKWithFreeCore.sh | 81 ++++++++++++ .circleci/setupAndTestWithFreeCore.sh | 33 ----- CHANGELOG.md | 9 ++ docs/classes/framework.BaseRequest.html | 2 +- docs/classes/framework.BaseResponse.html | 2 +- .../framework_custom.CollectingResponse.html | 2 +- .../framework_custom.PreParsedRequest.html | 2 +- docs/classes/index.RecipeUserId.html | 2 +- docs/classes/index.User.html | 2 +- docs/classes/index.default.html | 2 +- .../ingredients_emaildelivery.default.html | 2 +- .../ingredients_smsdelivery.default.html | 2 +- .../recipe_accountlinking.default.html | 6 +- docs/classes/recipe_dashboard.default.html | 2 +- .../classes/recipe_emailpassword.default.html | 4 +- .../recipe_emailverification.default.html | 2 +- docs/classes/recipe_jwt.default.html | 2 +- .../recipe_multifactorauth.default.html | 2 +- docs/classes/recipe_multitenancy.default.html | 2 +- docs/classes/recipe_openid.default.html | 2 +- docs/classes/recipe_passwordless.default.html | 6 +- docs/classes/recipe_session.default.html | 4 +- docs/classes/recipe_thirdparty.default.html | 2 +- docs/classes/recipe_totp.default.html | 2 +- docs/classes/recipe_usermetadata.default.html | 2 +- docs/classes/recipe_userroles.default.html | 2 +- .../framework_awsLambda.SessionEvent.html | 2 +- .../framework_awsLambda.SessionEventV2.html | 2 +- .../framework_express.SessionRequest.html | 2 +- .../framework_hapi.SessionRequest.html | 2 +- .../framework_koa.SessionContext.html | 2 +- .../framework_loopback.SessionContext.html | 2 +- .../recipe_session.SessionContainer.html | 2 +- .../recipe_session.VerifySessionOptions.html | 2 +- docs/modules/framework.html | 2 +- docs/modules/framework_awsLambda.html | 2 +- docs/modules/framework_custom.html | 2 +- docs/modules/framework_express.html | 2 +- docs/modules/framework_fastify.html | 2 +- docs/modules/framework_hapi.html | 2 +- docs/modules/framework_koa.html | 2 +- docs/modules/framework_loopback.html | 2 +- docs/modules/index.html | 2 +- docs/modules/recipe_accountlinking.html | 2 +- docs/modules/recipe_dashboard.html | 2 +- docs/modules/recipe_emailpassword.html | 4 +- docs/modules/recipe_emailverification.html | 2 +- docs/modules/recipe_jwt.html | 2 +- docs/modules/recipe_multifactorauth.html | 2 +- docs/modules/recipe_multitenancy.html | 2 +- docs/modules/recipe_openid.html | 2 +- docs/modules/recipe_passwordless.html | 2 +- docs/modules/recipe_session.html | 8 +- docs/modules/recipe_thirdparty.html | 2 +- docs/modules/recipe_totp.html | 2 +- docs/modules/recipe_usermetadata.html | 4 +- docs/modules/recipe_userroles.html | 2 +- frontendDriverInterfaceSupported.json | 2 +- lib/build/authUtils.d.ts | 13 +- lib/build/authUtils.js | 64 +++++++++- lib/build/recipe/accountlinking/index.js | 3 + lib/build/recipe/accountlinking/recipe.d.ts | 2 +- lib/build/recipe/accountlinking/recipe.js | 5 +- .../emailpassword/api/implementation.js | 22 +++- lib/build/recipe/emailpassword/api/signin.js | 19 ++- lib/build/recipe/emailpassword/api/signup.js | 14 ++- lib/build/recipe/emailpassword/index.js | 2 + .../emailpassword/recipeImplementation.js | 10 +- lib/build/recipe/emailpassword/types.d.ts | 4 + .../multifactorauth/multiFactorAuthClaim.d.ts | 6 +- .../multifactorauth/multiFactorAuthClaim.js | 6 +- .../oauth2provider/api/implementation.js | 1 + lib/build/recipe/oauth2provider/api/login.js | 17 +++ lib/build/recipe/oauth2provider/api/token.js | 5 +- .../recipe/oauth2provider/api/utils.d.ts | 2 + lib/build/recipe/oauth2provider/api/utils.js | 115 ++++++++++++++---- lib/build/recipe/oauth2provider/index.d.ts | 13 -- lib/build/recipe/oauth2provider/index.js | 10 +- .../oauth2provider/recipeImplementation.js | 48 ++------ lib/build/recipe/oauth2provider/types.d.ts | 13 +- .../recipe/passwordless/api/consumeCode.js | 12 +- .../recipe/passwordless/api/createCode.js | 14 +-- .../recipe/passwordless/api/implementation.js | 15 ++- .../recipe/passwordless/api/resendCode.js | 14 +-- lib/build/recipe/passwordless/index.js | 2 + lib/build/recipe/passwordless/recipe.js | 6 + .../passwordless/recipeImplementation.js | 3 +- lib/build/recipe/passwordless/types.d.ts | 6 + lib/build/recipe/session/recipe.d.ts | 8 ++ lib/build/recipe/session/recipe.js | 9 ++ .../session/sessionRequestFunctions.d.ts | 17 ++- .../recipe/session/sessionRequestFunctions.js | 101 +++++++-------- lib/build/recipe/session/utils.js | 2 +- .../recipe/thirdparty/api/implementation.js | 2 + lib/build/recipe/thirdparty/api/signinup.js | 14 ++- lib/build/recipe/thirdparty/index.js | 1 + .../recipe/thirdparty/recipeImplementation.js | 6 +- lib/build/recipe/thirdparty/types.d.ts | 3 + lib/build/utils.d.ts | 1 + lib/build/utils.js | 10 +- lib/build/version.d.ts | 2 +- lib/build/version.js | 2 +- lib/ts/authUtils.ts | 75 +++++++++++- lib/ts/recipe/accountlinking/index.ts | 4 + lib/ts/recipe/accountlinking/recipe.ts | 8 +- .../emailpassword/api/implementation.ts | 57 ++------- lib/ts/recipe/emailpassword/api/signin.ts | 21 ++-- lib/ts/recipe/emailpassword/api/signup.ts | 19 +-- lib/ts/recipe/emailpassword/index.ts | 2 + .../emailpassword/recipeImplementation.ts | 13 +- lib/ts/recipe/emailpassword/types.ts | 4 + .../multifactorauth/multiFactorAuthClaim.ts | 3 +- .../oauth2provider/api/implementation.ts | 1 + lib/ts/recipe/oauth2provider/api/login.ts | 17 +++ lib/ts/recipe/oauth2provider/api/token.ts | 5 +- lib/ts/recipe/oauth2provider/api/utils.ts | 97 ++++++++++++--- lib/ts/recipe/oauth2provider/index.ts | 18 --- .../oauth2provider/recipeImplementation.ts | 42 ++----- lib/ts/recipe/oauth2provider/types.ts | 11 +- lib/ts/recipe/passwordless/api/consumeCode.ts | 19 +-- lib/ts/recipe/passwordless/api/createCode.ts | 17 ++- .../recipe/passwordless/api/implementation.ts | 11 +- lib/ts/recipe/passwordless/api/resendCode.ts | 18 ++- lib/ts/recipe/passwordless/index.ts | 2 + lib/ts/recipe/passwordless/recipe.ts | 6 + .../passwordless/recipeImplementation.ts | 3 +- lib/ts/recipe/passwordless/types.ts | 6 + lib/ts/recipe/session/recipe.ts | 11 ++ .../recipe/session/sessionRequestFunctions.ts | 112 +++++++++-------- lib/ts/recipe/session/utils.ts | 2 +- .../recipe/thirdparty/api/implementation.ts | 2 + lib/ts/recipe/thirdparty/api/signinup.ts | 18 +-- lib/ts/recipe/thirdparty/index.ts | 1 + .../recipe/thirdparty/recipeImplementation.ts | 16 ++- lib/ts/recipe/thirdparty/types.ts | 3 + lib/ts/utils.ts | 6 + lib/ts/version.ts | 2 +- package-lock.json | 4 +- package.json | 2 +- .../overwriteSessionDuringSignInUp.test.js | 2 +- test/test-server/src/accountlinking.ts | 9 +- test/test-server/src/emailpassword.ts | 14 +-- test/test-server/src/oauth2provider.ts | 13 -- test/test-server/src/passwordless.ts | 20 +-- test/test-server/src/session.ts | 15 ++- test/test-server/src/thirdparty.ts | 8 +- test/test-server/src/utils.ts | 59 ++++++++- test/with-typescript/index.ts | 1 + 150 files changed, 1129 insertions(+), 640 deletions(-) create mode 100755 .circleci/doBackendSDKTests.sh create mode 100755 .circleci/setupAndTestBackendSDKWithFreeCore.sh diff --git a/.circleci/config_continue.yml b/.circleci/config_continue.yml index 49dcb27fd..7a2e18b78 100644 --- a/.circleci/config_continue.yml +++ b/.circleci/config_continue.yml @@ -36,6 +36,24 @@ jobs: - run: update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-15.0.1/bin/javac" 2 - run: (cd .circleci/ && ./doUnitTests.sh << parameters.cdi-version >>) - slack/status + test-backend-sdk-testing: + docker: + - image: rishabhpoddar/supertokens_node_driver_testing_node_20 + resource_class: large + parameters: + cdi-version: + type: string + fdi-version: + type: string + steps: + - checkout + - run: echo "127.0.0.1 localhost.org" >> /etc/hosts + - run: apt-get install lsof + - run: npm i -d --force + - run: update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-15.0.1/bin/java" 2 + - run: update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-15.0.1/bin/javac" 2 + - run: (cd .circleci/ && ./doBackendSDKTests.sh << parameters.cdi-version >> << parameters.fdi-version >>) + - slack/status test-website: docker: - image: rishabhpoddar/supertokens_website_sdk_testing @@ -114,6 +132,20 @@ workflows: matrix: parameters: cdi-version: placeholder + - test-backend-sdk-testing: + requires: + - test-dev-tag-as-not-passed + context: + - slack-notification + filters: + tags: + only: /dev-v[0-9]+(\.[0-9]+)*/ + branches: + only: /test-cicd\/.*/ + matrix: + parameters: + cdi-version: placeholder + fdi-version: placeholder - test-website: requires: - test-dev-tag-as-not-passed @@ -143,6 +175,7 @@ workflows: - test-success: requires: - test-unit + - test-backend-sdk-testing - test-website - test-authreact context: diff --git a/.circleci/doBackendSDKTests.sh b/.circleci/doBackendSDKTests.sh new file mode 100755 index 000000000..984a0b79b --- /dev/null +++ b/.circleci/doBackendSDKTests.sh @@ -0,0 +1,38 @@ +echo "Starting tests for CDI $1"; + +if [ -z "$SUPERTOKENS_API_KEY" ]; then + echo "SUPERTOKENS_API_KEY not set" + exit 1 +fi + +coreDriverVersion=$1 +coreDriverVersion=`echo $coreDriverVersion | tr -d '"'` + +frontendDriverVersion=$2 + +coreFree=`curl -s -X GET \ +"https://api.supertokens.io/0/core-driver-interface/dependency/core/latest?password=$SUPERTOKENS_API_KEY&planType=FREE&mode=DEV&version=$coreDriverVersion&driverName=node" \ +-H 'api-version: 1'` +if [[ `echo $coreFree | jq .core` == "null" ]] +then + echo "fetching latest X.Y version for core given core-driver-interface X.Y version: $coreDriverVersion, planType: FREE gave response: $coreFree. Please make sure all relevant cores have been pushed." + exit 1 +fi +coreFree=$(echo $coreFree | jq .core | tr -d '"') + +cd .. +./test/testExports.sh +if [[ $? -ne 0 ]] +then + echo "export test failed... exiting!" + exit 1 +fi +cd .circleci + +./setupAndTestBackendSDKWithFreeCore.sh $coreFree $coreDriverVersion $frontendDriverVersion +if [[ $? -ne 0 ]] +then + echo "test failed... exiting!" + exit 1 +fi +rm -rf ../../supertokens-root \ No newline at end of file diff --git a/.circleci/setupAndTestBackendSDKWithFreeCore.sh b/.circleci/setupAndTestBackendSDKWithFreeCore.sh new file mode 100755 index 000000000..a4a563b0b --- /dev/null +++ b/.circleci/setupAndTestBackendSDKWithFreeCore.sh @@ -0,0 +1,81 @@ +coreInfo=`curl -s -X GET \ +"https://api.supertokens.io/0/core/latest?password=$SUPERTOKENS_API_KEY&planType=FREE&mode=DEV&version=$1" \ +-H 'api-version: 0'` +if [[ `echo $coreInfo | jq .tag` == "null" ]] +then + echo "fetching latest X.Y.Z version for core, X.Y version: $1, planType: FREE gave response: $coreInfo" + exit 1 +fi +coreTag=$(echo $coreInfo | jq .tag | tr -d '"') +coreVersion=$(echo $coreInfo | jq .version | tr -d '"') + +pluginInterfaceVersionXY=`curl -s -X GET \ +"https://api.supertokens.io/0/core/dependency/plugin-interface/latest?password=$SUPERTOKENS_API_KEY&planType=FREE&mode=DEV&version=$1" \ +-H 'api-version: 0'` +if [[ `echo $pluginInterfaceVersionXY | jq .pluginInterface` == "null" ]] +then + echo "fetching latest X.Y version for plugin-interface, given core X.Y version: $1, planType: FREE gave response: $pluginInterfaceVersionXY" + exit 1 +fi +pluginInterfaceVersionXY=$(echo $pluginInterfaceVersionXY | jq .pluginInterface | tr -d '"') + +pluginInterfaceInfo=`curl -s -X GET \ +"https://api.supertokens.io/0/plugin-interface/latest?password=$SUPERTOKENS_API_KEY&planType=FREE&mode=DEV&version=$pluginInterfaceVersionXY" \ +-H 'api-version: 0'` +if [[ `echo $pluginInterfaceInfo | jq .tag` == "null" ]] +then + echo "fetching latest X.Y.Z version for plugin-interface, X.Y version: $pluginInterfaceVersionXY, planType: FREE gave response: $pluginInterfaceInfo" + exit 1 +fi +pluginInterfaceTag=$(echo $pluginInterfaceInfo | jq .tag | tr -d '"') +pluginInterfaceVersion=$(echo $pluginInterfaceInfo | jq .version | tr -d '"') + +echo "Testing with FREE core: $coreVersion, plugin-interface: $pluginInterfaceVersion" + +cd ../../ +git clone git@github.com:supertokens/supertokens-root.git +cd supertokens-root +if [[ $2 == "2.0" ]] || [[ $2 == "2.1" ]] || [[ $2 == "2.2" ]] +then + git checkout 36e5af1b9a4e3b07247d0cf333cf82a071a78681 +fi +echo -e "core,$1\nplugin-interface,$pluginInterfaceVersionXY" > modules.txt +./loadModules --ssh +cd supertokens-core +git checkout $coreTag +cd ../supertokens-plugin-interface +git checkout $pluginInterfaceTag +cd ../ +echo $SUPERTOKENS_API_KEY > apiPassword +./utils/setupTestEnvLocal +cd ../project/ + +# Set the script to exit on error +set -e + +API_PORT=3030 +ST_CONNECTION_URI=http://localhost:8081 + +# start test-server +pushd test/test-server +npm install +API_PORT=$API_PORT ST_CONNECTION_URI=$ST_CONNECTION_URI npm start & +popd + +frontendDriverVersion=$3 +# run tests +cd ../ +git clone git@github.com:supertokens/backend-sdk-testing.git +cd backend-sdk-testing +git checkout $frontendDriverVersion +npm install +npm run build + +if ! [[ -z "${CIRCLE_NODE_TOTAL}" ]]; then + API_PORT=$API_PORT TEST_MODE=testing SUPERTOKENS_CORE_TAG=$coreTag NODE_PORT=8081 INSTALL_PATH=../supertokens-root npx mocha --node-option no-experimental-fetch -r test/fetch-polyfill.mjs --no-config --timeout 500000 $(npx mocha-split-tests -r ./runtime.log -t $CIRCLE_NODE_TOTAL -g $CIRCLE_NODE_INDEX -f 'test/**/*.test.js') +else + API_PORT=$API_PORT TEST_MODE=testing SUPERTOKENS_CORE_TAG=$coreTag NODE_PORT=8081 INSTALL_PATH=../supertokens-root npm test +fi + +# kill test-server +kill $(lsof -t -i:$API_PORT) diff --git a/.circleci/setupAndTestWithFreeCore.sh b/.circleci/setupAndTestWithFreeCore.sh index 258b0a6d5..83b0f8201 100755 --- a/.circleci/setupAndTestWithFreeCore.sh +++ b/.circleci/setupAndTestWithFreeCore.sh @@ -58,36 +58,3 @@ if ! [[ -z "${CIRCLE_NODE_TOTAL}" ]]; then else TEST_MODE=testing SUPERTOKENS_CORE_TAG=$coreTag NODE_PORT=8081 INSTALL_PATH=../supertokens-root npm test fi - -API_PORT=3030 -ST_CONNECTION_URI=http://localhost:8081 - -# start test-server -pushd test/test-server -npm install -API_PORT=$API_PORT ST_CONNECTION_URI=$ST_CONNECTION_URI npm start & -popd - -# lets read frontendDriverInterfaceSupported -frontendDriverJson=`cat ./frontendDriverInterfaceSupported.json` -# get versions -frontendDriverArray=`echo $frontendDriverJson | jq ".versions"` -# use latest version -frontendDriverVersion=`echo $frontendDriverArray | jq ".[-1]" | tr -d '"'` - -# run tests -cd ../ -git clone git@github.com:supertokens/backend-sdk-testing.git -cd backend-sdk-testing -git checkout $frontendDriverVersion -npm install -npm run build - -if ! [[ -z "${CIRCLE_NODE_TOTAL}" ]]; then - API_PORT=$API_PORT TEST_MODE=testing SUPERTOKENS_CORE_TAG=$coreTag NODE_PORT=8081 INSTALL_PATH=../supertokens-root npx mocha --node-option no-experimental-fetch -r test/fetch-polyfill.mjs --no-config --timeout 500000 $(npx mocha-split-tests -r ./runtime.log -t $CIRCLE_NODE_TOTAL -g $CIRCLE_NODE_INDEX -f 'test/**/*.test.js') -else - API_PORT=$API_PORT TEST_MODE=testing SUPERTOKENS_CORE_TAG=$coreTag NODE_PORT=8081 INSTALL_PATH=../supertokens-root npm test -fi - -# kill test-server -kill $(lsof -t -i:$API_PORT) \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d877e545d..f7dfac511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added OAuth2Provider recipe +## [20.0.2] - 2024-08-08 + +- Fixes an issue where `shouldDoAutomaticAccountLinking` was called without a primary user when linking in some cases. + +## [20.0.1] - 2024-08-05 + +- Fixes an issue with `removeFromPayloadByMerge_internal` for `MultiFactorAuthClaim` where it was not retaining other claims while removing the claim from the payload. +- Updates testing with backend-sdk-testing repo to run against all supported FDI versions. + ## [20.0.0] - 2024-07-24 ### Changes diff --git a/docs/classes/framework.BaseRequest.html b/docs/classes/framework.BaseRequest.html index e3689decc..731db467e 100644 --- a/docs/classes/framework.BaseRequest.html +++ b/docs/classes/framework.BaseRequest.html @@ -1 +1 @@ -BaseRequest | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Class BaseRequest Abstract

    Hierarchy

    Index

    Constructors

    Properties

    getCookieValue: ((key_: string) => undefined | string)

    Type declaration

      • (key_: string): undefined | string
      • Parameters

        • key_: string

        Returns undefined | string

    getHeaderValue: ((key: string) => undefined | string)

    Type declaration

      • (key: string): undefined | string
      • Parameters

        • key: string

        Returns undefined | string

    getKeyValueFromQuery: ((key: string) => undefined | string)

    Type declaration

      • (key: string): undefined | string
      • Parameters

        • key: string

        Returns undefined | string

    getMethod: (() => HTTPMethod)

    Type declaration

      • (): HTTPMethod
      • Returns HTTPMethod

    getOriginalURL: (() => string)

    Type declaration

      • (): string
      • Returns string

    original: any
    parsedJSONBody: any
    parsedUrlEncodedFormData: any
    wrapperUsed: boolean

    Methods

    • getFormData(): Promise<any>
    • getFormDataFromRequestBody(): Promise<any>
    • getJSONBody(): Promise<any>
    • getJSONFromRequestBody(): Promise<any>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Property
    • Method
    • Interface
    • Protected method
    • Private property

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +BaseRequest | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Class BaseRequest Abstract

    Hierarchy

    Index

    Constructors

    Properties

    getCookieValue: ((key_: string) => undefined | string)

    Type declaration

      • (key_: string): undefined | string
      • Parameters

        • key_: string

        Returns undefined | string

    getHeaderValue: ((key: string) => undefined | string)

    Type declaration

      • (key: string): undefined | string
      • Parameters

        • key: string

        Returns undefined | string

    getKeyValueFromQuery: ((key: string) => undefined | string)

    Type declaration

      • (key: string): undefined | string
      • Parameters

        • key: string

        Returns undefined | string

    getMethod: (() => HTTPMethod)

    Type declaration

      • (): HTTPMethod
      • Returns HTTPMethod

    getOriginalURL: (() => string)

    Type declaration

      • (): string
      • Returns string

    original: any
    parsedJSONBody: any
    parsedUrlEncodedFormData: any
    wrapperUsed: boolean

    Methods

    • getFormData(): Promise<any>
    • getFormDataFromRequestBody(): Promise<any>
    • getJSONBody(): Promise<any>
    • getJSONFromRequestBody(): Promise<any>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Property
    • Method
    • Interface
    • Protected method
    • Private property

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/framework.BaseResponse.html b/docs/classes/framework.BaseResponse.html index 44e400a63..555ac7ec5 100644 --- a/docs/classes/framework.BaseResponse.html +++ b/docs/classes/framework.BaseResponse.html @@ -1 +1 @@ -BaseResponse | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Class BaseResponse Abstract

    Hierarchy

    Index

    Constructors

    Properties

    original: any
    removeHeader: ((key: string) => void)

    Type declaration

      • (key: string): void
      • Parameters

        • key: string

        Returns void

    sendHTMLResponse: ((html: string) => void)

    Type declaration

      • (html: string): void
      • Parameters

        • html: string

        Returns void

    sendJSONResponse: ((content: any) => void)

    Type declaration

      • (content: any): void
      • Parameters

        • content: any

        Returns void

    setCookie: ((key: string, value: string, domain: undefined | string, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void)

    Type declaration

      • (key: string, value: string, domain: undefined | string, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none"): void
      • Parameters

        • key: string
        • value: string
        • domain: undefined | string
        • secure: boolean
        • httpOnly: boolean
        • expires: number
        • path: string
        • sameSite: "strict" | "lax" | "none"

        Returns void

    setHeader: ((key: string, value: string, allowDuplicateKey: boolean) => void)

    Type declaration

      • (key: string, value: string, allowDuplicateKey: boolean): void
      • Parameters

        • key: string
        • value: string
        • allowDuplicateKey: boolean

        Returns void

    setStatusCode: ((statusCode: number) => void)

    Type declaration

      • (statusCode: number): void
      • Parameters

        • statusCode: number

        Returns void

    wrapperUsed: boolean

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Property
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +BaseResponse | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Class BaseResponse Abstract

    Hierarchy

    Index

    Constructors

    Properties

    original: any
    removeHeader: ((key: string) => void)

    Type declaration

      • (key: string): void
      • Parameters

        • key: string

        Returns void

    sendHTMLResponse: ((html: string) => void)

    Type declaration

      • (html: string): void
      • Parameters

        • html: string

        Returns void

    sendJSONResponse: ((content: any) => void)

    Type declaration

      • (content: any): void
      • Parameters

        • content: any

        Returns void

    setCookie: ((key: string, value: string, domain: undefined | string, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none") => void)

    Type declaration

      • (key: string, value: string, domain: undefined | string, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none"): void
      • Parameters

        • key: string
        • value: string
        • domain: undefined | string
        • secure: boolean
        • httpOnly: boolean
        • expires: number
        • path: string
        • sameSite: "strict" | "lax" | "none"

        Returns void

    setHeader: ((key: string, value: string, allowDuplicateKey: boolean) => void)

    Type declaration

      • (key: string, value: string, allowDuplicateKey: boolean): void
      • Parameters

        • key: string
        • value: string
        • allowDuplicateKey: boolean

        Returns void

    setStatusCode: ((statusCode: number) => void)

    Type declaration

      • (statusCode: number): void
      • Parameters

        • statusCode: number

        Returns void

    wrapperUsed: boolean

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Property
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/framework_custom.CollectingResponse.html b/docs/classes/framework_custom.CollectingResponse.html index 0390f9a2d..54d8bffe2 100644 --- a/docs/classes/framework_custom.CollectingResponse.html +++ b/docs/classes/framework_custom.CollectingResponse.html @@ -1,2 +1,2 @@ -CollectingResponse | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    Index

    Constructors

    Properties

    body?: string
    cookies: CookieInfo[]
    headers: Headers
    original: any
    statusCode: number
    wrapperUsed: boolean

    Methods

    • removeHeader(key: string): void
    • sendHTMLResponse(html: string): void
    • sendJSONResponse(content: any): void
    • setCookie(key: string, value: string, domain: undefined | string, secure: boolean, httpOnly: boolean, expires: number, path: string, sameSite: "strict" | "lax" | "none"): void
    • Parameters

      • key: string
      • value: string
      • domain: undefined | string
      • secure: boolean
      • httpOnly: boolean
      • expires: number
      • path: string
      • sameSite: "strict" | "lax" | "none"

      Returns void

    • setHeader(key: string, value: string, allowDuplicateKey: boolean): void
    • setStatusCode(statusCode: number): void
    • resetPasswordUsingToken(tenantId: string, token: string, newPassword: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
    • Parameters

      • tenantId: string
      • token: string
      • newPassword: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

    • sendEmail(input: TypeEmailPasswordPasswordResetEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendResetPasswordEmail(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • email: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>

    • signIn(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>
    • signIn(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>

    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • signUp(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>
    • signUp(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>

    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • updateEmailOrPassword(input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
    • Parameters

      • input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }
        • Optional applyPasswordPolicy?: boolean
        • Optional email?: string
        • Optional password?: string
        • recipeUserId: RecipeUserId
        • Optional tenantIdForPasswordPolicy?: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

    • verifyCredentials(tenantId: string, email: string, password: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>
    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_emailverification.default.html b/docs/classes/recipe_emailverification.default.html index 83c69ac42..26f506971 100644 --- a/docs/classes/recipe_emailverification.default.html +++ b/docs/classes/recipe_emailverification.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    EmailVerificationClaim: EmailVerificationClaimClass = EmailVerificationClaim
    Error: typeof default = SuperTokensError
    init: ((config: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config: TypeInput): RecipeListFunction
      • Parameters

        • config: TypeInput

        Returns RecipeListFunction

    Methods

    • createEmailVerificationLink(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • createEmailVerificationToken(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • isEmailVerified(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<boolean>
    • revokeEmailVerificationTokens(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • sendEmail(input: TypeEmailVerificationEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendEmailVerificationEmail(tenantId: string, userId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • unverifyEmail(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • verifyEmailUsingToken(tenantId: string, token: string, attemptAccountLinking?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    EmailVerificationClaim: EmailVerificationClaimClass = EmailVerificationClaim
    Error: typeof default = SuperTokensError
    init: ((config: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config: TypeInput): RecipeListFunction
      • Parameters

        • config: TypeInput

        Returns RecipeListFunction

    Methods

    • createEmailVerificationLink(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • createEmailVerificationToken(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • isEmailVerified(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<boolean>
    • revokeEmailVerificationTokens(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • sendEmail(input: TypeEmailVerificationEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendEmailVerificationEmail(tenantId: string, userId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • unverifyEmail(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • verifyEmailUsingToken(tenantId: string, token: string, attemptAccountLinking?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_jwt.default.html b/docs/classes/recipe_jwt.default.html index 1bf6e5c3f..b36f98416 100644 --- a/docs/classes/recipe_jwt.default.html +++ b/docs/classes/recipe_jwt.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    Methods

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • createJWT(payload: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    Methods

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • createJWT(payload: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_multifactorauth.default.html b/docs/classes/recipe_multifactorauth.default.html index 8eea4a71a..95fc4db80 100644 --- a/docs/classes/recipe_multifactorauth.default.html +++ b/docs/classes/recipe_multifactorauth.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    FactorIds: { EMAILPASSWORD: string; LINK_EMAIL: string; LINK_PHONE: string; OTP_EMAIL: string; OTP_PHONE: string; THIRDPARTY: string; TOTP: string } = FactorIds

    Type declaration

    • EMAILPASSWORD: string
    • LINK_EMAIL: string
    • LINK_PHONE: string
    • OTP_EMAIL: string
    • OTP_PHONE: string
    • THIRDPARTY: string
    • TOTP: string
    MultiFactorAuthClaim: MultiFactorAuthClaimClass = MultiFactorAuthClaim
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • addToRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>
    • assertAllowedToSetupFactorElseThrowInvalidClaimError(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • getFactorsSetupForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • getMFARequirementsForAuth(session: SessionContainer, userContext?: Record<string, any>): Promise<MFARequirementList>
    • getRequiredSecondaryFactorsForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • markFactorAsCompleteInSession(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • removeFromRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    FactorIds: { EMAILPASSWORD: string; LINK_EMAIL: string; LINK_PHONE: string; OTP_EMAIL: string; OTP_PHONE: string; THIRDPARTY: string; TOTP: string } = FactorIds

    Type declaration

    • EMAILPASSWORD: string
    • LINK_EMAIL: string
    • LINK_PHONE: string
    • OTP_EMAIL: string
    • OTP_PHONE: string
    • THIRDPARTY: string
    • TOTP: string
    MultiFactorAuthClaim: MultiFactorAuthClaimClass = MultiFactorAuthClaim
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • addToRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>
    • assertAllowedToSetupFactorElseThrowInvalidClaimError(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • getFactorsSetupForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • getMFARequirementsForAuth(session: SessionContainer, userContext?: Record<string, any>): Promise<MFARequirementList>
    • getRequiredSecondaryFactorsForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • markFactorAsCompleteInSession(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • removeFromRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_multitenancy.default.html b/docs/classes/recipe_multitenancy.default.html index 2ccabb278..f594b381b 100644 --- a/docs/classes/recipe_multitenancy.default.html +++ b/docs/classes/recipe_multitenancy.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • associateUserToTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
    • Parameters

      • tenantId: string
      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

    • createOrUpdateTenant(tenantId: string, config?: { coreConfig?: {}; firstFactors?: null | string[]; requiredSecondaryFactors?: null | string[] }, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • Optional config: { coreConfig?: {}; firstFactors?: null | string[]; requiredSecondaryFactors?: null | string[] }
        • Optional coreConfig?: {}
          • [key: string]: any
        • Optional firstFactors?: null | string[]
        • Optional requiredSecondaryFactors?: null | string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • createOrUpdateThirdPartyConfig(tenantId: string, config: ProviderConfig, skipValidation?: boolean, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • config: ProviderConfig
      • Optional skipValidation: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • deleteTenant(tenantId: string, userContext?: Record<string, any>): Promise<{ didExist: boolean; status: "OK" }>
    • deleteThirdPartyConfig(tenantId: string, thirdPartyId: string, userContext?: Record<string, any>): Promise<{ didConfigExist: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didConfigExist: boolean; status: "OK" }>

    • disassociateUserFromTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAssociated: boolean }>
    • getTenant(tenantId: string, userContext?: Record<string, any>): Promise<undefined | { status: "OK" } & TenantConfig>
    • listAllTenants(userContext?: Record<string, any>): Promise<{ status: "OK"; tenants: ({ tenantId: string } & TenantConfig)[] }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • associateUserToTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
    • Parameters

      • tenantId: string
      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

    • createOrUpdateTenant(tenantId: string, config?: { coreConfig?: {}; firstFactors?: null | string[]; requiredSecondaryFactors?: null | string[] }, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • Optional config: { coreConfig?: {}; firstFactors?: null | string[]; requiredSecondaryFactors?: null | string[] }
        • Optional coreConfig?: {}
          • [key: string]: any
        • Optional firstFactors?: null | string[]
        • Optional requiredSecondaryFactors?: null | string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • createOrUpdateThirdPartyConfig(tenantId: string, config: ProviderConfig, skipValidation?: boolean, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • config: ProviderConfig
      • Optional skipValidation: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • deleteTenant(tenantId: string, userContext?: Record<string, any>): Promise<{ didExist: boolean; status: "OK" }>
    • deleteThirdPartyConfig(tenantId: string, thirdPartyId: string, userContext?: Record<string, any>): Promise<{ didConfigExist: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didConfigExist: boolean; status: "OK" }>

    • disassociateUserFromTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAssociated: boolean }>
    • getTenant(tenantId: string, userContext?: Record<string, any>): Promise<undefined | { status: "OK" } & TenantConfig>
    • listAllTenants(userContext?: Record<string, any>): Promise<{ status: "OK"; tenants: ({ tenantId: string } & TenantConfig)[] }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_openid.default.html b/docs/classes/recipe_openid.default.html index 19749d08c..4aab539f7 100644 --- a/docs/classes/recipe_openid.default.html +++ b/docs/classes/recipe_openid.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = OpenIdRecipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • Optional payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
    • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
    • Parameters

      • Optional userContext: Record<string, any>

      Returns Promise<{ issuer: string; jwks_uri: string; status: "OK" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = OpenIdRecipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • Optional payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
    • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
    • Parameters

      • Optional userContext: Record<string, any>

      Returns Promise<{ issuer: string; jwks_uri: string; status: "OK" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_passwordless.default.html b/docs/classes/recipe_passwordless.default.html index 4a175eaa9..b3a464776 100644 --- a/docs/classes/recipe_passwordless.default.html +++ b/docs/classes/recipe_passwordless.default.html @@ -1,14 +1,14 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    Error: typeof default = SuperTokensError
    init: ((config: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config: TypeInput): RecipeListFunction
      • Parameters

        • config: TypeInput

        Returns RecipeListFunction

    Methods

    • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • +default | supertokens-node
      Options
      All
      • Public
      • Public/Protected
      • All
      Menu

      Hierarchy

      • default

      Index

      Constructors

      Properties

      Error: typeof default = SuperTokensError
      init: ((config: TypeInput) => RecipeListFunction) = Recipe.init

      Type declaration

        • (config: TypeInput): RecipeListFunction
        • Parameters

          • config: TypeInput

          Returns RecipeListFunction

      Methods

      • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
      • This function will only verify the code (not consume it), and: NOT create a new user if it doesn't exist NOT verify the user email if it exists NOT do any linking NOT delete the code unless it returned RESTART_FLOW_ERROR

        -

        Parameters

        • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

      • consumeCode(input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
      • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • +

        Parameters

        • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

      • consumeCode(input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
      • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
        1. verifies the code
        2. creates the user if it doesn't exist
        3. tries to link it
        4. marks the email as verified
        -

        Parameters

        • input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

      • createCode(input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
      • Parameters

        • input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }

        Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

      • createMagicLink(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<string>
      • Parameters

        • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

        Returns Promise<string>

      • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
      • Parameters

        • input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }
          • deviceId: string
          • tenantId: string
          • Optional userContext?: Record<string, any>
          • Optional userInputCode?: string

        Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

      • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
      • Parameters

        • input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }
          • deviceId: string
          • tenantId: string
          • Optional userContext?: Record<string, any>

        Returns Promise<undefined | DeviceType>

      • listCodesByEmail(input: { email: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
      • Parameters

        • input: { email: string; tenantId: string; userContext?: Record<string, any> }
          • email: string
          • tenantId: string
          • Optional userContext?: Record<string, any>

        Returns Promise<DeviceType[]>

      • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
      • Parameters

        • input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }
          • phoneNumber: string
          • tenantId: string
          • Optional userContext?: Record<string, any>

        Returns Promise<DeviceType[]>

      • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
      • Parameters

        • input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }
          • preAuthSessionId: string
          • tenantId: string
          • Optional userContext?: Record<string, any>

        Returns Promise<undefined | DeviceType>

      • revokeAllCodes(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
      • Parameters

        • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

        Returns Promise<{ status: "OK" }>

      • revokeCode(input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
      • Parameters

        • input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

        Returns Promise<{ status: "OK" }>

      • sendEmail(input: TypePasswordlessEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
      • sendSms(input: TypePasswordlessSmsDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
      • signInUp(input: { email: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: string; user: User }>
      • updateUser(input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
      • Parameters

        • input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }
          • Optional email?: null | string
          • Optional phoneNumber?: null | string
          • recipeUserId: RecipeUserId
          • Optional userContext?: Record<string, any>

        Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

      Legend

      • Variable
      • Function
      • Function with type parameter
      • Type alias
      • Type alias with type parameter
      • Class
      • Class with type parameter
      • Constructor
      • Static property
      • Static method
      • Interface

      Settings

      Theme

      Generated using TypeDoc

      \ No newline at end of file +

      Parameters

      • input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • createCode(input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
    • Parameters

      • input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

    • createMagicLink(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<string>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<string>

    • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>
        • Optional userInputCode?: string

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

    • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • listCodesByEmail(input: { email: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> }
        • email: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }
        • phoneNumber: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }
        • preAuthSessionId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • revokeAllCodes(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • revokeCode(input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • sendEmail(input: TypePasswordlessEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendSms(input: TypePasswordlessSmsDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • signInUp(input: { email: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: string; user: User }>
    • updateUser(input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
    • Parameters

      • input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }
        • Optional email?: null | string
        • Optional phoneNumber?: null | string
        • recipeUserId: RecipeUserId
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_session.default.html b/docs/classes/recipe_session.default.html index 5e579c72e..a51d4bfb3 100644 --- a/docs/classes/recipe_session.default.html +++ b/docs/classes/recipe_session.default.html @@ -1,4 +1,4 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    Error: typeof default = SuperTokensError
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • Optional payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • createNewSession(req: any, res: any, tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, userContext?: Record<string, any>): Promise<SessionContainer>
    • createNewSessionWithoutRequestResponse(tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, disableAntiCsrf?: boolean, userContext?: Record<string, any>): Promise<SessionContainer>
    • fetchAndSetClaim(sessionHandle: string, claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<boolean>
    • getAllSessionHandlesForUser(userId: string, fetchSessionsForAllLinkedAccounts?: boolean, tenantId?: string, userContext?: Record<string, any>): Promise<string[]>
    • Parameters

      • userId: string
      • fetchSessionsForAllLinkedAccounts: boolean = true
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<string[]>

    • getClaimValue<T>(sessionHandle: string, claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<{ status: "SESSION_DOES_NOT_EXIST_ERROR" } | { status: "OK"; value: undefined | T }>
    • Type Parameters

      • T

      Parameters

      • sessionHandle: string
      • claim: SessionClaim<T>
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "SESSION_DOES_NOT_EXIST_ERROR" } | { status: "OK"; value: undefined | T }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[] }>
    • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
    • getSessionInformation(sessionHandle: string, userContext?: Record<string, any>): Promise<undefined | SessionInformation>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string): Promise<SessionContainer>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { sessionRequired?: true }, userContext?: Record<string, any>): Promise<SessionContainer>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { sessionRequired: false }, userContext?: Record<string, any>): Promise<undefined | SessionContainer>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions, userContext?: Record<string, any>): Promise<undefined | SessionContainer>
    • mergeIntoAccessTokenPayload(sessionHandle: string, accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<boolean>
    • refreshSession(req: any, res: any, userContext?: Record<string, any>): Promise<SessionContainer>
    • refreshSessionWithoutRequestResponse(refreshToken: string, disableAntiCsrf?: boolean, antiCsrfToken?: string, userContext?: Record<string, any>): Promise<SessionContainer>
    • removeClaim(sessionHandle: string, claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<boolean>
    • revokeAllSessionsForUser(userId: string, revokeSessionsForLinkedAccounts?: boolean, tenantId?: string, userContext?: Record<string, any>): Promise<string[]>
    • Parameters

      • userId: string
      • revokeSessionsForLinkedAccounts: boolean = true
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<string[]>

    • revokeMultipleSessions(sessionHandles: string[], userContext?: Record<string, any>): Promise<string[]>
    • revokeSession(sessionHandle: string, userContext?: Record<string, any>): Promise<boolean>
    • setClaimValue<T>(sessionHandle: string, claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<boolean>
    • Type Parameters

      • T

      Parameters

      • sessionHandle: string
      • claim: SessionClaim<T>
      • value: T
      • Optional userContext: Record<string, any>

      Returns Promise<boolean>

    • updateSessionDataInDatabase(sessionHandle: string, newSessionData: any, userContext?: Record<string, any>): Promise<boolean>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +

    Returns Promise<SessionContainer>

  • Parameters

    • accessToken: string
    • Optional antiCsrfToken: string
    • Optional options: VerifySessionOptions & { sessionRequired?: true }
    • Optional userContext: Record<string, any>

    Returns Promise<SessionContainer>

  • Parameters

    • accessToken: string
    • Optional antiCsrfToken: string
    • Optional options: VerifySessionOptions & { sessionRequired: false }
    • Optional userContext: Record<string, any>

    Returns Promise<undefined | SessionContainer>

  • Parameters

    • accessToken: string
    • Optional antiCsrfToken: string
    • Optional options: VerifySessionOptions
    • Optional userContext: Record<string, any>

    Returns Promise<undefined | SessionContainer>

    • mergeIntoAccessTokenPayload(sessionHandle: string, accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<boolean>
    • refreshSession(req: any, res: any, userContext?: Record<string, any>): Promise<SessionContainer>
    • refreshSessionWithoutRequestResponse(refreshToken: string, disableAntiCsrf?: boolean, antiCsrfToken?: string, userContext?: Record<string, any>): Promise<SessionContainer>
    • removeClaim(sessionHandle: string, claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<boolean>
    • revokeAllSessionsForUser(userId: string, revokeSessionsForLinkedAccounts?: boolean, tenantId?: string, userContext?: Record<string, any>): Promise<string[]>
    • Parameters

      • userId: string
      • revokeSessionsForLinkedAccounts: boolean = true
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<string[]>

    • revokeMultipleSessions(sessionHandles: string[], userContext?: Record<string, any>): Promise<string[]>
    • revokeSession(sessionHandle: string, userContext?: Record<string, any>): Promise<boolean>
    • setClaimValue<T>(sessionHandle: string, claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<boolean>
    • Type Parameters

      • T

      Parameters

      • sessionHandle: string
      • claim: SessionClaim<T>
      • value: T
      • Optional userContext: Record<string, any>

      Returns Promise<boolean>

    • updateSessionDataInDatabase(sessionHandle: string, newSessionData: any, userContext?: Record<string, any>): Promise<boolean>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_thirdparty.default.html b/docs/classes/recipe_thirdparty.default.html index b49c29614..bd5c0994a 100644 --- a/docs/classes/recipe_thirdparty.default.html +++ b/docs/classes/recipe_thirdparty.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    Error: typeof default = SuperTokensError
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • getProvider(tenantId: string, thirdPartyId: string, clientType: undefined | string, userContext?: Record<string, any>): Promise<undefined | TypeProvider>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session?: undefined, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session: SessionContainer, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    Error: typeof default = SuperTokensError
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • getProvider(tenantId: string, thirdPartyId: string, clientType: undefined | string, userContext?: Record<string, any>): Promise<undefined | TypeProvider>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session?: undefined, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session: SessionContainer, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_totp.default.html b/docs/classes/recipe_totp.default.html index dcc0a9335..5b8523ffe 100644 --- a/docs/classes/recipe_totp.default.html +++ b/docs/classes/recipe_totp.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • createDevice(userId: string, userIdentifierInfo?: string, deviceName?: string, skew?: number, period?: number, userContext?: Record<string, any>): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • userId: string
      • Optional userIdentifierInfo: string
      • Optional deviceName: string
      • Optional skew: number
      • Optional period: number
      • Optional userContext: Record<string, any>

      Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • listDevices(userId: string, userContext?: Record<string, any>): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice(userId: string, deviceName: string, userContext?: Record<string, any>): Promise<{ didDeviceExist: boolean; status: "OK" }>
    • Parameters

      • userId: string
      • deviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice(userId: string, existingDeviceName: string, newDeviceName: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>
    • Parameters

      • userId: string
      • existingDeviceName: string
      • newDeviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>

    • verifyDevice(tenantId: string, userId: string, deviceName: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • deviceName: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP(tenantId: string, userId: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • createDevice(userId: string, userIdentifierInfo?: string, deviceName?: string, skew?: number, period?: number, userContext?: Record<string, any>): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • userId: string
      • Optional userIdentifierInfo: string
      • Optional deviceName: string
      • Optional skew: number
      • Optional period: number
      • Optional userContext: Record<string, any>

      Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • listDevices(userId: string, userContext?: Record<string, any>): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice(userId: string, deviceName: string, userContext?: Record<string, any>): Promise<{ didDeviceExist: boolean; status: "OK" }>
    • Parameters

      • userId: string
      • deviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice(userId: string, existingDeviceName: string, newDeviceName: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>
    • Parameters

      • userId: string
      • existingDeviceName: string
      • newDeviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>

    • verifyDevice(tenantId: string, userId: string, deviceName: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • deviceName: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP(tenantId: string, userId: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_usermetadata.default.html b/docs/classes/recipe_usermetadata.default.html index 675226b49..f874813af 100644 --- a/docs/classes/recipe_usermetadata.default.html +++ b/docs/classes/recipe_usermetadata.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • clearUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • getUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ metadata: any; status: "OK" }>
    • updateUserMetadata(userId: string, metadataUpdate: JSONObject, userContext?: Record<string, any>): Promise<{ metadata: JSONObject; status: "OK" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • clearUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • getUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ metadata: any; status: "OK" }>
    • updateUserMetadata(userId: string, metadataUpdate: JSONObject, userContext?: Record<string, any>): Promise<{ metadata: JSONObject; status: "OK" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/classes/recipe_userroles.default.html b/docs/classes/recipe_userroles.default.html index c67ad58ed..eb27b0dd8 100644 --- a/docs/classes/recipe_userroles.default.html +++ b/docs/classes/recipe_userroles.default.html @@ -1 +1 @@ -default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    PermissionClaim: PermissionClaimClass = PermissionClaim
    UserRoleClaim: UserRoleClaimClass = UserRoleClaim
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • addRoleToUser(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ createdNewRole: boolean; status: "OK" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole(role: string, userContext?: Record<string, any>): Promise<{ didRoleExist: boolean; status: "OK" }>
    • getAllRoles(userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole(role: string, userContext?: Record<string, any>): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser(tenantId: string, userId: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • Parameters

      • tenantId: string
      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ roles: string[]; status: "OK" }>

    • getRolesThatHavePermission(permission: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getUsersThatHaveRole(tenantId: string, role: string, userContext?: Record<string, any>): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • removePermissionsFromRole(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +default | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • default

    Index

    Constructors

    Properties

    PermissionClaim: PermissionClaimClass = PermissionClaim
    UserRoleClaim: UserRoleClaimClass = UserRoleClaim
    init: ((config?: TypeInput) => RecipeListFunction) = Recipe.init

    Type declaration

      • (config?: TypeInput): RecipeListFunction
      • Parameters

        • Optional config: TypeInput

        Returns RecipeListFunction

    Methods

    • addRoleToUser(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ createdNewRole: boolean; status: "OK" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole(role: string, userContext?: Record<string, any>): Promise<{ didRoleExist: boolean; status: "OK" }>
    • getAllRoles(userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole(role: string, userContext?: Record<string, any>): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser(tenantId: string, userId: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • Parameters

      • tenantId: string
      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ roles: string[]; status: "OK" }>

    • getRolesThatHavePermission(permission: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getUsersThatHaveRole(tenantId: string, role: string, userContext?: Record<string, any>): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • removePermissionsFromRole(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Constructor
    • Static property
    • Static method
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/interfaces/framework_awsLambda.SessionEvent.html b/docs/interfaces/framework_awsLambda.SessionEvent.html index 739fa12f1..25befe559 100644 --- a/docs/interfaces/framework_awsLambda.SessionEvent.html +++ b/docs/interfaces/framework_awsLambda.SessionEvent.html @@ -1 +1 @@ -SessionEvent | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SupertokensLambdaEvent
      • SessionEvent

    Index

    Properties

    body: null | string
    headers: APIGatewayProxyEventHeaders
    httpMethod: string
    isBase64Encoded: boolean
    multiValueHeaders: APIGatewayProxyEventMultiValueHeaders
    multiValueQueryStringParameters: null | APIGatewayProxyEventMultiValueQueryStringParameters
    path: string
    pathParameters: null | APIGatewayProxyEventPathParameters
    queryStringParameters: null | APIGatewayProxyEventQueryStringParameters
    requestContext: APIGatewayEventRequestContextWithAuthorizer<APIGatewayEventDefaultAuthorizerContext>
    resource: string
    stageVariables: null | APIGatewayProxyEventStageVariables
    supertokens: { response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] } }

    Type declaration

    • response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] }
      • cookies: string[]
      • headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[]

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +SessionEvent | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SupertokensLambdaEvent
      • SessionEvent

    Index

    Properties

    body: null | string
    headers: APIGatewayProxyEventHeaders
    httpMethod: string
    isBase64Encoded: boolean
    multiValueHeaders: APIGatewayProxyEventMultiValueHeaders
    multiValueQueryStringParameters: null | APIGatewayProxyEventMultiValueQueryStringParameters
    path: string
    pathParameters: null | APIGatewayProxyEventPathParameters
    queryStringParameters: null | APIGatewayProxyEventQueryStringParameters
    requestContext: APIGatewayEventRequestContextWithAuthorizer<APIGatewayEventDefaultAuthorizerContext>
    resource: string
    stageVariables: null | APIGatewayProxyEventStageVariables
    supertokens: { response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] } }

    Type declaration

    • response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] }
      • cookies: string[]
      • headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[]

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/interfaces/framework_awsLambda.SessionEventV2.html b/docs/interfaces/framework_awsLambda.SessionEventV2.html index 479021519..0fa6a3ca4 100644 --- a/docs/interfaces/framework_awsLambda.SessionEventV2.html +++ b/docs/interfaces/framework_awsLambda.SessionEventV2.html @@ -1 +1 @@ -SessionEventV2 | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SupertokensLambdaEventV2
      • SessionEventV2

    Index

    Properties

    body?: string
    cookies?: string[]
    headers: APIGatewayProxyEventHeaders
    isBase64Encoded: boolean
    pathParameters?: APIGatewayProxyEventPathParameters
    queryStringParameters?: APIGatewayProxyEventQueryStringParameters
    rawPath: string
    rawQueryString: string
    requestContext: { accountId: string; apiId: string; authorizer?: { jwt: { claims: {}; scopes: string[] } }; domainName: string; domainPrefix: string; http: { method: string; path: string; protocol: string; sourceIp: string; userAgent: string }; requestId: string; routeKey: string; stage: string; time: string; timeEpoch: number }

    Type declaration

    • accountId: string
    • apiId: string
    • Optional authorizer?: { jwt: { claims: {}; scopes: string[] } }
      • jwt: { claims: {}; scopes: string[] }
        • claims: {}
          • [name: string]: string | number | boolean | string[]
        • scopes: string[]
    • domainName: string
    • domainPrefix: string
    • http: { method: string; path: string; protocol: string; sourceIp: string; userAgent: string }
      • method: string
      • path: string
      • protocol: string
      • sourceIp: string
      • userAgent: string
    • requestId: string
    • routeKey: string
    • stage: string
    • time: string
    • timeEpoch: number
    routeKey: string
    stageVariables?: APIGatewayProxyEventStageVariables
    supertokens: { response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] } }

    Type declaration

    • response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] }
      • cookies: string[]
      • headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[]
    version: string

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +SessionEventV2 | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SupertokensLambdaEventV2
      • SessionEventV2

    Index

    Properties

    body?: string
    cookies?: string[]
    headers: APIGatewayProxyEventHeaders
    isBase64Encoded: boolean
    pathParameters?: APIGatewayProxyEventPathParameters
    queryStringParameters?: APIGatewayProxyEventQueryStringParameters
    rawPath: string
    rawQueryString: string
    requestContext: { accountId: string; apiId: string; authorizer?: { jwt: { claims: {}; scopes: string[] } }; domainName: string; domainPrefix: string; http: { method: string; path: string; protocol: string; sourceIp: string; userAgent: string }; requestId: string; routeKey: string; stage: string; time: string; timeEpoch: number }

    Type declaration

    • accountId: string
    • apiId: string
    • Optional authorizer?: { jwt: { claims: {}; scopes: string[] } }
      • jwt: { claims: {}; scopes: string[] }
        • claims: {}
          • [name: string]: string | number | boolean | string[]
        • scopes: string[]
    • domainName: string
    • domainPrefix: string
    • http: { method: string; path: string; protocol: string; sourceIp: string; userAgent: string }
      • method: string
      • path: string
      • protocol: string
      • sourceIp: string
      • userAgent: string
    • requestId: string
    • routeKey: string
    • stage: string
    • time: string
    • timeEpoch: number
    routeKey: string
    stageVariables?: APIGatewayProxyEventStageVariables
    supertokens: { response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] } }

    Type declaration

    • response: { cookies: string[]; headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[] }
      • cookies: string[]
      • headers: { allowDuplicateKey: boolean; key: string; value: string | number | boolean }[]
    version: string

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/interfaces/framework_express.SessionRequest.html b/docs/interfaces/framework_express.SessionRequest.html index 32a612667..06aa48d56 100644 --- a/docs/interfaces/framework_express.SessionRequest.html +++ b/docs/interfaces/framework_express.SessionRequest.html @@ -135,7 +135,7 @@
    route: any
    secure: boolean

    Short-hand for:

    req.protocol == 'https'

    -
    signedCookies: any
    socket: Socket
    +
    signedCookies: any
    socket: Socket

    The net.Socket object associated with the connection.

    With HTTPS support, use request.socket.getPeerCertificate() to obtain the client's authentication details.

    diff --git a/docs/interfaces/framework_hapi.SessionRequest.html b/docs/interfaces/framework_hapi.SessionRequest.html index 4a06fc25c..f49b73fb6 100644 --- a/docs/interfaces/framework_hapi.SessionRequest.html +++ b/docs/interfaces/framework_hapi.SessionRequest.html @@ -89,7 +89,7 @@
    server: Server

    Access: read only and the public server interface. The server object.

    -
    state: Dictionary<any>
    +
    state: Dictionary<any>

    An object containing parsed HTTP state information (cookies) where each key is the cookie name and value is the matching cookie content after processing using any registered cookie definition.

    url: URL

    The parsed request URI.

    diff --git a/docs/interfaces/framework_koa.SessionContext.html b/docs/interfaces/framework_koa.SessionContext.html index 406e848b9..9266c8230 100644 --- a/docs/interfaces/framework_koa.SessionContext.html +++ b/docs/interfaces/framework_koa.SessionContext.html @@ -81,7 +81,7 @@
    secure: boolean

    Short-hand for:

    this.protocol == 'https'

    -
    socket: Socket
    +
    socket: Socket

    Return the request socket.

    stale: boolean

    Check if the request is stale, aka diff --git a/docs/interfaces/framework_loopback.SessionContext.html b/docs/interfaces/framework_loopback.SessionContext.html index a0505df2a..e1e17acc9 100644 --- a/docs/interfaces/framework_loopback.SessionContext.html +++ b/docs/interfaces/framework_loopback.SessionContext.html @@ -14,7 +14,7 @@

    A flag to tell if the response is finished.

    scope: BindingScope

    Scope for binding resolution

    -
    subscriptionManager: ContextSubscriptionManager
    +
    subscriptionManager: ContextSubscriptionManager

    Manager for observer subscriptions

    tagIndexer: ContextTagIndexer

    Indexer for bindings by tag

    diff --git a/docs/interfaces/recipe_session.SessionContainer.html b/docs/interfaces/recipe_session.SessionContainer.html index 0a5129141..70aa42b9c 100644 --- a/docs/interfaces/recipe_session.SessionContainer.html +++ b/docs/interfaces/recipe_session.SessionContainer.html @@ -1 +1 @@ -SessionContainer | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SessionContainer

    Index

    Methods

    • attachToRequestResponse(reqResInfo: ReqResInfo, userContext?: Record<string, any>): void | Promise<void>
    • fetchAndSetClaim<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<void>
    • getAccessToken(userContext?: Record<string, any>): string
    • getAccessTokenPayload(userContext?: Record<string, any>): any
    • getAllSessionTokensDangerously(): { accessAndFrontTokenUpdated: boolean; accessToken: string; antiCsrfToken: undefined | string; frontToken: string; refreshToken: undefined | string }
    • Returns { accessAndFrontTokenUpdated: boolean; accessToken: string; antiCsrfToken: undefined | string; frontToken: string; refreshToken: undefined | string }

      • accessAndFrontTokenUpdated: boolean
      • accessToken: string
      • antiCsrfToken: undefined | string
      • frontToken: string
      • refreshToken: undefined | string
    • getClaimValue<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<undefined | T>
    • getExpiry(userContext?: Record<string, any>): Promise<number>
    • getHandle(userContext?: Record<string, any>): string
    • getRecipeUserId(userContext?: Record<string, any>): RecipeUserId
    • getSessionDataFromDatabase(userContext?: Record<string, any>): Promise<any>
    • getTenantId(userContext?: Record<string, any>): string
    • getTimeCreated(userContext?: Record<string, any>): Promise<number>
    • getUserId(userContext?: Record<string, any>): string
    • mergeIntoAccessTokenPayload(accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<void>
    • removeClaim(claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<void>
    • revokeSession(userContext?: Record<string, any>): Promise<void>
    • setClaimValue<T>(claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<void>
    • updateSessionDataInDatabase(newSessionData: any, userContext?: Record<string, any>): Promise<any>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Method
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +SessionContainer | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Hierarchy

    • SessionContainer

    Index

    Methods

    • attachToRequestResponse(reqResInfo: ReqResInfo, userContext?: Record<string, any>): void | Promise<void>
    • fetchAndSetClaim<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<void>
    • getAccessToken(userContext?: Record<string, any>): string
    • getAccessTokenPayload(userContext?: Record<string, any>): any
    • getAllSessionTokensDangerously(): { accessAndFrontTokenUpdated: boolean; accessToken: string; antiCsrfToken: undefined | string; frontToken: string; refreshToken: undefined | string }
    • Returns { accessAndFrontTokenUpdated: boolean; accessToken: string; antiCsrfToken: undefined | string; frontToken: string; refreshToken: undefined | string }

      • accessAndFrontTokenUpdated: boolean
      • accessToken: string
      • antiCsrfToken: undefined | string
      • frontToken: string
      • refreshToken: undefined | string
    • getClaimValue<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<undefined | T>
    • getExpiry(userContext?: Record<string, any>): Promise<number>
    • getHandle(userContext?: Record<string, any>): string
    • getRecipeUserId(userContext?: Record<string, any>): RecipeUserId
    • getSessionDataFromDatabase(userContext?: Record<string, any>): Promise<any>
    • getTenantId(userContext?: Record<string, any>): string
    • getTimeCreated(userContext?: Record<string, any>): Promise<number>
    • getUserId(userContext?: Record<string, any>): string
    • mergeIntoAccessTokenPayload(accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<void>
    • removeClaim(claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<void>
    • revokeSession(userContext?: Record<string, any>): Promise<void>
    • setClaimValue<T>(claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<void>
    • updateSessionDataInDatabase(newSessionData: any, userContext?: Record<string, any>): Promise<any>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Method
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/interfaces/recipe_session.VerifySessionOptions.html b/docs/interfaces/recipe_session.VerifySessionOptions.html index aa517aeb9..0ceaaff1b 100644 --- a/docs/interfaces/recipe_session.VerifySessionOptions.html +++ b/docs/interfaces/recipe_session.VerifySessionOptions.html @@ -1 +1 @@ -VerifySessionOptions | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Method
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +VerifySessionOptions | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Property
    • Method
    • Class
    • Class with type parameter

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework.html b/docs/modules/framework.html index b33d5ea90..1b768f60e 100644 --- a/docs/modules/framework.html +++ b/docs/modules/framework.html @@ -1 +1 @@ -framework | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework

    Index

    Variables

    awsLambda: framework/awsLambda = awsLambdaFramework
    default: { awsLambda: framework/awsLambda; express: framework/express; fastify: framework/fastify; hapi: framework/hapi; koa: framework/koa; loopback: framework/loopback }

    Type declaration

    express: framework/express = expressFramework
    fastify: framework/fastify = fastifyFramework
    hapi: framework/hapi = hapiFramework
    koa: framework/koa = koaFramework
    loopback: framework/loopback = loopbackFramework

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework

    Index

    Variables

    awsLambda: framework/awsLambda = awsLambdaFramework
    default: { awsLambda: framework/awsLambda; express: framework/express; fastify: framework/fastify; hapi: framework/hapi; koa: framework/koa; loopback: framework/loopback }

    Type declaration

    express: framework/express = expressFramework
    fastify: framework/fastify = fastifyFramework
    hapi: framework/hapi = hapiFramework
    koa: framework/koa = koaFramework
    loopback: framework/loopback = loopbackFramework

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_awsLambda.html b/docs/modules/framework_awsLambda.html index 9b8f1c596..a8d5e9fe6 100644 --- a/docs/modules/framework_awsLambda.html +++ b/docs/modules/framework_awsLambda.html @@ -1 +1 @@ -framework/awsLambda | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/awsLambda

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/awsLambda | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/awsLambda

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_custom.html b/docs/modules/framework_custom.html index 07e0f592d..b419ca8f5 100644 --- a/docs/modules/framework_custom.html +++ b/docs/modules/framework_custom.html @@ -1 +1 @@ -framework/custom | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/custom

    Index

    Functions

    • middleware<OrigReqType, OrigRespType>(wrapRequest?: ((req: OrigReqType) => BaseRequest), wrapResponse?: ((req: OrigRespType) => BaseResponse)): ((request: OrigReqType, response: OrigRespType, next?: NextFunction) => Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>)
    • Type Parameters

      Parameters

      Returns ((request: OrigReqType, response: OrigRespType, next?: NextFunction) => Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>)

        • (request: OrigReqType, response: OrigRespType, next?: NextFunction): Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>
        • Parameters

          • request: OrigReqType
          • response: OrigRespType
          • Optional next: NextFunction

          Returns Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/custom | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/custom

    Index

    Functions

    • middleware<OrigReqType, OrigRespType>(wrapRequest?: ((req: OrigReqType) => BaseRequest), wrapResponse?: ((req: OrigRespType) => BaseResponse)): ((request: OrigReqType, response: OrigRespType, next?: NextFunction) => Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>)
    • Type Parameters

      Parameters

      Returns ((request: OrigReqType, response: OrigRespType, next?: NextFunction) => Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>)

        • (request: OrigReqType, response: OrigRespType, next?: NextFunction): Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>
        • Parameters

          • request: OrigReqType
          • response: OrigRespType
          • Optional next: NextFunction

          Returns Promise<{ error: undefined; handled: boolean } | { error: any; handled: undefined }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_express.html b/docs/modules/framework_express.html index 924806d83..67fff866b 100644 --- a/docs/modules/framework_express.html +++ b/docs/modules/framework_express.html @@ -1 +1 @@ -framework/express | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/express

    Index

    Functions

    • errorHandler(): ((err: any, req: Request, res: Response, next: NextFunction) => Promise<void>)
    • Returns ((err: any, req: Request, res: Response, next: NextFunction) => Promise<void>)

        • (err: any, req: Request, res: Response, next: NextFunction): Promise<void>
        • Parameters

          • err: any
          • req: Request
          • res: Response
          • next: NextFunction

          Returns Promise<void>

    • middleware(): ((req: Request, res: Response, next: NextFunction) => Promise<void>)
    • Returns ((req: Request, res: Response, next: NextFunction) => Promise<void>)

        • (req: Request, res: Response, next: NextFunction): Promise<void>
        • Parameters

          • req: Request
          • res: Response
          • next: NextFunction

          Returns Promise<void>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/express | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/express

    Index

    Functions

    • errorHandler(): ((err: any, req: Request, res: Response, next: NextFunction) => Promise<void>)
    • Returns ((err: any, req: Request, res: Response, next: NextFunction) => Promise<void>)

        • (err: any, req: Request, res: Response, next: NextFunction): Promise<void>
        • Parameters

          • err: any
          • req: Request
          • res: Response
          • next: NextFunction

          Returns Promise<void>

    • middleware(): ((req: Request, res: Response, next: NextFunction) => Promise<void>)
    • Returns ((req: Request, res: Response, next: NextFunction) => Promise<void>)

        • (req: Request, res: Response, next: NextFunction): Promise<void>
        • Parameters

          • req: Request
          • res: Response
          • next: NextFunction

          Returns Promise<void>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_fastify.html b/docs/modules/framework_fastify.html index 16fc29c17..40c4e59f6 100644 --- a/docs/modules/framework_fastify.html +++ b/docs/modules/framework_fastify.html @@ -1 +1 @@ -framework/fastify | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/fastify

    Index

    Type Aliases

    SessionRequest<TRequest>: TRequest & { session?: SessionContainer }

    Type Parameters

    • TRequest extends OriginalFastifyRequest = OriginalFastifyRequest

    Functions

    • errorHandler(): ((err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>) => Promise<void>)
    • Returns ((err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>) => Promise<void>)

        • (err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>): Promise<void>
        • Parameters

          • err: any
          • req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>
          • res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>

          Returns Promise<void>

    • plugin(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyLoggerInstance>, opts: Record<never, never>, done: ((err?: Error) => void)): void
    • Parameters

      • instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyLoggerInstance>
      • opts: Record<never, never>
      • done: ((err?: Error) => void)
          • (err?: Error): void
          • Parameters

            • Optional err: Error

            Returns void

      Returns void

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/fastify | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/fastify

    Index

    Type Aliases

    SessionRequest<TRequest>: TRequest & { session?: SessionContainer }

    Type Parameters

    • TRequest extends OriginalFastifyRequest = OriginalFastifyRequest

    Functions

    • errorHandler(): ((err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>) => Promise<void>)
    • Returns ((err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>) => Promise<void>)

        • (err: any, req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>, res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>): Promise<void>
        • Parameters

          • err: any
          • req: FastifyRequest<RouteGenericInterface, RawServerDefault, IncomingMessage>
          • res: FastifyReply<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown>

          Returns Promise<void>

    • plugin(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyLoggerInstance>, opts: Record<never, never>, done: ((err?: Error) => void)): void
    • Parameters

      • instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyLoggerInstance>
      • opts: Record<never, never>
      • done: ((err?: Error) => void)
          • (err?: Error): void
          • Parameters

            • Optional err: Error

            Returns void

      Returns void

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_hapi.html b/docs/modules/framework_hapi.html index 424ec0684..47136250e 100644 --- a/docs/modules/framework_hapi.html +++ b/docs/modules/framework_hapi.html @@ -1 +1 @@ -framework/hapi | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/hapi

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/hapi | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/hapi

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_koa.html b/docs/modules/framework_koa.html index 6b0023ef9..263f666cf 100644 --- a/docs/modules/framework_koa.html +++ b/docs/modules/framework_koa.html @@ -1 +1 @@ -framework/koa | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/koa

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/koa | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/koa

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/framework_loopback.html b/docs/modules/framework_loopback.html index 2631d67d0..2026f8267 100644 --- a/docs/modules/framework_loopback.html +++ b/docs/modules/framework_loopback.html @@ -1 +1 @@ -framework/loopback | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/loopback

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +framework/loopback | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module framework/loopback

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/index.html b/docs/modules/index.html index 3fbe07480..075154af8 100644 --- a/docs/modules/index.html +++ b/docs/modules/index.html @@ -1 +1 @@ -index | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Index

    Variables

    Error: typeof default = SuperTokensWrapper.Error

    Functions

    • createUserIdMapping(input: { externalUserId: string; externalUserIdInfo?: string; force?: boolean; superTokensUserId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR" } | { doesExternalUserIdExist: boolean; doesSuperTokensUserIdExist: boolean; status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR" }>
    • Parameters

      • input: { externalUserId: string; externalUserIdInfo?: string; force?: boolean; superTokensUserId: string; userContext?: Record<string, any> }
        • externalUserId: string
        • Optional externalUserIdInfo?: string
        • Optional force?: boolean
        • superTokensUserId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR" } | { doesExternalUserIdExist: boolean; doesSuperTokensUserIdExist: boolean; status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR" }>

    • deleteUser(userId: string, removeAllLinkedAccounts?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • Parameters

      • userId: string
      • removeAllLinkedAccounts: boolean = true
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" }>

    • deleteUserIdMapping(input: { force?: boolean; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ didMappingExist: boolean; status: "OK" }>
    • Parameters

      • input: { force?: boolean; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional force?: boolean
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ didMappingExist: boolean; status: "OK" }>

    • getAllCORSHeaders(): string[]
    • getRequestFromUserContext(userContext: undefined | UserContext): undefined | BaseRequest
    • getUser(userId: string, userContext?: Record<string, any>): Promise<undefined | User>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<undefined | User>

    • getUserCount(includeRecipeIds?: string[], tenantId?: string, userContext?: Record<string, any>): Promise<number>
    • Parameters

      • Optional includeRecipeIds: string[]
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<number>

    • getUserIdMapping(input: { userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ externalUserId: string; externalUserIdInfo: undefined | string; status: "OK"; superTokensUserId: string } | { status: "UNKNOWN_MAPPING_ERROR" }>
    • Parameters

      • input: { userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ externalUserId: string; externalUserIdInfo: undefined | string; status: "OK"; superTokensUserId: string } | { status: "UNKNOWN_MAPPING_ERROR" }>

    • getUsersNewestFirst(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }): Promise<{ nextPaginationToken?: string; users: User[] }>
    • Parameters

      • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }
        • Optional includeRecipeIds?: string[]
        • Optional limit?: number
        • Optional paginationToken?: string
        • Optional query?: {}
          • [key: string]: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • getUsersOldestFirst(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }): Promise<{ nextPaginationToken?: string; users: User[] }>
    • Parameters

      • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }
        • Optional includeRecipeIds?: string[]
        • Optional limit?: number
        • Optional paginationToken?: string
        • Optional query?: {}
          • [key: string]: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • init(config: TypeInput): void
    • listUsersByAccountInfo(tenantId: string, accountInfo: AccountInfo, doUnionOfAccountInfo?: boolean, userContext?: Record<string, any>): Promise<User[]>
    • Parameters

      • tenantId: string
      • accountInfo: AccountInfo
      • doUnionOfAccountInfo: boolean = false
      • Optional userContext: Record<string, any>

      Returns Promise<User[]>

    • updateOrDeleteUserIdMappingInfo(input: { externalUserIdInfo?: string; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ status: "OK" | "UNKNOWN_MAPPING_ERROR" }>
    • Parameters

      • input: { externalUserIdInfo?: string; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional externalUserIdInfo?: string
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ status: "OK" | "UNKNOWN_MAPPING_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +index | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Index

    Variables

    Error: typeof default = SuperTokensWrapper.Error

    Functions

    • createUserIdMapping(input: { externalUserId: string; externalUserIdInfo?: string; force?: boolean; superTokensUserId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR" } | { doesExternalUserIdExist: boolean; doesSuperTokensUserIdExist: boolean; status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR" }>
    • Parameters

      • input: { externalUserId: string; externalUserIdInfo?: string; force?: boolean; superTokensUserId: string; userContext?: Record<string, any> }
        • externalUserId: string
        • Optional externalUserIdInfo?: string
        • Optional force?: boolean
        • superTokensUserId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_SUPERTOKENS_USER_ID_ERROR" } | { doesExternalUserIdExist: boolean; doesSuperTokensUserIdExist: boolean; status: "USER_ID_MAPPING_ALREADY_EXISTS_ERROR" }>

    • deleteUser(userId: string, removeAllLinkedAccounts?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • Parameters

      • userId: string
      • removeAllLinkedAccounts: boolean = true
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" }>

    • deleteUserIdMapping(input: { force?: boolean; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ didMappingExist: boolean; status: "OK" }>
    • Parameters

      • input: { force?: boolean; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional force?: boolean
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ didMappingExist: boolean; status: "OK" }>

    • getAllCORSHeaders(): string[]
    • getRequestFromUserContext(userContext: undefined | UserContext): undefined | BaseRequest
    • getUser(userId: string, userContext?: Record<string, any>): Promise<undefined | User>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<undefined | User>

    • getUserCount(includeRecipeIds?: string[], tenantId?: string, userContext?: Record<string, any>): Promise<number>
    • Parameters

      • Optional includeRecipeIds: string[]
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<number>

    • getUserIdMapping(input: { userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ externalUserId: string; externalUserIdInfo: undefined | string; status: "OK"; superTokensUserId: string } | { status: "UNKNOWN_MAPPING_ERROR" }>
    • Parameters

      • input: { userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ externalUserId: string; externalUserIdInfo: undefined | string; status: "OK"; superTokensUserId: string } | { status: "UNKNOWN_MAPPING_ERROR" }>

    • getUsersNewestFirst(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }): Promise<{ nextPaginationToken?: string; users: User[] }>
    • Parameters

      • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }
        • Optional includeRecipeIds?: string[]
        • Optional limit?: number
        • Optional paginationToken?: string
        • Optional query?: {}
          • [key: string]: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • getUsersOldestFirst(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }): Promise<{ nextPaginationToken?: string; users: User[] }>
    • Parameters

      • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; userContext?: Record<string, any> }
        • Optional includeRecipeIds?: string[]
        • Optional limit?: number
        • Optional paginationToken?: string
        • Optional query?: {}
          • [key: string]: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • init(config: TypeInput): void
    • listUsersByAccountInfo(tenantId: string, accountInfo: AccountInfo, doUnionOfAccountInfo?: boolean, userContext?: Record<string, any>): Promise<User[]>
    • Parameters

      • tenantId: string
      • accountInfo: AccountInfo
      • doUnionOfAccountInfo: boolean = false
      • Optional userContext: Record<string, any>

      Returns Promise<User[]>

    • updateOrDeleteUserIdMappingInfo(input: { externalUserIdInfo?: string; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }): Promise<{ status: "OK" | "UNKNOWN_MAPPING_ERROR" }>
    • Parameters

      • input: { externalUserIdInfo?: string; userContext?: Record<string, any>; userId: string; userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY" }
        • Optional externalUserIdInfo?: string
        • Optional userContext?: Record<string, any>
        • userId: string
        • Optional userIdType?: "SUPERTOKENS" | "EXTERNAL" | "ANY"

      Returns Promise<{ status: "OK" | "UNKNOWN_MAPPING_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_accountlinking.html b/docs/modules/recipe_accountlinking.html index b21f3b022..7b6739e27 100644 --- a/docs/modules/recipe_accountlinking.html +++ b/docs/modules/recipe_accountlinking.html @@ -1 +1 @@ -recipe/accountlinking | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/accountlinking

    Index

    Type Aliases

    RecipeInterface: { canCreatePrimaryUser: any; canLinkAccounts: any; createPrimaryUser: any; deleteUser: any; getUser: any; getUsers: any; linkAccounts: any; listUsersByAccountInfo: any; unlinkAccount: any }

    Type declaration

    • canCreatePrimaryUser:function
      • canCreatePrimaryUser(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
      • Parameters

        Returns Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • canLinkAccounts:function
      • canLinkAccounts(input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
      • Parameters

        • input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }
          • primaryUserId: string
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • createPrimaryUser:function
      • createPrimaryUser(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
      • Parameters

        Returns Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • deleteUser:function
      • deleteUser(input: { removeAllLinkedAccounts: boolean; userContext: UserContext; userId: string }): Promise<{ status: "OK" }>
      • Parameters

        • input: { removeAllLinkedAccounts: boolean; userContext: UserContext; userId: string }
          • removeAllLinkedAccounts: boolean
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" }>

    • getUser:function
      • getUser(input: { userContext: UserContext; userId: string }): Promise<undefined | User>
    • getUsers:function
      • getUsers(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; timeJoinedOrder: "ASC" | "DESC"; userContext: UserContext }): Promise<{ nextPaginationToken?: string; users: User[] }>
      • Parameters

        • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; timeJoinedOrder: "ASC" | "DESC"; userContext: UserContext }
          • Optional includeRecipeIds?: string[]
          • Optional limit?: number
          • Optional paginationToken?: string
          • Optional query?: {}
            • [key: string]: string
          • tenantId: string
          • timeJoinedOrder: "ASC" | "DESC"
          • userContext: UserContext

        Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • linkAccounts:function
      • linkAccounts(input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
      • Parameters

        • input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }
          • primaryUserId: string
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • listUsersByAccountInfo:function
      • listUsersByAccountInfo(input: { accountInfo: AccountInfo; doUnionOfAccountInfo: boolean; tenantId: string; userContext: UserContext }): Promise<User[]>
      • Parameters

        • input: { accountInfo: AccountInfo; doUnionOfAccountInfo: boolean; tenantId: string; userContext: UserContext }
          • accountInfo: AccountInfo
          • doUnionOfAccountInfo: boolean
          • tenantId: string
          • userContext: UserContext

        Returns Promise<User[]>

    • unlinkAccount:function
      • unlinkAccount(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; wasLinked: boolean; wasRecipeUserDeleted: boolean }>

    Functions

    • canCreatePrimaryUser(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • canLinkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record<string, any>): Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • primaryUserId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • createPrimaryUser(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • createPrimaryUserIdOrLinkAccounts(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainer, userContext?: Record<string, any>): Promise<User>
    • getPrimaryUserThatCanBeLinkedToRecipeUserId(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<undefined | User>
    • init(config?: TypeInput): RecipeListFunction
    • isEmailChangeAllowed(recipeUserId: RecipeUserId, newEmail: string, isVerified: boolean, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • isSignInAllowed(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • isSignUpAllowed(tenantId: string, newUser: AccountInfoWithRecipeId, isVerified: boolean, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • linkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record<string, any>): Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • primaryUserId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • unlinkAccount(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasLinked: boolean; wasRecipeUserDeleted: boolean }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/accountlinking | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/accountlinking

    Index

    Type Aliases

    RecipeInterface: { canCreatePrimaryUser: any; canLinkAccounts: any; createPrimaryUser: any; deleteUser: any; getUser: any; getUsers: any; linkAccounts: any; listUsersByAccountInfo: any; unlinkAccount: any }

    Type declaration

    • canCreatePrimaryUser:function
      • canCreatePrimaryUser(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
      • Parameters

        Returns Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • canLinkAccounts:function
      • canLinkAccounts(input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
      • Parameters

        • input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }
          • primaryUserId: string
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • createPrimaryUser:function
      • createPrimaryUser(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
      • Parameters

        Returns Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • deleteUser:function
      • deleteUser(input: { removeAllLinkedAccounts: boolean; userContext: UserContext; userId: string }): Promise<{ status: "OK" }>
      • Parameters

        • input: { removeAllLinkedAccounts: boolean; userContext: UserContext; userId: string }
          • removeAllLinkedAccounts: boolean
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" }>

    • getUser:function
      • getUser(input: { userContext: UserContext; userId: string }): Promise<undefined | User>
    • getUsers:function
      • getUsers(input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; timeJoinedOrder: "ASC" | "DESC"; userContext: UserContext }): Promise<{ nextPaginationToken?: string; users: User[] }>
      • Parameters

        • input: { includeRecipeIds?: string[]; limit?: number; paginationToken?: string; query?: {}; tenantId: string; timeJoinedOrder: "ASC" | "DESC"; userContext: UserContext }
          • Optional includeRecipeIds?: string[]
          • Optional limit?: number
          • Optional paginationToken?: string
          • Optional query?: {}
            • [key: string]: string
          • tenantId: string
          • timeJoinedOrder: "ASC" | "DESC"
          • userContext: UserContext

        Returns Promise<{ nextPaginationToken?: string; users: User[] }>

    • linkAccounts:function
      • linkAccounts(input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
      • Parameters

        • input: { primaryUserId: string; recipeUserId: RecipeUserId; userContext: UserContext }
          • primaryUserId: string
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • listUsersByAccountInfo:function
      • listUsersByAccountInfo(input: { accountInfo: AccountInfo; doUnionOfAccountInfo: boolean; tenantId: string; userContext: UserContext }): Promise<User[]>
      • Parameters

        • input: { accountInfo: AccountInfo; doUnionOfAccountInfo: boolean; tenantId: string; userContext: UserContext }
          • accountInfo: AccountInfo
          • doUnionOfAccountInfo: boolean
          • tenantId: string
          • userContext: UserContext

        Returns Promise<User[]>

    • unlinkAccount:function
      • unlinkAccount(input: { recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK"; wasLinked: boolean; wasRecipeUserDeleted: boolean }>

    Functions

    • canCreatePrimaryUser(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyAPrimaryUser: boolean } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • canLinkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record<string, any>): Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • primaryUserId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK" } | { description: string; primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • createPrimaryUser(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; user: User; wasAlreadyAPrimaryUser: boolean } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" }>

    • createPrimaryUserIdOrLinkAccounts(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainer, userContext?: Record<string, any>): Promise<User>
    • getPrimaryUserThatCanBeLinkedToRecipeUserId(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<undefined | User>
    • init(config?: TypeInput): RecipeListFunction
    • isEmailChangeAllowed(recipeUserId: RecipeUserId, newEmail: string, isVerified: boolean, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • isSignInAllowed(tenantId: string, recipeUserId: RecipeUserId, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • isSignUpAllowed(tenantId: string, newUser: AccountInfoWithRecipeId, isVerified: boolean, session?: SessionContainer, userContext?: Record<string, any>): Promise<boolean>
    • linkAccounts(recipeUserId: RecipeUserId, primaryUserId: string, userContext?: Record<string, any>): Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>
    • Parameters

      • recipeUserId: RecipeUserId
      • primaryUserId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ accountsAlreadyLinked: boolean; status: "OK"; user: User } | { primaryUserId: string; status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; user: User } | { description: string; primaryUserId: string; status: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" } | { status: "INPUT_USER_IS_NOT_A_PRIMARY_USER" }>

    • unlinkAccount(recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasLinked: boolean; wasRecipeUserDeleted: boolean }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_dashboard.html b/docs/modules/recipe_dashboard.html index 268770d55..97e81553d 100644 --- a/docs/modules/recipe_dashboard.html +++ b/docs/modules/recipe_dashboard.html @@ -1 +1 @@ -recipe/dashboard | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/dashboard

    Index

    Type Aliases

    APIInterface: { dashboardGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<string>) }

    Type declaration

    • dashboardGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<string>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { getDashboardBundleLocation: any; shouldAllowAccess: any }

    Type declaration

    • getDashboardBundleLocation:function
      • getDashboardBundleLocation(input: { userContext: UserContext }): Promise<string>
    • shouldAllowAccess:function
      • shouldAllowAccess(input: { config: TypeNormalisedInput; req: BaseRequest; userContext: UserContext }): Promise<boolean>

    Functions

    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/dashboard | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/dashboard

    Index

    Type Aliases

    APIInterface: { dashboardGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<string>) }

    Type declaration

    • dashboardGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<string>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { getDashboardBundleLocation: any; shouldAllowAccess: any }

    Type declaration

    • getDashboardBundleLocation:function
      • getDashboardBundleLocation(input: { userContext: UserContext }): Promise<string>
    • shouldAllowAccess:function
      • shouldAllowAccess(input: { config: TypeNormalisedInput; req: BaseRequest; userContext: UserContext }): Promise<boolean>

    Functions

    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_emailpassword.html b/docs/modules/recipe_emailpassword.html index 01e9a9c1a..4b2dde830 100644 --- a/docs/modules/recipe_emailpassword.html +++ b/docs/modules/recipe_emailpassword.html @@ -1,5 +1,5 @@ -recipe/emailpassword | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/emailpassword

    Index

    Type Aliases

    APIInterface: { emailExistsGET: undefined | ((input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ exists: boolean; status: "OK" } | GeneralErrorResponse>); generatePasswordResetTokenPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ status: "OK" } | { reason: string; status: "PASSWORD_RESET_NOT_ALLOWED" } | GeneralErrorResponse>); passwordResetPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; token: string; userContext: UserContext }) => Promise<{ email: string; status: "OK"; user: User } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" } | GeneralErrorResponse>); signInPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_NOT_ALLOWED" } | { status: "WRONG_CREDENTIALS_ERROR" } | GeneralErrorResponse>); signUpPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_UP_NOT_ALLOWED" } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>) }

    Type declaration

    • emailExistsGET: undefined | ((input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ exists: boolean; status: "OK" } | GeneralErrorResponse>)
    • generatePasswordResetTokenPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ status: "OK" } | { reason: string; status: "PASSWORD_RESET_NOT_ALLOWED" } | GeneralErrorResponse>)
    • passwordResetPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; token: string; userContext: UserContext }) => Promise<{ email: string; status: "OK"; user: User } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" } | GeneralErrorResponse>)
    • signInPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_NOT_ALLOWED" } | { status: "WRONG_CREDENTIALS_ERROR" } | GeneralErrorResponse>)
    • signUpPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_UP_NOT_ALLOWED" } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypeEmailPasswordEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { consumePasswordResetToken: any; createNewRecipeUser: any; createResetPasswordToken: any; signIn: any; signUp: any; updateEmailOrPassword: any; verifyCredentials: any }

    Type declaration

    • consumePasswordResetToken:function
      • consumePasswordResetToken(input: { tenantId: string; token: string; userContext: UserContext }): Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>
      • Parameters

        • input: { tenantId: string; token: string; userContext: UserContext }
          • tenantId: string
          • token: string
          • userContext: UserContext

        Returns Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>

    • createNewRecipeUser:function
      • createNewRecipeUser(input: { email: string; password: string; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>
      • Parameters

        • input: { email: string; password: string; tenantId: string; userContext: UserContext }
          • email: string
          • password: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>

    • createResetPasswordToken:function
      • createResetPasswordToken(input: { email: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>
      • +recipe/emailpassword | supertokens-node
        Options
        All
        • Public
        • Public/Protected
        • All
        Menu

        Module recipe/emailpassword

        Index

        Type Aliases

        APIInterface: { emailExistsGET: undefined | ((input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ exists: boolean; status: "OK" } | GeneralErrorResponse>); generatePasswordResetTokenPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ status: "OK" } | { reason: string; status: "PASSWORD_RESET_NOT_ALLOWED" } | GeneralErrorResponse>); passwordResetPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; token: string; userContext: UserContext }) => Promise<{ email: string; status: "OK"; user: User } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" } | GeneralErrorResponse>); signInPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_NOT_ALLOWED" } | { status: "WRONG_CREDENTIALS_ERROR" } | GeneralErrorResponse>); signUpPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_UP_NOT_ALLOWED" } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>) }

        Type declaration

        • emailExistsGET: undefined | ((input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ exists: boolean; status: "OK" } | GeneralErrorResponse>)
        • generatePasswordResetTokenPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; userContext: UserContext }) => Promise<{ status: "OK" } | { reason: string; status: "PASSWORD_RESET_NOT_ALLOWED" } | GeneralErrorResponse>)
        • passwordResetPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; tenantId: string; token: string; userContext: UserContext }) => Promise<{ email: string; status: "OK"; user: User } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" } | GeneralErrorResponse>)
        • signInPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_NOT_ALLOWED" } | { status: "WRONG_CREDENTIALS_ERROR" } | GeneralErrorResponse>)
        • signUpPOST: undefined | ((input: { formFields: { id: string; value: string }[]; options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }) => Promise<{ session: SessionContainer; status: "OK"; user: User } | { reason: string; status: "SIGN_UP_NOT_ALLOWED" } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>)
        APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypeEmailPasswordEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

        Type declaration

        RecipeInterface: { consumePasswordResetToken: any; createNewRecipeUser: any; createResetPasswordToken: any; signIn: any; signUp: any; updateEmailOrPassword: any; verifyCredentials: any }

        Type declaration

        • consumePasswordResetToken:function
          • consumePasswordResetToken(input: { tenantId: string; token: string; userContext: UserContext }): Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>
          • Parameters

            • input: { tenantId: string; token: string; userContext: UserContext }
              • tenantId: string
              • token: string
              • userContext: UserContext

            Returns Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>

        • createNewRecipeUser:function
          • createNewRecipeUser(input: { email: string; password: string; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>
          • Parameters

            • input: { email: string; password: string; tenantId: string; userContext: UserContext }
              • email: string
              • password: string
              • tenantId: string
              • userContext: UserContext

            Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>

        • createResetPasswordToken:function
          • createResetPasswordToken(input: { email: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>
          • We pass in the email as well to this function cause the input userId may not be associated with an emailpassword account. In this case, we need to know which email to use to create an emailpassword account later on.

            -

            Parameters

            • input: { email: string; tenantId: string; userContext: UserContext; userId: string }
              • email: string
              • tenantId: string
              • userContext: UserContext
              • userId: string

            Returns Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>

        • signIn:function
          • signIn(input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
          • Parameters

            • input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }
              • email: string
              • password: string
              • session: SessionContainer | undefined
              • tenantId: string
              • userContext: UserContext

            Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

        • signUp:function
          • signUp(input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
          • Parameters

            • input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }
              • email: string
              • password: string
              • session: SessionContainer | undefined
              • tenantId: string
              • userContext: UserContext

            Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

        • updateEmailOrPassword:function
          • updateEmailOrPassword(input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy: string; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
          • Parameters

            • input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy: string; userContext: UserContext }
              • Optional applyPasswordPolicy?: boolean
              • Optional email?: string
              • Optional password?: string
              • recipeUserId: RecipeUserId
              • tenantIdForPasswordPolicy: string
              • userContext: UserContext

            Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

        • verifyCredentials:function
          • verifyCredentials(input: { email: string; password: string; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>
          • Parameters

            • input: { email: string; password: string; tenantId: string; userContext: UserContext }
              • email: string
              • password: string
              • tenantId: string
              • userContext: UserContext

            Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>

        Variables

        Error: typeof default = Wrapper.Error

        Functions

        • consumePasswordResetToken(tenantId: string, token: string, userContext?: Record<string, any>): Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>
        • Parameters

          • tenantId: string
          • token: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>

        • createResetPasswordLink(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" }>
        • Parameters

          • tenantId: string
          • userId: string
          • email: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ link: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" }>

        • createResetPasswordToken(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>
        • Parameters

          • tenantId: string
          • userId: string
          • email: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>

        • init(config?: TypeInput): RecipeListFunction
        • resetPasswordUsingToken(tenantId: string, token: string, newPassword: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
        • Parameters

          • tenantId: string
          • token: string
          • newPassword: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

        • sendEmail(input: TypeEmailPasswordPasswordResetEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
        • sendResetPasswordEmail(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>
        • Parameters

          • tenantId: string
          • userId: string
          • email: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>

        • signIn(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>
        • signIn(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
        • Parameters

          • tenantId: string
          • email: string
          • password: string
          • Optional session: undefined
          • Optional userContext: Record<string, any>

          Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>

        • Parameters

          • tenantId: string
          • email: string
          • password: string
          • session: SessionContainer
          • Optional userContext: Record<string, any>

          Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

        • signUp(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>
        • signUp(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
        • Parameters

          • tenantId: string
          • email: string
          • password: string
          • Optional session: undefined
          • Optional userContext: Record<string, any>

          Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>

        • Parameters

          • tenantId: string
          • email: string
          • password: string
          • session: SessionContainer
          • Optional userContext: Record<string, any>

          Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

        • updateEmailOrPassword(input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
        • Parameters

          • input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }
            • Optional applyPasswordPolicy?: boolean
            • Optional email?: string
            • Optional password?: string
            • recipeUserId: RecipeUserId
            • Optional tenantIdForPasswordPolicy?: string
            • Optional userContext?: Record<string, any>

          Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

        • verifyCredentials(tenantId: string, email: string, password: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>
        • Parameters

          • tenantId: string
          • email: string
          • password: string
          • Optional userContext: Record<string, any>

          Returns Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>

        Legend

        • Variable
        • Function
        • Function with type parameter
        • Type alias
        • Type alias with type parameter
        • Class
        • Class with type parameter
        • Interface

        Settings

        Theme

        Generated using TypeDoc

        \ No newline at end of file +

        Parameters

        • input: { email: string; tenantId: string; userContext: UserContext; userId: string }
          • email: string
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • signIn:function
      • signIn(input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }
          • email: string
          • password: string
          • session: SessionContainer | undefined
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • signUp:function
      • signUp(input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; password: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }
          • email: string
          • password: string
          • session: SessionContainer | undefined
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • updateEmailOrPassword:function
      • updateEmailOrPassword(input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy: string; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
      • Parameters

        • input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy: string; userContext: UserContext }
          • Optional applyPasswordPolicy?: boolean
          • Optional email?: string
          • Optional password?: string
          • recipeUserId: RecipeUserId
          • tenantIdForPasswordPolicy: string
          • userContext: UserContext

        Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

    • verifyCredentials:function
      • verifyCredentials(input: { email: string; password: string; tenantId: string; userContext: UserContext }): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>
      • Parameters

        • input: { email: string; password: string; tenantId: string; userContext: UserContext }
          • email: string
          • password: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>

    Variables

    Error: typeof default = Wrapper.Error

    Functions

    • consumePasswordResetToken(tenantId: string, token: string, userContext?: Record<string, any>): Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>
    • Parameters

      • tenantId: string
      • token: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ email: string; status: "OK"; userId: string } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" }>

    • createResetPasswordLink(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • email: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ link: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • createResetPasswordToken(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • email: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • init(config?: TypeInput): RecipeListFunction
    • resetPasswordUsingToken(tenantId: string, token: string, newPassword: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
    • Parameters

      • tenantId: string
      • token: string
      • newPassword: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

    • sendEmail(input: TypeEmailPasswordPasswordResetEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendResetPasswordEmail(tenantId: string, userId: string, email: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • email: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" }>

    • signIn(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>
    • signIn(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" }>

    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "WRONG_CREDENTIALS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • signUp(tenantId: string, email: string, password: string, session?: undefined, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>
    • signUp(tenantId: string, email: string, password: string, session: SessionContainer, userContext?: Record<string, any>): Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" }>

    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ recipeUserId: RecipeUserId; status: "OK"; user: User } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • updateEmailOrPassword(input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>
    • Parameters

      • input: { applyPasswordPolicy?: boolean; email?: string; password?: string; recipeUserId: RecipeUserId; tenantIdForPasswordPolicy?: string; userContext?: Record<string, any> }
        • Optional applyPasswordPolicy?: boolean
        • Optional email?: string
        • Optional password?: string
        • recipeUserId: RecipeUserId
        • Optional tenantIdForPasswordPolicy?: string
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { failureReason: string; status: "PASSWORD_POLICY_VIOLATED_ERROR" }>

    • verifyCredentials(tenantId: string, email: string, password: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>
    • Parameters

      • tenantId: string
      • email: string
      • password: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "WRONG_CREDENTIALS_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_emailverification.html b/docs/modules/recipe_emailverification.html index 1aa3a9922..14add5217 100644 --- a/docs/modules/recipe_emailverification.html +++ b/docs/modules/recipe_emailverification.html @@ -1 +1 @@ -recipe/emailverification | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/emailverification

    Index

    Type Aliases

    APIInterface: { generateEmailVerifyTokenPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ status: "OK" } | { newSession?: SessionContainer; status: "EMAIL_ALREADY_VERIFIED_ERROR" } | GeneralErrorResponse>); isEmailVerifiedGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ isVerified: boolean; newSession?: SessionContainer; status: "OK" } | GeneralErrorResponse>); verifyEmailPOST: undefined | ((input: { options: APIOptions; session: SessionContainer | undefined; tenantId: string; token: string; userContext: UserContext }) => Promise<{ newSession?: SessionContainer; status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" } | GeneralErrorResponse>) }

    Type declaration

    • generateEmailVerifyTokenPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ status: "OK" } | { newSession?: SessionContainer; status: "EMAIL_ALREADY_VERIFIED_ERROR" } | GeneralErrorResponse>)
    • isEmailVerifiedGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ isVerified: boolean; newSession?: SessionContainer; status: "OK" } | GeneralErrorResponse>)
    • verifyEmailPOST: undefined | ((input: { options: APIOptions; session: SessionContainer | undefined; tenantId: string; token: string; userContext: UserContext }) => Promise<{ newSession?: SessionContainer; status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" } | GeneralErrorResponse>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypeEmailVerificationEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • emailDelivery: default<TypeEmailVerificationEmailDeliveryInput>
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    RecipeInterface: { createEmailVerificationToken: any; isEmailVerified: any; revokeEmailVerificationTokens: any; unverifyEmail: any; verifyEmailUsingToken: any }

    Type declaration

    • createEmailVerificationToken:function
      • createEmailVerificationToken(input: { email: string; recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • isEmailVerified:function
      • isEmailVerified(input: { email: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<boolean>
    • revokeEmailVerificationTokens:function
      • revokeEmailVerificationTokens(input: { email: string; recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
    • unverifyEmail:function
      • unverifyEmail(input: { email: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK" }>
    • verifyEmailUsingToken:function
      • verifyEmailUsingToken(input: { attemptAccountLinking: boolean; tenantId: string; token: string; userContext: UserContext }): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>
      • Parameters

        • input: { attemptAccountLinking: boolean; tenantId: string; token: string; userContext: UserContext }
          • attemptAccountLinking: boolean
          • tenantId: string
          • token: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

    UserEmailInfo: { email: string; recipeUserId: RecipeUserId }

    Type declaration

    Variables

    EmailVerificationClaim: EmailVerificationClaimClass = ...
    Error: typeof default = Wrapper.Error

    Functions

    • createEmailVerificationLink(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • createEmailVerificationToken(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • init(config: TypeInput): RecipeListFunction
    • isEmailVerified(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<boolean>
    • revokeEmailVerificationTokens(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • sendEmail(input: TypeEmailVerificationEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendEmailVerificationEmail(tenantId: string, userId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • unverifyEmail(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • verifyEmailUsingToken(tenantId: string, token: string, attemptAccountLinking?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/emailverification | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/emailverification

    Index

    Type Aliases

    APIInterface: { generateEmailVerifyTokenPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ status: "OK" } | { newSession?: SessionContainer; status: "EMAIL_ALREADY_VERIFIED_ERROR" } | GeneralErrorResponse>); isEmailVerifiedGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ isVerified: boolean; newSession?: SessionContainer; status: "OK" } | GeneralErrorResponse>); verifyEmailPOST: undefined | ((input: { options: APIOptions; session: SessionContainer | undefined; tenantId: string; token: string; userContext: UserContext }) => Promise<{ newSession?: SessionContainer; status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" } | GeneralErrorResponse>) }

    Type declaration

    • generateEmailVerifyTokenPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ status: "OK" } | { newSession?: SessionContainer; status: "EMAIL_ALREADY_VERIFIED_ERROR" } | GeneralErrorResponse>)
    • isEmailVerifiedGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ isVerified: boolean; newSession?: SessionContainer; status: "OK" } | GeneralErrorResponse>)
    • verifyEmailPOST: undefined | ((input: { options: APIOptions; session: SessionContainer | undefined; tenantId: string; token: string; userContext: UserContext }) => Promise<{ newSession?: SessionContainer; status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" } | GeneralErrorResponse>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypeEmailVerificationEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • emailDelivery: default<TypeEmailVerificationEmailDeliveryInput>
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    RecipeInterface: { createEmailVerificationToken: any; isEmailVerified: any; revokeEmailVerificationTokens: any; unverifyEmail: any; verifyEmailUsingToken: any }

    Type declaration

    • createEmailVerificationToken:function
      • createEmailVerificationToken(input: { email: string; recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • isEmailVerified:function
      • isEmailVerified(input: { email: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<boolean>
    • revokeEmailVerificationTokens:function
      • revokeEmailVerificationTokens(input: { email: string; recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
    • unverifyEmail:function
      • unverifyEmail(input: { email: string; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK" }>
    • verifyEmailUsingToken:function
      • verifyEmailUsingToken(input: { attemptAccountLinking: boolean; tenantId: string; token: string; userContext: UserContext }): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>
      • Parameters

        • input: { attemptAccountLinking: boolean; tenantId: string; token: string; userContext: UserContext }
          • attemptAccountLinking: boolean
          • tenantId: string
          • token: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

    UserEmailInfo: { email: string; recipeUserId: RecipeUserId }

    Type declaration

    Variables

    EmailVerificationClaim: EmailVerificationClaimClass = ...
    Error: typeof default = Wrapper.Error

    Functions

    • createEmailVerificationLink(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ link: string; status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • createEmailVerificationToken(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK"; token: string } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • init(config: TypeInput): RecipeListFunction
    • isEmailVerified(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<boolean>
    • revokeEmailVerificationTokens(tenantId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • sendEmail(input: TypeEmailVerificationEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendEmailVerificationEmail(tenantId: string, userId: string, recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: "OK" } | { status: "EMAIL_ALREADY_VERIFIED_ERROR" }>
    • unverifyEmail(recipeUserId: RecipeUserId, email?: string, userContext?: Record<string, any>): Promise<{ status: string }>
    • verifyEmailUsingToken(tenantId: string, token: string, attemptAccountLinking?: boolean, userContext?: Record<string, any>): Promise<{ status: "OK"; user: UserEmailInfo } | { status: "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_jwt.html b/docs/modules/recipe_jwt.html index 8451f9156..b8195e3ab 100644 --- a/docs/modules/recipe_jwt.html +++ b/docs/modules/recipe_jwt.html @@ -1 +1 @@ -recipe/jwt | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/jwt

    Index

    Type Aliases

    APIInterface: { getJWKSGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<{ keys: JsonWebKey[] } | GeneralErrorResponse>) }

    Type declaration

    • getJWKSGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<{ keys: JsonWebKey[] } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    JsonWebKey: { alg: string; e: string; kid: string; kty: string; n: string; use: string }

    Type declaration

    • alg: string
    • e: string
    • kid: string
    • kty: string
    • n: string
    • use: string
    RecipeInterface: { createJWT: any; getJWKS: any }

    Type declaration

    • createJWT:function
      • createJWT(input: { payload?: any; useStaticSigningKey?: boolean; userContext: UserContext; validitySeconds?: number }): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
      • Parameters

        • input: { payload?: any; useStaticSigningKey?: boolean; userContext: UserContext; validitySeconds?: number }
          • Optional payload?: any
          • Optional useStaticSigningKey?: boolean
          • userContext: UserContext
          • Optional validitySeconds?: number

        Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS:function
      • getJWKS(input: { userContext: UserContext }): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>

    Functions

    • createJWT(payload: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/jwt | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/jwt

    Index

    Type Aliases

    APIInterface: { getJWKSGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<{ keys: JsonWebKey[] } | GeneralErrorResponse>) }

    Type declaration

    • getJWKSGET: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<{ keys: JsonWebKey[] } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    JsonWebKey: { alg: string; e: string; kid: string; kty: string; n: string; use: string }

    Type declaration

    • alg: string
    • e: string
    • kid: string
    • kty: string
    • n: string
    • use: string
    RecipeInterface: { createJWT: any; getJWKS: any }

    Type declaration

    • createJWT:function
      • createJWT(input: { payload?: any; useStaticSigningKey?: boolean; userContext: UserContext; validitySeconds?: number }): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
      • Parameters

        • input: { payload?: any; useStaticSigningKey?: boolean; userContext: UserContext; validitySeconds?: number }
          • Optional payload?: any
          • Optional useStaticSigningKey?: boolean
          • userContext: UserContext
          • Optional validitySeconds?: number

        Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS:function
      • getJWKS(input: { userContext: UserContext }): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>

    Functions

    • createJWT(payload: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_multifactorauth.html b/docs/modules/recipe_multifactorauth.html index fba986690..38df48a22 100644 --- a/docs/modules/recipe_multifactorauth.html +++ b/docs/modules/recipe_multifactorauth.html @@ -1 +1 @@ -recipe/multifactorauth | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/multifactorauth

    Index

    Type Aliases

    APIInterface: { resyncSessionAndFetchMFAInfoPUT: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ emails: Record<string, string[] | undefined>; factors: { allowedToSetup: string[]; alreadySetup: string[]; next: string[] }; phoneNumbers: Record<string, string[] | undefined>; status: "OK" } | GeneralErrorResponse>) }

    Type declaration

    • resyncSessionAndFetchMFAInfoPUT: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ emails: Record<string, string[] | undefined>; factors: { allowedToSetup: string[]; alreadySetup: string[]; next: string[] }; phoneNumbers: Record<string, string[] | undefined>; status: "OK" } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; recipeInstance: Recipe; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { addToRequiredSecondaryFactorsForUser: any; assertAllowedToSetupFactorElseThrowInvalidClaimError: any; getFactorsSetupForUser: any; getMFARequirementsForAuth: any; getRequiredSecondaryFactorsForUser: any; markFactorAsCompleteInSession: any; removeFromRequiredSecondaryFactorsForUser: any }

    Type declaration

    • addToRequiredSecondaryFactorsForUser:function
      • addToRequiredSecondaryFactorsForUser(input: { factorId: string; userContext: UserContext; userId: string }): Promise<void>
      • Parameters

        • input: { factorId: string; userContext: UserContext; userId: string }
          • factorId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<void>

    • assertAllowedToSetupFactorElseThrowInvalidClaimError:function
      • assertAllowedToSetupFactorElseThrowInvalidClaimError(input: { factorId: string; factorsSetUpForUser: Promise<string[]>; mfaRequirementsForAuth: Promise<MFARequirementList>; session: SessionContainer; userContext: UserContext }): Promise<void>
      • Parameters

        • input: { factorId: string; factorsSetUpForUser: Promise<string[]>; mfaRequirementsForAuth: Promise<MFARequirementList>; session: SessionContainer; userContext: UserContext }
          • factorId: string
          • factorsSetUpForUser: Promise<string[]>
          • mfaRequirementsForAuth: Promise<MFARequirementList>
          • session: SessionContainer
          • userContext: UserContext

        Returns Promise<void>

    • getFactorsSetupForUser:function
      • getFactorsSetupForUser(input: { user: User; userContext: UserContext }): Promise<string[]>
    • getMFARequirementsForAuth:function
      • getMFARequirementsForAuth(input: { accessTokenPayload: JSONObject; completedFactors: MFAClaimValue["c"]; factorsSetUpForUser: Promise<string[]>; requiredSecondaryFactorsForTenant: Promise<string[]>; requiredSecondaryFactorsForUser: Promise<string[]>; tenantId: string; user: Promise<User>; userContext: UserContext }): MFARequirementList | Promise<MFARequirementList>
      • Parameters

        • input: { accessTokenPayload: JSONObject; completedFactors: MFAClaimValue["c"]; factorsSetUpForUser: Promise<string[]>; requiredSecondaryFactorsForTenant: Promise<string[]>; requiredSecondaryFactorsForUser: Promise<string[]>; tenantId: string; user: Promise<User>; userContext: UserContext }
          • accessTokenPayload: JSONObject
          • completedFactors: MFAClaimValue["c"]
          • factorsSetUpForUser: Promise<string[]>
          • requiredSecondaryFactorsForTenant: Promise<string[]>
          • requiredSecondaryFactorsForUser: Promise<string[]>
          • tenantId: string
          • user: Promise<User>
          • userContext: UserContext

        Returns MFARequirementList | Promise<MFARequirementList>

    • getRequiredSecondaryFactorsForUser:function
      • getRequiredSecondaryFactorsForUser(input: { userContext: UserContext; userId: string }): Promise<string[]>
    • markFactorAsCompleteInSession:function
      • markFactorAsCompleteInSession(input: { factorId: string; session: SessionContainer; userContext: UserContext }): Promise<void>
    • removeFromRequiredSecondaryFactorsForUser:function
      • removeFromRequiredSecondaryFactorsForUser(input: { factorId: string; userContext: UserContext; userId: string }): Promise<void>

    Variables

    FactorIds: { EMAILPASSWORD: string; LINK_EMAIL: string; LINK_PHONE: string; OTP_EMAIL: string; OTP_PHONE: string; THIRDPARTY: string; TOTP: string } = ...

    Type declaration

    • EMAILPASSWORD: string
    • LINK_EMAIL: string
    • LINK_PHONE: string
    • OTP_EMAIL: string
    • OTP_PHONE: string
    • THIRDPARTY: string
    • TOTP: string
    MultiFactorAuthClaim: MultiFactorAuthClaimClass = ...

    Functions

    • addToRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>
    • assertAllowedToSetupFactorElseThrowInvalidClaimError(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • getFactorsSetupForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • getMFARequirementsForAuth(session: SessionContainer, userContext?: Record<string, any>): Promise<MFARequirementList>
    • getRequiredSecondaryFactorsForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • init(config?: TypeInput): RecipeListFunction
    • markFactorAsCompleteInSession(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • removeFromRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/multifactorauth | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/multifactorauth

    Index

    Type Aliases

    APIInterface: { resyncSessionAndFetchMFAInfoPUT: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ emails: Record<string, string[] | undefined>; factors: { allowedToSetup: string[]; alreadySetup: string[]; next: string[] }; phoneNumbers: Record<string, string[] | undefined>; status: "OK" } | GeneralErrorResponse>) }

    Type declaration

    • resyncSessionAndFetchMFAInfoPUT: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ emails: Record<string, string[] | undefined>; factors: { allowedToSetup: string[]; alreadySetup: string[]; next: string[] }; phoneNumbers: Record<string, string[] | undefined>; status: "OK" } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; recipeInstance: Recipe; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { addToRequiredSecondaryFactorsForUser: any; assertAllowedToSetupFactorElseThrowInvalidClaimError: any; getFactorsSetupForUser: any; getMFARequirementsForAuth: any; getRequiredSecondaryFactorsForUser: any; markFactorAsCompleteInSession: any; removeFromRequiredSecondaryFactorsForUser: any }

    Type declaration

    • addToRequiredSecondaryFactorsForUser:function
      • addToRequiredSecondaryFactorsForUser(input: { factorId: string; userContext: UserContext; userId: string }): Promise<void>
      • Parameters

        • input: { factorId: string; userContext: UserContext; userId: string }
          • factorId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<void>

    • assertAllowedToSetupFactorElseThrowInvalidClaimError:function
      • assertAllowedToSetupFactorElseThrowInvalidClaimError(input: { factorId: string; factorsSetUpForUser: Promise<string[]>; mfaRequirementsForAuth: Promise<MFARequirementList>; session: SessionContainer; userContext: UserContext }): Promise<void>
      • Parameters

        • input: { factorId: string; factorsSetUpForUser: Promise<string[]>; mfaRequirementsForAuth: Promise<MFARequirementList>; session: SessionContainer; userContext: UserContext }
          • factorId: string
          • factorsSetUpForUser: Promise<string[]>
          • mfaRequirementsForAuth: Promise<MFARequirementList>
          • session: SessionContainer
          • userContext: UserContext

        Returns Promise<void>

    • getFactorsSetupForUser:function
      • getFactorsSetupForUser(input: { user: User; userContext: UserContext }): Promise<string[]>
    • getMFARequirementsForAuth:function
      • getMFARequirementsForAuth(input: { accessTokenPayload: JSONObject; completedFactors: MFAClaimValue["c"]; factorsSetUpForUser: Promise<string[]>; requiredSecondaryFactorsForTenant: Promise<string[]>; requiredSecondaryFactorsForUser: Promise<string[]>; tenantId: string; user: Promise<User>; userContext: UserContext }): MFARequirementList | Promise<MFARequirementList>
      • Parameters

        • input: { accessTokenPayload: JSONObject; completedFactors: MFAClaimValue["c"]; factorsSetUpForUser: Promise<string[]>; requiredSecondaryFactorsForTenant: Promise<string[]>; requiredSecondaryFactorsForUser: Promise<string[]>; tenantId: string; user: Promise<User>; userContext: UserContext }
          • accessTokenPayload: JSONObject
          • completedFactors: MFAClaimValue["c"]
          • factorsSetUpForUser: Promise<string[]>
          • requiredSecondaryFactorsForTenant: Promise<string[]>
          • requiredSecondaryFactorsForUser: Promise<string[]>
          • tenantId: string
          • user: Promise<User>
          • userContext: UserContext

        Returns MFARequirementList | Promise<MFARequirementList>

    • getRequiredSecondaryFactorsForUser:function
      • getRequiredSecondaryFactorsForUser(input: { userContext: UserContext; userId: string }): Promise<string[]>
    • markFactorAsCompleteInSession:function
      • markFactorAsCompleteInSession(input: { factorId: string; session: SessionContainer; userContext: UserContext }): Promise<void>
    • removeFromRequiredSecondaryFactorsForUser:function
      • removeFromRequiredSecondaryFactorsForUser(input: { factorId: string; userContext: UserContext; userId: string }): Promise<void>

    Variables

    FactorIds: { EMAILPASSWORD: string; LINK_EMAIL: string; LINK_PHONE: string; OTP_EMAIL: string; OTP_PHONE: string; THIRDPARTY: string; TOTP: string } = ...

    Type declaration

    • EMAILPASSWORD: string
    • LINK_EMAIL: string
    • LINK_PHONE: string
    • OTP_EMAIL: string
    • OTP_PHONE: string
    • THIRDPARTY: string
    • TOTP: string
    MultiFactorAuthClaim: MultiFactorAuthClaimClass = ...

    Functions

    • addToRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>
    • assertAllowedToSetupFactorElseThrowInvalidClaimError(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • getFactorsSetupForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • getMFARequirementsForAuth(session: SessionContainer, userContext?: Record<string, any>): Promise<MFARequirementList>
    • getRequiredSecondaryFactorsForUser(userId: string, userContext?: Record<string, any>): Promise<string[]>
    • init(config?: TypeInput): RecipeListFunction
    • markFactorAsCompleteInSession(session: SessionContainer, factorId: string, userContext?: Record<string, any>): Promise<void>
    • removeFromRequiredSecondaryFactorsForUser(userId: string, factorId: string, userContext?: Record<string, any>): Promise<void>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_multitenancy.html b/docs/modules/recipe_multitenancy.html index dc57c68f8..1960f9e27 100644 --- a/docs/modules/recipe_multitenancy.html +++ b/docs/modules/recipe_multitenancy.html @@ -1 +1 @@ -recipe/multitenancy | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/multitenancy

    Index

    Type Aliases

    APIInterface: { loginMethodsGET: any }

    Type declaration

    • loginMethodsGET:function
      • loginMethodsGET(input: { clientType?: string; options: APIOptions; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { emailPassword: { enabled: boolean }; firstFactors: string[]; passwordless: { enabled: boolean }; status: "OK"; thirdParty: { enabled: boolean; providers: { id: string; name?: string }[] } }>
      • Parameters

        • input: { clientType?: string; options: APIOptions; tenantId: string; userContext: UserContext }
          • Optional clientType?: string
          • options: APIOptions
          • tenantId: string
          • userContext: UserContext

        Returns Promise<GeneralErrorResponse | { emailPassword: { enabled: boolean }; firstFactors: string[]; passwordless: { enabled: boolean }; status: "OK"; thirdParty: { enabled: boolean; providers: { id: string; name?: string }[] } }>

    APIOptions: { allAvailableFirstFactors: string[]; config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse; staticFirstFactors: string[] | undefined; staticThirdPartyProviders: ProviderInput[] }

    Type declaration

    • allAvailableFirstFactors: string[]
    • config: TypeNormalisedInput
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    • staticFirstFactors: string[] | undefined
    • staticThirdPartyProviders: ProviderInput[]
    RecipeInterface: { associateUserToTenant: any; createOrUpdateTenant: any; createOrUpdateThirdPartyConfig: any; deleteTenant: any; deleteThirdPartyConfig: any; disassociateUserFromTenant: any; getTenant: any; getTenantId: any; listAllTenants: any }

    Type declaration

    • associateUserToTenant:function
      • associateUserToTenant(input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
      • Parameters

        • input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }
          • recipeUserId: RecipeUserId
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

    • createOrUpdateTenant:function
      • createOrUpdateTenant(input: { config?: { coreConfig?: {}; firstFactors?: string[] | null; requiredSecondaryFactors?: string[] | null }; tenantId: string; userContext: UserContext }): Promise<{ createdNew: boolean; status: "OK" }>
      • Parameters

        • input: { config?: { coreConfig?: {}; firstFactors?: string[] | null; requiredSecondaryFactors?: string[] | null }; tenantId: string; userContext: UserContext }
          • Optional config?: { coreConfig?: {}; firstFactors?: string[] | null; requiredSecondaryFactors?: string[] | null }
            • Optional coreConfig?: {}
              • [key: string]: any
            • Optional firstFactors?: string[] | null
            • Optional requiredSecondaryFactors?: string[] | null
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ createdNew: boolean; status: "OK" }>

    • createOrUpdateThirdPartyConfig:function
      • createOrUpdateThirdPartyConfig(input: { config: ProviderConfig; skipValidation?: boolean; tenantId: string; userContext: UserContext }): Promise<{ createdNew: boolean; status: "OK" }>
      • Parameters

        • input: { config: ProviderConfig; skipValidation?: boolean; tenantId: string; userContext: UserContext }
          • config: ProviderConfig
          • Optional skipValidation?: boolean
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ createdNew: boolean; status: "OK" }>

    • deleteTenant:function
      • deleteTenant(input: { tenantId: string; userContext: UserContext }): Promise<{ didExist: boolean; status: "OK" }>
      • Parameters

        • input: { tenantId: string; userContext: UserContext }
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ didExist: boolean; status: "OK" }>

    • deleteThirdPartyConfig:function
      • deleteThirdPartyConfig(input: { tenantId: string; thirdPartyId: string; userContext: UserContext }): Promise<{ didConfigExist: boolean; status: "OK" }>
      • Parameters

        • input: { tenantId: string; thirdPartyId: string; userContext: UserContext }
          • tenantId: string
          • thirdPartyId: string
          • userContext: UserContext

        Returns Promise<{ didConfigExist: boolean; status: "OK" }>

    • disassociateUserFromTenant:function
      • disassociateUserFromTenant(input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; wasAssociated: boolean }>
    • getTenant:function
      • getTenant(input: { tenantId: string; userContext: UserContext }): Promise<undefined | { status: "OK" } & TenantConfig>
      • Parameters

        • input: { tenantId: string; userContext: UserContext }
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | { status: "OK" } & TenantConfig>

    • getTenantId:function
      • getTenantId(input: { tenantIdFromFrontend: string; userContext: UserContext }): Promise<string>
      • Parameters

        • input: { tenantIdFromFrontend: string; userContext: UserContext }
          • tenantIdFromFrontend: string
          • userContext: UserContext

        Returns Promise<string>

    • listAllTenants:function
      • listAllTenants(input: { userContext: UserContext }): Promise<{ status: "OK"; tenants: (TenantConfig & { tenantId: string })[] }>
      • Parameters

        • input: { userContext: UserContext }
          • userContext: UserContext

        Returns Promise<{ status: "OK"; tenants: (TenantConfig & { tenantId: string })[] }>

    Variables

    AllowedDomainsClaim: AllowedDomainsClaimClass = ...

    Functions

    • associateUserToTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
    • Parameters

      • tenantId: string
      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

    • createOrUpdateTenant(tenantId: string, config?: { coreConfig?: {}; firstFactors?: null | string[]; requiredSecondaryFactors?: null | string[] }, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • Optional config: { coreConfig?: {}; firstFactors?: null | string[]; requiredSecondaryFactors?: null | string[] }
        • Optional coreConfig?: {}
          • [key: string]: any
        • Optional firstFactors?: null | string[]
        • Optional requiredSecondaryFactors?: null | string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • createOrUpdateThirdPartyConfig(tenantId: string, config: ProviderConfig, skipValidation?: boolean, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • config: ProviderConfig
      • Optional skipValidation: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • deleteTenant(tenantId: string, userContext?: Record<string, any>): Promise<{ didExist: boolean; status: "OK" }>
    • deleteThirdPartyConfig(tenantId: string, thirdPartyId: string, userContext?: Record<string, any>): Promise<{ didConfigExist: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didConfigExist: boolean; status: "OK" }>

    • disassociateUserFromTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAssociated: boolean }>
    • getTenant(tenantId: string, userContext?: Record<string, any>): Promise<undefined | { status: "OK" } & TenantConfig>
    • init(config?: TypeInput): RecipeListFunction
    • listAllTenants(userContext?: Record<string, any>): Promise<{ status: "OK"; tenants: ({ tenantId: string } & TenantConfig)[] }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/multitenancy | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/multitenancy

    Index

    Type Aliases

    APIInterface: { loginMethodsGET: any }

    Type declaration

    • loginMethodsGET:function
      • loginMethodsGET(input: { clientType?: string; options: APIOptions; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { emailPassword: { enabled: boolean }; firstFactors: string[]; passwordless: { enabled: boolean }; status: "OK"; thirdParty: { enabled: boolean; providers: { id: string; name?: string }[] } }>
      • Parameters

        • input: { clientType?: string; options: APIOptions; tenantId: string; userContext: UserContext }
          • Optional clientType?: string
          • options: APIOptions
          • tenantId: string
          • userContext: UserContext

        Returns Promise<GeneralErrorResponse | { emailPassword: { enabled: boolean }; firstFactors: string[]; passwordless: { enabled: boolean }; status: "OK"; thirdParty: { enabled: boolean; providers: { id: string; name?: string }[] } }>

    APIOptions: { allAvailableFirstFactors: string[]; config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse; staticFirstFactors: string[] | undefined; staticThirdPartyProviders: ProviderInput[] }

    Type declaration

    • allAvailableFirstFactors: string[]
    • config: TypeNormalisedInput
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    • staticFirstFactors: string[] | undefined
    • staticThirdPartyProviders: ProviderInput[]
    RecipeInterface: { associateUserToTenant: any; createOrUpdateTenant: any; createOrUpdateThirdPartyConfig: any; deleteTenant: any; deleteThirdPartyConfig: any; disassociateUserFromTenant: any; getTenant: any; getTenantId: any; listAllTenants: any }

    Type declaration

    • associateUserToTenant:function
      • associateUserToTenant(input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
      • Parameters

        • input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }
          • recipeUserId: RecipeUserId
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

    • createOrUpdateTenant:function
      • createOrUpdateTenant(input: { config?: { coreConfig?: {}; firstFactors?: string[] | null; requiredSecondaryFactors?: string[] | null }; tenantId: string; userContext: UserContext }): Promise<{ createdNew: boolean; status: "OK" }>
      • Parameters

        • input: { config?: { coreConfig?: {}; firstFactors?: string[] | null; requiredSecondaryFactors?: string[] | null }; tenantId: string; userContext: UserContext }
          • Optional config?: { coreConfig?: {}; firstFactors?: string[] | null; requiredSecondaryFactors?: string[] | null }
            • Optional coreConfig?: {}
              • [key: string]: any
            • Optional firstFactors?: string[] | null
            • Optional requiredSecondaryFactors?: string[] | null
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ createdNew: boolean; status: "OK" }>

    • createOrUpdateThirdPartyConfig:function
      • createOrUpdateThirdPartyConfig(input: { config: ProviderConfig; skipValidation?: boolean; tenantId: string; userContext: UserContext }): Promise<{ createdNew: boolean; status: "OK" }>
      • Parameters

        • input: { config: ProviderConfig; skipValidation?: boolean; tenantId: string; userContext: UserContext }
          • config: ProviderConfig
          • Optional skipValidation?: boolean
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ createdNew: boolean; status: "OK" }>

    • deleteTenant:function
      • deleteTenant(input: { tenantId: string; userContext: UserContext }): Promise<{ didExist: boolean; status: "OK" }>
      • Parameters

        • input: { tenantId: string; userContext: UserContext }
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ didExist: boolean; status: "OK" }>

    • deleteThirdPartyConfig:function
      • deleteThirdPartyConfig(input: { tenantId: string; thirdPartyId: string; userContext: UserContext }): Promise<{ didConfigExist: boolean; status: "OK" }>
      • Parameters

        • input: { tenantId: string; thirdPartyId: string; userContext: UserContext }
          • tenantId: string
          • thirdPartyId: string
          • userContext: UserContext

        Returns Promise<{ didConfigExist: boolean; status: "OK" }>

    • disassociateUserFromTenant:function
      • disassociateUserFromTenant(input: { recipeUserId: RecipeUserId; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; wasAssociated: boolean }>
    • getTenant:function
      • getTenant(input: { tenantId: string; userContext: UserContext }): Promise<undefined | { status: "OK" } & TenantConfig>
      • Parameters

        • input: { tenantId: string; userContext: UserContext }
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | { status: "OK" } & TenantConfig>

    • getTenantId:function
      • getTenantId(input: { tenantIdFromFrontend: string; userContext: UserContext }): Promise<string>
      • Parameters

        • input: { tenantIdFromFrontend: string; userContext: UserContext }
          • tenantIdFromFrontend: string
          • userContext: UserContext

        Returns Promise<string>

    • listAllTenants:function
      • listAllTenants(input: { userContext: UserContext }): Promise<{ status: "OK"; tenants: (TenantConfig & { tenantId: string })[] }>
      • Parameters

        • input: { userContext: UserContext }
          • userContext: UserContext

        Returns Promise<{ status: "OK"; tenants: (TenantConfig & { tenantId: string })[] }>

    Variables

    AllowedDomainsClaim: AllowedDomainsClaimClass = ...

    Functions

    • associateUserToTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>
    • Parameters

      • tenantId: string
      • recipeUserId: RecipeUserId
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyAssociated: boolean } | { status: "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" | "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "ASSOCIATION_NOT_ALLOWED_ERROR" }>

    • createOrUpdateTenant(tenantId: string, config?: { coreConfig?: {}; firstFactors?: null | string[]; requiredSecondaryFactors?: null | string[] }, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • Optional config: { coreConfig?: {}; firstFactors?: null | string[]; requiredSecondaryFactors?: null | string[] }
        • Optional coreConfig?: {}
          • [key: string]: any
        • Optional firstFactors?: null | string[]
        • Optional requiredSecondaryFactors?: null | string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • createOrUpdateThirdPartyConfig(tenantId: string, config: ProviderConfig, skipValidation?: boolean, userContext?: Record<string, any>): Promise<{ createdNew: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • config: ProviderConfig
      • Optional skipValidation: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNew: boolean; status: "OK" }>

    • deleteTenant(tenantId: string, userContext?: Record<string, any>): Promise<{ didExist: boolean; status: "OK" }>
    • deleteThirdPartyConfig(tenantId: string, thirdPartyId: string, userContext?: Record<string, any>): Promise<{ didConfigExist: boolean; status: "OK" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didConfigExist: boolean; status: "OK" }>

    • disassociateUserFromTenant(tenantId: string, recipeUserId: RecipeUserId, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAssociated: boolean }>
    • getTenant(tenantId: string, userContext?: Record<string, any>): Promise<undefined | { status: "OK" } & TenantConfig>
    • init(config?: TypeInput): RecipeListFunction
    • listAllTenants(userContext?: Record<string, any>): Promise<{ status: "OK"; tenants: ({ tenantId: string } & TenantConfig)[] }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_openid.html b/docs/modules/recipe_openid.html index 29bd8beb1..7ea37707f 100644 --- a/docs/modules/recipe_openid.html +++ b/docs/modules/recipe_openid.html @@ -1 +1 @@ -recipe/openid | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/openid

    Index

    Functions

    • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • Optional payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
    • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
    • Parameters

      • Optional userContext: Record<string, any>

      Returns Promise<{ issuer: string; jwks_uri: string; status: "OK" }>

    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/openid | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/openid

    Index

    Functions

    • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • Optional payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[]; validityInSeconds?: number }>
    • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
    • Parameters

      • Optional userContext: Record<string, any>

      Returns Promise<{ issuer: string; jwks_uri: string; status: "OK" }>

    • init(config?: TypeInput): RecipeListFunction

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_passwordless.html b/docs/modules/recipe_passwordless.html index 5f0d467ee..8c5157ab9 100644 --- a/docs/modules/recipe_passwordless.html +++ b/docs/modules/recipe_passwordless.html @@ -1 +1 @@ -recipe/passwordless | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/passwordless

    Index

    Type Aliases

    APIInterface: { consumeCodePOST?: any; createCodePOST?: any; emailExistsGET?: any; phoneNumberExistsGET?: any; resendCodePOST?: any }

    Type declaration

    • consumeCodePOST?:function
      • consumeCodePOST(input: { deviceId: string; preAuthSessionId: string; userInputCode: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { linkCode: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { createdNewRecipeUser: boolean; session: SessionContainer; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; userInputCode: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { linkCode: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<GeneralErrorResponse | { createdNewRecipeUser: boolean; session: SessionContainer; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • createCodePOST?:function
      • createCodePOST(input: { email: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { phoneNumber: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { deviceId: string; flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; preAuthSessionId: string; status: "OK" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
      • Parameters

        • input: { email: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { phoneNumber: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<GeneralErrorResponse | { deviceId: string; flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; preAuthSessionId: string; status: "OK" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • emailExistsGET?:function
      • emailExistsGET(input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>
    • phoneNumberExistsGET?:function
      • phoneNumberExistsGET(input: { options: APIOptions; phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>
      • Parameters

        • input: { options: APIOptions; phoneNumber: string; tenantId: string; userContext: UserContext }
          • options: APIOptions
          • phoneNumber: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>

    • resendCodePOST?:function
      • resendCodePOST(input: { deviceId: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { status: "RESTART_FLOW_ERROR" | "OK" }>
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypePasswordlessEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse; smsDelivery: default<TypePasswordlessSmsDeliveryInput> }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • emailDelivery: default<TypePasswordlessEmailDeliveryInput>
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    • smsDelivery: default<TypePasswordlessSmsDeliveryInput>
    RecipeInterface: { checkCode: any; consumeCode: any; createCode: any; createNewCodeForDevice: any; listCodesByDeviceId: any; listCodesByEmail: any; listCodesByPhoneNumber: any; listCodesByPreAuthSessionId: any; revokeAllCodes: any; revokeCode: any; updateUser: any }

    Type declaration

    • checkCode:function
      • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • consumeCode:function
      • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • createCode:function
      • createCode(input: { email: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string } & { phoneNumber: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
      • Parameters

        • input: { email: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string } & { phoneNumber: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string }

        Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

    • createNewCodeForDevice:function
      • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext: UserContext; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
      • Parameters

        • input: { deviceId: string; tenantId: string; userContext: UserContext; userInputCode?: string }
          • deviceId: string
          • tenantId: string
          • userContext: UserContext
          • Optional userInputCode?: string

        Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

    • listCodesByDeviceId:function
      • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext: UserContext }): Promise<undefined | DeviceType>
      • Parameters

        • input: { deviceId: string; tenantId: string; userContext: UserContext }
          • deviceId: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | DeviceType>

    • listCodesByEmail:function
      • listCodesByEmail(input: { email: string; tenantId: string; userContext: UserContext }): Promise<DeviceType[]>
      • Parameters

        • input: { email: string; tenantId: string; userContext: UserContext }
          • email: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<DeviceType[]>

    • listCodesByPhoneNumber:function
      • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<DeviceType[]>
      • Parameters

        • input: { phoneNumber: string; tenantId: string; userContext: UserContext }
          • phoneNumber: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<DeviceType[]>

    • listCodesByPreAuthSessionId:function
      • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<undefined | DeviceType>
      • Parameters

        • input: { preAuthSessionId: string; tenantId: string; userContext: UserContext }
          • preAuthSessionId: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | DeviceType>

    • revokeAllCodes:function
      • revokeAllCodes(input: { email: string; tenantId: string; userContext: UserContext } | { phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
      • Parameters

        • input: { email: string; tenantId: string; userContext: UserContext } | { phoneNumber: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ status: "OK" }>

    • revokeCode:function
      • revokeCode(input: { codeId: string; tenantId: string; userContext: UserContext } | { preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
      • Parameters

        • input: { codeId: string; tenantId: string; userContext: UserContext } | { preAuthSessionId: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ status: "OK" }>

    • updateUser:function
      • updateUser(input: { email?: string | null; phoneNumber?: string | null; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
      • Parameters

        • input: { email?: string | null; phoneNumber?: string | null; recipeUserId: RecipeUserId; userContext: UserContext }
          • Optional email?: string | null
          • Optional phoneNumber?: string | null
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

    Variables

    Error: typeof default = Wrapper.Error

    Functions

    • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • createCode(input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
    • Parameters

      • input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

    • createMagicLink(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<string>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<string>

    • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>
        • Optional userInputCode?: string

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

    • init(config: TypeInput): RecipeListFunction
    • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • listCodesByEmail(input: { email: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> }
        • email: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }
        • phoneNumber: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }
        • preAuthSessionId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • revokeAllCodes(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • revokeCode(input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • sendEmail(input: TypePasswordlessEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendSms(input: TypePasswordlessSmsDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • signInUp(input: { email: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: string; user: User }>
    • updateUser(input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
    • Parameters

      • input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }
        • Optional email?: null | string
        • Optional phoneNumber?: null | string
        • recipeUserId: RecipeUserId
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/passwordless | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/passwordless

    Index

    Type Aliases

    APIInterface: { consumeCodePOST?: any; createCodePOST?: any; emailExistsGET?: any; phoneNumberExistsGET?: any; resendCodePOST?: any }

    Type declaration

    • consumeCodePOST?:function
      • consumeCodePOST(input: { deviceId: string; preAuthSessionId: string; userInputCode: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { linkCode: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { createdNewRecipeUser: boolean; session: SessionContainer; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; userInputCode: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { linkCode: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<GeneralErrorResponse | { createdNewRecipeUser: boolean; session: SessionContainer; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • createCodePOST?:function
      • createCodePOST(input: { email: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { phoneNumber: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { deviceId: string; flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; preAuthSessionId: string; status: "OK" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
      • Parameters

        • input: { email: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & { phoneNumber: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<GeneralErrorResponse | { deviceId: string; flowType: "USER_INPUT_CODE" | "MAGIC_LINK" | "USER_INPUT_CODE_AND_MAGIC_LINK"; preAuthSessionId: string; status: "OK" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • emailExistsGET?:function
      • emailExistsGET(input: { email: string; options: APIOptions; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>
    • phoneNumberExistsGET?:function
      • phoneNumberExistsGET(input: { options: APIOptions; phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>
      • Parameters

        • input: { options: APIOptions; phoneNumber: string; tenantId: string; userContext: UserContext }
          • options: APIOptions
          • phoneNumber: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<GeneralErrorResponse | { exists: boolean; status: "OK" }>

    • resendCodePOST?:function
      • resendCodePOST(input: { deviceId: string; preAuthSessionId: string } & { options: APIOptions; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<GeneralErrorResponse | { status: "RESTART_FLOW_ERROR" | "OK" }>
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; emailDelivery: default<TypePasswordlessEmailDeliveryInput>; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse; smsDelivery: default<TypePasswordlessSmsDeliveryInput> }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • emailDelivery: default<TypePasswordlessEmailDeliveryInput>
    • isInServerlessEnv: boolean
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    • smsDelivery: default<TypePasswordlessSmsDeliveryInput>
    RecipeInterface: { checkCode: any; consumeCode: any; createCode: any; createNewCodeForDevice: any; listCodesByDeviceId: any; listCodesByEmail: any; listCodesByPhoneNumber: any; listCodesByPreAuthSessionId: any; revokeAllCodes: any; revokeCode: any; updateUser: any }

    Type declaration

    • checkCode:function
      • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • consumeCode:function
      • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer | undefined; tenantId: string; userContext: UserContext }

        Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • createCode:function
      • createCode(input: { email: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string } & { phoneNumber: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
      • Parameters

        • input: { email: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string } & { phoneNumber: string } & { session: SessionContainer | undefined; tenantId: string; userContext: UserContext; userInputCode?: string }

        Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

    • createNewCodeForDevice:function
      • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext: UserContext; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
      • Parameters

        • input: { deviceId: string; tenantId: string; userContext: UserContext; userInputCode?: string }
          • deviceId: string
          • tenantId: string
          • userContext: UserContext
          • Optional userInputCode?: string

        Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

    • listCodesByDeviceId:function
      • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext: UserContext }): Promise<undefined | DeviceType>
      • Parameters

        • input: { deviceId: string; tenantId: string; userContext: UserContext }
          • deviceId: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | DeviceType>

    • listCodesByEmail:function
      • listCodesByEmail(input: { email: string; tenantId: string; userContext: UserContext }): Promise<DeviceType[]>
      • Parameters

        • input: { email: string; tenantId: string; userContext: UserContext }
          • email: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<DeviceType[]>

    • listCodesByPhoneNumber:function
      • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<DeviceType[]>
      • Parameters

        • input: { phoneNumber: string; tenantId: string; userContext: UserContext }
          • phoneNumber: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<DeviceType[]>

    • listCodesByPreAuthSessionId:function
      • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<undefined | DeviceType>
      • Parameters

        • input: { preAuthSessionId: string; tenantId: string; userContext: UserContext }
          • preAuthSessionId: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<undefined | DeviceType>

    • revokeAllCodes:function
      • revokeAllCodes(input: { email: string; tenantId: string; userContext: UserContext } | { phoneNumber: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
      • Parameters

        • input: { email: string; tenantId: string; userContext: UserContext } | { phoneNumber: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ status: "OK" }>

    • revokeCode:function
      • revokeCode(input: { codeId: string; tenantId: string; userContext: UserContext } | { preAuthSessionId: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK" }>
      • Parameters

        • input: { codeId: string; tenantId: string; userContext: UserContext } | { preAuthSessionId: string; tenantId: string; userContext: UserContext }

        Returns Promise<{ status: "OK" }>

    • updateUser:function
      • updateUser(input: { email?: string | null; phoneNumber?: string | null; recipeUserId: RecipeUserId; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
      • Parameters

        • input: { email?: string | null; phoneNumber?: string | null; recipeUserId: RecipeUserId; userContext: UserContext }
          • Optional email?: string | null
          • Optional phoneNumber?: string | null
          • recipeUserId: RecipeUserId
          • userContext: UserContext

        Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

    Variables

    Error: typeof default = Wrapper.Error

    Functions

    • checkCode(input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; status: "OK" } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>
    • consumeCode(input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session?: undefined; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" }>

    • Parameters

      • input: { deviceId: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode: string } | { linkCode: string; preAuthSessionId: string; session: SessionContainer; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ consumedDevice: { email?: string; failedCodeInputAttemptCount: number; phoneNumber?: string; preAuthSessionId: string }; createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { failedCodeInputAttemptCount: number; maximumCodeInputAttempts: number; status: "INCORRECT_USER_INPUT_CODE_ERROR" | "EXPIRED_USER_INPUT_CODE_ERROR" } | { status: "RESTART_FLOW_ERROR" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • createCode(input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>
    • Parameters

      • input: { email: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string } & { phoneNumber: string } & { session?: SessionContainer; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string }>

    • createMagicLink(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<string>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<string>

    • createNewCodeForDevice(input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }): Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any>; userInputCode?: string }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>
        • Optional userInputCode?: string

      Returns Promise<{ codeId: string; codeLifetime: number; deviceId: string; linkCode: string; preAuthSessionId: string; status: "OK"; timeCreated: number; userInputCode: string } | { status: "RESTART_FLOW_ERROR" | "USER_INPUT_CODE_ALREADY_USED_ERROR" }>

    • init(config: TypeInput): RecipeListFunction
    • listCodesByDeviceId(input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { deviceId: string; tenantId: string; userContext?: Record<string, any> }
        • deviceId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • listCodesByEmail(input: { email: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> }
        • email: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPhoneNumber(input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<DeviceType[]>
    • Parameters

      • input: { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }
        • phoneNumber: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<DeviceType[]>

    • listCodesByPreAuthSessionId(input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<undefined | DeviceType>
    • Parameters

      • input: { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }
        • preAuthSessionId: string
        • tenantId: string
        • Optional userContext?: Record<string, any>

      Returns Promise<undefined | DeviceType>

    • revokeAllCodes(input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { email: string; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • revokeCode(input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }): Promise<{ status: "OK" }>
    • Parameters

      • input: { codeId: string; tenantId: string; userContext?: Record<string, any> } | { preAuthSessionId: string; tenantId: string; userContext?: Record<string, any> }

      Returns Promise<{ status: "OK" }>

    • sendEmail(input: TypePasswordlessEmailDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • sendSms(input: TypePasswordlessSmsDeliveryInput & { userContext?: Record<string, any> }): Promise<void>
    • signInUp(input: { email: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> } | { phoneNumber: string; session?: SessionContainer; tenantId: string; userContext?: Record<string, any> }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: string; user: User }>
    • updateUser(input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }): Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>
    • Parameters

      • input: { email?: null | string; phoneNumber?: null | string; recipeUserId: RecipeUserId; userContext?: Record<string, any> }
        • Optional email?: null | string
        • Optional phoneNumber?: null | string
        • recipeUserId: RecipeUserId
        • Optional userContext?: Record<string, any>

      Returns Promise<{ status: "OK" | "EMAIL_ALREADY_EXISTS_ERROR" | "UNKNOWN_USER_ID_ERROR" | "PHONE_NUMBER_ALREADY_EXISTS_ERROR" } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" | "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_session.html b/docs/modules/recipe_session.html index 9ffe5d8a8..57ccc0340 100644 --- a/docs/modules/recipe_session.html +++ b/docs/modules/recipe_session.html @@ -1,13 +1,13 @@ -recipe/session | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/session

    Index

    Type Aliases

    APIInterface: { refreshPOST: undefined | ((input: { options: APIOptions; userContext: UserContext }) => Promise<SessionContainer>); signOutPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ status: "OK" } | GeneralErrorResponse>); verifySession: any }

    Type declaration

  • mergeIntoAccessTokenPayload:function
    • mergeIntoAccessTokenPayload(input: { accessTokenPayloadUpdate: JSONObject; sessionHandle: string; userContext: UserContext }): Promise<boolean>
  • refreshSession:function
    • refreshSession(input: { antiCsrfToken?: string; disableAntiCsrf: boolean; refreshToken: string; userContext: UserContext }): Promise<SessionContainer>
    • Parameters

      • input: { antiCsrfToken?: string; disableAntiCsrf: boolean; refreshToken: string; userContext: UserContext }
        • Optional antiCsrfToken?: string
        • disableAntiCsrf: boolean
        • refreshToken: string
        • userContext: UserContext

      Returns Promise<SessionContainer>

  • regenerateAccessToken:function
    • regenerateAccessToken(input: { accessToken: string; newAccessTokenPayload?: any; userContext: UserContext }): Promise<undefined | { accessToken?: { createdTime: number; expiry: number; token: string }; session: { handle: string; recipeUserId: RecipeUserId; tenantId: string; userDataInJWT: any; userId: string }; status: "OK" }>
    • Parameters

      • input: { accessToken: string; newAccessTokenPayload?: any; userContext: UserContext }
        • accessToken: string
        • Optional newAccessTokenPayload?: any
        • userContext: UserContext

      Returns Promise<undefined | { accessToken?: { createdTime: number; expiry: number; token: string }; session: { handle: string; recipeUserId: RecipeUserId; tenantId: string; userDataInJWT: any; userId: string }; status: "OK" }>

      Returns false if the sessionHandle does not exist

      +
  • removeClaim:function
    • removeClaim(input: { claim: SessionClaim<any>; sessionHandle: string; userContext: UserContext }): Promise<boolean>
    • Parameters

      • input: { claim: SessionClaim<any>; sessionHandle: string; userContext: UserContext }
        • claim: SessionClaim<any>
        • sessionHandle: string
        • userContext: UserContext

      Returns Promise<boolean>

  • revokeAllSessionsForUser:function
    • revokeAllSessionsForUser(input: { revokeAcrossAllTenants?: boolean; revokeSessionsForLinkedAccounts: boolean; tenantId: string; userContext: UserContext; userId: string }): Promise<string[]>
    • Parameters

      • input: { revokeAcrossAllTenants?: boolean; revokeSessionsForLinkedAccounts: boolean; tenantId: string; userContext: UserContext; userId: string }
        • Optional revokeAcrossAllTenants?: boolean
        • revokeSessionsForLinkedAccounts: boolean
        • tenantId: string
        • userContext: UserContext
        • userId: string

      Returns Promise<string[]>

  • revokeMultipleSessions:function
    • revokeMultipleSessions(input: { sessionHandles: string[]; userContext: UserContext }): Promise<string[]>
    • Parameters

      • input: { sessionHandles: string[]; userContext: UserContext }
        • sessionHandles: string[]
        • userContext: UserContext

      Returns Promise<string[]>

  • revokeSession:function
    • revokeSession(input: { sessionHandle: string; userContext: UserContext }): Promise<boolean>
    • Parameters

      • input: { sessionHandle: string; userContext: UserContext }
        • sessionHandle: string
        • userContext: UserContext

      Returns Promise<boolean>

  • setClaimValue:function
    • setClaimValue<T>(input: { claim: SessionClaim<T>; sessionHandle: string; userContext: UserContext; value: T }): Promise<boolean>
    • Type Parameters

      • T

      Parameters

      • input: { claim: SessionClaim<T>; sessionHandle: string; userContext: UserContext; value: T }
        • claim: SessionClaim<T>
        • sessionHandle: string
        • userContext: UserContext
        • value: T

      Returns Promise<boolean>

  • updateSessionDataInDatabase:function
    • updateSessionDataInDatabase(input: { newSessionData: any; sessionHandle: string; userContext: UserContext }): Promise<boolean>
    • Parameters

      • input: { newSessionData: any; sessionHandle: string; userContext: UserContext }
        • newSessionData: any
        • sessionHandle: string
        • userContext: UserContext

      Returns Promise<boolean>

  • validateClaims:function
    • validateClaims(input: { accessTokenPayload: any; claimValidators: SessionClaimValidator[]; recipeUserId: RecipeUserId; userContext: UserContext; userId: string }): Promise<{ accessTokenPayloadUpdate?: any; invalidClaims: ClaimValidationError[] }>
  • SessionClaimValidator: ({ claim: SessionClaim<any>; shouldRefetch: any } | {}) & { id: string; validate: any }
    SessionInformation: { customClaimsInAccessTokenPayload: any; expiry: number; recipeUserId: RecipeUserId; sessionDataInDatabase: any; sessionHandle: string; tenantId: string; timeCreated: number; userId: string }

    Type declaration

    • customClaimsInAccessTokenPayload: any
    • expiry: number
    • recipeUserId: RecipeUserId
    • sessionDataInDatabase: any
    • sessionHandle: string
    • tenantId: string
    • timeCreated: number
    • userId: string

    Variables

    Error: typeof default = SessionWrapper.Error

    Functions

    • createJWT(payload?: any, validitySeconds?: number, useStaticSigningKey?: boolean, userContext?: Record<string, any>): Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>
    • Parameters

      • Optional payload: any
      • Optional validitySeconds: number
      • Optional useStaticSigningKey: boolean
      • Optional userContext: Record<string, any>

      Returns Promise<{ jwt: string; status: "OK" } | { status: "UNSUPPORTED_ALGORITHM_ERROR" }>

    • createNewSession(req: any, res: any, tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, userContext?: Record<string, any>): Promise<SessionContainer>
    • createNewSessionWithoutRequestResponse(tenantId: string, recipeUserId: RecipeUserId, accessTokenPayload?: any, sessionDataInDatabase?: any, disableAntiCsrf?: boolean, userContext?: Record<string, any>): Promise<SessionContainer>
    • fetchAndSetClaim(sessionHandle: string, claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<boolean>
    • getAllSessionHandlesForUser(userId: string, fetchSessionsForAllLinkedAccounts?: boolean, tenantId?: string, userContext?: Record<string, any>): Promise<string[]>
    • Parameters

      • userId: string
      • fetchSessionsForAllLinkedAccounts: boolean = true
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<string[]>

    • getClaimValue<T>(sessionHandle: string, claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<{ status: "SESSION_DOES_NOT_EXIST_ERROR" } | { status: "OK"; value: undefined | T }>
    • Type Parameters

      • T

      Parameters

      • sessionHandle: string
      • claim: SessionClaim<T>
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "SESSION_DOES_NOT_EXIST_ERROR" } | { status: "OK"; value: undefined | T }>

    • getJWKS(userContext?: Record<string, any>): Promise<{ keys: JsonWebKey[] }>
    • getOpenIdDiscoveryConfiguration(userContext?: Record<string, any>): Promise<{ issuer: string; jwks_uri: string; status: "OK" }>
    • getSessionInformation(sessionHandle: string, userContext?: Record<string, any>): Promise<undefined | SessionInformation>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string): Promise<SessionContainer>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { sessionRequired?: true }, userContext?: Record<string, any>): Promise<SessionContainer>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions & { sessionRequired: false }, userContext?: Record<string, any>): Promise<undefined | SessionContainer>
    • getSessionWithoutRequestResponse(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions, userContext?: Record<string, any>): Promise<undefined | SessionContainer>
    • init(config?: TypeInput): RecipeListFunction
    • mergeIntoAccessTokenPayload(sessionHandle: string, accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<boolean>
    • refreshSession(req: any, res: any, userContext?: Record<string, any>): Promise<SessionContainer>
    • refreshSessionWithoutRequestResponse(refreshToken: string, disableAntiCsrf?: boolean, antiCsrfToken?: string, userContext?: Record<string, any>): Promise<SessionContainer>
    • removeClaim(sessionHandle: string, claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<boolean>
    • revokeAllSessionsForUser(userId: string, revokeSessionsForLinkedAccounts?: boolean, tenantId?: string, userContext?: Record<string, any>): Promise<string[]>
    • Parameters

      • userId: string
      • revokeSessionsForLinkedAccounts: boolean = true
      • Optional tenantId: string
      • Optional userContext: Record<string, any>

      Returns Promise<string[]>

    • revokeMultipleSessions(sessionHandles: string[], userContext?: Record<string, any>): Promise<string[]>
    • revokeSession(sessionHandle: string, userContext?: Record<string, any>): Promise<boolean>
    • setClaimValue<T>(sessionHandle: string, claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<boolean>
    • Type Parameters

      • T

      Parameters

      • sessionHandle: string
      • claim: SessionClaim<T>
      • value: T
      • Optional userContext: Record<string, any>

      Returns Promise<boolean>

    • updateSessionDataInDatabase(sessionHandle: string, newSessionData: any, userContext?: Record<string, any>): Promise<boolean>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_thirdparty.html b/docs/modules/recipe_thirdparty.html index b8ff29461..b49958d51 100644 --- a/docs/modules/recipe_thirdparty.html +++ b/docs/modules/recipe_thirdparty.html @@ -1 +1 @@ -recipe/thirdparty | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/thirdparty

    Index

    Type Aliases

    APIInterface: { appleRedirectHandlerPOST: undefined | ((input: { formPostInfoFromProvider: {}; options: APIOptions; userContext: UserContext }) => Promise<void>); authorisationUrlGET: undefined | ((input: { options: APIOptions; provider: TypeProvider; redirectURIOnProviderDashboard: string; tenantId: string; userContext: UserContext }) => Promise<{ pkceCodeVerifier?: string; status: "OK"; urlWithQueryParams: string } | GeneralErrorResponse>); signInUpPOST: undefined | ((input: { options: APIOptions; provider: TypeProvider; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & ({ redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any } } | { oAuthTokens: {} })) => Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer; status: "OK"; user: User } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | GeneralErrorResponse>) }

    Type declaration

    • appleRedirectHandlerPOST: undefined | ((input: { formPostInfoFromProvider: {}; options: APIOptions; userContext: UserContext }) => Promise<void>)
    • authorisationUrlGET: undefined | ((input: { options: APIOptions; provider: TypeProvider; redirectURIOnProviderDashboard: string; tenantId: string; userContext: UserContext }) => Promise<{ pkceCodeVerifier?: string; status: "OK"; urlWithQueryParams: string } | GeneralErrorResponse>)
    • signInUpPOST: undefined | ((input: { options: APIOptions; provider: TypeProvider; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & ({ redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any } } | { oAuthTokens: {} })) => Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer; status: "OK"; user: User } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | GeneralErrorResponse>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; isInServerlessEnv: boolean; providers: ProviderInput[]; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • isInServerlessEnv: boolean
    • providers: ProviderInput[]
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    RecipeInterface: { getProvider: any; manuallyCreateOrUpdateUser: any; signInUp: any }

    Type declaration

    • getProvider:function
      • getProvider(input: { clientType?: string; tenantId: string; thirdPartyId: string; userContext: UserContext }): Promise<undefined | TypeProvider>
      • Parameters

        • input: { clientType?: string; tenantId: string; thirdPartyId: string; userContext: UserContext }
          • Optional clientType?: string
          • tenantId: string
          • thirdPartyId: string
          • userContext: UserContext

        Returns Promise<undefined | TypeProvider>

    • manuallyCreateOrUpdateUser:function
      • manuallyCreateOrUpdateUser(input: { email: string; isVerified: boolean; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; isVerified: boolean; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }
          • email: string
          • isVerified: boolean
          • session: SessionContainer | undefined
          • tenantId: string
          • thirdPartyId: string
          • thirdPartyUserId: string
          • userContext: UserContext

        Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • signInUp:function
      • signInUp(input: { email: string; isVerified: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }): Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; isVerified: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }
          • email: string
          • isVerified: boolean
          • oAuthTokens: {}
            • [key: string]: any
          • rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }
            • Optional fromIdTokenPayload?: {}
              • [key: string]: any
            • Optional fromUserInfoAPI?: {}
              • [key: string]: any
          • session: SessionContainer | undefined
          • tenantId: string
          • thirdPartyId: string
          • thirdPartyUserId: string
          • userContext: UserContext

        Returns Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    TypeProvider: { config: ProviderConfigForClientType; id: string; exchangeAuthCodeForOAuthTokens: any; getAuthorisationRedirectURL: any; getConfigForClientType: any; getUserInfo: any }

    Type declaration

    • config: ProviderConfigForClientType
    • id: string
    • exchangeAuthCodeForOAuthTokens:function
      • exchangeAuthCodeForOAuthTokens(input: { redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }; userContext: UserContext }): Promise<any>
      • Parameters

        • input: { redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }; userContext: UserContext }
          • redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }
            • Optional pkceCodeVerifier?: string
            • redirectURIOnProviderDashboard: string
            • redirectURIQueryParams: any
          • userContext: UserContext

        Returns Promise<any>

    • getAuthorisationRedirectURL:function
      • getAuthorisationRedirectURL(input: { redirectURIOnProviderDashboard: string; userContext: UserContext }): Promise<{ pkceCodeVerifier?: string; urlWithQueryParams: string }>
      • Parameters

        • input: { redirectURIOnProviderDashboard: string; userContext: UserContext }
          • redirectURIOnProviderDashboard: string
          • userContext: UserContext

        Returns Promise<{ pkceCodeVerifier?: string; urlWithQueryParams: string }>

    • getConfigForClientType:function
      • getConfigForClientType(input: { clientType?: string; userContext: UserContext }): Promise<ProviderConfigForClientType>
      • Parameters

        • input: { clientType?: string; userContext: UserContext }
          • Optional clientType?: string
          • userContext: UserContext

        Returns Promise<ProviderConfigForClientType>

    • getUserInfo:function
      • getUserInfo(input: { oAuthTokens: any; userContext: UserContext }): Promise<UserInfo>

    Variables

    Error: typeof default = Wrapper.Error

    Functions

    • getProvider(tenantId: string, thirdPartyId: string, clientType: undefined | string, userContext?: Record<string, any>): Promise<undefined | TypeProvider>
    • init(config?: TypeInput): RecipeListFunction
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session?: undefined, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session: SessionContainer, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/thirdparty | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/thirdparty

    Index

    Type Aliases

    APIInterface: { appleRedirectHandlerPOST: undefined | ((input: { formPostInfoFromProvider: {}; options: APIOptions; userContext: UserContext }) => Promise<void>); authorisationUrlGET: undefined | ((input: { options: APIOptions; provider: TypeProvider; redirectURIOnProviderDashboard: string; tenantId: string; userContext: UserContext }) => Promise<{ pkceCodeVerifier?: string; status: "OK"; urlWithQueryParams: string } | GeneralErrorResponse>); signInUpPOST: undefined | ((input: { options: APIOptions; provider: TypeProvider; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & ({ redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any } } | { oAuthTokens: {} })) => Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer; status: "OK"; user: User } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | GeneralErrorResponse>) }

    Type declaration

    • appleRedirectHandlerPOST: undefined | ((input: { formPostInfoFromProvider: {}; options: APIOptions; userContext: UserContext }) => Promise<void>)
    • authorisationUrlGET: undefined | ((input: { options: APIOptions; provider: TypeProvider; redirectURIOnProviderDashboard: string; tenantId: string; userContext: UserContext }) => Promise<{ pkceCodeVerifier?: string; status: "OK"; urlWithQueryParams: string } | GeneralErrorResponse>)
    • signInUpPOST: undefined | ((input: { options: APIOptions; provider: TypeProvider; session: SessionContainer | undefined; tenantId: string; userContext: UserContext } & ({ redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any } } | { oAuthTokens: {} })) => Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer; status: "OK"; user: User } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | GeneralErrorResponse>)
    APIOptions: { appInfo: NormalisedAppinfo; config: TypeNormalisedInput; isInServerlessEnv: boolean; providers: ProviderInput[]; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    • appInfo: NormalisedAppinfo
    • config: TypeNormalisedInput
    • isInServerlessEnv: boolean
    • providers: ProviderInput[]
    • recipeId: string
    • recipeImplementation: RecipeInterface
    • req: BaseRequest
    • res: BaseResponse
    RecipeInterface: { getProvider: any; manuallyCreateOrUpdateUser: any; signInUp: any }

    Type declaration

    • getProvider:function
      • getProvider(input: { clientType?: string; tenantId: string; thirdPartyId: string; userContext: UserContext }): Promise<undefined | TypeProvider>
      • Parameters

        • input: { clientType?: string; tenantId: string; thirdPartyId: string; userContext: UserContext }
          • Optional clientType?: string
          • tenantId: string
          • thirdPartyId: string
          • userContext: UserContext

        Returns Promise<undefined | TypeProvider>

    • manuallyCreateOrUpdateUser:function
      • manuallyCreateOrUpdateUser(input: { email: string; isVerified: boolean; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; isVerified: boolean; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }
          • email: string
          • isVerified: boolean
          • session: SessionContainer | undefined
          • tenantId: string
          • thirdPartyId: string
          • thirdPartyUserId: string
          • userContext: UserContext

        Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    • signInUp:function
      • signInUp(input: { email: string; isVerified: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }): Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
      • Parameters

        • input: { email: string; isVerified: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; session: SessionContainer | undefined; tenantId: string; thirdPartyId: string; thirdPartyUserId: string; userContext: UserContext }
          • email: string
          • isVerified: boolean
          • oAuthTokens: {}
            • [key: string]: any
          • rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }
            • Optional fromIdTokenPayload?: {}
              • [key: string]: any
            • Optional fromUserInfoAPI?: {}
              • [key: string]: any
          • session: SessionContainer | undefined
          • tenantId: string
          • thirdPartyId: string
          • thirdPartyUserId: string
          • userContext: UserContext

        Returns Promise<{ createdNewRecipeUser: boolean; oAuthTokens: {}; rawUserInfoFromProvider: { fromIdTokenPayload?: {}; fromUserInfoAPI?: {} }; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "EMAIL_VERIFICATION_REQUIRED" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    TypeProvider: { config: ProviderConfigForClientType; id: string; exchangeAuthCodeForOAuthTokens: any; getAuthorisationRedirectURL: any; getConfigForClientType: any; getUserInfo: any }

    Type declaration

    • config: ProviderConfigForClientType
    • id: string
    • exchangeAuthCodeForOAuthTokens:function
      • exchangeAuthCodeForOAuthTokens(input: { redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }; userContext: UserContext }): Promise<any>
      • Parameters

        • input: { redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }; userContext: UserContext }
          • redirectURIInfo: { pkceCodeVerifier?: string; redirectURIOnProviderDashboard: string; redirectURIQueryParams: any }
            • Optional pkceCodeVerifier?: string
            • redirectURIOnProviderDashboard: string
            • redirectURIQueryParams: any
          • userContext: UserContext

        Returns Promise<any>

    • getAuthorisationRedirectURL:function
      • getAuthorisationRedirectURL(input: { redirectURIOnProviderDashboard: string; userContext: UserContext }): Promise<{ pkceCodeVerifier?: string; urlWithQueryParams: string }>
      • Parameters

        • input: { redirectURIOnProviderDashboard: string; userContext: UserContext }
          • redirectURIOnProviderDashboard: string
          • userContext: UserContext

        Returns Promise<{ pkceCodeVerifier?: string; urlWithQueryParams: string }>

    • getConfigForClientType:function
      • getConfigForClientType(input: { clientType?: string; userContext: UserContext }): Promise<ProviderConfigForClientType>
      • Parameters

        • input: { clientType?: string; userContext: UserContext }
          • Optional clientType?: string
          • userContext: UserContext

        Returns Promise<ProviderConfigForClientType>

    • getUserInfo:function
      • getUserInfo(input: { oAuthTokens: any; userContext: UserContext }): Promise<UserInfo>

    Variables

    Error: typeof default = Wrapper.Error

    Functions

    • getProvider(tenantId: string, thirdPartyId: string, clientType: undefined | string, userContext?: Record<string, any>): Promise<undefined | TypeProvider>
    • init(config?: TypeInput): RecipeListFunction
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session?: undefined, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>
    • manuallyCreateOrUpdateUser(tenantId: string, thirdPartyId: string, thirdPartyUserId: string, email: string, isVerified: boolean, session: SessionContainer, userContext?: Record<string, any>): Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>
    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • Optional session: undefined
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" }>

    • Parameters

      • tenantId: string
      • thirdPartyId: string
      • thirdPartyUserId: string
      • email: string
      • isVerified: boolean
      • session: SessionContainer
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRecipeUser: boolean; recipeUserId: RecipeUserId; status: "OK"; user: User } | { reason: string; status: "EMAIL_CHANGE_NOT_ALLOWED_ERROR" } | { reason: string; status: "SIGN_IN_UP_NOT_ALLOWED" } | { reason: "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" | "EMAIL_VERIFICATION_REQUIRED" | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; status: "LINKING_TO_SESSION_USER_FAILED" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_totp.html b/docs/modules/recipe_totp.html index b5f1d1bf7..a4448fbe8 100644 --- a/docs/modules/recipe_totp.html +++ b/docs/modules/recipe_totp.html @@ -1 +1 @@ -recipe/totp | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/totp

    Index

    Type Aliases

    APIInterface: { createDevicePOST: undefined | ((input: { deviceName?: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>); listDevicesGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" } | GeneralErrorResponse>); removeDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ didDeviceExist: boolean; status: "OK" } | GeneralErrorResponse>); verifyDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>); verifyTOTPPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>) }

    Type declaration

    • createDevicePOST: undefined | ((input: { deviceName?: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>)
    • listDevicesGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" } | GeneralErrorResponse>)
    • removeDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ didDeviceExist: boolean; status: "OK" } | GeneralErrorResponse>)
    • verifyDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>)
    • verifyTOTPPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { createDevice: any; getUserIdentifierInfoForUserId: any; listDevices: any; removeDevice: any; updateDevice: any; verifyDevice: any; verifyTOTP: any }

    Type declaration

    • createDevice:function
      • createDevice(input: { deviceName?: string; period?: number; skew?: number; userContext: UserContext; userId: string; userIdentifierInfo?: string }): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
      • Parameters

        • input: { deviceName?: string; period?: number; skew?: number; userContext: UserContext; userId: string; userIdentifierInfo?: string }
          • Optional deviceName?: string
          • Optional period?: number
          • Optional skew?: number
          • userContext: UserContext
          • userId: string
          • Optional userIdentifierInfo?: string

        Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • getUserIdentifierInfoForUserId:function
      • getUserIdentifierInfoForUserId(input: { userContext: UserContext; userId: string }): Promise<{ info: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR" }>
      • Parameters

        • input: { userContext: UserContext; userId: string }
          • userContext: UserContext
          • userId: string

        Returns Promise<{ info: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR" }>

    • listDevices:function
      • listDevices(input: { userContext: UserContext; userId: string }): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
      • Parameters

        • input: { userContext: UserContext; userId: string }
          • userContext: UserContext
          • userId: string

        Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice:function
      • removeDevice(input: { deviceName: string; userContext: UserContext; userId: string }): Promise<{ didDeviceExist: boolean; status: "OK" }>
      • Parameters

        • input: { deviceName: string; userContext: UserContext; userId: string }
          • deviceName: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice:function
      • updateDevice(input: { existingDeviceName: string; newDeviceName: string; userContext: UserContext; userId: string }): Promise<{ status: "OK" | "UNKNOWN_DEVICE_ERROR" | "DEVICE_ALREADY_EXISTS_ERROR" }>
      • Parameters

        • input: { existingDeviceName: string; newDeviceName: string; userContext: UserContext; userId: string }
          • existingDeviceName: string
          • newDeviceName: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" | "UNKNOWN_DEVICE_ERROR" | "DEVICE_ALREADY_EXISTS_ERROR" }>

    • verifyDevice:function
      • verifyDevice(input: { deviceName: string; tenantId: string; totp: string; userContext: UserContext; userId: string }): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
      • Parameters

        • input: { deviceName: string; tenantId: string; totp: string; userContext: UserContext; userId: string }
          • deviceName: string
          • tenantId: string
          • totp: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP:function
      • verifyTOTP(input: { tenantId: string; totp: string; userContext: UserContext; userId: string }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
      • Parameters

        • input: { tenantId: string; totp: string; userContext: UserContext; userId: string }
          • tenantId: string
          • totp: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Functions

    • createDevice(userId: string, userIdentifierInfo?: string, deviceName?: string, skew?: number, period?: number, userContext?: Record<string, any>): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • userId: string
      • Optional userIdentifierInfo: string
      • Optional deviceName: string
      • Optional skew: number
      • Optional period: number
      • Optional userContext: Record<string, any>

      Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • init(config?: TypeInput): RecipeListFunction
    • listDevices(userId: string, userContext?: Record<string, any>): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice(userId: string, deviceName: string, userContext?: Record<string, any>): Promise<{ didDeviceExist: boolean; status: "OK" }>
    • Parameters

      • userId: string
      • deviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice(userId: string, existingDeviceName: string, newDeviceName: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>
    • Parameters

      • userId: string
      • existingDeviceName: string
      • newDeviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>

    • verifyDevice(tenantId: string, userId: string, deviceName: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • deviceName: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP(tenantId: string, userId: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/totp | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/totp

    Index

    Type Aliases

    APIInterface: { createDevicePOST: undefined | ((input: { deviceName?: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>); listDevicesGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" } | GeneralErrorResponse>); removeDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ didDeviceExist: boolean; status: "OK" } | GeneralErrorResponse>); verifyDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>); verifyTOTPPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>) }

    Type declaration

    • createDevicePOST: undefined | ((input: { deviceName?: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | GeneralErrorResponse>)
    • listDevicesGET: undefined | ((input: { options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" } | GeneralErrorResponse>)
    • removeDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; userContext: UserContext }) => Promise<{ didDeviceExist: boolean; status: "OK" } | GeneralErrorResponse>)
    • verifyDevicePOST: undefined | ((input: { deviceName: string; options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>)
    • verifyTOTPPOST: undefined | ((input: { options: APIOptions; session: SessionContainer; totp: string; userContext: UserContext }) => Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" } | GeneralErrorResponse>)
    APIOptions: { config: TypeNormalisedInput; isInServerlessEnv: boolean; recipeId: string; recipeImplementation: RecipeInterface; req: BaseRequest; res: BaseResponse }

    Type declaration

    RecipeInterface: { createDevice: any; getUserIdentifierInfoForUserId: any; listDevices: any; removeDevice: any; updateDevice: any; verifyDevice: any; verifyTOTP: any }

    Type declaration

    • createDevice:function
      • createDevice(input: { deviceName?: string; period?: number; skew?: number; userContext: UserContext; userId: string; userIdentifierInfo?: string }): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
      • Parameters

        • input: { deviceName?: string; period?: number; skew?: number; userContext: UserContext; userId: string; userIdentifierInfo?: string }
          • Optional deviceName?: string
          • Optional period?: number
          • Optional skew?: number
          • userContext: UserContext
          • userId: string
          • Optional userIdentifierInfo?: string

        Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • getUserIdentifierInfoForUserId:function
      • getUserIdentifierInfoForUserId(input: { userContext: UserContext; userId: string }): Promise<{ info: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR" }>
      • Parameters

        • input: { userContext: UserContext; userId: string }
          • userContext: UserContext
          • userId: string

        Returns Promise<{ info: string; status: "OK" } | { status: "UNKNOWN_USER_ID_ERROR" | "USER_IDENTIFIER_INFO_DOES_NOT_EXIST_ERROR" }>

    • listDevices:function
      • listDevices(input: { userContext: UserContext; userId: string }): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
      • Parameters

        • input: { userContext: UserContext; userId: string }
          • userContext: UserContext
          • userId: string

        Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice:function
      • removeDevice(input: { deviceName: string; userContext: UserContext; userId: string }): Promise<{ didDeviceExist: boolean; status: "OK" }>
      • Parameters

        • input: { deviceName: string; userContext: UserContext; userId: string }
          • deviceName: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice:function
      • updateDevice(input: { existingDeviceName: string; newDeviceName: string; userContext: UserContext; userId: string }): Promise<{ status: "OK" | "UNKNOWN_DEVICE_ERROR" | "DEVICE_ALREADY_EXISTS_ERROR" }>
      • Parameters

        • input: { existingDeviceName: string; newDeviceName: string; userContext: UserContext; userId: string }
          • existingDeviceName: string
          • newDeviceName: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" | "UNKNOWN_DEVICE_ERROR" | "DEVICE_ALREADY_EXISTS_ERROR" }>

    • verifyDevice:function
      • verifyDevice(input: { deviceName: string; tenantId: string; totp: string; userContext: UserContext; userId: string }): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
      • Parameters

        • input: { deviceName: string; tenantId: string; totp: string; userContext: UserContext; userId: string }
          • deviceName: string
          • tenantId: string
          • totp: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP:function
      • verifyTOTP(input: { tenantId: string; totp: string; userContext: UserContext; userId: string }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
      • Parameters

        • input: { tenantId: string; totp: string; userContext: UserContext; userId: string }
          • tenantId: string
          • totp: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Functions

    • createDevice(userId: string, userIdentifierInfo?: string, deviceName?: string, skew?: number, period?: number, userContext?: Record<string, any>): Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>
    • Parameters

      • userId: string
      • Optional userIdentifierInfo: string
      • Optional deviceName: string
      • Optional skew: number
      • Optional period: number
      • Optional userContext: Record<string, any>

      Returns Promise<{ deviceName: string; qrCodeString: string; secret: string; status: "OK" } | { status: "DEVICE_ALREADY_EXISTS_ERROR" } | { status: "UNKNOWN_USER_ID_ERROR" }>

    • init(config?: TypeInput): RecipeListFunction
    • listDevices(userId: string, userContext?: Record<string, any>): Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>
    • Parameters

      • userId: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ devices: { name: string; period: number; skew: number; verified: boolean }[]; status: "OK" }>

    • removeDevice(userId: string, deviceName: string, userContext?: Record<string, any>): Promise<{ didDeviceExist: boolean; status: "OK" }>
    • Parameters

      • userId: string
      • deviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didDeviceExist: boolean; status: "OK" }>

    • updateDevice(userId: string, existingDeviceName: string, newDeviceName: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>
    • Parameters

      • userId: string
      • existingDeviceName: string
      • newDeviceName: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "DEVICE_ALREADY_EXISTS_ERROR" | "UNKNOWN_DEVICE_ERROR" }>

    • verifyDevice(tenantId: string, userId: string, deviceName: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • deviceName: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; wasAlreadyVerified: boolean } | { status: "UNKNOWN_DEVICE_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    • verifyTOTP(tenantId: string, userId: string, totp: string, userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • totp: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" } | { currentNumberOfFailedAttempts: number; maxNumberOfFailedAttempts: number; status: "INVALID_TOTP_ERROR" } | { retryAfterMs: number; status: "LIMIT_REACHED_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_usermetadata.html b/docs/modules/recipe_usermetadata.html index ca6422bad..47d66b7d6 100644 --- a/docs/modules/recipe_usermetadata.html +++ b/docs/modules/recipe_usermetadata.html @@ -1,4 +1,4 @@ -recipe/usermetadata | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/usermetadata

    Index

    Type Aliases

    RecipeInterface: { clearUserMetadata: any; getUserMetadata: any; updateUserMetadata: any }

    Type declaration

    • clearUserMetadata:function
      • clearUserMetadata(input: { userContext: UserContext; userId: string }): Promise<{ status: "OK" }>
    • getUserMetadata:function
      • getUserMetadata(input: { userContext: UserContext; userId: string }): Promise<{ metadata: any; status: "OK" }>
      • Parameters

        • input: { userContext: UserContext; userId: string }
          • userContext: UserContext
          • userId: string

        Returns Promise<{ metadata: any; status: "OK" }>

    • updateUserMetadata:function
      • updateUserMetadata(input: { metadataUpdate: JSONObject; userContext: UserContext; userId: string }): Promise<{ metadata: JSONObject; status: "OK" }>
      • +recipe/usermetadata | supertokens-node
        Options
        All
        • Public
        • Public/Protected
        • All
        Menu

        Module recipe/usermetadata

        Index

        Type Aliases

        RecipeInterface: { clearUserMetadata: any; getUserMetadata: any; updateUserMetadata: any }

        Type declaration

        • clearUserMetadata:function
          • clearUserMetadata(input: { userContext: UserContext; userId: string }): Promise<{ status: "OK" }>
        • getUserMetadata:function
          • getUserMetadata(input: { userContext: UserContext; userId: string }): Promise<{ metadata: any; status: "OK" }>
          • Parameters

            • input: { userContext: UserContext; userId: string }
              • userContext: UserContext
              • userId: string

            Returns Promise<{ metadata: any; status: "OK" }>

        • updateUserMetadata:function
          • updateUserMetadata(input: { metadataUpdate: JSONObject; userContext: UserContext; userId: string }): Promise<{ metadata: JSONObject; status: "OK" }>
          • Updates the metadata object of the user by doing a shallow merge of the stored and the update JSONs and removing properties set to null on the root level of the update object. e.g.:

            @@ -7,4 +7,4 @@
          • update: { "notifications": { "sms": true }, "todos": null }
          • result: { "preferences": { "theme":"dark" }, "notifications": { "sms": true } }
          -

        Parameters

        • input: { metadataUpdate: JSONObject; userContext: UserContext; userId: string }
          • metadataUpdate: JSONObject
          • userContext: UserContext
          • userId: string

        Returns Promise<{ metadata: JSONObject; status: "OK" }>

    Functions

    • clearUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • getUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ metadata: any; status: "OK" }>
    • init(config?: TypeInput): RecipeListFunction
    • updateUserMetadata(userId: string, metadataUpdate: JSONObject, userContext?: Record<string, any>): Promise<{ metadata: JSONObject; status: "OK" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +

    Parameters

    • input: { metadataUpdate: JSONObject; userContext: UserContext; userId: string }
      • metadataUpdate: JSONObject
      • userContext: UserContext
      • userId: string

    Returns Promise<{ metadata: JSONObject; status: "OK" }>

    Functions

    • clearUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ status: "OK" }>
    • getUserMetadata(userId: string, userContext?: Record<string, any>): Promise<{ metadata: any; status: "OK" }>
    • init(config?: TypeInput): RecipeListFunction
    • updateUserMetadata(userId: string, metadataUpdate: JSONObject, userContext?: Record<string, any>): Promise<{ metadata: JSONObject; status: "OK" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/docs/modules/recipe_userroles.html b/docs/modules/recipe_userroles.html index 109d8a629..cbb8597a1 100644 --- a/docs/modules/recipe_userroles.html +++ b/docs/modules/recipe_userroles.html @@ -1 +1 @@ -recipe/userroles | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/userroles

    Index

    Type Aliases

    RecipeInterface: { addRoleToUser: any; createNewRoleOrAddPermissions: any; deleteRole: any; getAllRoles: any; getPermissionsForRole: any; getRolesForUser: any; getRolesThatHavePermission: any; getUsersThatHaveRole: any; removePermissionsFromRole: any; removeUserRole: any }

    Type declaration

    • addRoleToUser:function
      • addRoleToUser(input: { role: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext; userId: string }
          • role: string
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions:function
      • createNewRoleOrAddPermissions(input: { permissions: string[]; role: string; userContext: UserContext }): Promise<{ createdNewRole: boolean; status: "OK" }>
      • Parameters

        • input: { permissions: string[]; role: string; userContext: UserContext }
          • permissions: string[]
          • role: string
          • userContext: UserContext

        Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole:function
      • deleteRole(input: { role: string; userContext: UserContext }): Promise<{ didRoleExist: boolean; status: "OK" }>
      • Parameters

        • input: { role: string; userContext: UserContext }
          • role: string
          • userContext: UserContext

        Returns Promise<{ didRoleExist: boolean; status: "OK" }>

    • getAllRoles:function
      • getAllRoles(input: { userContext: UserContext }): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole:function
      • getPermissionsForRole(input: { role: string; userContext: UserContext }): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; userContext: UserContext }
          • role: string
          • userContext: UserContext

        Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser:function
      • getRolesForUser(input: { tenantId: string; userContext: UserContext; userId: string }): Promise<{ roles: string[]; status: "OK" }>
      • Parameters

        • input: { tenantId: string; userContext: UserContext; userId: string }
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ roles: string[]; status: "OK" }>

    • getRolesThatHavePermission:function
      • getRolesThatHavePermission(input: { permission: string; userContext: UserContext }): Promise<{ roles: string[]; status: "OK" }>
      • Parameters

        • input: { permission: string; userContext: UserContext }
          • permission: string
          • userContext: UserContext

        Returns Promise<{ roles: string[]; status: "OK" }>

    • getUsersThatHaveRole:function
      • getUsersThatHaveRole(input: { role: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext }
          • role: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • removePermissionsFromRole:function
      • removePermissionsFromRole(input: { permissions: string[]; role: string; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { permissions: string[]; role: string; userContext: UserContext }
          • permissions: string[]
          • role: string
          • userContext: UserContext

        Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole:function
      • removeUserRole(input: { role: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext; userId: string }
          • role: string
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Variables

    PermissionClaim: PermissionClaimClass = ...
    UserRoleClaim: UserRoleClaimClass = ...

    Functions

    • addRoleToUser(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ createdNewRole: boolean; status: "OK" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole(role: string, userContext?: Record<string, any>): Promise<{ didRoleExist: boolean; status: "OK" }>
    • getAllRoles(userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole(role: string, userContext?: Record<string, any>): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser(tenantId: string, userId: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getRolesThatHavePermission(permission: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getUsersThatHaveRole(tenantId: string, role: string, userContext?: Record<string, any>): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • init(config?: TypeInput): RecipeListFunction
    • removePermissionsFromRole(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +recipe/userroles | supertokens-node
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module recipe/userroles

    Index

    Type Aliases

    RecipeInterface: { addRoleToUser: any; createNewRoleOrAddPermissions: any; deleteRole: any; getAllRoles: any; getPermissionsForRole: any; getRolesForUser: any; getRolesThatHavePermission: any; getUsersThatHaveRole: any; removePermissionsFromRole: any; removeUserRole: any }

    Type declaration

    • addRoleToUser:function
      • addRoleToUser(input: { role: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext; userId: string }
          • role: string
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions:function
      • createNewRoleOrAddPermissions(input: { permissions: string[]; role: string; userContext: UserContext }): Promise<{ createdNewRole: boolean; status: "OK" }>
      • Parameters

        • input: { permissions: string[]; role: string; userContext: UserContext }
          • permissions: string[]
          • role: string
          • userContext: UserContext

        Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole:function
      • deleteRole(input: { role: string; userContext: UserContext }): Promise<{ didRoleExist: boolean; status: "OK" }>
      • Parameters

        • input: { role: string; userContext: UserContext }
          • role: string
          • userContext: UserContext

        Returns Promise<{ didRoleExist: boolean; status: "OK" }>

    • getAllRoles:function
      • getAllRoles(input: { userContext: UserContext }): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole:function
      • getPermissionsForRole(input: { role: string; userContext: UserContext }): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; userContext: UserContext }
          • role: string
          • userContext: UserContext

        Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser:function
      • getRolesForUser(input: { tenantId: string; userContext: UserContext; userId: string }): Promise<{ roles: string[]; status: "OK" }>
      • Parameters

        • input: { tenantId: string; userContext: UserContext; userId: string }
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ roles: string[]; status: "OK" }>

    • getRolesThatHavePermission:function
      • getRolesThatHavePermission(input: { permission: string; userContext: UserContext }): Promise<{ roles: string[]; status: "OK" }>
      • Parameters

        • input: { permission: string; userContext: UserContext }
          • permission: string
          • userContext: UserContext

        Returns Promise<{ roles: string[]; status: "OK" }>

    • getUsersThatHaveRole:function
      • getUsersThatHaveRole(input: { role: string; tenantId: string; userContext: UserContext }): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext }
          • role: string
          • tenantId: string
          • userContext: UserContext

        Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • removePermissionsFromRole:function
      • removePermissionsFromRole(input: { permissions: string[]; role: string; userContext: UserContext }): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { permissions: string[]; role: string; userContext: UserContext }
          • permissions: string[]
          • role: string
          • userContext: UserContext

        Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole:function
      • removeUserRole(input: { role: string; tenantId: string; userContext: UserContext; userId: string }): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
      • Parameters

        • input: { role: string; tenantId: string; userContext: UserContext; userId: string }
          • role: string
          • tenantId: string
          • userContext: UserContext
          • userId: string

        Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Variables

    PermissionClaim: PermissionClaimClass = ...
    UserRoleClaim: UserRoleClaimClass = ...

    Functions

    • addRoleToUser(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserAlreadyHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • createNewRoleOrAddPermissions(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ createdNewRole: boolean; status: "OK" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ createdNewRole: boolean; status: "OK" }>

    • deleteRole(role: string, userContext?: Record<string, any>): Promise<{ didRoleExist: boolean; status: "OK" }>
    • getAllRoles(userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getPermissionsForRole(role: string, userContext?: Record<string, any>): Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ permissions: string[]; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    • getRolesForUser(tenantId: string, userId: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getRolesThatHavePermission(permission: string, userContext?: Record<string, any>): Promise<{ roles: string[]; status: "OK" }>
    • getUsersThatHaveRole(tenantId: string, role: string, userContext?: Record<string, any>): Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK"; users: string[] } | { status: "UNKNOWN_ROLE_ERROR" }>

    • init(config?: TypeInput): RecipeListFunction
    • removePermissionsFromRole(role: string, permissions: string[], userContext?: Record<string, any>): Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • role: string
      • permissions: string[]
      • Optional userContext: Record<string, any>

      Returns Promise<{ status: "OK" | "UNKNOWN_ROLE_ERROR" }>

    • removeUserRole(tenantId: string, userId: string, role: string, userContext?: Record<string, any>): Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>
    • Parameters

      • tenantId: string
      • userId: string
      • role: string
      • Optional userContext: Record<string, any>

      Returns Promise<{ didUserHaveRole: boolean; status: "OK" } | { status: "UNKNOWN_ROLE_ERROR" }>

    Legend

    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Class
    • Class with type parameter
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file diff --git a/frontendDriverInterfaceSupported.json b/frontendDriverInterfaceSupported.json index c925842b1..cbdd87f7b 100644 --- a/frontendDriverInterfaceSupported.json +++ b/frontendDriverInterfaceSupported.json @@ -1,4 +1,4 @@ { "_comment": "contains a list of frontend-driver interfaces branch names that this core supports", - "versions": ["1.17", "1.18", "1.19", "2.0", "3.0"] + "versions": ["1.17", "1.18", "1.19", "2.0", "3.0", "3.1"] } diff --git a/lib/build/authUtils.d.ts b/lib/build/authUtils.d.ts index 9e714eb91..98ff4ad41 100644 --- a/lib/build/authUtils.d.ts +++ b/lib/build/authUtils.d.ts @@ -54,6 +54,7 @@ export declare const AuthUtils: { factorIds, skipSessionUserUpdateInCore, session, + shouldTryLinkingWithSessionUser, userContext, }: { authenticatingAccountInfo: AccountInfoWithRecipeId; @@ -65,6 +66,7 @@ export declare const AuthUtils: { signInVerifiesLoginMethod: boolean; skipSessionUserUpdateInCore: boolean; session?: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; userContext: UserContext; }) => Promise< | { @@ -194,6 +196,7 @@ export declare const AuthUtils: { */ checkAuthTypeAndLinkingStatus: ( session: SessionContainerInterface | undefined, + shouldTryLinkingWithSessionUser: boolean | undefined, accountInfo: AccountInfoWithRecipeId, inputUser: User | undefined, skipSessionUserUpdateInCore: boolean, @@ -235,17 +238,19 @@ export declare const AuthUtils: { * - LINKING_TO_SESSION_USER_FAILED (SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if the session user should be primary but we couldn't make it primary because of a conflicting primary user. */ - linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo: ({ + linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo: ({ tenantId, inputUser, recipeUserId, session, + shouldTryLinkingWithSessionUser, userContext, }: { tenantId: string; inputUser: User; recipeUserId: RecipeUserId; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; userContext: UserContext; }) => Promise< | { @@ -335,4 +340,10 @@ export declare const AuthUtils: { hasSession: boolean, userContext: UserContext ) => Promise; + loadSessionInAuthAPIIfNeeded: ( + req: BaseRequest, + res: BaseResponse, + shouldTryLinkingWithSessionUser: boolean | undefined, + userContext: UserContext + ) => Promise; }; diff --git a/lib/build/authUtils.js b/lib/build/authUtils.js index a0afc304a..c340c7930 100644 --- a/lib/build/authUtils.js +++ b/lib/build/authUtils.js @@ -78,6 +78,7 @@ exports.AuthUtils = { factorIds, skipSessionUserUpdateInCore, session, + shouldTryLinkingWithSessionUser, userContext, }) { let validFactorIds; @@ -91,6 +92,7 @@ exports.AuthUtils = { // We also load the session user here if it is available. const authTypeInfo = await exports.AuthUtils.checkAuthTypeAndLinkingStatus( session, + shouldTryLinkingWithSessionUser, authenticatingAccountInfo, authenticatingUser, skipSessionUserUpdateInCore, @@ -255,6 +257,9 @@ exports.AuthUtils = { } } } else { + // We do not have to care about overwriting the session here, since we either: + // - have overwriteSessionDuringSignInUp true and can ignore it + // - have overwriteSessionDuringSignInUp false and we checked in the api imlp that there is no session logger_1.logDebugMessage(`postAuthChecks creating session for first factor sign in/up`); // If there is no input session, we do not need to do anything other checks and create a new session respSession = await session_1.default.createNewSession( @@ -439,6 +444,7 @@ exports.AuthUtils = { */ checkAuthTypeAndLinkingStatus: async function ( session, + shouldTryLinkingWithSessionUser, accountInfo, inputUser, skipSessionUserUpdateInCore, @@ -447,19 +453,37 @@ exports.AuthUtils = { logger_1.logDebugMessage(`checkAuthTypeAndLinkingStatus called`); let sessionUser = undefined; if (session === undefined) { + if (shouldTryLinkingWithSessionUser === true) { + throw new error_1.default({ + type: error_1.default.UNAUTHORISED, + message: "Session not found but shouldTryLinkingWithSessionUser is true", + }); + } logger_1.logDebugMessage( `checkAuthTypeAndLinkingStatus returning first factor because there is no session` ); // If there is no active session we have nothing to link to - so this has to be a first factor sign in return { status: "OK", isFirstFactor: true }; } else { + if (shouldTryLinkingWithSessionUser === false) { + // In our normal flows this should never happen - but some user overrides might do this. + // Anyway, since shouldTryLinkingWithSessionUser explicitly set to false, it's safe to consider this a firstFactor + return { status: "OK", isFirstFactor: true }; + } if (!utils_3.recipeInitDefinedShouldDoAutomaticAccountLinking(recipe_1.default.getInstance().config)) { - if (recipe_2.default.getInstance() !== undefined) { + if (shouldTryLinkingWithSessionUser === true) { throw new Error( "Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA" ); } else { - return { status: "OK", isFirstFactor: true }; + // This is the legacy case where shouldTryLinkingWithSessionUser is undefined + if (recipe_2.default.getInstance() !== undefined) { + throw new Error( + "Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA" + ); + } else { + return { status: "OK", isFirstFactor: true }; + } } } // If the input and the session user are the same @@ -489,6 +513,13 @@ exports.AuthUtils = { userContext ); if (sessionUserResult.status === "SHOULD_AUTOMATICALLY_LINK_FALSE") { + if (shouldTryLinkingWithSessionUser === true) { + throw new _1.Error({ + message: + "shouldDoAutomaticAccountLinking returned false when creating primary user but shouldTryLinkingWithSessionUser is true", + type: "BAD_INPUT_ERROR", + }); + } return { status: "OK", isFirstFactor: true, @@ -519,6 +550,13 @@ exports.AuthUtils = { )}` ); if (shouldLink.shouldAutomaticallyLink === false) { + if (shouldTryLinkingWithSessionUser === true) { + throw new _1.Error({ + message: + "shouldDoAutomaticAccountLinking returned false when creating primary user but shouldTryLinkingWithSessionUser is true", + type: "BAD_INPUT_ERROR", + }); + } return { status: "OK", isFirstFactor: true }; } else { return { @@ -545,20 +583,22 @@ exports.AuthUtils = { * - LINKING_TO_SESSION_USER_FAILED (SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if the session user should be primary but we couldn't make it primary because of a conflicting primary user. */ - linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo: async function ({ + linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo: async function ({ tenantId, inputUser, recipeUserId, session, + shouldTryLinkingWithSessionUser, userContext, }) { logger_1.logDebugMessage("linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo called"); const retry = () => { logger_1.logDebugMessage("linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo retrying...."); - return exports.AuthUtils.linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo({ + return exports.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ tenantId, inputUser: inputUser, session, + shouldTryLinkingWithSessionUser, recipeUserId, userContext, }); @@ -575,6 +615,7 @@ exports.AuthUtils = { } const authTypeRes = await exports.AuthUtils.checkAuthTypeAndLinkingStatus( session, + shouldTryLinkingWithSessionUser, authLoginMethod, inputUser, false, @@ -846,6 +887,21 @@ exports.AuthUtils = { } return validFactorIds; }, + loadSessionInAuthAPIIfNeeded: async function (req, res, shouldTryLinkingWithSessionUser, userContext) { + const overwriteSessionDuringSignInUp = recipe_3.default.getInstanceOrThrowError().config + .overwriteSessionDuringSignInUp; + return shouldTryLinkingWithSessionUser !== false || !overwriteSessionDuringSignInUp + ? await session_1.default.getSession( + req, + res, + { + sessionRequired: shouldTryLinkingWithSessionUser === true, + overrideGlobalClaimValidators: () => [], + }, + userContext + ) + : undefined; + }, }; async function filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid( factorIds, diff --git a/lib/build/recipe/accountlinking/index.js b/lib/build/recipe/accountlinking/index.js index 68e7fbbb5..f65eeeaee 100644 --- a/lib/build/recipe/accountlinking/index.js +++ b/lib/build/recipe/accountlinking/index.js @@ -129,6 +129,9 @@ class Wrapper { } static async isEmailChangeAllowed(recipeUserId, newEmail, isVerified, session, userContext) { const user = await __1.getUser(recipeUserId.getAsString(), userContext); + if (user === undefined) { + throw new Error("Passed in recipe user id does not exist"); + } const res = await recipe_1.default.getInstance().isEmailChangeAllowed({ user, newEmail, diff --git a/lib/build/recipe/accountlinking/recipe.d.ts b/lib/build/recipe/accountlinking/recipe.d.ts index 0bf9c36d0..5a850c2a0 100644 --- a/lib/build/recipe/accountlinking/recipe.d.ts +++ b/lib/build/recipe/accountlinking/recipe.d.ts @@ -99,7 +99,7 @@ export default class Recipe extends RecipeModule { userContext: UserContext; }) => Promise; isEmailChangeAllowed: (input: { - user?: User; + user: User; newEmail: string; isVerified: boolean; session: SessionContainerInterface | undefined; diff --git a/lib/build/recipe/accountlinking/recipe.js b/lib/build/recipe/accountlinking/recipe.js index 0e29dd06f..e9eeeb363 100644 --- a/lib/build/recipe/accountlinking/recipe.js +++ b/lib/build/recipe/accountlinking/recipe.js @@ -385,9 +385,6 @@ class Recipe extends recipeModule_1.default { * in account take over if this recipe user is malicious. */ let inputUser = input.user; - if (inputUser === undefined) { - throw new Error("Passed in recipe user id does not exist"); - } for (const tenantId of inputUser.tenantIds) { let existingUsersWithNewEmail = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, @@ -800,7 +797,7 @@ class Recipe extends recipeModule_1.default { // we can use the 0 index cause targetUser is not a primary user. let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( inputUser.loginMethods[0], - primaryUserThatCanBeLinkedToTheInputUser, + createPrimaryUserResult.user, session, tenantId, userContext diff --git a/lib/build/recipe/emailpassword/api/implementation.js b/lib/build/recipe/emailpassword/api/implementation.js index 6ed81771b..8e5e65db9 100644 --- a/lib/build/recipe/emailpassword/api/implementation.js +++ b/lib/build/recipe/emailpassword/api/implementation.js @@ -481,7 +481,14 @@ function getAPIImplementation() { ); } }, - signInPOST: async function ({ formFields, tenantId, session, options, userContext }) { + signInPOST: async function ({ + formFields, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }) { const errorCodeMap = { SIGN_IN_NOT_ALLOWED: "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)", @@ -546,6 +553,7 @@ function getAPIImplementation() { tenantId, userContext, session, + shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status === "SIGN_UP_NOT_ALLOWED") { throw new Error("This should never happen: pre-auth checks should not fail for sign in"); @@ -567,6 +575,7 @@ function getAPIImplementation() { email, password, session, + shouldTryLinkingWithSessionUser, tenantId, userContext, }); @@ -604,7 +613,14 @@ function getAPIImplementation() { user: postAuthChecks.user, }; }, - signUpPOST: async function ({ formFields, tenantId, session, options, userContext }) { + signUpPOST: async function ({ + formFields, + tenantId, + session, + shouldTryLinkingWithSessionUser, + options, + userContext, + }) { const errorCodeMap = { SIGN_UP_NOT_ALLOWED: "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", @@ -635,6 +651,7 @@ function getAPIImplementation() { tenantId, userContext, session, + shouldTryLinkingWithSessionUser, }); if (preAuthCheckRes.status === "SIGN_UP_NOT_ALLOWED") { const conflictingUsers = await recipe_1.default @@ -675,6 +692,7 @@ function getAPIImplementation() { email, password, session, + shouldTryLinkingWithSessionUser, userContext, }); if (signUpResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") { diff --git a/lib/build/recipe/emailpassword/api/signin.js b/lib/build/recipe/emailpassword/api/signin.js index 0ddb015d1..42a7f16b3 100644 --- a/lib/build/recipe/emailpassword/api/signin.js +++ b/lib/build/recipe/emailpassword/api/signin.js @@ -13,34 +13,28 @@ * 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 utils_2 = require("./utils"); -const session_1 = __importDefault(require("../../session")); +const authUtils_1 = require("../../../authUtils"); async function signInAPI(apiImplementation, tenantId, options, userContext) { // Logic as per https://github.com/supertokens/supertokens-node/issues/20#issuecomment-710346362 if (apiImplementation.signInPOST === undefined) { return false; } + const body = await options.req.getJSONBody(); // step 1 let formFields = await utils_2.validateFormFieldsOrThrowError( options.config.signInFeature.formFields, - (await options.req.getJSONBody()).formFields, + body.formFields, tenantId, userContext ); - let session = await session_1.default.getSession( + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); if (session !== undefined) { @@ -50,6 +44,7 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) { formFields, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext, }); diff --git a/lib/build/recipe/emailpassword/api/signup.js b/lib/build/recipe/emailpassword/api/signup.js index 0987b15fb..6a793ec2c 100644 --- a/lib/build/recipe/emailpassword/api/signup.js +++ b/lib/build/recipe/emailpassword/api/signup.js @@ -22,7 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const utils_2 = require("./utils"); const error_1 = __importDefault(require("../error")); -const session_1 = __importDefault(require("../../session")); +const authUtils_1 = require("../../../authUtils"); async function signUpAPI(apiImplementation, tenantId, options, userContext) { // Logic as per https://github.com/supertokens/supertokens-node/issues/21#issuecomment-710423536 if (apiImplementation.signUpPOST === undefined) { @@ -36,13 +36,14 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { tenantId, userContext ); - let session = await session_1.default.getSession( + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( + options.req, + requestBody + ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); if (session !== undefined) { @@ -52,6 +53,7 @@ async function signUpAPI(apiImplementation, tenantId, options, userContext) { formFields, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext: userContext, }); diff --git a/lib/build/recipe/emailpassword/index.js b/lib/build/recipe/emailpassword/index.js index 7fcbacf41..2c77224c0 100644 --- a/lib/build/recipe/emailpassword/index.js +++ b/lib/build/recipe/emailpassword/index.js @@ -33,6 +33,7 @@ class Wrapper { email, password, session, + shouldTryLinkingWithSessionUser: !!session, tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, userContext: utils_2.getUserContext(userContext), }); @@ -42,6 +43,7 @@ class Wrapper { email, password, session, + shouldTryLinkingWithSessionUser: !!session, tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, userContext: utils_2.getUserContext(userContext), }); diff --git a/lib/build/recipe/emailpassword/recipeImplementation.js b/lib/build/recipe/emailpassword/recipeImplementation.js index d5734d770..acbbb1c7f 100644 --- a/lib/build/recipe/emailpassword/recipeImplementation.js +++ b/lib/build/recipe/emailpassword/recipeImplementation.js @@ -16,7 +16,7 @@ const user_1 = require("../../user"); const authUtils_1 = require("../../authUtils"); function getRecipeInterface(querier, getEmailPasswordConfig) { return { - signUp: async function ({ email, password, tenantId, session, userContext }) { + signUp: async function ({ email, password, tenantId, session, shouldTryLinkingWithSessionUser, userContext }) { const response = await this.createNewRecipeUser({ email, password, @@ -27,12 +27,13 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { return response; } let updatedUser = response.user; - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo( + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( { tenantId, inputUser: response.user, recipeUserId: response.recipeUserId, session, + shouldTryLinkingWithSessionUser, userContext, } ); @@ -68,7 +69,7 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { // we do not do email verification here cause it's a new user and email password // users are always initially unverified. }, - signIn: async function ({ email, password, tenantId, session, userContext }) { + signIn: async function ({ email, password, tenantId, session, shouldTryLinkingWithSessionUser, userContext }) { const response = await this.verifyCredentials({ email, password, tenantId, userContext }); if (response.status === "OK") { const loginMethod = response.user.loginMethods.find( @@ -93,12 +94,13 @@ function getRecipeInterface(querier, getEmailPasswordConfig) { // function updated the verification status) and can return that response.user = await __1.getUser(response.recipeUserId.getAsString(), userContext); } - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo( + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( { tenantId, inputUser: response.user, recipeUserId: response.recipeUserId, session, + shouldTryLinkingWithSessionUser, userContext, } ); diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index 58be90676..ee247490e 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -67,6 +67,7 @@ export declare type RecipeInterface = { email: string; password: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< @@ -106,6 +107,7 @@ export declare type RecipeInterface = { email: string; password: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< @@ -275,6 +277,7 @@ export declare type APIInterface = { }[]; tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; }) => Promise< @@ -301,6 +304,7 @@ export declare type APIInterface = { }[]; tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; }) => Promise< diff --git a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts index 9952b0124..07976ada7 100644 --- a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts +++ b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.d.ts @@ -55,8 +55,10 @@ export declare class MultiFactorAuthClaimClass extends SessionClaim { [x: string]: import("../../types").JSONValue; }; - removeFromPayloadByMerge_internal: () => { - [x: string]: null; + removeFromPayloadByMerge_internal: ( + payload: JSONObject + ) => { + [x: string]: import("../../types").JSONValue; }; getValueFromPayload: (payload: JSONObject) => MFAClaimValue; } diff --git a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js index 45fe7885c..da46bd6b1 100644 --- a/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js +++ b/lib/build/recipe/multifactorauth/multiFactorAuthClaim.js @@ -53,10 +53,8 @@ class MultiFactorAuthClaimClass extends claims_1.SessionClaim { delete retVal[this.key]; return retVal; }; - this.removeFromPayloadByMerge_internal = () => { - return { - [this.key]: null, - }; + this.removeFromPayloadByMerge_internal = (payload) => { + return Object.assign(Object.assign({}, payload), { [this.key]: null }); }; this.getValueFromPayload = (payload) => { return payload[this.key]; diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index 472fff58a..bfb5c3ee8 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -22,6 +22,7 @@ function getAPIImplementation() { recipeImplementation: options.recipeImplementation, loginChallenge, session, + isDirectCall: true, userContext, }); return utils_1.handleInternalRedirects({ diff --git a/lib/build/recipe/oauth2provider/api/login.js b/lib/build/recipe/oauth2provider/api/login.js index dc42e7ceb..57dbed8c4 100644 --- a/lib/build/recipe/oauth2provider/api/login.js +++ b/lib/build/recipe/oauth2provider/api/login.js @@ -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")); @@ -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; diff --git a/lib/build/recipe/oauth2provider/api/token.js b/lib/build/recipe/oauth2provider/api/token.js index 4d0d9e873..3ff27158c 100644 --- a/lib/build/recipe/oauth2provider/api/token.js +++ b/lib/build/recipe/oauth2provider/api/token.js @@ -27,7 +27,10 @@ async function tokenPOST(apiImplementation, options, userContext) { userContext, }); if ("statusCode" in response && response.statusCode !== 200) { - utils_1.sendNon200Response(options.res, response.statusCode, response); + utils_1.sendNon200Response(options.res, response.statusCode, { + error: response.error, + error_description: response.errorDescription, + }); } else { utils_1.send200Response(options.res, response); } diff --git a/lib/build/recipe/oauth2provider/api/utils.d.ts b/lib/build/recipe/oauth2provider/api/utils.d.ts index 0a430568c..2271ba148 100644 --- a/lib/build/recipe/oauth2provider/api/utils.d.ts +++ b/lib/build/recipe/oauth2provider/api/utils.d.ts @@ -7,6 +7,7 @@ export declare function loginGET({ loginChallenge, session, setCookie, + isDirectCall, userContext, }: { recipeImplementation: RecipeInterface; @@ -14,6 +15,7 @@ export declare function loginGET({ session?: SessionContainerInterface; setCookie?: string; userContext: UserContext; + isDirectCall: boolean; }): Promise<{ redirectTo: string; setCookie: string | undefined; diff --git a/lib/build/recipe/oauth2provider/api/utils.js b/lib/build/recipe/oauth2provider/api/utils.js index c65d1dbb2..b7cb5cd80 100644 --- a/lib/build/recipe/oauth2provider/api/utils.js +++ b/lib/build/recipe/oauth2provider/api/utils.js @@ -7,38 +7,90 @@ var __importDefault = Object.defineProperty(exports, "__esModule", { value: true }); exports.handleInternalRedirects = exports.loginGET = void 0; const supertokens_1 = __importDefault(require("../../../supertokens")); -const constants_1 = require("../constants"); +const constants_1 = require("../../multitenancy/constants"); +const session_1 = require("../../session"); +const constants_2 = 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 }) { - var _a; - const request = await recipeImplementation.getLoginRequest({ +async function loginGET({ recipeImplementation, loginChallenge, session, setCookie, isDirectCall, userContext }) { + var _a, _b; + 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; + } + const incomingAuthUrlQueryParams = new URLSearchParams(loginRequest.requestUrl.split("?")[1]); + const promptParam = + (_a = incomingAuthUrlQueryParams.get("prompt")) !== null && _a !== void 0 + ? _a + : incomingAuthUrlQueryParams.get("st_prompt"); + const maxAgeParam = incomingAuthUrlQueryParams.get("max_age"); + if (maxAgeParam !== null) { + try { + const maxAgeParsed = Number.parseInt(maxAgeParam); + if (maxAgeParsed < 0) { + const reject = await recipeImplementation.rejectLoginRequest({ + challenge: loginChallenge, + error: { + error: "invalid_request", + errorDescription: "max_age cannot be negative", + }, + userContext, + }); + return { redirectTo: reject.redirectTo, setCookie }; + } + } catch (_c) { + const reject = await recipeImplementation.rejectLoginRequest({ + challenge: loginChallenge, + error: { + error: "invalid_request", + errorDescription: "max_age must be an integer", + }, + userContext, + }); + return { redirectTo: reject.redirectTo, setCookie }; + } } - if (request.skip) { + const tenantIdParam = incomingAuthUrlQueryParams.get("tenant_id"); + if ( + session && + (["", undefined].includes(loginRequest.subject) || session.getUserId() === loginRequest.subject) && + (["", null].includes(tenantIdParam) || session.getTenantId() === tenantIdParam) && + (promptParam !== "login" || isDirectCall) && + (maxAgeParam === null || + (maxAgeParam === "0" && isDirectCall) || + Number.parseInt(maxAgeParam) * 1000 > Date.now() - sessionInfo.timeCreated) + ) { const accept = await recipeImplementation.acceptLoginRequest({ challenge: loginChallenge, - identityProviderSessionId: session === null || session === void 0 ? void 0 : session.getHandle(), - subject: request.subject, + subject: session.getUserId(), + identityProviderSessionId: session.getHandle(), + remember: true, + rememberFor: 3600, userContext, }); return { redirectTo: accept.redirectTo, setCookie }; - } else if (session && (!request.subject || session.getUserId() === request.subject)) { - const accept = await recipeImplementation.acceptLoginRequest({ + } + if (promptParam === "none") { + const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, - subject: session.getUserId(), - identityProviderSessionId: session.getHandle(), + error: { + error: "login_required", + errorDescription: + "The Authorization Server requires End-User authentication. Prompt 'none' was requested, but no existing or expired login session was found.", + }, userContext, }); - return { redirectTo: accept.redirectTo, setCookie }; + return { redirectTo: reject.redirectTo, setCookie }; } const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; const websiteDomain = appInfo @@ -48,8 +100,20 @@ async function loginGET({ recipeImplementation, loginChallenge, session, setCook }) .getAsStringDangerous(); const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); + const queryParamsForAuthPage = new URLSearchParams({ + loginChallenge, + }); + if ((_b = loginRequest.oidcContext) === null || _b === void 0 ? void 0 : _b.login_hint) { + queryParamsForAuthPage.set("hint", loginRequest.oidcContext.login_hint); + } + if (session !== undefined || promptParam === "login") { + queryParamsForAuthPage.set("forceFreshAuth", "true"); + } + if (tenantIdParam !== null && tenantIdParam !== constants_1.DEFAULT_TENANT_ID) { + queryParamsForAuthPage.set("tenantId", tenantIdParam); + } return { - redirectTo: websiteDomain + websiteBasePath + `?${queryParams.toString()}`, + redirectTo: websiteDomain + websiteBasePath + `?${queryParamsForAuthPage.toString()}`, setCookie, }; } @@ -81,16 +145,16 @@ 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; const basePath = `${apiDomain.getAsStringDangerous()}${apiBasePath.getAsStringDangerous()}`; return [ - constants_1.LOGIN_PATH, - constants_1.AUTH_PATH, - constants_1.LOGIN_PATH.replace("oauth", "oauth2"), - constants_1.AUTH_PATH.replace("oauth", "oauth2"), + constants_2.LOGIN_PATH, + constants_2.AUTH_PATH, + constants_2.LOGIN_PATH.replace("oauth", "oauth2"), + constants_2.AUTH_PATH.replace("oauth", "oauth2"), ].some((path) => redirectTo.startsWith(`${basePath}${path}`)); } // In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. @@ -109,7 +173,7 @@ async function handleInternalRedirects({ response, recipeImplementation, session cookie = getMergedCookies({ cookie, setCookie: response.setCookie }); const queryString = response.redirectTo.split("?")[1]; const params = new URLSearchParams(queryString); - if (response.redirectTo.includes(constants_1.LOGIN_PATH)) { + if (response.redirectTo.includes(constants_2.LOGIN_PATH)) { const loginChallenge = (_a = params.get("login_challenge")) !== null && _a !== void 0 ? _a : params.get("loginChallenge"); if (!loginChallenge) { @@ -120,13 +184,14 @@ async function handleInternalRedirects({ response, recipeImplementation, session loginChallenge, session, setCookie: response.setCookie, + isDirectCall: false, userContext, }); response = { redirectTo: loginRes.redirectTo, setCookie: mergeSetCookieHeaders(loginRes.setCookie, response.setCookie), }; - } else if (response.redirectTo.includes(constants_1.AUTH_PATH)) { + } else if (response.redirectTo.includes(constants_2.AUTH_PATH)) { const authRes = await recipeImplementation.authorization({ params: Object.fromEntries(params.entries()), cookies: cookie, diff --git a/lib/build/recipe/oauth2provider/index.d.ts b/lib/build/recipe/oauth2provider/index.d.ts index b3f45edd7..7a6f3c8f3 100644 --- a/lib/build/recipe/oauth2provider/index.d.ts +++ b/lib/build/recipe/oauth2provider/index.d.ts @@ -94,18 +94,6 @@ export default class Wrapper { status: "OK"; payload: import("../usermetadata").JSONObject; }>; - static validateOAuth2IdToken( - token: string, - requirements?: { - clientId?: string; - scopes?: string[]; - audience?: string; - }, - userContext?: Record - ): Promise<{ - status: "OK"; - payload: import("../usermetadata").JSONObject; - }>; static createTokenForClientCredentials( clientId: string, clientSecret: string, @@ -136,7 +124,6 @@ export declare let createOAuth2Client: typeof Wrapper.createOAuth2Client; export declare let updateOAuth2Client: typeof Wrapper.updateOAuth2Client; export declare let deleteOAuth2Client: typeof Wrapper.deleteOAuth2Client; export declare let validateOAuth2AccessToken: typeof Wrapper.validateOAuth2AccessToken; -export declare let validateOAuth2IdToken: typeof Wrapper.validateOAuth2IdToken; export declare let createTokenForClientCredentials: typeof Wrapper.createTokenForClientCredentials; export declare let revokeToken: typeof Wrapper.revokeToken; export type { APIInterface, APIOptions, RecipeInterface }; diff --git a/lib/build/recipe/oauth2provider/index.js b/lib/build/recipe/oauth2provider/index.js index 3a0d0166c..ac13eead6 100644 --- a/lib/build/recipe/oauth2provider/index.js +++ b/lib/build/recipe/oauth2provider/index.js @@ -19,7 +19,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.revokeToken = exports.createTokenForClientCredentials = exports.validateOAuth2IdToken = exports.validateOAuth2AccessToken = exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.init = void 0; +exports.revokeToken = exports.createTokenForClientCredentials = exports.validateOAuth2AccessToken = exports.deleteOAuth2Client = exports.updateOAuth2Client = exports.createOAuth2Client = exports.getOAuth2Clients = exports.init = void 0; const utils_1 = require("../../utils"); const recipe_1 = __importDefault(require("./recipe")); class Wrapper { @@ -56,13 +56,6 @@ class Wrapper { userContext: utils_1.getUserContext(userContext), }); } - static validateOAuth2IdToken(token, requirements, userContext) { - return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2IdToken({ - token, - requirements, - userContext: utils_1.getUserContext(userContext), - }); - } static createTokenForClientCredentials(clientId, clientSecret, scope, audience, userContext) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.tokenExchange({ body: { @@ -119,6 +112,5 @@ exports.createOAuth2Client = Wrapper.createOAuth2Client; exports.updateOAuth2Client = Wrapper.updateOAuth2Client; exports.deleteOAuth2Client = Wrapper.deleteOAuth2Client; exports.validateOAuth2AccessToken = Wrapper.validateOAuth2AccessToken; -exports.validateOAuth2IdToken = Wrapper.validateOAuth2IdToken; exports.createTokenForClientCredentials = Wrapper.createTokenForClientCredentials; exports.revokeToken = Wrapper.revokeToken; diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 2a6d65129..bf043da11 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -197,10 +197,17 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, }, authorization: async function (input) { var _a, _b; + if (input.session !== undefined) { + if (input.params.prompt === "none") { + input.params["st_prompt"] = "none"; + delete input.params.prompt; + } + } const resp = await querier.sendGetRequestWithResponseHeaders( 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 @@ -243,6 +250,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, challenge: consentRequest.challenge, grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, grantScope: consentRequest.requestedScope, + remember: true, session: { id_token: idToken, access_token: accessTokenPayload, @@ -482,46 +490,6 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, } return { status: "OK", payload: payload }; }, - validateOAuth2IdToken: async function (input) { - var _a, _b, _c, _d, _e; - const payload = (await jose.jwtVerify(input.token, combinedRemoteJWKSet_1.getCombinedJWKS())).payload; - // TODO: we should be able uncomment this after we get proper core support - // TODO: make this configurable? - // const expectedIssuer = - // appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); - // if (payload.iss !== expectedIssuer) { - // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); - // } - // if (payload.stt !== 2) { - // throw new Error("Wrong token type"); - // } - if ( - ((_a = input.requirements) === null || _a === void 0 ? void 0 : _a.clientId) !== undefined && - payload.client_id !== input.requirements.clientId - ) { - throw new Error("The token doesn't belong to the specified client"); - } - if ( - ((_b = input.requirements) === null || _b === void 0 ? void 0 : _b.scopes) !== undefined && - input.requirements.scopes.some((scope) => !payload.scp.includes(scope)) - ) { - throw new Error("The token is missing some required scopes"); - } - const aud = - payload.aud instanceof Array - ? payload.aud - : (_d = (_c = payload.aud) === null || _c === void 0 ? void 0 : _c.split(" ")) !== null && - _d !== void 0 - ? _d - : []; - if ( - ((_e = input.requirements) === null || _e === void 0 ? void 0 : _e.audience) !== undefined && - !aud.includes(input.requirements.audience) - ) { - throw new Error("The token doesn't belong to the specified audience"); - } - return { status: "OK", payload: payload }; - }, revokeToken: async function (input) { const requestBody = { $isFormData: true, diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index b57e00a2c..da01b04b8 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -234,18 +234,6 @@ export declare type RecipeInterface = { status: "OK"; payload: JSONObject; }>; - validateOAuth2IdToken(input: { - token: string; - requirements?: { - clientId?: string; - scopes?: string[]; - audience?: string; - }; - userContext: UserContext; - }): Promise<{ - status: "OK"; - payload: JSONObject; - }>; buildAccessTokenPayload(input: { user: User; client: OAuth2Client; @@ -303,6 +291,7 @@ export declare type APIInterface = { }) => Promise< | { redirectTo: string; + setCookie: string | undefined; } | GeneralErrorResponse >); diff --git a/lib/build/recipe/passwordless/api/consumeCode.js b/lib/build/recipe/passwordless/api/consumeCode.js index fefe0a19b..7179971cf 100644 --- a/lib/build/recipe/passwordless/api/consumeCode.js +++ b/lib/build/recipe/passwordless/api/consumeCode.js @@ -21,7 +21,7 @@ var __importDefault = Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); -const session_1 = __importDefault(require("../../session")); +const authUtils_1 = require("../../../authUtils"); async function consumeCode(apiImplementation, tenantId, options, userContext) { if (apiImplementation.consumeCodePOST === undefined) { return false; @@ -56,13 +56,11 @@ async function consumeCode(apiImplementation, tenantId, options, userContext) { message: "Please provide one of (linkCode) or (deviceId+userInputCode) and not both", }); } - let session = await session_1.default.getSession( + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); if (session !== undefined) { @@ -76,6 +74,7 @@ async function consumeCode(apiImplementation, tenantId, options, userContext) { preAuthSessionId, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext, } @@ -85,6 +84,7 @@ async function consumeCode(apiImplementation, tenantId, options, userContext) { preAuthSessionId, tenantId, session, + shouldTryLinkingWithSessionUser, userContext, } ); diff --git a/lib/build/recipe/passwordless/api/createCode.js b/lib/build/recipe/passwordless/api/createCode.js index 06b05ceb5..13617a262 100644 --- a/lib/build/recipe/passwordless/api/createCode.js +++ b/lib/build/recipe/passwordless/api/createCode.js @@ -22,7 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); const max_1 = __importDefault(require("libphonenumber-js/max")); -const session_1 = __importDefault(require("../../session")); +const authUtils_1 = require("../../../authUtils"); async function createCode(apiImplementation, tenantId, options, userContext) { if (apiImplementation.createCodePOST === undefined) { return false; @@ -84,13 +84,11 @@ async function createCode(apiImplementation, tenantId, options, userContext) { phoneNumber = parsedPhoneNumber.format("E.164"); } } - let session = await session_1.default.getSession( + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); if (session !== undefined) { @@ -98,8 +96,8 @@ async function createCode(apiImplementation, tenantId, options, userContext) { } let result = await apiImplementation.createCodePOST( email !== undefined - ? { email, session, tenantId, options, userContext } - : { phoneNumber: phoneNumber, session, tenantId, options, userContext } + ? { email, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext } + : { phoneNumber: phoneNumber, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext } ); utils_1.send200Response(options.res, result); return true; diff --git a/lib/build/recipe/passwordless/api/implementation.js b/lib/build/recipe/passwordless/api/implementation.js index f0bcdc3ab..0936acc2c 100644 --- a/lib/build/recipe/passwordless/api/implementation.js +++ b/lib/build/recipe/passwordless/api/implementation.js @@ -178,6 +178,7 @@ function getAPIImplementation() { tenantId: input.tenantId, userContext: input.userContext, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status !== "OK") { // On the frontend, this should show a UI of asking the user @@ -203,6 +204,7 @@ function getAPIImplementation() { deviceId: input.deviceId, userInputCode: input.userInputCode, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId: input.tenantId, userContext: input.userContext, } @@ -210,6 +212,7 @@ function getAPIImplementation() { preAuthSessionId: input.preAuthSessionId, linkCode: input.linkCode, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId: input.tenantId, userContext: input.userContext, } @@ -324,6 +327,7 @@ function getAPIImplementation() { factorIds, userContext: input.userContext, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status !== "OK") { // On the frontend, this should show a UI of asking the user @@ -347,6 +351,7 @@ function getAPIImplementation() { input.userContext ), session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId: input.tenantId, } : { @@ -360,11 +365,16 @@ function getAPIImplementation() { input.userContext ), session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId: input.tenantId, } ); if (response.status !== "OK") { - return authUtils_1.AuthUtils.getErrorStatusResponseWithReason(response, {}, "SIGN_IN_UP_NOT_ALLOWED"); + return authUtils_1.AuthUtils.getErrorStatusResponseWithReason( + response, + errorCodeMap, + "SIGN_IN_UP_NOT_ALLOWED" + ); } // now we send the email / text message. let magicLink = undefined; @@ -493,6 +503,7 @@ function getAPIImplementation() { ); const authTypeInfo = await authUtils_1.AuthUtils.checkAuthTypeAndLinkingStatus( input.session, + input.shouldTryLinkingWithSessionUser, { recipeId: "passwordless", email: deviceInfo.email, @@ -543,7 +554,7 @@ function getAPIImplementation() { let userInputCode = undefined; // This mirrors how we construct factorIds in createCodePOST let factorIds; - if (input.session !== undefined) { + if (!authTypeInfo.isFirstFactor) { if (deviceInfo.email !== undefined) { factorIds = [multifactorauth_1.FactorIds.OTP_EMAIL]; } else { diff --git a/lib/build/recipe/passwordless/api/resendCode.js b/lib/build/recipe/passwordless/api/resendCode.js index 419a905ac..131c04f89 100644 --- a/lib/build/recipe/passwordless/api/resendCode.js +++ b/lib/build/recipe/passwordless/api/resendCode.js @@ -21,7 +21,7 @@ var __importDefault = Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const error_1 = __importDefault(require("../error")); -const session_1 = __importDefault(require("../../session")); +const authUtils_1 = require("../../../authUtils"); async function resendCode(apiImplementation, tenantId, options, userContext) { if (apiImplementation.resendCodePOST === undefined) { return false; @@ -41,23 +41,19 @@ async function resendCode(apiImplementation, tenantId, options, userContext) { message: "Please provide a deviceId", }); } - let session = await session_1.default.getSession( + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); - if (session !== undefined) { - tenantId = session.getTenantId(); - } let result = await apiImplementation.resendCodePOST({ deviceId, preAuthSessionId, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext, }); diff --git a/lib/build/recipe/passwordless/index.js b/lib/build/recipe/passwordless/index.js index 74774d368..285ccb9ed 100644 --- a/lib/build/recipe/passwordless/index.js +++ b/lib/build/recipe/passwordless/index.js @@ -29,6 +29,7 @@ class Wrapper { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createCode( Object.assign(Object.assign({}, input), { session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, userContext: utils_1.getUserContext(input.userContext), }) ); @@ -44,6 +45,7 @@ class Wrapper { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.consumeCode( Object.assign(Object.assign({}, input), { session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, userContext: utils_1.getUserContext(input.userContext), }) ); diff --git a/lib/build/recipe/passwordless/recipe.js b/lib/build/recipe/passwordless/recipe.js index a18ca5fc2..641ed598b 100644 --- a/lib/build/recipe/passwordless/recipe.js +++ b/lib/build/recipe/passwordless/recipe.js @@ -140,6 +140,7 @@ class Recipe extends recipeModule_1.default { email: input.email, userInputCode, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, tenantId: input.tenantId, userContext: input.userContext, } @@ -147,6 +148,7 @@ class Recipe extends recipeModule_1.default { phoneNumber: input.phoneNumber, userInputCode, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, tenantId: input.tenantId, userContext: input.userContext, } @@ -179,12 +181,14 @@ class Recipe extends recipeModule_1.default { email: input.email, tenantId: input.tenantId, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, userContext: input.userContext, } : { phoneNumber: input.phoneNumber, tenantId: input.tenantId, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, userContext: input.userContext, } ); @@ -197,6 +201,7 @@ class Recipe extends recipeModule_1.default { preAuthSessionId: codeInfo.preAuthSessionId, linkCode: codeInfo.linkCode, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, tenantId: input.tenantId, userContext: input.userContext, } @@ -205,6 +210,7 @@ class Recipe extends recipeModule_1.default { deviceId: codeInfo.deviceId, userInputCode: codeInfo.userInputCode, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, tenantId: input.tenantId, userContext: input.userContext, } diff --git a/lib/build/recipe/passwordless/recipeImplementation.js b/lib/build/recipe/passwordless/recipeImplementation.js index f75801f1f..2ec207ff3 100644 --- a/lib/build/recipe/passwordless/recipeImplementation.js +++ b/lib/build/recipe/passwordless/recipeImplementation.js @@ -45,12 +45,13 @@ function getRecipeInterface(querier) { } // Attempt account linking (this is a sign up) let updatedUser = response.user; - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo( + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( { tenantId: input.tenantId, inputUser: response.user, recipeUserId: response.recipeUserId, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, userContext: input.userContext, } ); diff --git a/lib/build/recipe/passwordless/types.d.ts b/lib/build/recipe/passwordless/types.d.ts index 2535a78f6..434f7902e 100644 --- a/lib/build/recipe/passwordless/types.d.ts +++ b/lib/build/recipe/passwordless/types.d.ts @@ -92,6 +92,7 @@ export declare type RecipeInterface = { ) & { userInputCode?: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; } @@ -132,6 +133,7 @@ export declare type RecipeInterface = { deviceId: string; preAuthSessionId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; } @@ -139,6 +141,7 @@ export declare type RecipeInterface = { linkCode: string; preAuthSessionId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; } @@ -305,6 +308,7 @@ export declare type APIInterface = { ) & { tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; } @@ -328,6 +332,7 @@ export declare type APIInterface = { } & { tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; } @@ -351,6 +356,7 @@ export declare type APIInterface = { ) & { tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; } diff --git a/lib/build/recipe/session/recipe.d.ts b/lib/build/recipe/session/recipe.d.ts index 0489b1e81..d6259116f 100644 --- a/lib/build/recipe/session/recipe.d.ts +++ b/lib/build/recipe/session/recipe.d.ts @@ -56,4 +56,12 @@ export default class SessionRecipe extends RecipeModule { response: BaseResponse, userContext: UserContext ) => Promise; + getAccessTokenFromRequest: ( + req: any, + userContext: UserContext + ) => { + requestTransferMethod: import("./types").TokenTransferMethod | undefined; + accessToken: import("./jwt").ParsedJWTInfo | undefined; + allowedTransferMethod: import("./types").TokenTransferMethod | "any"; + }; } diff --git a/lib/build/recipe/session/recipe.js b/lib/build/recipe/session/recipe.js index b8436a829..b7307ae08 100644 --- a/lib/build/recipe/session/recipe.js +++ b/lib/build/recipe/session/recipe.js @@ -34,6 +34,7 @@ const supertokens_js_override_1 = __importDefault(require("supertokens-js-overri const recipe_1 = __importDefault(require("../openid/recipe")); const logger_1 = require("../../logger"); const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); +const sessionRequestFunctions_1 = require("./sessionRequestFunctions"); // For Express class SessionRecipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config) { @@ -183,6 +184,14 @@ class SessionRecipe extends recipeModule_1.default { userContext, }); }; + this.getAccessTokenFromRequest = (req, userContext) => { + const allowedTransferMethod = this.config.getTokenTransferMethod({ + req, + forCreateNewSession: false, + userContext, + }); + return sessionRequestFunctions_1.getAccessTokenFromRequest(req, allowedTransferMethod); + }; this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); const antiCsrfToLog = typeof this.config.antiCsrfFunctionOrString === "string" diff --git a/lib/build/recipe/session/sessionRequestFunctions.d.ts b/lib/build/recipe/session/sessionRequestFunctions.d.ts index 7204931a1..3002f3bd2 100644 --- a/lib/build/recipe/session/sessionRequestFunctions.d.ts +++ b/lib/build/recipe/session/sessionRequestFunctions.d.ts @@ -1,6 +1,13 @@ // @ts-nocheck import Recipe from "./recipe"; -import { VerifySessionOptions, RecipeInterface, TypeNormalisedInput, SessionContainerInterface } from "./types"; +import { + VerifySessionOptions, + RecipeInterface, + TokenTransferMethod, + TypeNormalisedInput, + SessionContainerInterface, +} from "./types"; +import { ParsedJWTInfo } from "./jwt"; import { NormalisedAppinfo, UserContext } from "../../types"; import RecipeUserId from "../../recipeUserId"; export declare function getSessionFromRequest({ @@ -18,6 +25,14 @@ export declare function getSessionFromRequest({ options?: VerifySessionOptions; userContext: UserContext; }): Promise; +export declare function getAccessTokenFromRequest( + req: any, + allowedTransferMethod: TokenTransferMethod | "any" +): { + requestTransferMethod: TokenTransferMethod | undefined; + accessToken: ParsedJWTInfo | undefined; + allowedTransferMethod: TokenTransferMethod | "any"; +}; export declare function refreshSessionInRequest({ res, req, diff --git a/lib/build/recipe/session/sessionRequestFunctions.js b/lib/build/recipe/session/sessionRequestFunctions.js index 1d33897a7..ea6c2819f 100644 --- a/lib/build/recipe/session/sessionRequestFunctions.js +++ b/lib/build/recipe/session/sessionRequestFunctions.js @@ -5,7 +5,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.createNewSessionInRequest = exports.refreshSessionInRequest = exports.getSessionFromRequest = void 0; +exports.createNewSessionInRequest = exports.refreshSessionInRequest = exports.getAccessTokenFromRequest = exports.getSessionFromRequest = void 0; const framework_1 = __importDefault(require("../../framework")); const supertokens_1 = __importDefault(require("../../supertokens")); const utils_1 = require("./utils"); @@ -44,58 +44,12 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op } const sessionOptional = (options === null || options === void 0 ? void 0 : options.sessionRequired) === false; logger_1.logDebugMessage("getSession: optional validation: " + sessionOptional); - const accessTokens = {}; - // We check all token transfer methods for available access tokens - for (const transferMethod of constants_1.availableTokenTransferMethods) { - const tokenString = cookieAndHeaders_1.getToken(req, "access", transferMethod); - if (tokenString !== undefined) { - try { - const info = jwt_1.parseJWTWithoutSignatureVerification(tokenString); - accessToken_1.validateAccessTokenStructure(info.payload, info.version); - logger_1.logDebugMessage("getSession: got access token from " + transferMethod); - accessTokens[transferMethod] = info; - } catch (_a) { - logger_1.logDebugMessage( - `getSession: ignoring token in ${transferMethod}, because it doesn't match our access token structure` - ); - } - } - } const allowedTransferMethod = config.getTokenTransferMethod({ req, forCreateNewSession: false, userContext, }); - let requestTransferMethod; - let accessToken; - if ( - (allowedTransferMethod === "any" || allowedTransferMethod === "header") && - accessTokens["header"] !== undefined - ) { - logger_1.logDebugMessage("getSession: using header transfer method"); - requestTransferMethod = "header"; - accessToken = accessTokens["header"]; - } else if ( - (allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && - accessTokens["cookie"] !== undefined - ) { - logger_1.logDebugMessage("getSession: using cookie transfer method"); - // If multiple access tokens exist in the request cookie, throw TRY_REFRESH_TOKEN. - // This prompts the client to call the refresh endpoint, clearing olderCookieDomain cookies (if set). - // ensuring outdated token payload isn't used. - const hasMultipleAccessTokenCookies = cookieAndHeaders_1.hasMultipleCookiesForTokenType(req, "access"); - if (hasMultipleAccessTokenCookies) { - logger_1.logDebugMessage( - "getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies" - ); - throw new error_1.default({ - message: "Multiple access tokens present in the request cookies.", - type: error_1.default.TRY_REFRESH_TOKEN, - }); - } - requestTransferMethod = "cookie"; - accessToken = accessTokens["cookie"]; - } + const { requestTransferMethod, accessToken } = getAccessTokenFromRequest(req, allowedTransferMethod); let antiCsrfToken = cookieAndHeaders_1.getAntiCsrfTokenFromHeaders(req); let doAntiCsrfCheck = options !== undefined ? options.antiCsrfCheck : undefined; if (doAntiCsrfCheck === undefined) { @@ -168,6 +122,57 @@ async function getSessionFromRequest({ req, res, config, recipeInterfaceImpl, op return session; } exports.getSessionFromRequest = getSessionFromRequest; +function getAccessTokenFromRequest(req, allowedTransferMethod) { + const accessTokens = {}; + // We check all token transfer methods for available access tokens + for (const transferMethod of constants_1.availableTokenTransferMethods) { + const tokenString = cookieAndHeaders_1.getToken(req, "access", transferMethod); + if (tokenString !== undefined) { + try { + const info = jwt_1.parseJWTWithoutSignatureVerification(tokenString); + accessToken_1.validateAccessTokenStructure(info.payload, info.version); + logger_1.logDebugMessage("getSession: got access token from " + transferMethod); + accessTokens[transferMethod] = info; + } catch (_a) { + logger_1.logDebugMessage( + `getSession: ignoring token in ${transferMethod}, because it doesn't match our access token structure` + ); + } + } + } + let requestTransferMethod; + let accessToken; + if ( + (allowedTransferMethod === "any" || allowedTransferMethod === "header") && + accessTokens["header"] !== undefined + ) { + logger_1.logDebugMessage("getSession: using header transfer method"); + requestTransferMethod = "header"; + accessToken = accessTokens["header"]; + } else if ( + (allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && + accessTokens["cookie"] !== undefined + ) { + logger_1.logDebugMessage("getSession: using cookie transfer method"); + // If multiple access tokens exist in the request cookie, throw TRY_REFRESH_TOKEN. + // This prompts the client to call the refresh endpoint, clearing olderCookieDomain cookies (if set). + // ensuring outdated token payload isn't used. + const hasMultipleAccessTokenCookies = cookieAndHeaders_1.hasMultipleCookiesForTokenType(req, "access"); + if (hasMultipleAccessTokenCookies) { + logger_1.logDebugMessage( + "getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies" + ); + throw new error_1.default({ + message: "Multiple access tokens present in the request cookies.", + type: error_1.default.TRY_REFRESH_TOKEN, + }); + } + requestTransferMethod = "cookie"; + accessToken = accessTokens["cookie"]; + } + return { requestTransferMethod, accessToken, allowedTransferMethod }; +} +exports.getAccessTokenFromRequest = getAccessTokenFromRequest; /* In all cases: if sIdRefreshToken token exists (so it's a legacy session) we clear it. Check http://localhost:3002/docs/contribute/decisions/session/0008 for further details and a table of expected behaviours diff --git a/lib/build/recipe/session/utils.js b/lib/build/recipe/session/utils.js index e8f0af0b1..5011474a8 100644 --- a/lib/build/recipe/session/utils.js +++ b/lib/build/recipe/session/utils.js @@ -234,7 +234,7 @@ function validateAndNormaliseUserInput(recipeInstance, appInfo, config) { (_d = config === null || config === void 0 ? void 0 : config.overwriteSessionDuringSignInUp) !== null && _d !== void 0 ? _d - : false, + : true, jwksRefreshIntervalSec: (_e = config === null || config === void 0 ? void 0 : config.jwksRefreshIntervalSec) !== null && _e !== void 0 diff --git a/lib/build/recipe/thirdparty/api/implementation.js b/lib/build/recipe/thirdparty/api/implementation.js index e688b95ae..0188b26dd 100644 --- a/lib/build/recipe/thirdparty/api/implementation.js +++ b/lib/build/recipe/thirdparty/api/implementation.js @@ -128,6 +128,7 @@ function getAPIInterface() { tenantId: input.tenantId, userContext: input.userContext, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status !== "OK") { logger_1.logDebugMessage( @@ -149,6 +150,7 @@ function getAPIInterface() { oAuthTokens: oAuthTokensToUse, rawUserInfoFromProvider: userInfo.rawUserInfoFromProvider, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId, userContext, }); diff --git a/lib/build/recipe/thirdparty/api/signinup.js b/lib/build/recipe/thirdparty/api/signinup.js index 027fcc4d7..772fd4bcd 100644 --- a/lib/build/recipe/thirdparty/api/signinup.js +++ b/lib/build/recipe/thirdparty/api/signinup.js @@ -21,7 +21,7 @@ var __importDefault = Object.defineProperty(exports, "__esModule", { value: true }); const error_1 = __importDefault(require("../error")); const utils_1 = require("../../../utils"); -const session_1 = __importDefault(require("../../session")); +const authUtils_1 = require("../../../authUtils"); async function signInUpAPI(apiImplementation, tenantId, options, userContext) { if (apiImplementation.signInUpPOST === undefined) { return false; @@ -66,13 +66,14 @@ async function signInUpAPI(apiImplementation, tenantId, options, userContext) { }); } const provider = providerResponse; - let session = await session_1.default.getSession( + const shouldTryLinkingWithSessionUser = utils_1.getNormalisedShouldTryLinkingWithSessionUserFlag( + options.req, + bodyParams + ); + const session = await authUtils_1.AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); if (session !== undefined) { @@ -84,6 +85,7 @@ async function signInUpAPI(apiImplementation, tenantId, options, userContext) { oAuthTokens, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext, }); diff --git a/lib/build/recipe/thirdparty/index.js b/lib/build/recipe/thirdparty/index.js index 6e8aadbe4..2ee060ce9 100644 --- a/lib/build/recipe/thirdparty/index.js +++ b/lib/build/recipe/thirdparty/index.js @@ -49,6 +49,7 @@ class Wrapper { tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId, isVerified, session, + shouldTryLinkingWithSessionUser: !!session, userContext: utils_1.getUserContext(userContext), }); } diff --git a/lib/build/recipe/thirdparty/recipeImplementation.js b/lib/build/recipe/thirdparty/recipeImplementation.js index 0e8ee80f1..dce4ef1e6 100644 --- a/lib/build/recipe/thirdparty/recipeImplementation.js +++ b/lib/build/recipe/thirdparty/recipeImplementation.js @@ -23,6 +23,7 @@ function getRecipeImplementation(querier, providers) { isVerified, tenantId, session, + shouldTryLinkingWithSessionUser, userContext, }) { const accountLinking = recipe_1.default.getInstance(); @@ -73,9 +74,10 @@ function getRecipeImplementation(querier, providers) { // we do this so that we get the updated user (in case the above // function updated the verification status) and can return that response.user = await __1.getUser(response.recipeUserId.getAsString(), userContext); - const linkResult = await authUtils_1.AuthUtils.linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo( + const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo( { tenantId, + shouldTryLinkingWithSessionUser, inputUser: response.user, recipeUserId: response.recipeUserId, session, @@ -101,6 +103,7 @@ function getRecipeImplementation(querier, providers) { userContext, oAuthTokens, session, + shouldTryLinkingWithSessionUser, rawUserInfoFromProvider, }) { let response = await this.manuallyCreateOrUpdateUser({ @@ -110,6 +113,7 @@ function getRecipeImplementation(querier, providers) { tenantId, isVerified, session, + shouldTryLinkingWithSessionUser, userContext, }); if (response.status === "EMAIL_CHANGE_NOT_ALLOWED_ERROR") { diff --git a/lib/build/recipe/thirdparty/types.d.ts b/lib/build/recipe/thirdparty/types.d.ts index 041656d62..8d0198e3b 100644 --- a/lib/build/recipe/thirdparty/types.d.ts +++ b/lib/build/recipe/thirdparty/types.d.ts @@ -175,6 +175,7 @@ export declare type RecipeInterface = { }; }; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< @@ -214,6 +215,7 @@ export declare type RecipeInterface = { email: string; isVerified: boolean; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< @@ -275,6 +277,7 @@ export declare type APIInterface = { provider: TypeProvider; tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; } & ( diff --git a/lib/build/utils.d.ts b/lib/build/utils.d.ts index f583171fe..8743e5767 100644 --- a/lib/build/utils.d.ts +++ b/lib/build/utils.d.ts @@ -12,6 +12,7 @@ export declare function sendNon200ResponseWithMessage(res: BaseResponse, message export declare function sendNon200Response(res: BaseResponse, statusCode: number, body: JSONObject): void; export declare function send200Response(res: BaseResponse, responseJson: any): void; export declare function isAnIpAddress(ipaddress: string): boolean; +export declare function getNormalisedShouldTryLinkingWithSessionUserFlag(req: BaseRequest, body: any): any; export declare function getBackwardsCompatibleUserInfo( req: BaseRequest, result: { diff --git a/lib/build/utils.js b/lib/build/utils.js index 913001f36..30bbed8ea 100644 --- a/lib/build/utils.js +++ b/lib/build/utils.js @@ -41,7 +41,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.transformObjectKeys = exports.toSnakeCase = exports.toCamelCase = exports.normaliseEmail = exports.postWithFetch = exports.getFromObjectCaseInsensitive = exports.getTopLevelDomainForSameSiteResolution = exports.setRequestInUserContextIfNotDefined = exports.getUserContext = exports.makeDefaultUserContextFromAPI = exports.humaniseMilliseconds = exports.frontendHasInterceptor = exports.getRidFromHeader = exports.hasGreaterThanEqualToFDI = exports.getLatestFDIVersionFromFDIList = exports.getBackwardsCompatibleUserInfo = exports.isAnIpAddress = exports.send200Response = exports.sendNon200Response = exports.sendNon200ResponseWithMessage = exports.normaliseHttpMethod = exports.normaliseInputAppInfoOrThrowError = exports.maxVersion = exports.getLargestVersionFromIntersection = exports.doFetch = void 0; +exports.transformObjectKeys = exports.toSnakeCase = exports.toCamelCase = exports.normaliseEmail = exports.postWithFetch = exports.getFromObjectCaseInsensitive = exports.getTopLevelDomainForSameSiteResolution = exports.setRequestInUserContextIfNotDefined = exports.getUserContext = exports.makeDefaultUserContextFromAPI = exports.humaniseMilliseconds = exports.frontendHasInterceptor = exports.getRidFromHeader = exports.hasGreaterThanEqualToFDI = exports.getLatestFDIVersionFromFDIList = exports.getBackwardsCompatibleUserInfo = exports.getNormalisedShouldTryLinkingWithSessionUserFlag = exports.isAnIpAddress = exports.send200Response = exports.sendNon200Response = exports.sendNon200ResponseWithMessage = exports.normaliseHttpMethod = exports.normaliseInputAppInfoOrThrowError = exports.maxVersion = exports.getLargestVersionFromIntersection = exports.doFetch = void 0; const psl = __importStar(require("psl")); const normalisedURLDomain_1 = __importDefault(require("./normalisedURLDomain")); const normalisedURLPath_1 = __importDefault(require("./normalisedURLPath")); @@ -224,6 +224,14 @@ function isAnIpAddress(ipaddress) { ); } exports.isAnIpAddress = isAnIpAddress; +function getNormalisedShouldTryLinkingWithSessionUserFlag(req, body) { + var _a; + if (hasGreaterThanEqualToFDI(req, "3.1")) { + return (_a = body.shouldTryLinkingWithSessionUser) !== null && _a !== void 0 ? _a : false; + } + return undefined; +} +exports.getNormalisedShouldTryLinkingWithSessionUserFlag = getNormalisedShouldTryLinkingWithSessionUserFlag; function getBackwardsCompatibleUserInfo(req, result, userContext) { let resp; // (>= 1.18 && < 2.0) || >= 3.0: This is because before 1.18, and between 2 and 3, FDI does not diff --git a/lib/build/version.d.ts b/lib/build/version.d.ts index 46c0c5e1a..ba4e3898a 100644 --- a/lib/build/version.d.ts +++ b/lib/build/version.d.ts @@ -1,4 +1,4 @@ // @ts-nocheck -export declare const version = "20.0.0"; +export declare const version = "20.0.2"; export declare const cdiSupported: string[]; export declare const dashboardVersion = "0.13"; diff --git a/lib/build/version.js b/lib/build/version.js index 17148bd6b..0d9902d49 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 = "20.0.0"; +exports.version = "20.0.2"; exports.cdiSupported = ["5.1"]; // Note: The actual script import for dashboard uses v{DASHBOARD_VERSION} exports.dashboardVersion = "0.13"; diff --git a/lib/ts/authUtils.ts b/lib/ts/authUtils.ts index 0443941bf..551563934 100644 --- a/lib/ts/authUtils.ts +++ b/lib/ts/authUtils.ts @@ -10,7 +10,7 @@ import RecipeUserId from "./recipeUserId"; import { updateAndGetMFARelatedInfoInSession } from "./recipe/multifactorauth/utils"; import { isValidFirstFactor } from "./recipe/multitenancy/utils"; import SessionError from "./recipe/session/error"; -import { getUser } from "."; +import { Error as STError, getUser } from "."; import { AccountInfoWithRecipeId } from "./recipe/accountlinking/types"; import { BaseRequest, BaseResponse } from "./framework"; import SessionRecipe from "./recipe/session/recipe"; @@ -82,6 +82,7 @@ export const AuthUtils = { factorIds, skipSessionUserUpdateInCore, session, + shouldTryLinkingWithSessionUser, userContext, }: { authenticatingAccountInfo: AccountInfoWithRecipeId; @@ -93,6 +94,7 @@ export const AuthUtils = { signInVerifiesLoginMethod: boolean; skipSessionUserUpdateInCore: boolean; session?: SessionContainerInterface; + shouldTryLinkingWithSessionUser: boolean | undefined; userContext: UserContext; }): Promise< | { status: "OK"; validFactorIds: string[]; isFirstFactor: boolean } @@ -118,6 +120,7 @@ export const AuthUtils = { // We also load the session user here if it is available. const authTypeInfo = await AuthUtils.checkAuthTypeAndLinkingStatus( session, + shouldTryLinkingWithSessionUser, authenticatingAccountInfo, authenticatingUser, skipSessionUserUpdateInCore, @@ -287,6 +290,9 @@ export const AuthUtils = { } } } else { + // We do not have to care about overwriting the session here, since we either: + // - have overwriteSessionDuringSignInUp true and can ignore it + // - have overwriteSessionDuringSignInUp false and we checked in the api imlp that there is no session logDebugMessage(`postAuthChecks creating session for first factor sign in/up`); // If there is no input session, we do not need to do anything other checks and create a new session respSession = await Session.createNewSession(req, res, tenantId, recipeUserId, {}, {}, userContext); @@ -480,6 +486,7 @@ export const AuthUtils = { */ checkAuthTypeAndLinkingStatus: async function ( session: SessionContainerInterface | undefined, + shouldTryLinkingWithSessionUser: boolean | undefined, accountInfo: AccountInfoWithRecipeId, inputUser: User | undefined, skipSessionUserUpdateInCore: boolean, @@ -503,17 +510,36 @@ export const AuthUtils = { logDebugMessage(`checkAuthTypeAndLinkingStatus called`); let sessionUser: User | undefined = undefined; if (session === undefined) { + if (shouldTryLinkingWithSessionUser === true) { + throw new SessionError({ + type: SessionError.UNAUTHORISED, + message: "Session not found but shouldTryLinkingWithSessionUser is true", + }); + } logDebugMessage(`checkAuthTypeAndLinkingStatus returning first factor because there is no session`); // If there is no active session we have nothing to link to - so this has to be a first factor sign in return { status: "OK", isFirstFactor: true }; } else { + if (shouldTryLinkingWithSessionUser === false) { + // In our normal flows this should never happen - but some user overrides might do this. + // Anyway, since shouldTryLinkingWithSessionUser explicitly set to false, it's safe to consider this a firstFactor + return { status: "OK", isFirstFactor: true }; + } + if (!recipeInitDefinedShouldDoAutomaticAccountLinking(AccountLinking.getInstance().config)) { - if (MultiFactorAuthRecipe.getInstance() !== undefined) { + if (shouldTryLinkingWithSessionUser === true) { throw new Error( "Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA" ); } else { - return { status: "OK", isFirstFactor: true }; + // This is the legacy case where shouldTryLinkingWithSessionUser is undefined + if (MultiFactorAuthRecipe.getInstance() !== undefined) { + throw new Error( + "Please initialise the account linking recipe and define shouldDoAutomaticAccountLinking to enable MFA" + ); + } else { + return { status: "OK", isFirstFactor: true }; + } } } @@ -542,6 +568,14 @@ export const AuthUtils = { userContext ); if (sessionUserResult.status === "SHOULD_AUTOMATICALLY_LINK_FALSE") { + if (shouldTryLinkingWithSessionUser === true) { + throw new STError({ + message: + "shouldDoAutomaticAccountLinking returned false when creating primary user but shouldTryLinkingWithSessionUser is true", + type: "BAD_INPUT_ERROR", + }); + } + return { status: "OK", isFirstFactor: true, @@ -572,6 +606,13 @@ export const AuthUtils = { ); if (shouldLink.shouldAutomaticallyLink === false) { + if (shouldTryLinkingWithSessionUser === true) { + throw new STError({ + message: + "shouldDoAutomaticAccountLinking returned false when creating primary user but shouldTryLinkingWithSessionUser is true", + type: "BAD_INPUT_ERROR", + }); + } return { status: "OK", isFirstFactor: true }; } else { return { @@ -599,17 +640,19 @@ export const AuthUtils = { * - LINKING_TO_SESSION_USER_FAILED (SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR): * if the session user should be primary but we couldn't make it primary because of a conflicting primary user. */ - linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo: async function ({ + linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo: async function ({ tenantId, inputUser, recipeUserId, session, + shouldTryLinkingWithSessionUser, userContext, }: { tenantId: string; inputUser: User; recipeUserId: RecipeUserId; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; userContext: UserContext; }): Promise< | { status: "OK"; user: User } @@ -625,10 +668,11 @@ export const AuthUtils = { logDebugMessage("linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo called"); const retry = () => { logDebugMessage("linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo retrying...."); - return AuthUtils.linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo({ + return AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ tenantId, inputUser: inputUser, session, + shouldTryLinkingWithSessionUser, recipeUserId, userContext, }); @@ -647,6 +691,7 @@ export const AuthUtils = { const authTypeRes = await AuthUtils.checkAuthTypeAndLinkingStatus( session, + shouldTryLinkingWithSessionUser, authLoginMethod, inputUser, false, @@ -959,6 +1004,26 @@ export const AuthUtils = { return validFactorIds; }, + loadSessionInAuthAPIIfNeeded: async function ( + req: BaseRequest, + res: BaseResponse, + shouldTryLinkingWithSessionUser: boolean | undefined, + userContext: UserContext + ) { + const overwriteSessionDuringSignInUp = SessionRecipe.getInstanceOrThrowError().config + .overwriteSessionDuringSignInUp; + return shouldTryLinkingWithSessionUser !== false || !overwriteSessionDuringSignInUp + ? await Session.getSession( + req, + res, + { + sessionRequired: shouldTryLinkingWithSessionUser === true, + overrideGlobalClaimValidators: () => [], + }, + userContext + ) + : undefined; + }, }; async function filterOutInvalidSecondFactorsOrThrowIfAllAreInvalid( diff --git a/lib/ts/recipe/accountlinking/index.ts b/lib/ts/recipe/accountlinking/index.ts index 995a341e0..e878130e2 100644 --- a/lib/ts/recipe/accountlinking/index.ts +++ b/lib/ts/recipe/accountlinking/index.ts @@ -165,6 +165,10 @@ export default class Wrapper { ) { const user = await getUser(recipeUserId.getAsString(), userContext); + if (user === undefined) { + throw new Error("Passed in recipe user id does not exist"); + } + const res = await Recipe.getInstance().isEmailChangeAllowed({ user, newEmail, diff --git a/lib/ts/recipe/accountlinking/recipe.ts b/lib/ts/recipe/accountlinking/recipe.ts index d7f5a33ce..b57b9329a 100644 --- a/lib/ts/recipe/accountlinking/recipe.ts +++ b/lib/ts/recipe/accountlinking/recipe.ts @@ -523,7 +523,7 @@ export default class Recipe extends RecipeModule { }; isEmailChangeAllowed = async (input: { - user?: User; + user: User; newEmail: string; isVerified: boolean; session: SessionContainerInterface | undefined; @@ -546,10 +546,6 @@ export default class Recipe extends RecipeModule { let inputUser = input.user; - if (inputUser === undefined) { - throw new Error("Passed in recipe user id does not exist"); - } - for (const tenantId of inputUser.tenantIds) { let existingUsersWithNewEmail = await this.recipeInterfaceImpl.listUsersByAccountInfo({ tenantId, @@ -929,7 +925,7 @@ export default class Recipe extends RecipeModule { // we can use the 0 index cause targetUser is not a primary user. let shouldDoAccountLinking = await this.config.shouldDoAutomaticAccountLinking( inputUser.loginMethods[0], - primaryUserThatCanBeLinkedToTheInputUser, + createPrimaryUserResult.user, session, tenantId, userContext diff --git a/lib/ts/recipe/emailpassword/api/implementation.ts b/lib/ts/recipe/emailpassword/api/implementation.ts index 843e270cd..2e35c5edf 100644 --- a/lib/ts/recipe/emailpassword/api/implementation.ts +++ b/lib/ts/recipe/emailpassword/api/implementation.ts @@ -1,6 +1,5 @@ import { APIInterface, APIOptions } from "../"; import { logDebugMessage } from "../../../logger"; -import { SessionContainerInterface } from "../../session/types"; import { GeneralErrorResponse, User, UserContext } from "../../../types"; import { getUser } from "../../../"; import AccountLinking from "../../accountlinking/recipe"; @@ -591,32 +590,10 @@ export default function getAPIImplementation(): APIInterface { formFields, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext, - }: { - formFields: { - id: string; - value: string; - }[]; - tenantId: string; - session?: SessionContainerInterface; - options: APIOptions; - userContext: UserContext; - }): Promise< - | { - status: "OK"; - session: SessionContainerInterface; - user: User; - } - | { - status: "WRONG_CREDENTIALS_ERROR"; - } - | { - status: "SIGN_IN_NOT_ALLOWED"; - reason: string; - } - | GeneralErrorResponse - > { + }) { const errorCodeMap = { SIGN_IN_NOT_ALLOWED: "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)", @@ -683,6 +660,7 @@ export default function getAPIImplementation(): APIInterface { tenantId, userContext, session, + shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status === "SIGN_UP_NOT_ALLOWED") { throw new Error("This should never happen: pre-auth checks should not fail for sign in"); @@ -702,6 +680,7 @@ export default function getAPIImplementation(): APIInterface { email, password, session, + shouldTryLinkingWithSessionUser, tenantId, userContext, }); @@ -740,32 +719,10 @@ export default function getAPIImplementation(): APIInterface { formFields, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext, - }: { - formFields: { - id: string; - value: string; - }[]; - tenantId: string; - session?: SessionContainerInterface; - options: APIOptions; - userContext: UserContext; - }): Promise< - | { - status: "OK"; - session: SessionContainerInterface; - user: User; - } - | { - status: "SIGN_UP_NOT_ALLOWED"; - reason: string; - } - | { - status: "EMAIL_ALREADY_EXISTS_ERROR"; - } - | GeneralErrorResponse - > { + }) { const errorCodeMap = { SIGN_UP_NOT_ALLOWED: "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", @@ -797,6 +754,7 @@ export default function getAPIImplementation(): APIInterface { tenantId, userContext, session, + shouldTryLinkingWithSessionUser, }); if (preAuthCheckRes.status === "SIGN_UP_NOT_ALLOWED") { @@ -834,6 +792,7 @@ export default function getAPIImplementation(): APIInterface { email, password, session, + shouldTryLinkingWithSessionUser, userContext, }); diff --git a/lib/ts/recipe/emailpassword/api/signin.ts b/lib/ts/recipe/emailpassword/api/signin.ts index 77c1161ff..2899715cf 100644 --- a/lib/ts/recipe/emailpassword/api/signin.ts +++ b/lib/ts/recipe/emailpassword/api/signin.ts @@ -13,11 +13,15 @@ * under the License. */ -import { getBackwardsCompatibleUserInfo, send200Response } from "../../../utils"; +import { + getBackwardsCompatibleUserInfo, + getNormalisedShouldTryLinkingWithSessionUserFlag, + send200Response, +} from "../../../utils"; import { validateFormFieldsOrThrowError } from "./utils"; import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -import Session from "../../session"; +import { AuthUtils } from "../../../authUtils"; export default async function signInAPI( apiImplementation: APIInterface, @@ -30,24 +34,24 @@ export default async function signInAPI( return false; } + const body = await options.req.getJSONBody(); // step 1 let formFields: { id: string; value: string; }[] = await validateFormFieldsOrThrowError( options.config.signInFeature.formFields, - (await options.req.getJSONBody()).formFields, + body.formFields, tenantId, userContext ); - let session = await Session.getSession( + const shouldTryLinkingWithSessionUser = getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); + + const session = await AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); @@ -59,6 +63,7 @@ export default async function signInAPI( formFields, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext, }); diff --git a/lib/ts/recipe/emailpassword/api/signup.ts b/lib/ts/recipe/emailpassword/api/signup.ts index ec9a2781d..67a4b49e1 100644 --- a/lib/ts/recipe/emailpassword/api/signup.ts +++ b/lib/ts/recipe/emailpassword/api/signup.ts @@ -13,12 +13,16 @@ * under the License. */ -import { getBackwardsCompatibleUserInfo, send200Response } from "../../../utils"; +import { + getBackwardsCompatibleUserInfo, + getNormalisedShouldTryLinkingWithSessionUserFlag, + send200Response, +} from "../../../utils"; import { validateFormFieldsOrThrowError } from "./utils"; import { APIInterface, APIOptions } from "../"; import STError from "../error"; import { UserContext } from "../../../types"; -import Session from "../../session"; +import { AuthUtils } from "../../../authUtils"; export default async function signUpAPI( apiImplementation: APIInterface, @@ -45,16 +49,14 @@ export default async function signUpAPI( userContext ); - let session = await Session.getSession( + const shouldTryLinkingWithSessionUser = getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, requestBody); + + const session = await AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); - if (session !== undefined) { tenantId = session.getTenantId(); } @@ -63,6 +65,7 @@ export default async function signUpAPI( formFields, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext: userContext, }); diff --git a/lib/ts/recipe/emailpassword/index.ts b/lib/ts/recipe/emailpassword/index.ts index e654d16b4..d5b071b51 100644 --- a/lib/ts/recipe/emailpassword/index.ts +++ b/lib/ts/recipe/emailpassword/index.ts @@ -91,6 +91,7 @@ export default class Wrapper { email, password, session, + shouldTryLinkingWithSessionUser: !!session, tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, userContext: getUserContext(userContext), }); @@ -143,6 +144,7 @@ export default class Wrapper { email, password, session, + shouldTryLinkingWithSessionUser: !!session, tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, userContext: getUserContext(userContext), }); diff --git a/lib/ts/recipe/emailpassword/recipeImplementation.ts b/lib/ts/recipe/emailpassword/recipeImplementation.ts index 618f2eb2e..1c883021c 100644 --- a/lib/ts/recipe/emailpassword/recipeImplementation.ts +++ b/lib/ts/recipe/emailpassword/recipeImplementation.ts @@ -18,7 +18,7 @@ export default function getRecipeInterface( return { signUp: async function ( this: RecipeInterface, - { email, password, tenantId, session, userContext } + { email, password, tenantId, session, shouldTryLinkingWithSessionUser, userContext } ): Promise< | { status: "OK"; @@ -47,11 +47,12 @@ export default function getRecipeInterface( let updatedUser = response.user; - const linkResult = await AuthUtils.linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo({ + const linkResult = await AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ tenantId, inputUser: response.user, recipeUserId: response.recipeUserId, session, + shouldTryLinkingWithSessionUser, userContext, }); @@ -103,7 +104,10 @@ export default function getRecipeInterface( // users are always initially unverified. }, - signIn: async function (this: RecipeInterface, { email, password, tenantId, session, userContext }) { + signIn: async function ( + this: RecipeInterface, + { email, password, tenantId, session, shouldTryLinkingWithSessionUser, userContext } + ) { const response = await this.verifyCredentials({ email, password, tenantId, userContext }); if (response.status === "OK") { @@ -133,11 +137,12 @@ export default function getRecipeInterface( response.user = (await getUser(response.recipeUserId!.getAsString(), userContext))!; } - const linkResult = await AuthUtils.linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo({ + const linkResult = await AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ tenantId, inputUser: response.user, recipeUserId: response.recipeUserId, session, + shouldTryLinkingWithSessionUser, userContext, }); if (linkResult.status === "LINKING_TO_SESSION_USER_FAILED") { diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index ee8b481df..563c95d4f 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -88,6 +88,7 @@ export type RecipeInterface = { email: string; password: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< @@ -128,6 +129,7 @@ export type RecipeInterface = { email: string; password: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< @@ -277,6 +279,7 @@ export type APIInterface = { }[]; tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; }) => Promise< @@ -304,6 +307,7 @@ export type APIInterface = { }[]; tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; }) => Promise< diff --git a/lib/ts/recipe/multifactorauth/multiFactorAuthClaim.ts b/lib/ts/recipe/multifactorauth/multiFactorAuthClaim.ts index 2d7fd36a0..70dd5ed50 100644 --- a/lib/ts/recipe/multifactorauth/multiFactorAuthClaim.ts +++ b/lib/ts/recipe/multifactorauth/multiFactorAuthClaim.ts @@ -232,8 +232,9 @@ export class MultiFactorAuthClaimClass extends SessionClaim { return retVal; }; - public removeFromPayloadByMerge_internal = () => { + public removeFromPayloadByMerge_internal = (payload: JSONObject) => { return { + ...payload, [this.key]: null, }; }; diff --git a/lib/ts/recipe/oauth2provider/api/implementation.ts b/lib/ts/recipe/oauth2provider/api/implementation.ts index 8ca86eb63..0e3bb71bc 100644 --- a/lib/ts/recipe/oauth2provider/api/implementation.ts +++ b/lib/ts/recipe/oauth2provider/api/implementation.ts @@ -23,6 +23,7 @@ export default function getAPIImplementation(): APIInterface { recipeImplementation: options.recipeImplementation, loginChallenge, session, + isDirectCall: true, userContext, }); return handleInternalRedirects({ diff --git a/lib/ts/recipe/oauth2provider/api/login.ts b/lib/ts/recipe/oauth2provider/api/login.ts index ef0c2cb9f..78ef67780 100644 --- a/lib/ts/recipe/oauth2provider/api/login.ts +++ b/lib/ts/recipe/oauth2provider/api/login.ts @@ -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"; @@ -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; diff --git a/lib/ts/recipe/oauth2provider/api/token.ts b/lib/ts/recipe/oauth2provider/api/token.ts index 2414aea33..0ed290282 100644 --- a/lib/ts/recipe/oauth2provider/api/token.ts +++ b/lib/ts/recipe/oauth2provider/api/token.ts @@ -36,7 +36,10 @@ export default async function tokenPOST( }); if ("statusCode" in response && response.statusCode !== 200) { - sendNon200Response(options.res, response.statusCode!, response); + sendNon200Response(options.res, response.statusCode!, { + error: response.error, + error_description: response.errorDescription, + }); } else { send200Response(options.res, response); } diff --git a/lib/ts/recipe/oauth2provider/api/utils.ts b/lib/ts/recipe/oauth2provider/api/utils.ts index 3eba3cd15..735979690 100644 --- a/lib/ts/recipe/oauth2provider/api/utils.ts +++ b/lib/ts/recipe/oauth2provider/api/utils.ts @@ -1,5 +1,7 @@ import SuperTokens from "../../../supertokens"; import { UserContext } from "../../../types"; +import { DEFAULT_TENANT_ID } from "../../multitenancy/constants"; +import { getSessionInformation } from "../../session"; import { SessionContainerInterface } from "../../session/types"; import { AUTH_PATH, LOGIN_PATH } from "../constants"; import { RecipeInterface } from "../types"; @@ -12,6 +14,7 @@ export async function loginGET({ loginChallenge, session, setCookie, + isDirectCall, userContext, }: { recipeImplementation: RecipeInterface; @@ -19,37 +22,78 @@ export async function loginGET({ 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") ?? incomingAuthUrlQueryParams.get("st_prompt"); + const maxAgeParam = incomingAuthUrlQueryParams.get("max_age"); + if (maxAgeParam !== null) { + try { + const maxAgeParsed = Number.parseInt(maxAgeParam); + if (maxAgeParsed < 0) { + const reject = await recipeImplementation.rejectLoginRequest({ + challenge: loginChallenge, + error: { + error: "invalid_request", + errorDescription: "max_age cannot be negative", + }, + userContext, + }); + return { redirectTo: reject.redirectTo, setCookie }; + } + } catch { + const reject = await recipeImplementation.rejectLoginRequest({ + challenge: loginChallenge, + error: { + error: "invalid_request", + errorDescription: "max_age must be an integer", + }, + userContext, + }); + return { redirectTo: reject.redirectTo, setCookie }; + } + } + const tenantIdParam = incomingAuthUrlQueryParams.get("tenant_id"); + if ( + session && + (["", undefined].includes(loginRequest.subject) || session.getUserId() === loginRequest.subject) && + (["", null].includes(tenantIdParam) || session.getTenantId() === tenantIdParam) && + (promptParam !== "login" || isDirectCall) && + (maxAgeParam === null || + (maxAgeParam === "0" && isDirectCall) || + Number.parseInt(maxAgeParam) * 1000 > Date.now() - sessionInfo!.timeCreated) + ) { const accept = await recipeImplementation.acceptLoginRequest({ challenge: loginChallenge, - identityProviderSessionId: session?.getHandle(), - subject: request.subject, + subject: session.getUserId(), + identityProviderSessionId: session.getHandle(), + remember: true, + rememberFor: 3600, userContext, }); - return { redirectTo: accept.redirectTo, setCookie }; - } else if (session && (!request.subject || session.getUserId() === request.subject)) { - const accept = await recipeImplementation.acceptLoginRequest({ + } + if (promptParam === "none") { + const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, - subject: session.getUserId(), - identityProviderSessionId: session.getHandle(), + error: { + error: "login_required", + errorDescription: + "The Authorization Server requires End-User authentication. Prompt 'none' was requested, but no existing or expired login session was found.", + }, userContext, }); - return { redirectTo: accept.redirectTo, setCookie }; + return { redirectTo: reject.redirectTo, setCookie }; } const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; const websiteDomain = appInfo @@ -60,8 +104,24 @@ 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 || promptParam === "login") { + queryParamsForAuthPage.set("forceFreshAuth", "true"); + } + + if (tenantIdParam !== null && tenantIdParam !== DEFAULT_TENANT_ID) { + queryParamsForAuthPage.set("tenantId", tenantIdParam); + } + return { - redirectTo: websiteDomain + websiteBasePath + `?${queryParams.toString()}`, + redirectTo: websiteDomain + websiteBasePath + `?${queryParamsForAuthPage.toString()}`, setCookie, }; } @@ -98,7 +158,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 { @@ -154,6 +214,7 @@ export async function handleInternalRedirects({ loginChallenge, session, setCookie: response.setCookie, + isDirectCall: false, userContext, }); diff --git a/lib/ts/recipe/oauth2provider/index.ts b/lib/ts/recipe/oauth2provider/index.ts index 6bc70f067..9fb20b0ba 100644 --- a/lib/ts/recipe/oauth2provider/index.ts +++ b/lib/ts/recipe/oauth2provider/index.ts @@ -77,22 +77,6 @@ export default class Wrapper { }); } - static validateOAuth2IdToken( - token: string, - requirements?: { - clientId?: string; - scopes?: string[]; - audience?: string; - }, - userContext?: Record - ) { - return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2IdToken({ - token, - requirements, - userContext: getUserContext(userContext), - }); - } - static createTokenForClientCredentials( clientId: string, clientSecret: string, @@ -174,8 +158,6 @@ export let deleteOAuth2Client = Wrapper.deleteOAuth2Client; export let validateOAuth2AccessToken = Wrapper.validateOAuth2AccessToken; -export let validateOAuth2IdToken = Wrapper.validateOAuth2IdToken; - export let createTokenForClientCredentials = Wrapper.createTokenForClientCredentials; export let revokeToken = Wrapper.revokeToken; diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index 21f2dd1d3..f870812d6 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -176,10 +176,18 @@ export default function getRecipeInterface( }; }, authorization: async function (this: RecipeInterface, input) { + if (input.session !== undefined) { + if (input.params.prompt === "none") { + input.params["st_prompt"] = "none"; + delete input.params.prompt; + } + } + const resp = await querier.sendGetRequestWithResponseHeaders( new NormalisedURLPath(`/recipe/oauth2/pub/auth`), input.params, { + // TODO: if session is not set also clear the oauth2 cookie Cookie: `${input.cookies}`, }, input.userContext @@ -227,6 +235,7 @@ export default function getRecipeInterface( challenge: consentRequest.challenge, grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, grantScope: consentRequest.requestedScope, + remember: true, // TODO: verify that we need this session: { id_token: idToken, access_token: accessTokenPayload, @@ -474,39 +483,6 @@ export default function getRecipeInterface( } return { status: "OK", payload: payload as JSONObject }; }, - validateOAuth2IdToken: async function (input) { - const payload = (await jose.jwtVerify(input.token, getCombinedJWKS())).payload; - - // TODO: we should be able uncomment this after we get proper core support - // TODO: make this configurable? - // const expectedIssuer = - // appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); - // if (payload.iss !== expectedIssuer) { - // throw new Error("Issuer mismatch: this token was likely issued by another application or spoofed"); - // } - // if (payload.stt !== 2) { - // throw new Error("Wrong token type"); - // } - - if (input.requirements?.clientId !== undefined && payload.client_id !== input.requirements.clientId) { - throw new Error("The token doesn't belong to the specified client"); - } - - if ( - input.requirements?.scopes !== undefined && - input.requirements.scopes.some((scope) => !(payload.scp as string[]).includes(scope)) - ) { - throw new Error("The token is missing some required scopes"); - } - - const aud = payload.aud instanceof Array ? payload.aud : payload.aud?.split(" ") ?? []; - if (input.requirements?.audience !== undefined && !aud.includes(input.requirements.audience)) { - throw new Error("The token doesn't belong to the specified audience"); - } - - return { status: "OK", payload: payload as JSONObject }; - }, - revokeToken: async function (this: RecipeInterface, input) { const requestBody: Record = { $isFormData: true, diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index accaad4a2..255966931 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -345,15 +345,6 @@ export type RecipeInterface = { checkDatabase?: boolean; userContext: UserContext; }): Promise<{ status: "OK"; payload: JSONObject }>; - validateOAuth2IdToken(input: { - token: string; - requirements?: { - clientId?: string; - scopes?: string[]; - audience?: string; - }; - userContext: UserContext; - }): Promise<{ status: "OK"; payload: JSONObject }>; buildAccessTokenPayload(input: { user: User; @@ -402,7 +393,7 @@ export type APIInterface = { options: APIOptions; session?: SessionContainerInterface; userContext: UserContext; - }) => Promise<{ redirectTo: string } | GeneralErrorResponse>); + }) => Promise<{ redirectTo: string; setCookie: string | undefined } | GeneralErrorResponse>); authGET: | undefined diff --git a/lib/ts/recipe/passwordless/api/consumeCode.ts b/lib/ts/recipe/passwordless/api/consumeCode.ts index 38aa06c4b..39229cca7 100644 --- a/lib/ts/recipe/passwordless/api/consumeCode.ts +++ b/lib/ts/recipe/passwordless/api/consumeCode.ts @@ -13,11 +13,15 @@ * under the License. */ -import { getBackwardsCompatibleUserInfo, send200Response } from "../../../utils"; +import { + getBackwardsCompatibleUserInfo, + getNormalisedShouldTryLinkingWithSessionUserFlag, + send200Response, +} from "../../../utils"; import STError from "../error"; import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -import Session from "../../session"; +import { AuthUtils } from "../../../authUtils"; export default async function consumeCode( apiImplementation: APIInterface, @@ -62,13 +66,12 @@ export default async function consumeCode( }); } - let session = await Session.getSession( + const shouldTryLinkingWithSessionUser = getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); + + const session = await AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); @@ -84,6 +87,7 @@ export default async function consumeCode( preAuthSessionId, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext, } @@ -93,6 +97,7 @@ export default async function consumeCode( preAuthSessionId, tenantId, session, + shouldTryLinkingWithSessionUser, userContext, } ); diff --git a/lib/ts/recipe/passwordless/api/createCode.ts b/lib/ts/recipe/passwordless/api/createCode.ts index af46ff86d..246742c8c 100644 --- a/lib/ts/recipe/passwordless/api/createCode.ts +++ b/lib/ts/recipe/passwordless/api/createCode.ts @@ -13,12 +13,12 @@ * under the License. */ -import { send200Response } from "../../../utils"; +import { getNormalisedShouldTryLinkingWithSessionUserFlag, send200Response } from "../../../utils"; import STError from "../error"; import { APIInterface, APIOptions } from ".."; import parsePhoneNumber from "libphonenumber-js/max"; import { UserContext } from "../../../types"; -import Session from "../../session"; +import { AuthUtils } from "../../../authUtils"; export default async function createCode( apiImplementation: APIInterface, @@ -93,13 +93,12 @@ export default async function createCode( } } - let session = await Session.getSession( + const shouldTryLinkingWithSessionUser = getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); + + const session = await AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); @@ -109,8 +108,8 @@ export default async function createCode( let result = await apiImplementation.createCodePOST( email !== undefined - ? { email, session, tenantId, options, userContext } - : { phoneNumber: phoneNumber!, session, tenantId, options, userContext } + ? { email, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext } + : { phoneNumber: phoneNumber!, session, tenantId, shouldTryLinkingWithSessionUser, options, userContext } ); send200Response(options.res, result); diff --git a/lib/ts/recipe/passwordless/api/implementation.ts b/lib/ts/recipe/passwordless/api/implementation.ts index 7b7894bb8..c769ad6ea 100644 --- a/lib/ts/recipe/passwordless/api/implementation.ts +++ b/lib/ts/recipe/passwordless/api/implementation.ts @@ -179,6 +179,7 @@ export default function getAPIImplementation(): APIInterface { tenantId: input.tenantId, userContext: input.userContext, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status !== "OK") { @@ -207,6 +208,7 @@ export default function getAPIImplementation(): APIInterface { deviceId: input.deviceId, userInputCode: input.userInputCode, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId: input.tenantId, userContext: input.userContext, } @@ -214,6 +216,7 @@ export default function getAPIImplementation(): APIInterface { preAuthSessionId: input.preAuthSessionId, linkCode: input.linkCode, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId: input.tenantId, userContext: input.userContext, } @@ -319,6 +322,7 @@ export default function getAPIImplementation(): APIInterface { factorIds, userContext: input.userContext, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status !== "OK") { @@ -344,6 +348,7 @@ export default function getAPIImplementation(): APIInterface { input.userContext ), session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId: input.tenantId, } : { @@ -357,12 +362,13 @@ export default function getAPIImplementation(): APIInterface { input.userContext ), session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId: input.tenantId, } ); if (response.status !== "OK") { - return AuthUtils.getErrorStatusResponseWithReason(response, {}, "SIGN_IN_UP_NOT_ALLOWED"); + return AuthUtils.getErrorStatusResponseWithReason(response, errorCodeMap, "SIGN_IN_UP_NOT_ALLOWED"); } // now we send the email / text message. @@ -500,6 +506,7 @@ export default function getAPIImplementation(): APIInterface { }); const authTypeInfo = await AuthUtils.checkAuthTypeAndLinkingStatus( input.session, + input.shouldTryLinkingWithSessionUser, { recipeId: "passwordless", email: deviceInfo.email, @@ -553,7 +560,7 @@ export default function getAPIImplementation(): APIInterface { // This mirrors how we construct factorIds in createCodePOST let factorIds; - if (input.session !== undefined) { + if (!authTypeInfo.isFirstFactor) { if (deviceInfo.email !== undefined) { factorIds = [FactorIds.OTP_EMAIL]; } else { diff --git a/lib/ts/recipe/passwordless/api/resendCode.ts b/lib/ts/recipe/passwordless/api/resendCode.ts index cb946546a..1f1abe48d 100644 --- a/lib/ts/recipe/passwordless/api/resendCode.ts +++ b/lib/ts/recipe/passwordless/api/resendCode.ts @@ -13,11 +13,11 @@ * under the License. */ -import { send200Response } from "../../../utils"; +import { getNormalisedShouldTryLinkingWithSessionUserFlag, send200Response } from "../../../utils"; import STError from "../error"; import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; -import Session from "../../session"; +import { AuthUtils } from "../../../authUtils"; export default async function resendCode( apiImplementation: APIInterface, @@ -47,25 +47,21 @@ export default async function resendCode( }); } - let session = await Session.getSession( + const shouldTryLinkingWithSessionUser = getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, body); + + const session = await AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); - if (session !== undefined) { - tenantId = session.getTenantId(); - } - let result = await apiImplementation.resendCodePOST({ deviceId, preAuthSessionId, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext, }); diff --git a/lib/ts/recipe/passwordless/index.ts b/lib/ts/recipe/passwordless/index.ts index 92b15e493..43d00fd2a 100644 --- a/lib/ts/recipe/passwordless/index.ts +++ b/lib/ts/recipe/passwordless/index.ts @@ -51,6 +51,7 @@ export default class Wrapper { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createCode({ ...input, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, userContext: getUserContext(input.userContext), }); } @@ -203,6 +204,7 @@ export default class Wrapper { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.consumeCode({ ...input, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, userContext: getUserContext(input.userContext), }); } diff --git a/lib/ts/recipe/passwordless/recipe.ts b/lib/ts/recipe/passwordless/recipe.ts index 9410bfb00..5ec867387 100644 --- a/lib/ts/recipe/passwordless/recipe.ts +++ b/lib/ts/recipe/passwordless/recipe.ts @@ -577,6 +577,7 @@ export default class Recipe extends RecipeModule { email: input.email, userInputCode, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, tenantId: input.tenantId, userContext: input.userContext, } @@ -584,6 +585,7 @@ export default class Recipe extends RecipeModule { phoneNumber: input.phoneNumber, userInputCode, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, tenantId: input.tenantId, userContext: input.userContext, } @@ -635,12 +637,14 @@ export default class Recipe extends RecipeModule { email: input.email, tenantId: input.tenantId, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, userContext: input.userContext, } : { phoneNumber: input.phoneNumber, tenantId: input.tenantId, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, userContext: input.userContext, } ); @@ -655,6 +659,7 @@ export default class Recipe extends RecipeModule { preAuthSessionId: codeInfo.preAuthSessionId, linkCode: codeInfo.linkCode, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, tenantId: input.tenantId, userContext: input.userContext, } @@ -663,6 +668,7 @@ export default class Recipe extends RecipeModule { deviceId: codeInfo.deviceId, userInputCode: codeInfo.userInputCode, session: input.session, + shouldTryLinkingWithSessionUser: !!input.session, tenantId: input.tenantId, userContext: input.userContext, } diff --git a/lib/ts/recipe/passwordless/recipeImplementation.ts b/lib/ts/recipe/passwordless/recipeImplementation.ts index 37110ace2..df4ad34c4 100644 --- a/lib/ts/recipe/passwordless/recipeImplementation.ts +++ b/lib/ts/recipe/passwordless/recipeImplementation.ts @@ -51,11 +51,12 @@ export default function getRecipeInterface(querier: Querier): RecipeInterface { // Attempt account linking (this is a sign up) let updatedUser = response.user; - const linkResult = await AuthUtils.linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo({ + const linkResult = await AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ tenantId: input.tenantId, inputUser: response.user, recipeUserId: response.recipeUserId, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, userContext: input.userContext, }); diff --git a/lib/ts/recipe/passwordless/types.ts b/lib/ts/recipe/passwordless/types.ts index 317d809ef..cbbf6f95f 100644 --- a/lib/ts/recipe/passwordless/types.ts +++ b/lib/ts/recipe/passwordless/types.ts @@ -119,6 +119,7 @@ export type RecipeInterface = { ) & { userInputCode?: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; } @@ -158,6 +159,7 @@ export type RecipeInterface = { deviceId: string; preAuthSessionId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; } @@ -165,6 +167,7 @@ export type RecipeInterface = { linkCode: string; preAuthSessionId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; } @@ -334,6 +337,7 @@ export type APIInterface = { input: ({ email: string } | { phoneNumber: string }) & { tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; } @@ -355,6 +359,7 @@ export type APIInterface = { input: { deviceId: string; preAuthSessionId: string } & { tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; } @@ -374,6 +379,7 @@ export type APIInterface = { ) & { tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; } diff --git a/lib/ts/recipe/session/recipe.ts b/lib/ts/recipe/session/recipe.ts index a1762f065..57a7b42be 100644 --- a/lib/ts/recipe/session/recipe.ts +++ b/lib/ts/recipe/session/recipe.ts @@ -43,6 +43,7 @@ import { APIOptions } from "."; import OpenIdRecipe from "../openid/recipe"; import { logDebugMessage } from "../../logger"; import { resetCombinedJWKS } from "../../combinedRemoteJWKSet"; +import { getAccessTokenFromRequest } from "./sessionRequestFunctions"; // For Express export default class SessionRecipe extends RecipeModule { @@ -284,4 +285,14 @@ export default class SessionRecipe extends RecipeModule { userContext, }); }; + + getAccessTokenFromRequest = (req: any, userContext: UserContext) => { + const allowedTransferMethod = this.config.getTokenTransferMethod({ + req, + forCreateNewSession: false, + userContext, + }); + + return getAccessTokenFromRequest(req, allowedTransferMethod); + }; } diff --git a/lib/ts/recipe/session/sessionRequestFunctions.ts b/lib/ts/recipe/session/sessionRequestFunctions.ts index de4c5b3bb..c6cb64158 100644 --- a/lib/ts/recipe/session/sessionRequestFunctions.ts +++ b/lib/ts/recipe/session/sessionRequestFunctions.ts @@ -73,65 +73,13 @@ export async function getSessionFromRequest({ const sessionOptional = options?.sessionRequired === false; logDebugMessage("getSession: optional validation: " + sessionOptional); - const accessTokens: { - [key in TokenTransferMethod]?: ParsedJWTInfo; - } = {}; - - // We check all token transfer methods for available access tokens - for (const transferMethod of availableTokenTransferMethods) { - const tokenString = getToken(req, "access", transferMethod); - if (tokenString !== undefined) { - try { - const info = parseJWTWithoutSignatureVerification(tokenString); - validateAccessTokenStructure(info.payload, info.version); - logDebugMessage("getSession: got access token from " + transferMethod); - accessTokens[transferMethod] = info; - } catch { - logDebugMessage( - `getSession: ignoring token in ${transferMethod}, because it doesn't match our access token structure` - ); - } - } - } - const allowedTransferMethod = config.getTokenTransferMethod({ req, forCreateNewSession: false, userContext, }); - let requestTransferMethod: TokenTransferMethod | undefined; - let accessToken: ParsedJWTInfo | undefined; - if ( - (allowedTransferMethod === "any" || allowedTransferMethod === "header") && - accessTokens["header"] !== undefined - ) { - logDebugMessage("getSession: using header transfer method"); - requestTransferMethod = "header"; - accessToken = accessTokens["header"]; - } else if ( - (allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && - accessTokens["cookie"] !== undefined - ) { - logDebugMessage("getSession: using cookie transfer method"); - - // If multiple access tokens exist in the request cookie, throw TRY_REFRESH_TOKEN. - // This prompts the client to call the refresh endpoint, clearing olderCookieDomain cookies (if set). - // ensuring outdated token payload isn't used. - const hasMultipleAccessTokenCookies = hasMultipleCookiesForTokenType(req, "access"); - if (hasMultipleAccessTokenCookies) { - logDebugMessage( - "getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies" - ); - throw new SessionError({ - message: "Multiple access tokens present in the request cookies.", - type: SessionError.TRY_REFRESH_TOKEN, - }); - } - - requestTransferMethod = "cookie"; - accessToken = accessTokens["cookie"]; - } + const { requestTransferMethod, accessToken } = getAccessTokenFromRequest(req, allowedTransferMethod); let antiCsrfToken = getAntiCsrfTokenFromHeaders(req); let doAntiCsrfCheck = options !== undefined ? options.antiCsrfCheck : undefined; @@ -212,6 +160,64 @@ export async function getSessionFromRequest({ return session; } +export function getAccessTokenFromRequest(req: any, allowedTransferMethod: TokenTransferMethod | "any") { + const accessTokens: { + [key in TokenTransferMethod]?: ParsedJWTInfo; + } = {}; + + // We check all token transfer methods for available access tokens + for (const transferMethod of availableTokenTransferMethods) { + const tokenString = getToken(req, "access", transferMethod); + if (tokenString !== undefined) { + try { + const info = parseJWTWithoutSignatureVerification(tokenString); + validateAccessTokenStructure(info.payload, info.version); + logDebugMessage("getSession: got access token from " + transferMethod); + accessTokens[transferMethod] = info; + } catch { + logDebugMessage( + `getSession: ignoring token in ${transferMethod}, because it doesn't match our access token structure` + ); + } + } + } + + let requestTransferMethod: TokenTransferMethod | undefined; + let accessToken: ParsedJWTInfo | undefined; + + if ( + (allowedTransferMethod === "any" || allowedTransferMethod === "header") && + accessTokens["header"] !== undefined + ) { + logDebugMessage("getSession: using header transfer method"); + requestTransferMethod = "header"; + accessToken = accessTokens["header"]; + } else if ( + (allowedTransferMethod === "any" || allowedTransferMethod === "cookie") && + accessTokens["cookie"] !== undefined + ) { + logDebugMessage("getSession: using cookie transfer method"); + + // If multiple access tokens exist in the request cookie, throw TRY_REFRESH_TOKEN. + // This prompts the client to call the refresh endpoint, clearing olderCookieDomain cookies (if set). + // ensuring outdated token payload isn't used. + const hasMultipleAccessTokenCookies = hasMultipleCookiesForTokenType(req, "access"); + if (hasMultipleAccessTokenCookies) { + logDebugMessage( + "getSession: Throwing TRY_REFRESH_TOKEN because multiple access tokens are present in request cookies" + ); + throw new SessionError({ + message: "Multiple access tokens present in the request cookies.", + type: SessionError.TRY_REFRESH_TOKEN, + }); + } + + requestTransferMethod = "cookie"; + accessToken = accessTokens["cookie"]; + } + return { requestTransferMethod, accessToken, allowedTransferMethod }; +} + /* In all cases: if sIdRefreshToken token exists (so it's a legacy session) we clear it. Check http://localhost:3002/docs/contribute/decisions/session/0008 for further details and a table of expected behaviours diff --git a/lib/ts/recipe/session/utils.ts b/lib/ts/recipe/session/utils.ts index f43570f77..336152352 100644 --- a/lib/ts/recipe/session/utils.ts +++ b/lib/ts/recipe/session/utils.ts @@ -303,7 +303,7 @@ export function validateAndNormaliseUserInput( antiCsrfFunctionOrString: antiCsrf, override, invalidClaimStatusCode, - overwriteSessionDuringSignInUp: config?.overwriteSessionDuringSignInUp ?? false, + overwriteSessionDuringSignInUp: config?.overwriteSessionDuringSignInUp ?? true, jwksRefreshIntervalSec: config?.jwksRefreshIntervalSec ?? 3600 * 4, }; } diff --git a/lib/ts/recipe/thirdparty/api/implementation.ts b/lib/ts/recipe/thirdparty/api/implementation.ts index e91bb2dc3..889ba3685 100644 --- a/lib/ts/recipe/thirdparty/api/implementation.ts +++ b/lib/ts/recipe/thirdparty/api/implementation.ts @@ -137,6 +137,7 @@ export default function getAPIInterface(): APIInterface { tenantId: input.tenantId, userContext: input.userContext, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, }); if (preAuthChecks.status !== "OK") { @@ -159,6 +160,7 @@ export default function getAPIInterface(): APIInterface { oAuthTokens: oAuthTokensToUse, rawUserInfoFromProvider: userInfo.rawUserInfoFromProvider, session: input.session, + shouldTryLinkingWithSessionUser: input.shouldTryLinkingWithSessionUser, tenantId, userContext, }); diff --git a/lib/ts/recipe/thirdparty/api/signinup.ts b/lib/ts/recipe/thirdparty/api/signinup.ts index f50813fe8..cc57dff1d 100644 --- a/lib/ts/recipe/thirdparty/api/signinup.ts +++ b/lib/ts/recipe/thirdparty/api/signinup.ts @@ -14,10 +14,14 @@ */ import STError from "../error"; -import { getBackwardsCompatibleUserInfo, send200Response } from "../../../utils"; +import { + getBackwardsCompatibleUserInfo, + getNormalisedShouldTryLinkingWithSessionUserFlag, + send200Response, +} from "../../../utils"; import { APIInterface, APIOptions } from "../"; import { UserContext } from "../../../types"; -import Session from "../../session"; +import { AuthUtils } from "../../../authUtils"; export default async function signInUpAPI( apiImplementation: APIInterface, @@ -82,13 +86,12 @@ export default async function signInUpAPI( const provider = providerResponse; - let session = await Session.getSession( + const shouldTryLinkingWithSessionUser = getNormalisedShouldTryLinkingWithSessionUserFlag(options.req, bodyParams); + + const session = await AuthUtils.loadSessionInAuthAPIIfNeeded( options.req, options.res, - { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], - }, + shouldTryLinkingWithSessionUser, userContext ); @@ -102,6 +105,7 @@ export default async function signInUpAPI( oAuthTokens, tenantId, session, + shouldTryLinkingWithSessionUser, options, userContext, }); diff --git a/lib/ts/recipe/thirdparty/index.ts b/lib/ts/recipe/thirdparty/index.ts index 2dc3566f0..697bf96aa 100644 --- a/lib/ts/recipe/thirdparty/index.ts +++ b/lib/ts/recipe/thirdparty/index.ts @@ -136,6 +136,7 @@ export default class Wrapper { tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId, isVerified, session, + shouldTryLinkingWithSessionUser: !!session, userContext: getUserContext(userContext), }); } diff --git a/lib/ts/recipe/thirdparty/recipeImplementation.ts b/lib/ts/recipe/thirdparty/recipeImplementation.ts index f9a602e3c..6225009bd 100644 --- a/lib/ts/recipe/thirdparty/recipeImplementation.ts +++ b/lib/ts/recipe/thirdparty/recipeImplementation.ts @@ -15,7 +15,16 @@ export default function getRecipeImplementation(querier: Querier, providers: Pro return { manuallyCreateOrUpdateUser: async function ( this: RecipeInterface, - { thirdPartyId, thirdPartyUserId, email, isVerified, tenantId, session, userContext } + { + thirdPartyId, + thirdPartyUserId, + email, + isVerified, + tenantId, + session, + shouldTryLinkingWithSessionUser, + userContext, + } ) { const accountLinking = AccountLinking.getInstance(); const users = await listUsersByAccountInfo( @@ -71,8 +80,9 @@ export default function getRecipeImplementation(querier: Querier, providers: Pro // function updated the verification status) and can return that response.user = (await getUser(response.recipeUserId.getAsString(), userContext))!; - const linkResult = await AuthUtils.linkToSessionIfProvidedElseCreatePrimaryUserIdOrLinkByAccountInfo({ + const linkResult = await AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({ tenantId, + shouldTryLinkingWithSessionUser, inputUser: response.user, recipeUserId: response.recipeUserId, session, @@ -102,6 +112,7 @@ export default function getRecipeImplementation(querier: Querier, providers: Pro userContext, oAuthTokens, session, + shouldTryLinkingWithSessionUser, rawUserInfoFromProvider, } ): Promise< @@ -136,6 +147,7 @@ export default function getRecipeImplementation(querier: Querier, providers: Pro tenantId, isVerified, session, + shouldTryLinkingWithSessionUser, userContext, }); diff --git a/lib/ts/recipe/thirdparty/types.ts b/lib/ts/recipe/thirdparty/types.ts index 21c7608da..71e9847d3 100644 --- a/lib/ts/recipe/thirdparty/types.ts +++ b/lib/ts/recipe/thirdparty/types.ts @@ -174,6 +174,7 @@ export type RecipeInterface = { fromUserInfoAPI?: { [key: string]: any }; }; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< @@ -208,6 +209,7 @@ export type RecipeInterface = { email: string; isVerified: boolean; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; tenantId: string; userContext: UserContext; }): Promise< @@ -272,6 +274,7 @@ export type APIInterface = { provider: TypeProvider; tenantId: string; session: SessionContainerInterface | undefined; + shouldTryLinkingWithSessionUser: boolean | undefined; options: APIOptions; userContext: UserContext; } & ( diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts index 6a7afde9c..144fd04a9 100644 --- a/lib/ts/utils.ts +++ b/lib/ts/utils.ts @@ -194,6 +194,12 @@ export function isAnIpAddress(ipaddress: string) { ipaddress ); } +export function getNormalisedShouldTryLinkingWithSessionUserFlag(req: BaseRequest, body: any) { + if (hasGreaterThanEqualToFDI(req, "3.1")) { + return body.shouldTryLinkingWithSessionUser ?? false; + } + return undefined; +} export function getBackwardsCompatibleUserInfo( req: BaseRequest, diff --git a/lib/ts/version.ts b/lib/ts/version.ts index f7a917b2c..22817ea60 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 = "20.0.0"; +export const version = "20.0.2"; export const cdiSupported = ["5.1"]; diff --git a/package-lock.json b/package-lock.json index dda99ff2b..238781e13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supertokens-node", - "version": "20.0.0", + "version": "20.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "supertokens-node", - "version": "20.0.0", + "version": "20.0.2", "license": "Apache-2.0", "dependencies": { "content-type": "^1.0.5", diff --git a/package.json b/package.json index 02380e324..fa879d3ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "supertokens-node", - "version": "20.0.0", + "version": "20.0.2", "description": "NodeJS driver for SuperTokens core", "main": "index.js", "scripts": { diff --git a/test/session/overwriteSessionDuringSignInUp.test.js b/test/session/overwriteSessionDuringSignInUp.test.js index 3bf12c781..dd397811a 100644 --- a/test/session/overwriteSessionDuringSignInUp.test.js +++ b/test/session/overwriteSessionDuringSignInUp.test.js @@ -119,7 +119,7 @@ describe(`overwriteSessionDuringSignInUp config: ${printPath( ); cookies = extractInfoFromResponse(res); - assert(cookies.accessTokenFromAny === undefined); + assert.notStrictEqual(cookies.accessTokenFromAny, undefined); }); it("test false", async function () { diff --git a/test/test-server/src/accountlinking.ts b/test/test-server/src/accountlinking.ts index 280e47ccc..6763deaac 100644 --- a/test/test-server/src/accountlinking.ts +++ b/test/test-server/src/accountlinking.ts @@ -4,7 +4,7 @@ import AccountLinkingRecipe from "../../../lib/build/recipe/accountlinking/recip import AccountLinking from "../../../recipe/accountlinking"; import * as supertokens from "../../../lib/build"; import { logger } from "./logger"; -import { serializeUser } from "./utils"; +import { serializeResponse, serializeUser } from "./utils"; const namespace = "com.supertokens:node-test-server:accountlinking"; const { logDebugMessage } = logger(namespace); @@ -15,7 +15,7 @@ const router = Router() logDebugMessage("AccountLinking:createPrimaryUser %j", req.body); const recipeUserId = supertokens.convertToRecipeUserId(req.body.recipeUserId); const response = await AccountLinking.createPrimaryUser(recipeUserId, req.body.userContext); - res.json({ ...response, ...serializeUser(response) }); + await serializeResponse(req, res, response); } catch (e) { next(e); } @@ -29,10 +29,7 @@ const router = Router() req.body.primaryUserId, req.body.userContext ); - res.json({ - ...response, - ...serializeUser(response), - }); + await serializeResponse(req, res, response); } catch (e) { next(e); } diff --git a/test/test-server/src/emailpassword.ts b/test/test-server/src/emailpassword.ts index 633c4bfd6..7770898f4 100644 --- a/test/test-server/src/emailpassword.ts +++ b/test/test-server/src/emailpassword.ts @@ -1,6 +1,6 @@ import { Router } from "express"; import EmailPassword from "../../../recipe/emailpassword"; -import { convertRequestSessionToSessionObject, serializeRecipeUserId, serializeUser } from "./utils"; +import { convertRequestSessionToSessionObject, serializeRecipeUserId, serializeResponse, serializeUser } from "./utils"; import * as supertokens from "../../../lib/build"; import { logger } from "./logger"; @@ -19,11 +19,7 @@ const router = Router() session, req.body.userContext ); - res.json({ - ...response, - ...serializeUser(response), - ...serializeRecipeUserId(response), - }); + await serializeResponse(req, res, response); } catch (e) { next(e); } @@ -39,11 +35,7 @@ const router = Router() session, req.body.userContext ); - res.json({ - ...response, - ...serializeUser(response), - ...serializeRecipeUserId(response), - }); + await serializeResponse(req, res, response); } catch (e) { next(e); } diff --git a/test/test-server/src/oauth2provider.ts b/test/test-server/src/oauth2provider.ts index ce5b4beb1..1da664e3c 100644 --- a/test/test-server/src/oauth2provider.ts +++ b/test/test-server/src/oauth2provider.ts @@ -56,19 +56,6 @@ const router = Router() next(e); } }) - .post("/validateoauth2idtoken", async (req, res, next) => { - try { - logDebugMessage("OAuth2Provider:validateOAuth2IdToken %j", req.body); - const response = await OAuth2Provider.validateOAuth2IdToken( - req.body.token, - req.body.expectedAudience, - req.body.userContext - ); - res.json(response); - } catch (e) { - next(e); - } - }) .post("/createtokenforclientcredentials", async (req, res, next) => { try { logDebugMessage("OAuth2Provider:createTokenForClientCredentials %j", req.body); diff --git a/test/test-server/src/passwordless.ts b/test/test-server/src/passwordless.ts index cc8c7fd99..6815eb2b9 100644 --- a/test/test-server/src/passwordless.ts +++ b/test/test-server/src/passwordless.ts @@ -1,7 +1,7 @@ import { Router } from "express"; import SuperTokens from "../../.."; import Passwordless from "../../../recipe/passwordless"; -import { convertRequestSessionToSessionObject, serializeRecipeUserId, serializeUser } from "./utils"; +import { convertRequestSessionToSessionObject, serializeRecipeUserId, serializeResponse, serializeUser } from "./utils"; import { logger } from "./logger"; const namespace = "com.supertokens:node-test-server:passwordless"; @@ -23,11 +23,7 @@ const router = Router() session: req.body.session && (await convertRequestSessionToSessionObject(req.body.session)), userContext: req.body.userContext, }); - res.json({ - ...response, - ...serializeUser(response), - ...serializeRecipeUserId(response), - }); + await serializeResponse(req, res, response); } catch (e) { next(e); } @@ -60,11 +56,7 @@ const router = Router() session: req.body.session && (await convertRequestSessionToSessionObject(req.body.session)), userContext: req.body.userContext, }); - res.json({ - ...response, - ...serializeUser(response), - ...serializeRecipeUserId(response), - }); + await serializeResponse(req, res, response); } catch (e) { next(e); } @@ -78,11 +70,7 @@ const router = Router() phoneNumber: req.body.phoneNumber, userContext: req.body.userContext, }); - res.json({ - ...response, - ...serializeUser(response), - ...serializeRecipeUserId(response), - }); + await serializeResponse(req, res, response); } catch (e) { next(e); } diff --git a/test/test-server/src/session.ts b/test/test-server/src/session.ts index a6f6ac9c3..49c2eab59 100644 --- a/test/test-server/src/session.ts +++ b/test/test-server/src/session.ts @@ -4,7 +4,7 @@ import * as supertokens from "../../../lib/build"; import SessionRecipe from "../../../lib/build/recipe/session/recipe"; import { logger } from "./logger"; import { getFunc } from "./testFunctionMapper"; -import { convertRequestSessionToSessionObject, deserializeClaim, deserializeValidator } from "./utils"; +import { convertRequestSessionToSessionObject, deserializeClaim, deserializeValidator, maxVersion } from "./utils"; import { logOverrideEvent } from "./overrideLogging"; const namespace = "com.supertokens:node-test-server:session"; @@ -12,9 +12,20 @@ const { logDebugMessage } = logger(namespace); const router = Router() .post("/createnewsessionwithoutrequestresponse", async (req, res, next) => { + const fdiVersion = req.headers["fdi-version"] as string; + try { logDebugMessage("Session.createNewSessionWithoutRequestResponse %j", req.body); - const recipeUserId = supertokens.convertToRecipeUserId(req.body.recipeUserId); + let recipeUserId; + if ( + maxVersion("1.17", fdiVersion) === "1.17" || + (maxVersion("2.0", fdiVersion) === fdiVersion && maxVersion("3.0", fdiVersion) !== fdiVersion) + ) { + // fdiVersion <= "1.17" || (fdiVersion >= "2.0" && fdiVersion < "3.0") + recipeUserId = supertokens.convertToRecipeUserId(req.body.userId); + } else { + recipeUserId = supertokens.convertToRecipeUserId(req.body.recipeUserId); + } const response = await Session.createNewSessionWithoutRequestResponse( req.body.tenantId || "public", recipeUserId, diff --git a/test/test-server/src/thirdparty.ts b/test/test-server/src/thirdparty.ts index a1e574356..53fcdfbaa 100644 --- a/test/test-server/src/thirdparty.ts +++ b/test/test-server/src/thirdparty.ts @@ -1,6 +1,6 @@ import { Router } from "express"; import ThirdParty from "../../../recipe/thirdparty"; -import { convertRequestSessionToSessionObject, serializeRecipeUserId, serializeUser } from "./utils"; +import { convertRequestSessionToSessionObject, serializeRecipeUserId, serializeResponse, serializeUser } from "./utils"; import { logger } from "./logger"; const namespace = "com.supertokens:node-test-server:thirdparty"; @@ -19,11 +19,7 @@ const router = Router().post("/manuallycreateorupdateuser", async (req, res, nex session, req.body.userContext ); - res.json({ - ...response, - ...serializeUser(response), - ...serializeRecipeUserId(response), - }); + await serializeResponse(req, res, response); } catch (e) { next(e); } diff --git a/test/test-server/src/utils.ts b/test/test-server/src/utils.ts index 69ecb21ce..3830825a5 100644 --- a/test/test-server/src/utils.ts +++ b/test/test-server/src/utils.ts @@ -137,7 +137,36 @@ export async function convertRequestSessionToSessionObject( return tokens; } -export function serializeUser(response) { +export async function serializeResponse(req, res, response) { + const fdiVersion: string = req.headers["fdi-version"] as string; + + await res.json({ + ...response, + ...serializeUser(response, fdiVersion), + ...serializeRecipeUserId(response, fdiVersion), + }); +} + +export function serializeUser(response, fdiVersion: string) { + // fdiVersion <= "1.17" || (fdiVersion >= "2.0" && fdiVersion < "3.0") + if ( + maxVersion("1.17", fdiVersion) === "1.17" || + (maxVersion("2.0", fdiVersion) === fdiVersion && maxVersion("3.0", fdiVersion) !== fdiVersion) + ) { + return { + ...("user" in response && response.user instanceof supertokens.User + ? { + user: { + id: (response.user as supertokens.User).id, + email: (response.user as supertokens.User).emails[0], + timeJoined: (response.user as supertokens.User).timeJoined, + tenantIds: (response.user as supertokens.User).tenantIds, + }, + } + : {}), + }; + } + return { ...("user" in response && response.user instanceof supertokens.User ? { @@ -147,7 +176,14 @@ export function serializeUser(response) { }; } -export function serializeRecipeUserId(response) { +export function serializeRecipeUserId(response, fdiVersion: string) { + if ( + maxVersion("1.17", fdiVersion) === "1.17" || + (maxVersion("2.0", fdiVersion) === fdiVersion && maxVersion("3.0", fdiVersion) !== fdiVersion) + ) { + // fdiVersion <= "1.17" || (fdiVersion >= "2.0" && fdiVersion < "3.0") + return {}; + } return { ...("recipeUserId" in response && response.recipeUserId instanceof supertokens.RecipeUserId ? { @@ -166,3 +202,22 @@ function popOrUseVal(arrOrValue: T | T[]): T { } return arrOrValue; } + +export function maxVersion(version1: string, version2: string): string { + let splittedv1 = version1.split("."); + let splittedv2 = version2.split("."); + let minLength = Math.min(splittedv1.length, splittedv2.length); + for (let i = 0; i < minLength; i++) { + let v1 = Number(splittedv1[i]); + let v2 = Number(splittedv2[i]); + if (v1 > v2) { + return version1; + } else if (v2 > v1) { + return version2; + } + } + if (splittedv1.length >= splittedv2.length) { + return version1; + } + return version2; +} diff --git a/test/with-typescript/index.ts b/test/with-typescript/index.ts index 5edd52ccb..ae0b9fcd8 100644 --- a/test/with-typescript/index.ts +++ b/test/with-typescript/index.ts @@ -1356,6 +1356,7 @@ EmailPassword.init({ password, tenantId: input.tenantId, session: input.session, + shouldTryLinkingWithSessionUser: false, userContext: input.userContext, }); if (response.status === "WRONG_CREDENTIALS_ERROR") { From 9f7866c2b962a783220332316b824234fb4bb573 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Mon, 19 Aug 2024 00:02:30 +0200 Subject: [PATCH 29/35] feat: add shouldTryRefresh plus self-review and test related fixes --- lib/build/recipe/oauth2provider/api/auth.js | 20 ++-- .../oauth2provider/api/implementation.js | 8 +- lib/build/recipe/oauth2provider/api/login.js | 12 ++- .../recipe/oauth2provider/api/utils.d.ts | 4 + lib/build/recipe/oauth2provider/api/utils.js | 52 +++++++-- lib/build/recipe/oauth2provider/index.js | 25 +++-- lib/build/recipe/oauth2provider/recipe.d.ts | 18 +++- lib/build/recipe/oauth2provider/recipe.js | 61 ++++++++++- .../oauth2provider/recipeImplementation.d.ts | 6 +- .../oauth2provider/recipeImplementation.js | 102 +++++++++++++----- lib/build/recipe/oauth2provider/types.d.ts | 36 ++++--- lib/build/recipe/userroles/recipe.js | 41 ++++++- lib/ts/recipe/oauth2provider/api/auth.ts | 20 ++-- .../oauth2provider/api/implementation.ts | 8 +- lib/ts/recipe/oauth2provider/api/login.ts | 12 ++- lib/ts/recipe/oauth2provider/api/utils.ts | 39 +++++-- lib/ts/recipe/oauth2provider/index.ts | 42 ++++---- lib/ts/recipe/oauth2provider/recipe.ts | 68 +++++++++++- .../oauth2provider/recipeImplementation.ts | 99 +++++++++++------ lib/ts/recipe/oauth2provider/types.ts | 42 +++++--- lib/ts/recipe/userroles/recipe.ts | 66 +++++++++++- 21 files changed, 613 insertions(+), 168 deletions(-) diff --git a/lib/build/recipe/oauth2provider/api/auth.js b/lib/build/recipe/oauth2provider/api/auth.js index 9e8b5d7a0..a064f9bb4 100644 --- a/lib/build/recipe/oauth2provider/api/auth.js +++ b/lib/build/recipe/oauth2provider/api/auth.js @@ -22,6 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../../../utils"); const set_cookie_parser_1 = __importDefault(require("set-cookie-parser")); const session_1 = __importDefault(require("../../session")); +const error_1 = __importDefault(require("../../../recipe/session/error")); async function authGET(apiImplementation, options, userContext) { if (apiImplementation.authGET === undefined) { return false; @@ -29,21 +30,26 @@ async function authGET(apiImplementation, options, userContext) { const origURL = options.req.getOriginalURL(); const splitURL = origURL.split("?"); const params = new URLSearchParams(splitURL[1]); - let session; + let session, shouldTryRefresh; try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); - } catch (_a) { - // We ignore this here since the authGET is called from the auth endpoint which is used in full-page redirections - // Returning a 401 would break the sign-in flow. - // In theory we could serve some JS that handles refreshing and retrying, but this is not implemented. - // What we do is that the auth endpoint will redirect to the login page, and the login page handles refreshing and - // redirect to the auth endpoint again. This is not optimal, but it works for now. + shouldTryRefresh = false; + } catch (error) { + session = undefined; + if (error_1.default.isErrorFromSuperTokens(error) && error.type === error_1.default.TRY_REFRESH_TOKEN) { + shouldTryRefresh = true; + } else { + // This should generally not happen, but we can handle this as if the session is not present, + // because then we redirect to the frontend, which should handle the validation error + shouldTryRefresh = false; + } } let response = await apiImplementation.authGET({ options, params: Object.fromEntries(params.entries()), cookie: options.req.getHeaderValue("cookie"), session, + shouldTryRefresh, userContext, }); if ("redirectTo" in response) { diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index bfb5c3ee8..fbe42c1c4 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -17,11 +17,12 @@ Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("./utils"); function getAPIImplementation() { return { - loginGET: async ({ loginChallenge, options, session, userContext }) => { + loginGET: async ({ loginChallenge, options, session, shouldTryRefresh, userContext }) => { const response = await utils_1.loginGET({ recipeImplementation: options.recipeImplementation, loginChallenge, session, + shouldTryRefresh, isDirectCall: true, userContext, }); @@ -30,10 +31,11 @@ function getAPIImplementation() { cookie: options.req.getHeaderValue("cookie"), recipeImplementation: options.recipeImplementation, session, + shouldTryRefresh, userContext, }); }, - authGET: async ({ options, params, cookie, session, userContext }) => { + authGET: async ({ options, params, cookie, session, shouldTryRefresh, userContext }) => { const response = await options.recipeImplementation.authorization({ params, cookies: cookie, @@ -45,6 +47,7 @@ function getAPIImplementation() { recipeImplementation: options.recipeImplementation, cookie, session, + shouldTryRefresh, userContext, }); }, @@ -63,6 +66,7 @@ function getAPIImplementation() { return { status: "OK", info: { + clientId: client.clientId, clientName: client.clientName, tosUri: client.tosUri, policyUri: client.policyUri, diff --git a/lib/build/recipe/oauth2provider/api/login.js b/lib/build/recipe/oauth2provider/api/login.js index 57dbed8c4..6ca65a5e3 100644 --- a/lib/build/recipe/oauth2provider/api/login.js +++ b/lib/build/recipe/oauth2provider/api/login.js @@ -23,18 +23,25 @@ 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")); +const error_2 = __importDefault(require("../../../recipe/session/error")); async function login(apiImplementation, options, userContext) { var _a; if (apiImplementation.loginGET === undefined) { return false; } - let session; + let session, shouldTryRefresh; try { session = await session_1.default.getSession(options.req, options.res, { sessionRequired: false }, userContext); - } catch (_b) { + shouldTryRefresh = false; + } catch (error) { // We can handle this as if the session is not present, because then we redirect to the frontend, // which should handle the validation error session = undefined; + if (error_1.default.isErrorFromSuperTokens(error) && error.type === error_2.default.TRY_REFRESH_TOKEN) { + shouldTryRefresh = true; + } else { + shouldTryRefresh = false; + } } const loginChallenge = (_a = options.req.getKeyValueFromQuery("login_challenge")) !== null && _a !== void 0 @@ -50,6 +57,7 @@ async function login(apiImplementation, options, userContext) { options, loginChallenge, session, + shouldTryRefresh, userContext, }); if ("status" in response) { diff --git a/lib/build/recipe/oauth2provider/api/utils.d.ts b/lib/build/recipe/oauth2provider/api/utils.d.ts index 2271ba148..cff72a21a 100644 --- a/lib/build/recipe/oauth2provider/api/utils.d.ts +++ b/lib/build/recipe/oauth2provider/api/utils.d.ts @@ -5,6 +5,7 @@ import { RecipeInterface } from "../types"; export declare function loginGET({ recipeImplementation, loginChallenge, + shouldTryRefresh, session, setCookie, isDirectCall, @@ -13,6 +14,7 @@ export declare function loginGET({ recipeImplementation: RecipeInterface; loginChallenge: string; session?: SessionContainerInterface; + shouldTryRefresh: boolean; setCookie?: string; userContext: UserContext; isDirectCall: boolean; @@ -24,6 +26,7 @@ export declare function handleInternalRedirects({ response, recipeImplementation, session, + shouldTryRefresh, cookie, userContext, }: { @@ -33,6 +36,7 @@ export declare function handleInternalRedirects({ }; recipeImplementation: RecipeInterface; session?: SessionContainerInterface; + shouldTryRefresh: boolean; cookie?: string; userContext: UserContext; }): Promise<{ diff --git a/lib/build/recipe/oauth2provider/api/utils.js b/lib/build/recipe/oauth2provider/api/utils.js index b7cb5cd80..b3d9a94d3 100644 --- a/lib/build/recipe/oauth2provider/api/utils.js +++ b/lib/build/recipe/oauth2provider/api/utils.js @@ -13,7 +13,15 @@ const constants_2 = 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, isDirectCall, userContext }) { +async function loginGET({ + recipeImplementation, + loginChallenge, + shouldTryRefresh, + session, + setCookie, + isDirectCall, + userContext, +}) { var _a, _b; const loginRequest = await recipeImplementation.getLoginRequest({ challenge: loginChallenge, @@ -80,6 +88,30 @@ async function loginGET({ recipeImplementation, loginChallenge, session, setCook }); return { redirectTo: accept.redirectTo, setCookie }; } + const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; + const websiteDomain = appInfo + .getOrigin({ + request: undefined, + userContext: userContext, + }) + .getAsStringDangerous(); + const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); + if (shouldTryRefresh) { + const websiteDomain = appInfo + .getOrigin({ + request: undefined, + userContext: userContext, + }) + .getAsStringDangerous(); + const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); + const queryParamsForTryRefreshPage = new URLSearchParams({ + loginChallenge, + }); + return { + redirectTo: websiteDomain + websiteBasePath + `/try-refresh?${queryParamsForTryRefreshPage.toString()}`, + setCookie, + }; + } if (promptParam === "none") { const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, @@ -92,14 +124,6 @@ async function loginGET({ recipeImplementation, loginChallenge, session, setCook }); return { redirectTo: reject.redirectTo, setCookie }; } - const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo; - const websiteDomain = appInfo - .getOrigin({ - request: undefined, - userContext: userContext, - }) - .getAsStringDangerous(); - const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); const queryParamsForAuthPage = new URLSearchParams({ loginChallenge, }); @@ -160,7 +184,14 @@ function isInternalRedirect(redirectTo) { // In the OAuth2 flow, we do several internal redirects. These redirects don't require a frontend-to-api-server round trip. // If an internal redirect is identified, it's handled directly by this function. // Currently, we only need to handle redirects to /oauth/login and /oauth/auth endpoints. -async function handleInternalRedirects({ response, recipeImplementation, session, cookie = "", userContext }) { +async function handleInternalRedirects({ + response, + recipeImplementation, + session, + shouldTryRefresh, + cookie = "", + userContext, +}) { var _a; if (!isInternalRedirect(response.redirectTo)) { return response; @@ -183,6 +214,7 @@ async function handleInternalRedirects({ response, recipeImplementation, session recipeImplementation, loginChallenge, session, + shouldTryRefresh, setCookie: response.setCookie, isDirectCall: false, userContext, diff --git a/lib/build/recipe/oauth2provider/index.js b/lib/build/recipe/oauth2provider/index.js index ac13eead6..3a769700e 100644 --- a/lib/build/recipe/oauth2provider/index.js +++ b/lib/build/recipe/oauth2provider/index.js @@ -24,29 +24,38 @@ const utils_1 = require("../../utils"); const recipe_1 = __importDefault(require("./recipe")); class Wrapper { static async getOAuth2Client(clientId, userContext) { - return await recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.getOAuth2Client({ clientId }, utils_1.getUserContext(userContext)); + return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Client({ + clientId, + userContext: utils_1.getUserContext(userContext), + }); } static async getOAuth2Clients(input, userContext) { return await recipe_1.default .getInstanceOrThrowError() - .recipeInterfaceImpl.getOAuth2Clients(input, utils_1.getUserContext(userContext)); + .recipeInterfaceImpl.getOAuth2Clients( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) + ); } static async createOAuth2Client(input, userContext) { return await recipe_1.default .getInstanceOrThrowError() - .recipeInterfaceImpl.createOAuth2Client(input, utils_1.getUserContext(userContext)); + .recipeInterfaceImpl.createOAuth2Client( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) + ); } static async updateOAuth2Client(input, userContext) { return await recipe_1.default .getInstanceOrThrowError() - .recipeInterfaceImpl.updateOAuth2Client(input, utils_1.getUserContext(userContext)); + .recipeInterfaceImpl.updateOAuth2Client( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) + ); } static async deleteOAuth2Client(input, userContext) { return await recipe_1.default .getInstanceOrThrowError() - .recipeInterfaceImpl.deleteOAuth2Client(input, utils_1.getUserContext(userContext)); + .recipeInterfaceImpl.deleteOAuth2Client( + Object.assign(Object.assign({}, input), { userContext: utils_1.getUserContext(userContext) }) + ); } static validateOAuth2AccessToken(token, requirements, checkDatabase, userContext) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.validateOAuth2AccessToken({ @@ -72,7 +81,7 @@ class Wrapper { let authorizationHeader = undefined; const normalisedUserContext = utils_1.getUserContext(userContext); const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl; - const res = await recipeInterfaceImpl.getOAuth2Client({ clientId }, normalisedUserContext); + const res = await recipeInterfaceImpl.getOAuth2Client({ clientId, userContext: normalisedUserContext }); if (res.status !== "OK") { throw new Error(`Failed to get OAuth2 client with id ${clientId}: ${res.error}`); } diff --git a/lib/build/recipe/oauth2provider/recipe.d.ts b/lib/build/recipe/oauth2provider/recipe.d.ts index 19a890e21..25c12cc57 100644 --- a/lib/build/recipe/oauth2provider/recipe.d.ts +++ b/lib/build/recipe/oauth2provider/recipe.d.ts @@ -6,6 +6,7 @@ import RecipeModule from "../../recipeModule"; import { APIHandled, HTTPMethod, JSONObject, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; import { APIInterface, + PayloadBuilderFunction, RecipeInterface, TypeInput, TypeNormalisedInput, @@ -16,6 +17,7 @@ import { User } from "../../user"; export default class Recipe extends RecipeModule { static RECIPE_ID: string; private static instance; + private accessTokenBuilders; private idTokenBuilders; private userInfoBuilders; config: TypeNormalisedInput; @@ -28,6 +30,9 @@ export default class Recipe extends RecipeModule { static init(config?: TypeInput): RecipeListFunction; static reset(): void; addUserInfoBuilderFromOtherRecipe: (userInfoBuilderFn: UserInfoBuilderFunction) => void; + addAccessTokenBuilderFromOtherRecipe: (accessTokenBuilders: PayloadBuilderFunction) => void; + addIdTokenBuilderFromOtherRecipe: (idTokenBuilder: PayloadBuilderFunction) => void; + saveTokensForHook: (sessionHandle: string, idToken: JSONObject, accessToken: JSONObject) => void; getAPIsHandled(): APIHandled[]; handleAPIRequest: ( id: string, @@ -41,7 +46,18 @@ export default class Recipe extends RecipeModule { handleError(error: error, _: BaseRequest, __: BaseResponse, _userContext: UserContext): Promise; getAllCORSHeaders(): string[]; isErrorFromThisRecipe(err: any): err is error; - getDefaultIdTokenPayload(user: User, scopes: string[], userContext: UserContext): Promise; + getDefaultAccessTokenPayload( + user: User, + scopes: string[], + sessionHandle: string, + userContext: UserContext + ): Promise; + getDefaultIdTokenPayload( + user: User, + scopes: string[], + sessionHandle: string, + userContext: UserContext + ): Promise; getDefaultUserInfoPayload( user: User, accessTokenPayload: JSONObject, diff --git a/lib/build/recipe/oauth2provider/recipe.js b/lib/build/recipe/oauth2provider/recipe.js index 7f0965f78..fbd77ed7c 100644 --- a/lib/build/recipe/oauth2provider/recipe.js +++ b/lib/build/recipe/oauth2provider/recipe.js @@ -36,14 +36,27 @@ const userInfo_1 = __importDefault(require("./api/userInfo")); const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); const revokeToken_1 = __importDefault(require("./api/revokeToken")); const introspectToken_1 = __importDefault(require("./api/introspectToken")); +const session_1 = require("../session"); +const utils_2 = require("../../utils"); +const tokenHookMap = new Map(); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config) { super(recipeId, appInfo); + this.accessTokenBuilders = []; this.idTokenBuilders = []; this.userInfoBuilders = []; this.addUserInfoBuilderFromOtherRecipe = (userInfoBuilderFn) => { this.userInfoBuilders.push(userInfoBuilderFn); }; + this.addAccessTokenBuilderFromOtherRecipe = (accessTokenBuilders) => { + this.accessTokenBuilders.push(accessTokenBuilders); + }; + this.addIdTokenBuilderFromOtherRecipe = (idTokenBuilder) => { + this.idTokenBuilders.push(idTokenBuilder); + }; + this.saveTokensForHook = (sessionHandle, idToken, accessToken) => { + tokenHookMap.set(sessionHandle, { idToken, accessToken }); + }; this.handleAPIRequest = async (id, tenantId, req, res, _path, _method, userContext) => { let options = { config: this.config, @@ -74,6 +87,23 @@ class Recipe extends recipeModule_1.default { if (id === constants_1.INTROSPECT_TOKEN_PATH) { return introspectToken_1.default(this.apiImpl, options, userContext); } + if (id === "token-hook") { + const body = await options.req.getBodyAsJSONOrFormData(); + const sessionHandle = body.session.extra.sessionHandle; + const tokens = tokenHookMap.get(sessionHandle); + if (tokens !== undefined) { + const { idToken, accessToken } = tokens; + utils_2.send200Response(options.res, { + session: { + access_token: accessToken, + id_token: idToken, + }, + }); + } else { + utils_2.send200Response(options.res, {}); + } + return true; + } throw new Error("Should never come here: handleAPIRequest called with unknown id"); }; this.config = utils_1.validateAndNormaliseUserInput(this, appInfo, config); @@ -84,8 +114,10 @@ class Recipe extends recipeModule_1.default { querier_1.Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo, + this.getDefaultAccessTokenPayload.bind(this), this.getDefaultIdTokenPayload.bind(this), - this.getDefaultUserInfoPayload.bind(this) + this.getDefaultUserInfoPayload.bind(this), + this.saveTokensForHook.bind(this) ) ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); @@ -167,6 +199,13 @@ class Recipe extends recipeModule_1.default { id: constants_1.INTROSPECT_TOKEN_PATH, disabled: this.apiImpl.introspectTokenPOST === undefined, }, + { + // TODO: remove this once we get core support + method: "post", + pathWithoutApiBasePath: new normalisedURLPath_1.default("/oauth/token-hook"), + id: "token-hook", + disabled: false, + }, ]; } handleError(error, _, __, _userContext) { @@ -178,7 +217,23 @@ class Recipe extends recipeModule_1.default { isErrorFromThisRecipe(err) { return error_1.default.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; } - async getDefaultIdTokenPayload(user, scopes, userContext) { + async getDefaultAccessTokenPayload(user, scopes, sessionHandle, userContext) { + const sessionInfo = await session_1.getSessionInformation(sessionHandle); + if (sessionInfo === undefined) { + throw new Error("Session not found"); + } + let payload = { + iss: this.appInfo.apiDomain.getAsStringDangerous() + this.appInfo.apiBasePath.getAsStringDangerous(), + tId: sessionInfo.tenantId, + rsub: sessionInfo.recipeUserId.getAsString(), + sessionHandle: sessionHandle, + }; + for (const fn of this.accessTokenBuilders) { + payload = Object.assign(Object.assign({}, payload), await fn(user, scopes, sessionHandle, userContext)); + } + return payload; + } + async getDefaultIdTokenPayload(user, scopes, sessionHandle, userContext) { let payload = { iss: this.appInfo.apiDomain.getAsStringDangerous() + this.appInfo.apiBasePath.getAsStringDangerous(), }; @@ -197,7 +252,7 @@ class Recipe extends recipeModule_1.default { ); } for (const fn of this.idTokenBuilders) { - payload = Object.assign(Object.assign({}, payload), await fn(user, scopes, userContext)); + payload = Object.assign(Object.assign({}, payload), await fn(user, scopes, sessionHandle, userContext)); } return payload; } diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.d.ts b/lib/build/recipe/oauth2provider/recipeImplementation.d.ts index 3d7982106..3b6fee065 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.d.ts +++ b/lib/build/recipe/oauth2provider/recipeImplementation.d.ts @@ -1,11 +1,13 @@ // @ts-nocheck import { Querier } from "../../querier"; -import { NormalisedAppinfo } from "../../types"; +import { JSONObject, NormalisedAppinfo } from "../../types"; import { RecipeInterface, TypeNormalisedInput, PayloadBuilderFunction, UserInfoBuilderFunction } from "./types"; export default function getRecipeInterface( querier: Querier, _config: TypeNormalisedInput, appInfo: NormalisedAppinfo, + getDefaultAccessTokenPayload: PayloadBuilderFunction, getDefaultIdTokenPayload: PayloadBuilderFunction, - getDefaultUserInfoPayload: UserInfoBuilderFunction + getDefaultUserInfoPayload: UserInfoBuilderFunction, + saveTokensForHook: (sessionHandle: string, idToken: JSONObject, accessToken: JSONObject) => void ): RecipeInterface; diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index bf043da11..c0371d998 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -72,7 +72,15 @@ function getUpdatedRedirectTo(appInfo, redirectTo) { ) .replace("oauth2/", "oauth/"); } -function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, getDefaultUserInfoPayload) { +function getRecipeInterface( + querier, + _config, + appInfo, + getDefaultAccessTokenPayload, + getDefaultIdTokenPayload, + getDefaultUserInfoPayload, + saveTokensForHook +) { return { getLoginRequest: async function (input) { const resp = await querier.sendGetRequest( @@ -227,17 +235,17 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, if (!user) { throw new Error("Should not happen"); } - const idToken = this.buildIdTokenPayload({ + const idToken = await this.buildIdTokenPayload({ user, client: consentRequest.client, - session: input.session, + sessionHandle: input.session.getHandle(), scopes: consentRequest.requestedScope || [], userContext: input.userContext, }); const accessTokenPayload = await this.buildAccessTokenPayload({ user, client: consentRequest.client, - session: input.session, + sessionHandle: input.session.getHandle(), scopes: consentRequest.requestedScope || [], userContext: input.userContext, }); @@ -269,10 +277,61 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, }; }, tokenExchange: async function (input) { + var _a, _b; const body = { $isFormData: true }; // TODO: we ideally want to avoid using formdata, the core can do the translation for (const key in input.body) { body[key] = input.body[key]; } + if (input.body.grant_type === "refresh_token") { + const scopes = + (_b = (_a = input.body.scope) === null || _a === void 0 ? void 0 : _a.split(" ")) !== null && + _b !== void 0 + ? _b + : []; + const tokenInfo = await this.introspectToken({ + token: input.body.refresh_token, + scopes, + userContext: input.userContext, + }); + if (tokenInfo.active === true) { + const sessionHandle = tokenInfo.ext.sessionHandle; + const clientInfo = await this.getOAuth2Client({ + clientId: tokenInfo.client_id, + userContext: input.userContext, + }); + if (clientInfo.status === "ERROR") { + return { + statusCode: 400, + error: clientInfo.error, + errorDescription: clientInfo.errorHint, + }; + } + const client = clientInfo.client; + const user = await __1.getUser(tokenInfo.sub); + if (!user) { + throw new Error("User not found"); + } + const idToken = await this.buildIdTokenPayload({ + user, + client, + sessionHandle: sessionHandle, + scopes, + userContext: input.userContext, + }); + const accessTokenPayload = await this.buildAccessTokenPayload({ + user, + client, + sessionHandle: sessionHandle, + scopes, + userContext: input.userContext, + }); + body["session"] = { + id_token: idToken, + access_token: accessTokenPayload, + }; + saveTokensForHook(sessionHandle, idToken, accessTokenPayload); + } + } if (input.authorizationHeader) { body["authorizationHeader"] = input.authorizationHeader; } @@ -290,7 +349,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, } return res.data; }, - getOAuth2Clients: async function (input, userContext) { + getOAuth2Clients: async function (input) { var _a; let response = await querier.sendGetRequestWithResponseHeaders( new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients`), @@ -298,7 +357,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, page_token: input.paginationToken, }), {}, - userContext + input.userContext ); if (response.body.status === "OK") { // Pagination info is in the Link header, containing comma-separated links: @@ -326,12 +385,12 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, }; } }, - getOAuth2Client: async function (input, userContext) { + getOAuth2Client: async function (input) { let response = await querier.sendGetRequestWithResponseHeaders( new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients/${input.clientId}`), {}, {}, - userContext + input.userContext ); if (response.body.status === "OK") { return { @@ -346,7 +405,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, }; } }, - createOAuth2Client: async function (input, userContext) { + createOAuth2Client: async function (input) { let response = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients`), Object.assign(Object.assign({}, utils_1.transformObjectKeys(input, "snake-case")), { @@ -355,7 +414,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, skip_consent: true, subject_type: "public", }), - userContext + input.userContext ); if (response.status === "OK") { return { @@ -370,7 +429,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, }; } }, - updateOAuth2Client: async function (input, userContext) { + updateOAuth2Client: async function (input) { // We convert the input into an array of "replace" operations const requestBody = Object.entries(input).reduce((result, [key, value]) => { result.push({ @@ -384,7 +443,7 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, let response = await querier.sendPatchRequest( new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients/${input.clientId}`), requestBody, - userContext + input.userContext ); if (response.status === "OK") { return { @@ -399,12 +458,12 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, }; } }, - deleteOAuth2Client: async function (input, userContext) { + deleteOAuth2Client: async function (input) { let response = await querier.sendDeleteRequest( new normalisedURLPath_1.default(`/recipe/oauth2/admin/clients/${input.clientId}`), undefined, undefined, - userContext + input.userContext ); if (response.status === "OK") { return { status: "OK" }; @@ -417,21 +476,10 @@ function getRecipeInterface(querier, _config, appInfo, getDefaultIdTokenPayload, } }, buildAccessTokenPayload: async function (input) { - const stAccessTokenPayload = input.session.getAccessTokenPayload(input.userContext); - const sessionInfo = await session_1.getSessionInformation(stAccessTokenPayload.sessionHandle); - if (sessionInfo === undefined) { - throw new Error("Session not found"); - } - return { - tId: stAccessTokenPayload.tId, - rsub: stAccessTokenPayload.rsub, - sessionHandle: stAccessTokenPayload.sessionHandle, - // auth_time: sessionInfo?.timeCreated, - iss: appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(), - }; + return getDefaultAccessTokenPayload(input.user, input.scopes, input.sessionHandle, input.userContext); }, buildIdTokenPayload: async function (input) { - return getDefaultIdTokenPayload(input.user, input.scopes, input.userContext); + return getDefaultIdTokenPayload(input.user, input.scopes, input.sessionHandle, input.userContext); }, buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) { return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext); diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index da01b04b8..d8569f07d 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -70,6 +70,7 @@ export declare type TokenInfo = { token_type: string; }; export declare type LoginInfo = { + clientId: string; clientName: string; tosUri?: string; policyUri?: string; @@ -151,10 +152,10 @@ export declare type RecipeInterface = { }): Promise<{ redirectTo: string; }>; - getOAuth2Client( - input: Pick, - userContext: UserContext - ): Promise< + getOAuth2Client(input: { + clientId: string; + userContext: UserContext; + }): Promise< | { status: "OK"; client: OAuth2Client; @@ -166,8 +167,9 @@ export declare type RecipeInterface = { } >; getOAuth2Clients( - input: GetOAuth2ClientsInput, - userContext: UserContext + input: GetOAuth2ClientsInput & { + userContext: UserContext; + } ): Promise< | { status: "OK"; @@ -181,8 +183,9 @@ export declare type RecipeInterface = { } >; createOAuth2Client( - input: CreateOAuth2ClientInput, - userContext: UserContext + input: CreateOAuth2ClientInput & { + userContext: UserContext; + } ): Promise< | { status: "OK"; @@ -195,8 +198,9 @@ export declare type RecipeInterface = { } >; updateOAuth2Client( - input: UpdateOAuth2ClientInput, - userContext: UserContext + input: UpdateOAuth2ClientInput & { + userContext: UserContext; + } ): Promise< | { status: "OK"; @@ -209,8 +213,9 @@ export declare type RecipeInterface = { } >; deleteOAuth2Client( - input: DeleteOAuth2ClientInput, - userContext: UserContext + input: DeleteOAuth2ClientInput & { + userContext: UserContext; + } ): Promise< | { status: "OK"; @@ -237,14 +242,14 @@ export declare type RecipeInterface = { buildAccessTokenPayload(input: { user: User; client: OAuth2Client; - session: SessionContainerInterface; + sessionHandle: string; scopes: string[]; userContext: UserContext; }): Promise; buildIdTokenPayload(input: { user: User; client: OAuth2Client; - session: SessionContainerInterface; + sessionHandle: string; scopes: string[]; userContext: UserContext; }): Promise; @@ -287,6 +292,7 @@ export declare type APIInterface = { loginChallenge: string; options: APIOptions; session?: SessionContainerInterface; + shouldTryRefresh: boolean; userContext: UserContext; }) => Promise< | { @@ -301,6 +307,7 @@ export declare type APIInterface = { params: any; cookie: string | undefined; session: SessionContainerInterface | undefined; + shouldTryRefresh: boolean; options: APIOptions; userContext: UserContext; }) => Promise< @@ -437,6 +444,7 @@ export declare type DeleteOAuth2ClientInput = { export declare type PayloadBuilderFunction = ( user: User, scopes: string[], + sessionHandle: string, userContext: UserContext ) => Promise; export declare type UserInfoBuilderFunction = ( diff --git a/lib/build/recipe/userroles/recipe.js b/lib/build/recipe/userroles/recipe.js index f9829586d..c341f1c19 100644 --- a/lib/build/recipe/userroles/recipe.js +++ b/lib/build/recipe/userroles/recipe.js @@ -30,6 +30,7 @@ const recipe_1 = __importDefault(require("../session/recipe")); const recipe_2 = __importDefault(require("../oauth2provider/recipe")); const userRoleClaim_1 = require("./userRoleClaim"); const permissionClaim_1 = require("./permissionClaim"); +const session_1 = require("../session"); class Recipe extends recipeModule_1.default { constructor(recipeId, appInfo, isInServerlessEnv, config) { super(recipeId, appInfo); @@ -52,6 +53,44 @@ class Recipe extends recipeModule_1.default { if (!this.config.skipAddingPermissionsToAccessToken) { recipe_1.default.getInstanceOrThrowError().addClaimFromOtherRecipe(permissionClaim_1.PermissionClaim); } + const tokenPayloadBuilder = async (user, scopes, sessionHandle, userContext) => { + let payload = {}; + const sessionInfo = await session_1.getSessionInformation(sessionHandle, userContext); + let userRoles = []; + if (scopes.includes("roles") || scopes.includes("permissions")) { + const res = await this.recipeInterfaceImpl.getRolesForUser({ + userId: user.id, + tenantId: sessionInfo.tenantId, + userContext, + }); + if (res.status !== "OK") { + throw new Error("Failed to fetch roles for the user"); + } + userRoles = res.roles; + } + if (scopes.includes("roles")) { + payload.roles = userRoles; + } + if (scopes.includes("permissions")) { + const userPermissions = new Set(); + for (const role of userRoles) { + const rolePermissions = await this.recipeInterfaceImpl.getPermissionsForRole({ + role, + userContext, + }); + if (rolePermissions.status !== "OK") { + throw new Error("Failed to fetch permissions for the role"); + } + for (const perm of rolePermissions.permissions) { + userPermissions.add(perm); + } + } + payload.permissions = Array.from(userPermissions); + } + return payload; + }; + recipe_2.default.getInstanceOrThrowError().addAccessTokenBuilderFromOtherRecipe(tokenPayloadBuilder); + recipe_2.default.getInstanceOrThrowError().addIdTokenBuilderFromOtherRecipe(tokenPayloadBuilder); recipe_2.default .getInstanceOrThrowError() .addUserInfoBuilderFromOtherRecipe(async (user, _accessTokenPayload, scopes, tenantId, userContext) => { @@ -85,7 +124,7 @@ class Recipe extends recipeModule_1.default { userPermissions.add(perm); } } - userInfo.permissons = Array.from(userPermissions); + userInfo.permissions = Array.from(userPermissions); } return userInfo; }); diff --git a/lib/ts/recipe/oauth2provider/api/auth.ts b/lib/ts/recipe/oauth2provider/api/auth.ts index 3a75fd563..c6fc92e10 100644 --- a/lib/ts/recipe/oauth2provider/api/auth.ts +++ b/lib/ts/recipe/oauth2provider/api/auth.ts @@ -18,6 +18,7 @@ import { APIInterface, APIOptions } from ".."; import { UserContext } from "../../../types"; import setCookieParser from "set-cookie-parser"; import Session from "../../session"; +import SessionError from "../../../recipe/session/error"; export default async function authGET( apiImplementation: APIInterface, @@ -30,15 +31,19 @@ export default async function authGET( const origURL = options.req.getOriginalURL(); const splitURL = origURL.split("?"); const params = new URLSearchParams(splitURL[1]); - let session; + let session, shouldTryRefresh; try { session = await Session.getSession(options.req, options.res, { sessionRequired: false }, userContext); - } catch { - // We ignore this here since the authGET is called from the auth endpoint which is used in full-page redirections - // Returning a 401 would break the sign-in flow. - // In theory we could serve some JS that handles refreshing and retrying, but this is not implemented. - // What we do is that the auth endpoint will redirect to the login page, and the login page handles refreshing and - // redirect to the auth endpoint again. This is not optimal, but it works for now. + shouldTryRefresh = false; + } catch (error) { + session = undefined; + if (SessionError.isErrorFromSuperTokens(error) && error.type === SessionError.TRY_REFRESH_TOKEN) { + shouldTryRefresh = true; + } else { + // This should generally not happen, but we can handle this as if the session is not present, + // because then we redirect to the frontend, which should handle the validation error + shouldTryRefresh = false; + } } let response = await apiImplementation.authGET({ @@ -46,6 +51,7 @@ export default async function authGET( params: Object.fromEntries(params.entries()), cookie: options.req.getHeaderValue("cookie"), session, + shouldTryRefresh, userContext, }); if ("redirectTo" in response) { diff --git a/lib/ts/recipe/oauth2provider/api/implementation.ts b/lib/ts/recipe/oauth2provider/api/implementation.ts index 0e3bb71bc..6a09e7316 100644 --- a/lib/ts/recipe/oauth2provider/api/implementation.ts +++ b/lib/ts/recipe/oauth2provider/api/implementation.ts @@ -18,11 +18,12 @@ import { handleInternalRedirects, loginGET } from "./utils"; export default function getAPIImplementation(): APIInterface { return { - loginGET: async ({ loginChallenge, options, session, userContext }) => { + loginGET: async ({ loginChallenge, options, session, shouldTryRefresh, userContext }) => { const response = await loginGET({ recipeImplementation: options.recipeImplementation, loginChallenge, session, + shouldTryRefresh, isDirectCall: true, userContext, }); @@ -31,11 +32,12 @@ export default function getAPIImplementation(): APIInterface { cookie: options.req.getHeaderValue("cookie"), recipeImplementation: options.recipeImplementation, session, + shouldTryRefresh, userContext, }); }, - authGET: async ({ options, params, cookie, session, userContext }) => { + authGET: async ({ options, params, cookie, session, shouldTryRefresh, userContext }) => { const response = await options.recipeImplementation.authorization({ params, cookies: cookie, @@ -48,6 +50,7 @@ export default function getAPIImplementation(): APIInterface { recipeImplementation: options.recipeImplementation, cookie, session, + shouldTryRefresh, userContext, }); }, @@ -67,6 +70,7 @@ export default function getAPIImplementation(): APIInterface { return { status: "OK", info: { + clientId: client.clientId, clientName: client.clientName, tosUri: client.tosUri, policyUri: client.policyUri, diff --git a/lib/ts/recipe/oauth2provider/api/login.ts b/lib/ts/recipe/oauth2provider/api/login.ts index 78ef67780..9284c1e29 100644 --- a/lib/ts/recipe/oauth2provider/api/login.ts +++ b/lib/ts/recipe/oauth2provider/api/login.ts @@ -19,6 +19,7 @@ import { APIInterface, APIOptions } from ".."; import Session from "../../session"; import { UserContext } from "../../../types"; import SuperTokensError from "../../../error"; +import SessionError from "../../../recipe/session/error"; export default async function login( apiImplementation: APIInterface, @@ -29,13 +30,19 @@ export default async function login( return false; } - let session; + let session, shouldTryRefresh; try { session = await Session.getSession(options.req, options.res, { sessionRequired: false }, userContext); - } catch { + shouldTryRefresh = false; + } catch (error) { // We can handle this as if the session is not present, because then we redirect to the frontend, // which should handle the validation error session = undefined; + if (SuperTokensError.isErrorFromSuperTokens(error) && error.type === SessionError.TRY_REFRESH_TOKEN) { + shouldTryRefresh = true; + } else { + shouldTryRefresh = false; + } } const loginChallenge = @@ -50,6 +57,7 @@ export default async function login( options, loginChallenge, session, + shouldTryRefresh, userContext, }); if ("status" in response) { diff --git a/lib/ts/recipe/oauth2provider/api/utils.ts b/lib/ts/recipe/oauth2provider/api/utils.ts index 735979690..d0b2b088d 100644 --- a/lib/ts/recipe/oauth2provider/api/utils.ts +++ b/lib/ts/recipe/oauth2provider/api/utils.ts @@ -12,6 +12,7 @@ import setCookieParser from "set-cookie-parser"; export async function loginGET({ recipeImplementation, loginChallenge, + shouldTryRefresh, session, setCookie, isDirectCall, @@ -20,6 +21,7 @@ export async function loginGET({ recipeImplementation: RecipeInterface; loginChallenge: string; session?: SessionContainerInterface; + shouldTryRefresh: boolean; setCookie?: string; userContext: UserContext; isDirectCall: boolean; @@ -83,6 +85,32 @@ export async function loginGET({ }); return { redirectTo: accept.redirectTo, setCookie }; } + const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; + const websiteDomain = appInfo + .getOrigin({ + request: undefined, + userContext: userContext, + }) + .getAsStringDangerous(); + const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); + if (shouldTryRefresh) { + const websiteDomain = appInfo + .getOrigin({ + request: undefined, + userContext: userContext, + }) + .getAsStringDangerous(); + const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); + + const queryParamsForTryRefreshPage = new URLSearchParams({ + loginChallenge, + }); + + return { + redirectTo: websiteDomain + websiteBasePath + `/try-refresh?${queryParamsForTryRefreshPage.toString()}`, + setCookie, + }; + } if (promptParam === "none") { const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, @@ -95,14 +123,6 @@ export async function loginGET({ }); return { redirectTo: reject.redirectTo, setCookie }; } - const appInfo = SuperTokens.getInstanceOrThrowError().appInfo; - const websiteDomain = appInfo - .getOrigin({ - request: undefined, - userContext: userContext, - }) - .getAsStringDangerous(); - const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous(); const queryParamsForAuthPage = new URLSearchParams({ loginChallenge, @@ -179,12 +199,14 @@ export async function handleInternalRedirects({ response, recipeImplementation, session, + shouldTryRefresh, cookie = "", userContext, }: { response: { redirectTo: string; setCookie: string | undefined }; recipeImplementation: RecipeInterface; session?: SessionContainerInterface; + shouldTryRefresh: boolean; cookie?: string; userContext: UserContext; }): Promise<{ redirectTo: string; setCookie: string | undefined }> { @@ -213,6 +235,7 @@ export async function handleInternalRedirects({ recipeImplementation, loginChallenge, session, + shouldTryRefresh, setCookie: response.setCookie, isDirectCall: false, userContext, diff --git a/lib/ts/recipe/oauth2provider/index.ts b/lib/ts/recipe/oauth2provider/index.ts index 9fb20b0ba..01424b051 100644 --- a/lib/ts/recipe/oauth2provider/index.ts +++ b/lib/ts/recipe/oauth2provider/index.ts @@ -29,34 +29,34 @@ export default class Wrapper { static init = Recipe.init; static async getOAuth2Client(clientId: string, userContext?: Record) { - return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Client( - { clientId }, - getUserContext(userContext) - ); + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Client({ + clientId, + userContext: getUserContext(userContext), + }); } static async getOAuth2Clients(input: GetOAuth2ClientsInput, userContext?: Record) { - return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Clients( - input, - getUserContext(userContext) - ); + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getOAuth2Clients({ + ...input, + userContext: getUserContext(userContext), + }); } static async createOAuth2Client(input: CreateOAuth2ClientInput, userContext?: Record) { - return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createOAuth2Client( - input, - getUserContext(userContext) - ); + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createOAuth2Client({ + ...input, + userContext: getUserContext(userContext), + }); } static async updateOAuth2Client(input: UpdateOAuth2ClientInput, userContext?: Record) { - return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.updateOAuth2Client( - input, - getUserContext(userContext) - ); + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.updateOAuth2Client({ + ...input, + userContext: getUserContext(userContext), + }); } static async deleteOAuth2Client(input: DeleteOAuth2ClientInput, userContext?: Record) { - return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.deleteOAuth2Client( - input, - getUserContext(userContext) - ); + return await Recipe.getInstanceOrThrowError().recipeInterfaceImpl.deleteOAuth2Client({ + ...input, + userContext: getUserContext(userContext), + }); } static validateOAuth2AccessToken( @@ -107,7 +107,7 @@ export default class Wrapper { const normalisedUserContext = getUserContext(userContext); const recipeInterfaceImpl = Recipe.getInstanceOrThrowError().recipeInterfaceImpl; - const res = await recipeInterfaceImpl.getOAuth2Client({ clientId }, normalisedUserContext); + const res = await recipeInterfaceImpl.getOAuth2Client({ clientId, userContext: normalisedUserContext }); if (res.status !== "OK") { throw new Error(`Failed to get OAuth2 client with id ${clientId}: ${res.error}`); diff --git a/lib/ts/recipe/oauth2provider/recipe.ts b/lib/ts/recipe/oauth2provider/recipe.ts index f671b7b9b..054c44281 100644 --- a/lib/ts/recipe/oauth2provider/recipe.ts +++ b/lib/ts/recipe/oauth2provider/recipe.ts @@ -51,10 +51,15 @@ import userInfoGET from "./api/userInfo"; import { resetCombinedJWKS } from "../../combinedRemoteJWKSet"; import revokeTokenPOST from "./api/revokeToken"; import introspectTokenPOST from "./api/introspectToken"; +import { getSessionInformation } from "../session"; +import { send200Response } from "../../utils"; + +const tokenHookMap = new Map(); export default class Recipe extends RecipeModule { static RECIPE_ID = "oauth2provider"; private static instance: Recipe | undefined = undefined; + private accessTokenBuilders: PayloadBuilderFunction[] = []; private idTokenBuilders: PayloadBuilderFunction[] = []; private userInfoBuilders: UserInfoBuilderFunction[] = []; @@ -74,8 +79,10 @@ export default class Recipe extends RecipeModule { Querier.getNewInstanceOrThrowError(recipeId), this.config, appInfo, + this.getDefaultAccessTokenPayload.bind(this), this.getDefaultIdTokenPayload.bind(this), - this.getDefaultUserInfoPayload.bind(this) + this.getDefaultUserInfoPayload.bind(this), + this.saveTokensForHook.bind(this) ) ); this.recipeInterfaceImpl = builder.override(this.config.override.functions).build(); @@ -120,6 +127,15 @@ export default class Recipe extends RecipeModule { addUserInfoBuilderFromOtherRecipe = (userInfoBuilderFn: UserInfoBuilderFunction) => { this.userInfoBuilders.push(userInfoBuilderFn); }; + addAccessTokenBuilderFromOtherRecipe = (accessTokenBuilders: PayloadBuilderFunction) => { + this.accessTokenBuilders.push(accessTokenBuilders); + }; + addIdTokenBuilderFromOtherRecipe = (idTokenBuilder: PayloadBuilderFunction) => { + this.idTokenBuilders.push(idTokenBuilder); + }; + saveTokensForHook = (sessionHandle: string, idToken: JSONObject, accessToken: JSONObject) => { + tokenHookMap.set(sessionHandle, { idToken, accessToken }); + }; /* RecipeModule functions */ @@ -167,6 +183,13 @@ export default class Recipe extends RecipeModule { id: INTROSPECT_TOKEN_PATH, disabled: this.apiImpl.introspectTokenPOST === undefined, }, + { + // TODO: remove this once we get core support + method: "post", + pathWithoutApiBasePath: new NormalisedURLPath("/oauth/token-hook"), + id: "token-hook", + disabled: false, + }, ]; } @@ -209,6 +232,24 @@ export default class Recipe extends RecipeModule { if (id === INTROSPECT_TOKEN_PATH) { return introspectTokenPOST(this.apiImpl, options, userContext); } + if (id === "token-hook") { + const body = await options.req.getBodyAsJSONOrFormData(); + const sessionHandle = body.session.extra.sessionHandle; + const tokens = tokenHookMap.get(sessionHandle); + + if (tokens !== undefined) { + const { idToken, accessToken } = tokens; + send200Response(options.res, { + session: { + access_token: accessToken, + id_token: idToken, + }, + }); + } else { + send200Response(options.res, {}); + } + return true; + } throw new Error("Should never come here: handleAPIRequest called with unknown id"); }; @@ -224,7 +265,28 @@ export default class Recipe extends RecipeModule { return SuperTokensError.isErrorFromSuperTokens(err) && err.fromRecipe === Recipe.RECIPE_ID; } - async getDefaultIdTokenPayload(user: User, scopes: string[], userContext: UserContext) { + async getDefaultAccessTokenPayload(user: User, scopes: string[], sessionHandle: string, userContext: UserContext) { + const sessionInfo = await getSessionInformation(sessionHandle); + if (sessionInfo === undefined) { + throw new Error("Session not found"); + } + let payload: JSONObject = { + iss: this.appInfo.apiDomain.getAsStringDangerous() + this.appInfo.apiBasePath.getAsStringDangerous(), + tId: sessionInfo.tenantId, + rsub: sessionInfo.recipeUserId.getAsString(), + sessionHandle: sessionHandle, + }; + + for (const fn of this.accessTokenBuilders) { + payload = { + ...payload, + ...(await fn(user, scopes, sessionHandle, userContext)), + }; + } + + return payload; + } + async getDefaultIdTokenPayload(user: User, scopes: string[], sessionHandle: string, userContext: UserContext) { let payload: JSONObject = { iss: this.appInfo.apiDomain.getAsStringDangerous() + this.appInfo.apiBasePath.getAsStringDangerous(), }; @@ -242,7 +304,7 @@ export default class Recipe extends RecipeModule { for (const fn of this.idTokenBuilders) { payload = { ...payload, - ...(await fn(user, scopes, userContext)), + ...(await fn(user, scopes, sessionHandle, userContext)), }; } diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index f870812d6..77aa57abc 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -16,7 +16,7 @@ import * as jose from "jose"; import NormalisedURLPath from "../../normalisedURLPath"; import { Querier, hydraPubDomain } from "../../querier"; -import { NormalisedAppinfo } from "../../types"; +import { JSONObject, NormalisedAppinfo } from "../../types"; import { RecipeInterface, TypeNormalisedInput, @@ -29,7 +29,6 @@ import { toSnakeCase, transformObjectKeys } from "../../utils"; import { OAuth2Client } from "./OAuth2Client"; import { getUser } from "../.."; import { getCombinedJWKS } from "../../combinedRemoteJWKSet"; -import { JSONObject } from "@loopback/core"; import { getSessionInformation } from "../session"; // TODO: Remove this core changes are done @@ -43,8 +42,10 @@ export default function getRecipeInterface( querier: Querier, _config: TypeNormalisedInput, appInfo: NormalisedAppinfo, + getDefaultAccessTokenPayload: PayloadBuilderFunction, getDefaultIdTokenPayload: PayloadBuilderFunction, - getDefaultUserInfoPayload: UserInfoBuilderFunction + getDefaultUserInfoPayload: UserInfoBuilderFunction, + saveTokensForHook: (sessionHandle: string, idToken: JSONObject, accessToken: JSONObject) => void ): RecipeInterface { return { getLoginRequest: async function (this: RecipeInterface, input): Promise { @@ -209,18 +210,17 @@ export default function getRecipeInterface( if (!user) { throw new Error("Should not happen"); } - const idToken = this.buildIdTokenPayload({ + const idToken = await this.buildIdTokenPayload({ user, client: consentRequest.client!, - session: input.session, + sessionHandle: input.session.getHandle(), scopes: consentRequest.requestedScope || [], userContext: input.userContext, }); - const accessTokenPayload = await this.buildAccessTokenPayload({ user, client: consentRequest.client!, - session: input.session, + sessionHandle: input.session.getHandle(), scopes: consentRequest.requestedScope || [], userContext: input.userContext, }); @@ -257,6 +257,56 @@ export default function getRecipeInterface( body[key] = input.body[key]; } + if (input.body.grant_type === "refresh_token") { + const scopes = input.body.scope?.split(" ") ?? []; + const tokenInfo = await this.introspectToken({ + token: input.body.refresh_token!, + scopes, + userContext: input.userContext, + }); + + if (tokenInfo.active === true) { + const sessionHandle = (tokenInfo.ext as any).sessionHandle as string; + + const clientInfo = await this.getOAuth2Client({ + clientId: tokenInfo.client_id as string, + userContext: input.userContext, + }); + if (clientInfo.status === "ERROR") { + return { + statusCode: 400, + error: clientInfo.error, + errorDescription: clientInfo.errorHint, + }; + } + const client = clientInfo.client; + const user = await getUser(tokenInfo.sub as string); + if (!user) { + throw new Error("User not found"); + } + const idToken = await this.buildIdTokenPayload({ + user, + client, + sessionHandle: sessionHandle, + scopes, + userContext: input.userContext, + }); + const accessTokenPayload = await this.buildAccessTokenPayload({ + user, + client, + sessionHandle: sessionHandle, + scopes, + userContext: input.userContext, + }); + body["session"] = { + id_token: idToken, + access_token: accessTokenPayload, + }; + + saveTokensForHook(sessionHandle, idToken, accessTokenPayload); + } + } + if (input.authorizationHeader) { body["authorizationHeader"] = input.authorizationHeader; } @@ -277,7 +327,7 @@ export default function getRecipeInterface( return res.data; }, - getOAuth2Clients: async function (input, userContext) { + getOAuth2Clients: async function (input) { let response = await querier.sendGetRequestWithResponseHeaders( new NormalisedURLPath(`/recipe/oauth2/admin/clients`), { @@ -285,7 +335,7 @@ export default function getRecipeInterface( page_token: input.paginationToken, }, {}, - userContext + input.userContext ); if (response.body.status === "OK") { @@ -317,12 +367,12 @@ export default function getRecipeInterface( }; } }, - getOAuth2Client: async function (input, userContext) { + getOAuth2Client: async function (input) { let response = await querier.sendGetRequestWithResponseHeaders( new NormalisedURLPath(`/recipe/oauth2/admin/clients/${input.clientId}`), {}, {}, - userContext + input.userContext ); if (response.body.status === "OK") { @@ -338,7 +388,7 @@ export default function getRecipeInterface( }; } }, - createOAuth2Client: async function (input, userContext) { + createOAuth2Client: async function (input) { let response = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth2/admin/clients`), { @@ -348,7 +398,7 @@ export default function getRecipeInterface( skip_consent: true, subject_type: "public", }, - userContext + input.userContext ); if (response.status === "OK") { @@ -364,7 +414,7 @@ export default function getRecipeInterface( }; } }, - updateOAuth2Client: async function (input, userContext) { + updateOAuth2Client: async function (input) { // We convert the input into an array of "replace" operations const requestBody = Object.entries(input).reduce< Array<{ from: string; op: "replace"; path: string; value: any }> @@ -381,7 +431,7 @@ export default function getRecipeInterface( let response = await querier.sendPatchRequest( new NormalisedURLPath(`/recipe/oauth2/admin/clients/${input.clientId}`), requestBody, - userContext + input.userContext ); if (response.status === "OK") { @@ -397,12 +447,12 @@ export default function getRecipeInterface( }; } }, - deleteOAuth2Client: async function (input, userContext) { + deleteOAuth2Client: async function (input) { let response = await querier.sendDeleteRequest( new NormalisedURLPath(`/recipe/oauth2/admin/clients/${input.clientId}`), undefined, undefined, - userContext + input.userContext ); if (response.status === "OK") { @@ -416,21 +466,10 @@ export default function getRecipeInterface( } }, buildAccessTokenPayload: async function (input) { - const stAccessTokenPayload = input.session.getAccessTokenPayload(input.userContext); - const sessionInfo = await getSessionInformation(stAccessTokenPayload.sessionHandle); - if (sessionInfo === undefined) { - throw new Error("Session not found"); - } - return { - tId: stAccessTokenPayload.tId, - rsub: stAccessTokenPayload.rsub, - sessionHandle: stAccessTokenPayload.sessionHandle, - // auth_time: sessionInfo?.timeCreated, - iss: appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(), - }; + return getDefaultAccessTokenPayload(input.user, input.scopes, input.sessionHandle, input.userContext); }, buildIdTokenPayload: async function (input) { - return getDefaultIdTokenPayload(input.user, input.scopes, input.userContext); + return getDefaultIdTokenPayload(input.user, input.scopes, input.sessionHandle, input.userContext); }, buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) { return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext); diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index 255966931..2bd79ef3f 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -148,6 +148,7 @@ export type TokenInfo = { }; export type LoginInfo = { + clientId: string; // The name of the client. clientName: string; // The URI of the client's terms of service. @@ -259,10 +260,10 @@ export type RecipeInterface = { userContext: UserContext; }): Promise<{ redirectTo: string }>; - getOAuth2Client( - input: Pick, - userContext: UserContext - ): Promise< + getOAuth2Client(input: { + clientId: string; + userContext: UserContext; + }): Promise< | { status: "OK"; client: OAuth2Client; @@ -275,8 +276,9 @@ export type RecipeInterface = { } >; getOAuth2Clients( - input: GetOAuth2ClientsInput, - userContext: UserContext + input: GetOAuth2ClientsInput & { + userContext: UserContext; + } ): Promise< | { status: "OK"; @@ -291,8 +293,9 @@ export type RecipeInterface = { } >; createOAuth2Client( - input: CreateOAuth2ClientInput, - userContext: UserContext + input: CreateOAuth2ClientInput & { + userContext: UserContext; + } ): Promise< | { status: "OK"; @@ -306,8 +309,9 @@ export type RecipeInterface = { } >; updateOAuth2Client( - input: UpdateOAuth2ClientInput, - userContext: UserContext + input: UpdateOAuth2ClientInput & { + userContext: UserContext; + } ): Promise< | { status: "OK"; @@ -321,8 +325,9 @@ export type RecipeInterface = { } >; deleteOAuth2Client( - input: DeleteOAuth2ClientInput, - userContext: UserContext + input: DeleteOAuth2ClientInput & { + userContext: UserContext; + } ): Promise< | { status: "OK"; @@ -349,14 +354,14 @@ export type RecipeInterface = { buildAccessTokenPayload(input: { user: User; client: OAuth2Client; - session: SessionContainerInterface; + sessionHandle: string; scopes: string[]; userContext: UserContext; }): Promise; buildIdTokenPayload(input: { user: User; client: OAuth2Client; - session: SessionContainerInterface; + sessionHandle: string; scopes: string[]; userContext: UserContext; }): Promise; @@ -392,6 +397,7 @@ export type APIInterface = { loginChallenge: string; options: APIOptions; session?: SessionContainerInterface; + shouldTryRefresh: boolean; userContext: UserContext; }) => Promise<{ redirectTo: string; setCookie: string | undefined } | GeneralErrorResponse>); @@ -401,6 +407,7 @@ export type APIInterface = { params: any; cookie: string | undefined; session: SessionContainerInterface | undefined; + shouldTryRefresh: boolean; options: APIOptions; userContext: UserContext; }) => Promise<{ redirectTo: string; setCookie: string | undefined } | ErrorOAuth2 | GeneralErrorResponse>); @@ -523,7 +530,12 @@ export type DeleteOAuth2ClientInput = { clientId: string; }; -export type PayloadBuilderFunction = (user: User, scopes: string[], userContext: UserContext) => Promise; +export type PayloadBuilderFunction = ( + user: User, + scopes: string[], + sessionHandle: string, + userContext: UserContext +) => Promise; export type UserInfoBuilderFunction = ( user: User, accessTokenPayload: JSONObject, diff --git a/lib/ts/recipe/userroles/recipe.ts b/lib/ts/recipe/userroles/recipe.ts index 07583783f..36befb4f2 100644 --- a/lib/ts/recipe/userroles/recipe.ts +++ b/lib/ts/recipe/userroles/recipe.ts @@ -19,7 +19,7 @@ import type { BaseRequest, BaseResponse } from "../../framework"; import normalisedURLPath from "../../normalisedURLPath"; import { Querier } from "../../querier"; import RecipeModule from "../../recipeModule"; -import { APIHandled, HTTPMethod, NormalisedAppinfo, RecipeListFunction } from "../../types"; +import { APIHandled, HTTPMethod, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types"; import RecipeImplementation from "./recipeImplementation"; import { RecipeInterface, TypeInput, TypeNormalisedInput } from "./types"; @@ -30,6 +30,8 @@ import SessionRecipe from "../session/recipe"; import OAuth2Recipe from "../oauth2provider/recipe"; import { UserRoleClaim } from "./userRoleClaim"; import { PermissionClaim } from "./permissionClaim"; +import { User } from "../../user"; +import { getSessionInformation } from "../session"; export default class Recipe extends RecipeModule { static RECIPE_ID = "userroles"; @@ -57,11 +59,69 @@ export default class Recipe extends RecipeModule { SessionRecipe.getInstanceOrThrowError().addClaimFromOtherRecipe(PermissionClaim); } + const tokenPayloadBuilder = async ( + user: User, + scopes: string[], + sessionHandle: string, + userContext: UserContext + ) => { + let payload: { + roles?: string[]; + permissions?: string[]; + } = {}; + + const sessionInfo = await getSessionInformation(sessionHandle, userContext); + + let userRoles: string[] = []; + + if (scopes.includes("roles") || scopes.includes("permissions")) { + const res = await this.recipeInterfaceImpl.getRolesForUser({ + userId: user.id, + tenantId: sessionInfo!.tenantId, + userContext, + }); + + if (res.status !== "OK") { + throw new Error("Failed to fetch roles for the user"); + } + userRoles = res.roles; + } + + if (scopes.includes("roles")) { + payload.roles = userRoles; + } + + if (scopes.includes("permissions")) { + const userPermissions = new Set(); + for (const role of userRoles) { + const rolePermissions = await this.recipeInterfaceImpl.getPermissionsForRole({ + role, + userContext, + }); + + if (rolePermissions.status !== "OK") { + throw new Error("Failed to fetch permissions for the role"); + } + + for (const perm of rolePermissions.permissions) { + userPermissions.add(perm); + } + } + + payload.permissions = Array.from(userPermissions); + } + + return payload; + }; + + OAuth2Recipe.getInstanceOrThrowError().addAccessTokenBuilderFromOtherRecipe(tokenPayloadBuilder); + OAuth2Recipe.getInstanceOrThrowError().addIdTokenBuilderFromOtherRecipe(tokenPayloadBuilder); + OAuth2Recipe.getInstanceOrThrowError().addUserInfoBuilderFromOtherRecipe( async (user, _accessTokenPayload, scopes, tenantId, userContext) => { let userInfo: { roles?: string[]; - permissons?: string[]; + permissions?: string[]; } = {}; let userRoles: string[] = []; @@ -100,7 +160,7 @@ export default class Recipe extends RecipeModule { } } - userInfo.permissons = Array.from(userPermissions); + userInfo.permissions = Array.from(userPermissions); } return userInfo; From 6f0992606a7cd47dc4c27e6f24b194862f0db16f Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 21 Aug 2024 21:12:21 +0530 Subject: [PATCH 30/35] fix: auth and token endpoint integration --- .../oauth2provider/recipeImplementation.js | 29 +++++++++---------- .../oauth2provider/recipeImplementation.ts | 25 +++++++++------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index c0371d998..0b37959b0 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -211,16 +211,16 @@ function getRecipeInterface( delete input.params.prompt; } } - const resp = await querier.sendGetRequestWithResponseHeaders( - new normalisedURLPath_1.default(`/recipe/oauth2/pub/auth`), - input.params, - { - // TODO: if session is not set also clear the oauth2 cookie - Cookie: `${input.cookies}`, - }, + const resp = await querier.sendPostRequest( + new normalisedURLPath_1.default(`/recipe/oauth/auth`), + Object.assign(Object.assign({}, input.params), { cookie: `${input.cookies}` }), + // { + // // TODO: if session is not set also clear the oauth2 cookie + // Cookie: `${input.cookies}`, + // }, input.userContext ); - const redirectTo = getUpdatedRedirectTo(appInfo, resp.headers.get("Location")); + const redirectTo = resp.redirectTo; if (redirectTo === undefined) { throw new Error(resp.body); } @@ -268,17 +268,14 @@ function getRecipeInterface( ); return { redirectTo: consentRes.redirectTo, - setCookie: (_a = resp.headers.get("set-cookie")) !== null && _a !== void 0 ? _a : undefined, + setCookie: (_a = resp.cookies) !== null && _a !== void 0 ? _a : undefined, }; } - return { - redirectTo, - setCookie: (_b = resp.headers.get("set-cookie")) !== null && _b !== void 0 ? _b : undefined, - }; + return { redirectTo, setCookie: (_b = resp.cookies) !== null && _b !== void 0 ? _b : undefined }; }, tokenExchange: async function (input) { var _a, _b; - const body = { $isFormData: true }; // TODO: we ideally want to avoid using formdata, the core can do the translation + const body = {}; for (const key in input.body) { body[key] = input.body[key]; } @@ -336,7 +333,7 @@ function getRecipeInterface( body["authorizationHeader"] = input.authorizationHeader; } const res = await querier.sendPostRequest( - new normalisedURLPath_1.default(`/recipe/oauth2/pub/token`), + new normalisedURLPath_1.default(`/recipe/oauth/token`), body, input.userContext ); @@ -347,7 +344,7 @@ function getRecipeInterface( errorDescription: res.data.error_description, }; } - return res.data; + return res; }, getOAuth2Clients: async function (input) { var _a; diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index 77aa57abc..9ab09301e 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -184,17 +184,20 @@ export default function getRecipeInterface( } } - const resp = await querier.sendGetRequestWithResponseHeaders( - new NormalisedURLPath(`/recipe/oauth2/pub/auth`), - input.params, + const resp = await querier.sendPostRequest( + new NormalisedURLPath(`/recipe/oauth/auth`), { - // TODO: if session is not set also clear the oauth2 cookie - Cookie: `${input.cookies}`, + ...input.params, + cookie: `${input.cookies}`, }, + // { + // // TODO: if session is not set also clear the oauth2 cookie + // Cookie: `${input.cookies}`, + // }, input.userContext ); - const redirectTo = getUpdatedRedirectTo(appInfo, resp.headers.get("Location")!); + const redirectTo = resp.redirectTo; if (redirectTo === undefined) { throw new Error(resp.body); } @@ -245,14 +248,14 @@ export default function getRecipeInterface( return { redirectTo: consentRes.redirectTo, - setCookie: resp.headers.get("set-cookie") ?? undefined, + setCookie: resp.cookies ?? undefined, }; } - return { redirectTo, setCookie: resp.headers.get("set-cookie") ?? undefined }; + return { redirectTo, setCookie: resp.cookies ?? undefined }; }, tokenExchange: async function (this: RecipeInterface, input) { - const body: any = { $isFormData: true }; // TODO: we ideally want to avoid using formdata, the core can do the translation + const body: any = {}; for (const key in input.body) { body[key] = input.body[key]; } @@ -312,7 +315,7 @@ export default function getRecipeInterface( } const res = await querier.sendPostRequest( - new NormalisedURLPath(`/recipe/oauth2/pub/token`), + new NormalisedURLPath(`/recipe/oauth/token`), body, input.userContext ); @@ -324,7 +327,7 @@ export default function getRecipeInterface( errorDescription: res.data.error_description, }; } - return res.data; + return res; }, getOAuth2Clients: async function (input) { From 93c379045e1062338e05e143363611f43416bd72 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Fri, 23 Aug 2024 17:42:00 +0530 Subject: [PATCH 31/35] fix: token endpoint --- lib/build/recipe/oauth2provider/recipeImplementation.js | 2 +- lib/ts/recipe/oauth2provider/recipeImplementation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 0b37959b0..6bf4dcfd3 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -334,7 +334,7 @@ function getRecipeInterface( } const res = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth/token`), - body, + { body }, input.userContext ); if (res.status !== "OK") { diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index 9ab09301e..d5bc00558 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -316,7 +316,7 @@ export default function getRecipeInterface( const res = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth/token`), - body, + { body }, input.userContext ); From 2ec47841794e5b868e46e089f3bfc6252404b699 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 27 Aug 2024 15:41:36 +0530 Subject: [PATCH 32/35] fix: refactor issuer --- lib/build/recipe/oauth2provider/recipe.js | 5 +---- .../oauth2provider/recipeImplementation.js | 16 +++++++++++----- lib/build/recipe/oauth2provider/types.d.ts | 1 + lib/ts/recipe/oauth2provider/recipe.ts | 5 +---- .../oauth2provider/recipeImplementation.ts | 16 ++++++++++------ lib/ts/recipe/oauth2provider/types.ts | 2 ++ 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/build/recipe/oauth2provider/recipe.js b/lib/build/recipe/oauth2provider/recipe.js index fbd77ed7c..1b1cc2f50 100644 --- a/lib/build/recipe/oauth2provider/recipe.js +++ b/lib/build/recipe/oauth2provider/recipe.js @@ -223,7 +223,6 @@ class Recipe extends recipeModule_1.default { throw new Error("Session not found"); } let payload = { - iss: this.appInfo.apiDomain.getAsStringDangerous() + this.appInfo.apiBasePath.getAsStringDangerous(), tId: sessionInfo.tenantId, rsub: sessionInfo.recipeUserId.getAsString(), sessionHandle: sessionHandle, @@ -234,9 +233,7 @@ class Recipe extends recipeModule_1.default { return payload; } async getDefaultIdTokenPayload(user, scopes, sessionHandle, userContext) { - let payload = { - iss: this.appInfo.apiDomain.getAsStringDangerous() + this.appInfo.apiBasePath.getAsStringDangerous(), - }; + let payload = {}; if (scopes.includes("email")) { payload.email = user === null || user === void 0 ? void 0 : user.emails[0]; payload.email_verified = user.loginMethods.some( diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 6bf4dcfd3..e6b0deb68 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -213,7 +213,10 @@ function getRecipeInterface( } const resp = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth/auth`), - Object.assign(Object.assign({}, input.params), { cookie: `${input.cookies}` }), + { + params: input.params, + cookies: `${input.cookies}`, + }, // { // // TODO: if session is not set also clear the oauth2 cookie // Cookie: `${input.cookies}`, @@ -334,14 +337,14 @@ function getRecipeInterface( } const res = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth/token`), - { body }, + { body, iss: await this.getIssuer({ userContext: input.userContext }) }, input.userContext ); if (res.status !== "OK") { return { - statusCode: res.statusCode, - error: res.data.error, - errorDescription: res.data.error_description, + statusCode: res.status_code, + error: res.error, + errorDescription: res.error_description, }; } return res; @@ -472,6 +475,9 @@ function getRecipeInterface( }; } }, + getIssuer: async function (_) { + return appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); + }, buildAccessTokenPayload: async function (input) { return getDefaultAccessTokenPayload(input.user, input.scopes, input.sessionHandle, input.userContext); }, diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index d8569f07d..0fad2ba09 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -239,6 +239,7 @@ export declare type RecipeInterface = { status: "OK"; payload: JSONObject; }>; + getIssuer(input: { userContext: UserContext }): Promise; buildAccessTokenPayload(input: { user: User; client: OAuth2Client; diff --git a/lib/ts/recipe/oauth2provider/recipe.ts b/lib/ts/recipe/oauth2provider/recipe.ts index 054c44281..861d40f68 100644 --- a/lib/ts/recipe/oauth2provider/recipe.ts +++ b/lib/ts/recipe/oauth2provider/recipe.ts @@ -271,7 +271,6 @@ export default class Recipe extends RecipeModule { throw new Error("Session not found"); } let payload: JSONObject = { - iss: this.appInfo.apiDomain.getAsStringDangerous() + this.appInfo.apiBasePath.getAsStringDangerous(), tId: sessionInfo.tenantId, rsub: sessionInfo.recipeUserId.getAsString(), sessionHandle: sessionHandle, @@ -287,9 +286,7 @@ export default class Recipe extends RecipeModule { return payload; } async getDefaultIdTokenPayload(user: User, scopes: string[], sessionHandle: string, userContext: UserContext) { - let payload: JSONObject = { - iss: this.appInfo.apiDomain.getAsStringDangerous() + this.appInfo.apiBasePath.getAsStringDangerous(), - }; + let payload: JSONObject = {}; if (scopes.includes("email")) { payload.email = user?.emails[0]; payload.email_verified = user.loginMethods.some((lm) => lm.hasSameEmailAs(user?.emails[0]) && lm.verified); diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index d5bc00558..817c129da 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -187,8 +187,8 @@ export default function getRecipeInterface( const resp = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth/auth`), { - ...input.params, - cookie: `${input.cookies}`, + params: input.params, + cookies: `${input.cookies}`, }, // { // // TODO: if session is not set also clear the oauth2 cookie @@ -316,17 +316,18 @@ export default function getRecipeInterface( const res = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth/token`), - { body }, + { body, iss: await this.getIssuer({ userContext: input.userContext }) }, input.userContext ); if (res.status !== "OK") { return { - statusCode: res.statusCode, - error: res.data.error, - errorDescription: res.data.error_description, + statusCode: res.status_code, + error: res.error, + errorDescription: res.error_description, }; } + return res; }, @@ -468,6 +469,9 @@ export default function getRecipeInterface( }; } }, + getIssuer: async function (_) { + return appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous(); + }, buildAccessTokenPayload: async function (input) { return getDefaultAccessTokenPayload(input.user, input.scopes, input.sessionHandle, input.userContext); }, diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index 2bd79ef3f..b646d154d 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -351,6 +351,8 @@ export type RecipeInterface = { userContext: UserContext; }): Promise<{ status: "OK"; payload: JSONObject }>; + getIssuer(input: { userContext: UserContext }): Promise; + buildAccessTokenPayload(input: { user: User; client: OAuth2Client; From cc9d278ca1966d2f33a24221bb7e89c08e1096da Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 5 Sep 2024 18:38:54 +0530 Subject: [PATCH 33/35] fix: consent integration --- .../oauth2provider/api/implementation.js | 19 +- lib/build/recipe/oauth2provider/api/utils.js | 13 +- .../oauth2provider/recipeImplementation.js | 164 +++++++++-------- lib/build/recipe/oauth2provider/types.d.ts | 15 +- .../oauth2provider/api/implementation.ts | 19 +- lib/ts/recipe/oauth2provider/api/utils.ts | 13 +- .../oauth2provider/recipeImplementation.ts | 171 ++++++++++-------- lib/ts/recipe/oauth2provider/types.ts | 10 +- 8 files changed, 237 insertions(+), 187 deletions(-) diff --git a/lib/build/recipe/oauth2provider/api/implementation.js b/lib/build/recipe/oauth2provider/api/implementation.js index fbe42c1c4..1ba2baf4d 100644 --- a/lib/build/recipe/oauth2provider/api/implementation.js +++ b/lib/build/recipe/oauth2provider/api/implementation.js @@ -42,14 +42,17 @@ function getAPIImplementation() { session, userContext, }); - return utils_1.handleInternalRedirects({ - response, - recipeImplementation: options.recipeImplementation, - cookie, - session, - shouldTryRefresh, - userContext, - }); + if (response.status === "OK") { + return utils_1.handleInternalRedirects({ + response, + recipeImplementation: options.recipeImplementation, + cookie, + session, + shouldTryRefresh, + userContext, + }); + } + return response; }, tokenPOST: async (input) => { return input.options.recipeImplementation.tokenExchange({ diff --git a/lib/build/recipe/oauth2provider/api/utils.js b/lib/build/recipe/oauth2provider/api/utils.js index b3d9a94d3..762963794 100644 --- a/lib/build/recipe/oauth2provider/api/utils.js +++ b/lib/build/recipe/oauth2provider/api/utils.js @@ -49,6 +49,7 @@ async function loginGET({ const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, error: { + status: "OAUTH_ERROR", error: "invalid_request", errorDescription: "max_age cannot be negative", }, @@ -60,6 +61,7 @@ async function loginGET({ const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, error: { + status: "OAUTH_ERROR", error: "invalid_request", errorDescription: "max_age must be an integer", }, @@ -116,6 +118,7 @@ async function loginGET({ const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, error: { + status: "OAUTH_ERROR", error: "login_required", errorDescription: "The Authorization Server requires End-User authentication. Prompt 'none' was requested, but no existing or expired login session was found.", @@ -230,10 +233,12 @@ async function handleInternalRedirects({ session, userContext, }); - response = { - redirectTo: authRes.redirectTo, - setCookie: mergeSetCookieHeaders(authRes.setCookie, response.setCookie), - }; + if (authRes.status === "OK") { + response = { + redirectTo: authRes.redirectTo, + setCookie: mergeSetCookieHeaders(authRes.setCookie, response.setCookie), + }; + } } else { throw new Error(`Unexpected internal redirect ${response.redirectTo}`); } diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index e6b0deb68..4ef7b4467 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -63,8 +63,14 @@ const OAuth2Client_1 = require("./OAuth2Client"); const __1 = require("../.."); const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet"); const session_1 = require("../session"); -// TODO: Remove this core changes are done function getUpdatedRedirectTo(appInfo, redirectTo) { + if (redirectTo.includes("{apiDomain}")) { + return redirectTo.replace( + "{apiDomain}", + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() + ); + } + // TODO: Remove this core changes are done return redirectTo .replace( querier_1.hydraPubDomain, @@ -144,63 +150,61 @@ function getRecipeInterface( }, getConsentRequest: async function (input) { const resp = await querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/oauth2/admin/oauth2/auth/requests/consent"), - { consent_challenge: input.challenge }, + new normalisedURLPath_1.default("/recipe/oauth/auth/requests/consent"), + { challenge: input.challenge }, input.userContext ); return { - acr: resp.data.acr, - amr: resp.data.amr, - challenge: resp.data.challenge, - client: OAuth2Client_1.OAuth2Client.fromAPIResponse(resp.data.client), - context: resp.data.context, - loginChallenge: resp.data.login_challenge, - loginSessionId: resp.data.login_session_id, - oidcContext: resp.data.oidc_context, - requestedAccessTokenAudience: resp.data.requested_access_token_audience, - requestedScope: resp.data.requested_scope, - skip: resp.data.skip, - subject: resp.data.subject, + acr: resp.acr, + amr: resp.amr, + challenge: resp.challenge, + client: OAuth2Client_1.OAuth2Client.fromAPIResponse(resp.client), + context: resp.context, + loginChallenge: resp.loginChallenge, + loginSessionId: resp.loginSessionId, + oidcContext: resp.oidcContext, + requestedAccessTokenAudience: resp.requestedAccessTokenAudience, + requestedScope: resp.requestedScope, + skip: resp.skip, + subject: resp.subject, }; }, acceptConsentRequest: async function (input) { const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/consent/accept`), + new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/consent/accept`), { context: input.context, - grant_access_token_audience: input.grantAccessTokenAudience, - grant_scope: input.grantScope, - handled_at: input.handledAt, + grantAccessTokenAudience: input.grantAccessTokenAudience, + grantScope: input.grantScope, + handledAt: input.handledAt, remember: input.remember, - remember_for: input.rememberFor, + rememberFor: input.rememberFor, session: input.session, }, { - consent_challenge: input.challenge, + challenge: input.challenge, }, input.userContext ); return { - // TODO: FIXME!!! - redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), + redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, rejectConsentRequest: async function (input) { const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/consent/reject`), + new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/consent/reject`), { error: input.error.error, error_description: input.error.errorDescription, status_code: input.error.statusCode, }, { - consent_challenge: input.challenge, + consentChallenge: input.challenge, }, input.userContext ); return { - // TODO: FIXME!!! - redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), + redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, authorization: async function (input) { @@ -223,58 +227,61 @@ function getRecipeInterface( // }, input.userContext ); - const redirectTo = resp.redirectTo; - if (redirectTo === undefined) { - throw new Error(resp.body); - } - const redirectToURL = new URL(redirectTo); - const consentChallenge = redirectToURL.searchParams.get("consent_challenge"); - if (consentChallenge !== null && input.session !== undefined) { - const consentRequest = await this.getConsentRequest({ - challenge: consentChallenge, - userContext: input.userContext, - }); - const user = await __1.getUser(input.session.getUserId()); - if (!user) { - throw new Error("Should not happen"); + if (resp.status === "OK") { + const redirectTo = resp.redirectTo; + if (redirectTo === undefined) { + throw new Error(resp.body); } - const idToken = await this.buildIdTokenPayload({ - user, - client: consentRequest.client, - sessionHandle: input.session.getHandle(), - scopes: consentRequest.requestedScope || [], - userContext: input.userContext, - }); - const accessTokenPayload = await this.buildAccessTokenPayload({ - user, - client: consentRequest.client, - sessionHandle: input.session.getHandle(), - scopes: consentRequest.requestedScope || [], - userContext: input.userContext, - }); - const sessionInfo = await session_1.getSessionInformation(input.session.getHandle()); - if (!sessionInfo) { - throw new Error("Session not found"); + const redirectToURL = new URL(redirectTo); + const consentChallenge = redirectToURL.searchParams.get("consent_challenge"); + if (consentChallenge !== null && input.session !== undefined) { + const consentRequest = await this.getConsentRequest({ + challenge: consentChallenge, + userContext: input.userContext, + }); + const user = await __1.getUser(input.session.getUserId()); + if (!user) { + throw new Error("Should not happen"); + } + const idToken = await this.buildIdTokenPayload({ + user, + client: consentRequest.client, + sessionHandle: input.session.getHandle(), + scopes: consentRequest.requestedScope || [], + userContext: input.userContext, + }); + const accessTokenPayload = await this.buildAccessTokenPayload({ + user, + client: consentRequest.client, + sessionHandle: input.session.getHandle(), + scopes: consentRequest.requestedScope || [], + userContext: input.userContext, + }); + const sessionInfo = await session_1.getSessionInformation(input.session.getHandle()); + if (!sessionInfo) { + throw new Error("Session not found"); + } + const consentRes = await this.acceptConsentRequest( + Object.assign(Object.assign({}, input), { + challenge: consentRequest.challenge, + grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, + grantScope: consentRequest.requestedScope, + remember: true, + session: { + id_token: idToken, + access_token: accessTokenPayload, + }, + handledAt: new Date(sessionInfo.timeCreated).toISOString(), + }) + ); + return { + redirectTo: consentRes.redirectTo, + setCookie: (_a = resp.cookies) !== null && _a !== void 0 ? _a : undefined, + }; } - const consentRes = await this.acceptConsentRequest( - Object.assign(Object.assign({}, input), { - challenge: consentRequest.challenge, - grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, - grantScope: consentRequest.requestedScope, - remember: true, - session: { - id_token: idToken, - access_token: accessTokenPayload, - }, - handledAt: new Date(sessionInfo.timeCreated).toISOString(), - }) - ); - return { - redirectTo: consentRes.redirectTo, - setCookie: (_a = resp.cookies) !== null && _a !== void 0 ? _a : undefined, - }; + return { redirectTo, setCookie: (_b = resp.cookies) !== null && _b !== void 0 ? _b : undefined }; } - return { redirectTo, setCookie: (_b = resp.cookies) !== null && _b !== void 0 ? _b : undefined }; + return resp; }, tokenExchange: async function (input) { var _a, _b; @@ -342,9 +349,9 @@ function getRecipeInterface( ); if (res.status !== "OK") { return { - statusCode: res.status_code, + statusCode: res.statusCode, error: res.error, - errorDescription: res.error_description, + errorDescription: res.errorDescription, }; } return res; @@ -563,6 +570,7 @@ function getRecipeInterface( ); if (res.status !== "OK") { return { + status: "OAUTH_ERROR", statusCode: res.statusCode, error: res.data.error, errorDescription: res.data.error_description, diff --git a/lib/build/recipe/oauth2provider/types.d.ts b/lib/build/recipe/oauth2provider/types.d.ts index 0fad2ba09..7f439c767 100644 --- a/lib/build/recipe/oauth2provider/types.d.ts +++ b/lib/build/recipe/oauth2provider/types.d.ts @@ -32,8 +32,11 @@ export declare type APIOptions = { res: BaseResponse; }; export declare type ErrorOAuth2 = { + status: "OAUTH_ERROR"; error: string; errorDescription: string; + errorDebug?: string; + errorHint?: string; statusCode?: number; }; export declare type ConsentRequest = { @@ -99,10 +102,14 @@ export declare type RecipeInterface = { cookies: string | undefined; session: SessionContainerInterface | undefined; userContext: UserContext; - }): Promise<{ - redirectTo: string; - setCookie: string | undefined; - }>; + }): Promise< + | { + status: "OK"; + redirectTo: string; + setCookie: string | undefined; + } + | ErrorOAuth2 + >; tokenExchange(input: { authorizationHeader?: string; body: Record; diff --git a/lib/ts/recipe/oauth2provider/api/implementation.ts b/lib/ts/recipe/oauth2provider/api/implementation.ts index 6a09e7316..6745cec18 100644 --- a/lib/ts/recipe/oauth2provider/api/implementation.ts +++ b/lib/ts/recipe/oauth2provider/api/implementation.ts @@ -45,14 +45,17 @@ export default function getAPIImplementation(): APIInterface { userContext, }); - return handleInternalRedirects({ - response, - recipeImplementation: options.recipeImplementation, - cookie, - session, - shouldTryRefresh, - userContext, - }); + if (response.status === "OK") { + return handleInternalRedirects({ + response, + recipeImplementation: options.recipeImplementation, + cookie, + session, + shouldTryRefresh, + userContext, + }); + } + return response; }, tokenPOST: async (input) => { return input.options.recipeImplementation.tokenExchange({ diff --git a/lib/ts/recipe/oauth2provider/api/utils.ts b/lib/ts/recipe/oauth2provider/api/utils.ts index d0b2b088d..c062a7fb7 100644 --- a/lib/ts/recipe/oauth2provider/api/utils.ts +++ b/lib/ts/recipe/oauth2provider/api/utils.ts @@ -46,6 +46,7 @@ export async function loginGET({ const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, error: { + status: "OAUTH_ERROR", error: "invalid_request", errorDescription: "max_age cannot be negative", }, @@ -57,6 +58,7 @@ export async function loginGET({ const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, error: { + status: "OAUTH_ERROR", error: "invalid_request", errorDescription: "max_age must be an integer", }, @@ -115,6 +117,7 @@ export async function loginGET({ const reject = await recipeImplementation.rejectLoginRequest({ challenge: loginChallenge, error: { + status: "OAUTH_ERROR", error: "login_required", errorDescription: "The Authorization Server requires End-User authentication. Prompt 'none' was requested, but no existing or expired login session was found.", @@ -253,10 +256,12 @@ export async function handleInternalRedirects({ userContext, }); - response = { - redirectTo: authRes.redirectTo, - setCookie: mergeSetCookieHeaders(authRes.setCookie, response.setCookie), - }; + if (authRes.status === "OK") { + response = { + redirectTo: authRes.redirectTo, + setCookie: mergeSetCookieHeaders(authRes.setCookie, response.setCookie), + }; + } } else { throw new Error(`Unexpected internal redirect ${response.redirectTo}`); } diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index 817c129da..bf6114cb2 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -31,8 +31,15 @@ import { getUser } from "../.."; import { getCombinedJWKS } from "../../combinedRemoteJWKSet"; import { getSessionInformation } from "../session"; -// TODO: Remove this core changes are done function getUpdatedRedirectTo(appInfo: NormalisedAppinfo, redirectTo: string) { + if (redirectTo.includes("{apiDomain}")) { + return redirectTo.replace( + "{apiDomain}", + appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous() + ); + } + + // TODO: Remove this core changes are done return redirectTo .replace(hydraPubDomain, appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous()) .replace("oauth2/", "oauth/"); @@ -111,71 +118,70 @@ export default function getRecipeInterface( redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), }; }, + getConsentRequest: async function (this: RecipeInterface, input): Promise { const resp = await querier.sendGetRequest( - new NormalisedURLPath("/recipe/oauth2/admin/oauth2/auth/requests/consent"), - { consent_challenge: input.challenge }, + new NormalisedURLPath("/recipe/oauth/auth/requests/consent"), + { challenge: input.challenge }, input.userContext ); return { - acr: resp.data.acr, - amr: resp.data.amr, - challenge: resp.data.challenge, - client: OAuth2Client.fromAPIResponse(resp.data.client), - context: resp.data.context, - loginChallenge: resp.data.login_challenge, - loginSessionId: resp.data.login_session_id, - oidcContext: resp.data.oidc_context, - requestedAccessTokenAudience: resp.data.requested_access_token_audience, - requestedScope: resp.data.requested_scope, - skip: resp.data.skip, - subject: resp.data.subject, + acr: resp.acr, + amr: resp.amr, + challenge: resp.challenge, + client: OAuth2Client.fromAPIResponse(resp.client), + context: resp.context, + loginChallenge: resp.loginChallenge, + loginSessionId: resp.loginSessionId, + oidcContext: resp.oidcContext, + requestedAccessTokenAudience: resp.requestedAccessTokenAudience, + requestedScope: resp.requestedScope, + skip: resp.skip, + subject: resp.subject, }; }, acceptConsentRequest: async function (this: RecipeInterface, input): Promise<{ redirectTo: string }> { const resp = await querier.sendPutRequest( - new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/consent/accept`), + new NormalisedURLPath(`/recipe/oauth/auth/requests/consent/accept`), { context: input.context, - grant_access_token_audience: input.grantAccessTokenAudience, - grant_scope: input.grantScope, - handled_at: input.handledAt, + grantAccessTokenAudience: input.grantAccessTokenAudience, + grantScope: input.grantScope, + handledAt: input.handledAt, remember: input.remember, - remember_for: input.rememberFor, + rememberFor: input.rememberFor, session: input.session, }, { - consent_challenge: input.challenge, + challenge: input.challenge, }, input.userContext ); return { - // TODO: FIXME!!! - redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), + redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, - rejectConsentRequest: async function (this: RecipeInterface, input) { const resp = await querier.sendPutRequest( - new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/consent/reject`), + new NormalisedURLPath(`/recipe/oauth/auth/requests/consent/reject`), { error: input.error.error, error_description: input.error.errorDescription, status_code: input.error.statusCode, }, { - consent_challenge: input.challenge, + consentChallenge: input.challenge, }, input.userContext ); return { - // TODO: FIXME!!! - redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), + redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, + authorization: async function (this: RecipeInterface, input) { if (input.session !== undefined) { if (input.params.prompt === "none") { @@ -197,61 +203,65 @@ export default function getRecipeInterface( input.userContext ); - const redirectTo = resp.redirectTo; - if (redirectTo === undefined) { - throw new Error(resp.body); - } - const redirectToURL = new URL(redirectTo); - const consentChallenge = redirectToURL.searchParams.get("consent_challenge"); - if (consentChallenge !== null && input.session !== undefined) { - const consentRequest = await this.getConsentRequest({ - challenge: consentChallenge, - userContext: input.userContext, - }); - - const user = await getUser(input.session.getUserId()); - if (!user) { - throw new Error("Should not happen"); + if (resp.status === "OK") { + const redirectTo = resp.redirectTo; + if (redirectTo === undefined) { + throw new Error(resp.body); } - const idToken = await this.buildIdTokenPayload({ - user, - client: consentRequest.client!, - sessionHandle: input.session.getHandle(), - scopes: consentRequest.requestedScope || [], - userContext: input.userContext, - }); - const accessTokenPayload = await this.buildAccessTokenPayload({ - user, - client: consentRequest.client!, - sessionHandle: input.session.getHandle(), - scopes: consentRequest.requestedScope || [], - userContext: input.userContext, - }); + const redirectToURL = new URL(redirectTo); + const consentChallenge = redirectToURL.searchParams.get("consent_challenge"); + if (consentChallenge !== null && input.session !== undefined) { + const consentRequest = await this.getConsentRequest({ + challenge: consentChallenge, + userContext: input.userContext, + }); - const sessionInfo = await getSessionInformation(input.session.getHandle()); - if (!sessionInfo) { - throw new Error("Session not found"); - } + const user = await getUser(input.session.getUserId()); + if (!user) { + throw new Error("Should not happen"); + } + const idToken = await this.buildIdTokenPayload({ + user, + client: consentRequest.client!, + sessionHandle: input.session.getHandle(), + scopes: consentRequest.requestedScope || [], + userContext: input.userContext, + }); + const accessTokenPayload = await this.buildAccessTokenPayload({ + user, + client: consentRequest.client!, + sessionHandle: input.session.getHandle(), + scopes: consentRequest.requestedScope || [], + userContext: input.userContext, + }); - const consentRes = await this.acceptConsentRequest({ - ...input, - challenge: consentRequest.challenge, - grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, - grantScope: consentRequest.requestedScope, - remember: true, // TODO: verify that we need this - session: { - id_token: idToken, - access_token: accessTokenPayload, - }, - handledAt: new Date(sessionInfo.timeCreated).toISOString(), - }); + const sessionInfo = await getSessionInformation(input.session.getHandle()); + if (!sessionInfo) { + throw new Error("Session not found"); + } - return { - redirectTo: consentRes.redirectTo, - setCookie: resp.cookies ?? undefined, - }; + const consentRes = await this.acceptConsentRequest({ + ...input, + challenge: consentRequest.challenge, + grantAccessTokenAudience: consentRequest.requestedAccessTokenAudience, + grantScope: consentRequest.requestedScope, + remember: true, // TODO: verify that we need this + session: { + id_token: idToken, + access_token: accessTokenPayload, + }, + handledAt: new Date(sessionInfo.timeCreated).toISOString(), + }); + + return { + redirectTo: consentRes.redirectTo, + setCookie: resp.cookies ?? undefined, + }; + } + return { redirectTo, setCookie: resp.cookies ?? undefined }; } - return { redirectTo, setCookie: resp.cookies ?? undefined }; + + return resp; }, tokenExchange: async function (this: RecipeInterface, input) { @@ -322,9 +332,9 @@ export default function getRecipeInterface( if (res.status !== "OK") { return { - statusCode: res.status_code, + statusCode: res.statusCode, error: res.error, - errorDescription: res.error_description, + errorDescription: res.errorDescription, }; } @@ -554,6 +564,7 @@ export default function getRecipeInterface( if (res.status !== "OK") { return { + status: "OAUTH_ERROR", statusCode: res.statusCode, error: res.data.error, errorDescription: res.data.error_description, diff --git a/lib/ts/recipe/oauth2provider/types.ts b/lib/ts/recipe/oauth2provider/types.ts index b646d154d..6784056d5 100644 --- a/lib/ts/recipe/oauth2provider/types.ts +++ b/lib/ts/recipe/oauth2provider/types.ts @@ -51,6 +51,8 @@ export type APIOptions = { }; export type ErrorOAuth2 = { + status: "OAUTH_ERROR"; + // The error should follow the OAuth2 error format (e.g. invalid_request, login_required). // Defaults to request_denied. error: string; @@ -58,6 +60,10 @@ export type ErrorOAuth2 = { // Description of the error in a human readable format. errorDescription: string; + errorDebug?: string; + + errorHint?: string; + // Represents the HTTP status code of the error (e.g. 401 or 403) // Defaults to 400 statusCode?: number; @@ -180,12 +186,14 @@ export type RecipeInterface = { cookies: string | undefined; session: SessionContainerInterface | undefined; userContext: UserContext; - }): Promise<{ redirectTo: string; setCookie: string | undefined }>; + }): Promise<{ status: "OK"; redirectTo: string; setCookie: string | undefined } | ErrorOAuth2>; + tokenExchange(input: { authorizationHeader?: string; body: Record; userContext: UserContext; }): Promise; + getConsentRequest(input: { challenge: string; userContext: UserContext }): Promise; acceptConsentRequest(input: { challenge: string; From cba1ce5d1eec1996fa2996816d7c14908882c9fc Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Fri, 6 Sep 2024 13:34:59 +0530 Subject: [PATCH 34/35] fix: login integration --- .../oauth2provider/recipeImplementation.js | 42 +++++++++---------- .../oauth2provider/recipeImplementation.ts | 42 +++++++++---------- 2 files changed, 40 insertions(+), 44 deletions(-) diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 4ef7b4467..23936bbac 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -90,49 +90,48 @@ function getRecipeInterface( return { getLoginRequest: async function (input) { const resp = await querier.sendGetRequest( - new normalisedURLPath_1.default("/recipe/oauth2/admin/oauth2/auth/requests/login"), - { login_challenge: input.challenge }, + new normalisedURLPath_1.default("/recipe/oauth/auth/requests/login"), + { challenge: input.challenge }, input.userContext ); return { - challenge: resp.data.challenge, - client: OAuth2Client_1.OAuth2Client.fromAPIResponse(resp.data.client), - oidcContext: resp.data.oidc_context, - requestUrl: resp.data.request_url, - requestedAccessTokenAudience: resp.data.requested_access_token_audience, - requestedScope: resp.data.requested_scope, - sessionId: resp.data.session_id, - skip: resp.data.skip, - subject: resp.data.subject, + challenge: resp.challenge, + client: OAuth2Client_1.OAuth2Client.fromAPIResponse(resp.client), + oidcContext: resp.oidcContext, + requestUrl: resp.requestUrl, + requestedAccessTokenAudience: resp.requestedAccessTokenAudience, + requestedScope: resp.requestedScope, + sessionId: resp.sessionId, + skip: resp.skip, + subject: resp.subject, }; }, acceptLoginRequest: async function (input) { const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/login/accept`), + new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/login/accept`), { acr: input.acr, amr: input.amr, context: input.context, - extend_session_lifespan: input.extendSessionLifespan, - force_subject_identifier: input.forceSubjectIdentifier, - identity_provider_session_id: input.identityProviderSessionId, + extendSessionLifespan: input.extendSessionLifespan, + forceSubjectIdentifier: input.forceSubjectIdentifier, + identityProviderSessionId: input.identityProviderSessionId, remember: input.remember, - remember_for: input.rememberFor, + rememberFor: input.rememberFor, subject: input.subject, }, { - login_challenge: input.challenge, + challenge: input.challenge, }, input.userContext ); return { - // TODO: FIXME!!! - redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), + redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, rejectLoginRequest: async function (input) { const resp = await querier.sendPutRequest( - new normalisedURLPath_1.default(`/recipe/oauth2/admin/oauth2/auth/requests/login/reject`), + new normalisedURLPath_1.default(`/recipe/oauth/auth/requests/login/reject`), { error: input.error.error, error_description: input.error.errorDescription, @@ -144,8 +143,7 @@ function getRecipeInterface( input.userContext ); return { - // TODO: FIXME!!! - redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), + redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, getConsentRequest: async function (input) { diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index bf6114cb2..fc1d9cc3d 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -57,51 +57,50 @@ export default function getRecipeInterface( return { getLoginRequest: async function (this: RecipeInterface, input): Promise { const resp = await querier.sendGetRequest( - new NormalisedURLPath("/recipe/oauth2/admin/oauth2/auth/requests/login"), - { login_challenge: input.challenge }, + new NormalisedURLPath("/recipe/oauth/auth/requests/login"), + { challenge: input.challenge }, input.userContext ); return { - challenge: resp.data.challenge, - client: OAuth2Client.fromAPIResponse(resp.data.client), - oidcContext: resp.data.oidc_context, - requestUrl: resp.data.request_url, - requestedAccessTokenAudience: resp.data.requested_access_token_audience, - requestedScope: resp.data.requested_scope, - sessionId: resp.data.session_id, - skip: resp.data.skip, - subject: resp.data.subject, + challenge: resp.challenge, + client: OAuth2Client.fromAPIResponse(resp.client), + oidcContext: resp.oidcContext, + requestUrl: resp.requestUrl, + requestedAccessTokenAudience: resp.requestedAccessTokenAudience, + requestedScope: resp.requestedScope, + sessionId: resp.sessionId, + skip: resp.skip, + subject: resp.subject, }; }, acceptLoginRequest: async function (this: RecipeInterface, input): Promise<{ redirectTo: string }> { const resp = await querier.sendPutRequest( - new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/login/accept`), + new NormalisedURLPath(`/recipe/oauth/auth/requests/login/accept`), { acr: input.acr, amr: input.amr, context: input.context, - extend_session_lifespan: input.extendSessionLifespan, - force_subject_identifier: input.forceSubjectIdentifier, - identity_provider_session_id: input.identityProviderSessionId, + extendSessionLifespan: input.extendSessionLifespan, + forceSubjectIdentifier: input.forceSubjectIdentifier, + identityProviderSessionId: input.identityProviderSessionId, remember: input.remember, - remember_for: input.rememberFor, + rememberFor: input.rememberFor, subject: input.subject, }, { - login_challenge: input.challenge, + challenge: input.challenge, }, input.userContext ); return { - // TODO: FIXME!!! - redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), + redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, rejectLoginRequest: async function (this: RecipeInterface, input): Promise<{ redirectTo: string }> { const resp = await querier.sendPutRequest( - new NormalisedURLPath(`/recipe/oauth2/admin/oauth2/auth/requests/login/reject`), + new NormalisedURLPath(`/recipe/oauth/auth/requests/login/reject`), { error: input.error.error, error_description: input.error.errorDescription, @@ -114,8 +113,7 @@ export default function getRecipeInterface( ); return { - // TODO: FIXME!!! - redirectTo: getUpdatedRedirectTo(appInfo, resp.data.redirect_to), + redirectTo: getUpdatedRedirectTo(appInfo, resp.redirectTo), }; }, From 471e282a570aa2fb07b00b3fc78623ee53a9774e Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 11 Sep 2024 15:46:42 +0530 Subject: [PATCH 35/35] fix: minor updates --- .../recipe/oauth2provider/recipeImplementation.js | 12 ++++++------ lib/ts/recipe/oauth2provider/recipeImplementation.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/build/recipe/oauth2provider/recipeImplementation.js b/lib/build/recipe/oauth2provider/recipeImplementation.js index 23936bbac..8428130ce 100644 --- a/lib/build/recipe/oauth2provider/recipeImplementation.js +++ b/lib/build/recipe/oauth2provider/recipeImplementation.js @@ -226,7 +226,7 @@ function getRecipeInterface( input.userContext ); if (resp.status === "OK") { - const redirectTo = resp.redirectTo; + const redirectTo = getUpdatedRedirectTo(appInfo, resp.redirectTo); if (redirectTo === undefined) { throw new Error(resp.body); } @@ -283,9 +283,9 @@ function getRecipeInterface( }, tokenExchange: async function (input) { var _a, _b; - const body = {}; + const inputBody = {}; for (const key in input.body) { - body[key] = input.body[key]; + inputBody[key] = input.body[key]; } if (input.body.grant_type === "refresh_token") { const scopes = @@ -330,7 +330,7 @@ function getRecipeInterface( scopes, userContext: input.userContext, }); - body["session"] = { + inputBody["session"] = { id_token: idToken, access_token: accessTokenPayload, }; @@ -338,11 +338,11 @@ function getRecipeInterface( } } if (input.authorizationHeader) { - body["authorizationHeader"] = input.authorizationHeader; + inputBody["authorizationHeader"] = input.authorizationHeader; } const res = await querier.sendPostRequest( new normalisedURLPath_1.default(`/recipe/oauth/token`), - { body, iss: await this.getIssuer({ userContext: input.userContext }) }, + { inputBody, iss: await this.getIssuer({ userContext: input.userContext }) }, input.userContext ); if (res.status !== "OK") { diff --git a/lib/ts/recipe/oauth2provider/recipeImplementation.ts b/lib/ts/recipe/oauth2provider/recipeImplementation.ts index fc1d9cc3d..538377ed8 100644 --- a/lib/ts/recipe/oauth2provider/recipeImplementation.ts +++ b/lib/ts/recipe/oauth2provider/recipeImplementation.ts @@ -202,7 +202,7 @@ export default function getRecipeInterface( ); if (resp.status === "OK") { - const redirectTo = resp.redirectTo; + const redirectTo = getUpdatedRedirectTo(appInfo, resp.redirectTo); if (redirectTo === undefined) { throw new Error(resp.body); } @@ -263,9 +263,9 @@ export default function getRecipeInterface( }, tokenExchange: async function (this: RecipeInterface, input) { - const body: any = {}; + const inputBody: any = {}; for (const key in input.body) { - body[key] = input.body[key]; + inputBody[key] = input.body[key]; } if (input.body.grant_type === "refresh_token") { @@ -309,7 +309,7 @@ export default function getRecipeInterface( scopes, userContext: input.userContext, }); - body["session"] = { + inputBody["session"] = { id_token: idToken, access_token: accessTokenPayload, }; @@ -319,12 +319,12 @@ export default function getRecipeInterface( } if (input.authorizationHeader) { - body["authorizationHeader"] = input.authorizationHeader; + inputBody["authorizationHeader"] = input.authorizationHeader; } const res = await querier.sendPostRequest( new NormalisedURLPath(`/recipe/oauth/token`), - { body, iss: await this.getIssuer({ userContext: input.userContext }) }, + { inputBody, iss: await this.getIssuer({ userContext: input.userContext }) }, input.userContext );