Skip to content

Commit

Permalink
feat!: separating the OpenId recipe from the Session recipe
Browse files Browse the repository at this point in the history
  • Loading branch information
porcellus committed Oct 2, 2024
1 parent 8879f10 commit a1517e5
Show file tree
Hide file tree
Showing 36 changed files with 338 additions and 542 deletions.
93 changes: 93 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,99 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- subject_types_supported
- id_token_signing_alg_values_supported
- response_types_supported
- Exposing the OpenId recipe separately and remove it from the Session recipe
- This means that we removed `override.openIdFeature` from the Session recipe configuration
- Removed `getJWKS` from the OpenId recipe, as it is already exposed by the JWT recipe
- We now automatically initialize the OpenId and JWT recipes even if you do not use the Session recipe

### Migration

#### Separating the OpenId recipe from Session recipe

If you used to use the `openIdFeature` in the Session recipe, you should now use the OpenId recipe directly instead:

Before:

```tsx
import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";

SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
recipeList: [
Session.init({
override: {
openIdFeature: {
jwtFeature: {
functions: originalImplementation => ({
...originalImplementation,
getJWKS: async (input) => {
console.log("getJWKS called");
return originalImplementation.getJWKS(input);
},
})
},
functions: originalImplementation => ({
...originalImplementation,
getOpenIdDiscoveryConfiguration: async (input) => ({
issuer: "your issuer",
jwks_uri: "https://your.api.domain/auth/jwt/jwks.json",
status: "OK"
}),
})
}
}
});
],
});
```

After:

```tsx
import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import OpenId from "supertokens-node/recipe/openid";
import JWT from "supertokens-node/recipe/jwt";

SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
recipeList: [
Session.init(),
JWT.init({
override: {
functions: originalImplementation => ({
...originalImplementation,
getJWKS: async (input) => {
console.log("getJWKS called");
return originalImplementation.getJWKS(input);
},
})
}
}),
OpenId.init({
override: {
functions: originalImplementation => ({
...originalImplementation,
getOpenIdDiscoveryConfiguration: async (input) => ({
issuer: "your issuer",
jwks_uri: "https://your.api.domain/auth/jwt/jwks.json",
status: "OK"
}),
})
}
});
],
});
```

## [20.1.3] - 2024-09-30

Expand Down
13 changes: 3 additions & 10 deletions lib/build/recipe/oauth2provider/recipeImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const OAuth2Client_1 = require("./OAuth2Client");
const __1 = require("../..");
const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet");
const recipe_1 = __importDefault(require("../session/recipe"));
const recipe_2 = __importDefault(require("../openid/recipe"));
const constants_1 = require("../multitenancy/constants");
function getUpdatedRedirectTo(appInfo, redirectTo) {
return redirectTo.replace(
Expand Down Expand Up @@ -177,7 +178,7 @@ function getRecipeInterface(
grantAccessTokenAudience: input.grantAccessTokenAudience,
grantScope: input.grantScope,
handledAt: input.handledAt,
iss: await getIssuer(input.userContext),
iss: await recipe_2.default.getIssuer(input.userContext),
tId: input.tenantId,
rsub: input.rsub,
sessionHandle: input.sessionHandle,
Expand Down Expand Up @@ -348,7 +349,7 @@ function getRecipeInterface(
inputBody: input.body,
authorizationHeader: input.authorizationHeader,
};
body.iss = await getIssuer(input.userContext);
body.iss = await recipe_2.default.getIssuer(input.userContext);
if (input.body.grant_type === "password") {
return {
status: "ERROR",
Expand Down Expand Up @@ -842,11 +843,3 @@ function getRecipeInterface(
};
}
exports.default = getRecipeInterface;
async function getIssuer(userContext) {
// We already depend on the Session recipe being initialized elsewhere in this recipe
const openIdConfig = await recipe_1.default
.getInstanceOrThrowError()
.openIdRecipe.recipeImplementation.getOpenIdDiscoveryConfiguration({ userContext });
// We grab it from the openIdConfig because that is the way we used to tell people to override
return openIdConfig.issuer;
}
22 changes: 0 additions & 22 deletions lib/build/recipe/openid/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,6 @@ export default class OpenIdRecipeWrapper {
id_token_signing_alg_values_supported: string[];
response_types_supported: string[];
}>;
static createJWT(
payload?: any,
validitySeconds?: number,
useStaticSigningKey?: boolean,
userContext?: Record<string, any>
): Promise<
| {
status: "OK";
jwt: string;
}
| {
status: "UNSUPPORTED_ALGORITHM_ERROR";
}
>;
static getJWKS(
userContext?: Record<string, any>
): Promise<{
keys: import("../jwt").JsonWebKey[];
validityInSeconds?: number | undefined;
}>;
}
export declare let init: typeof OpenIdRecipe.init;
export declare let getOpenIdDiscoveryConfiguration: typeof OpenIdRecipeWrapper.getOpenIdDiscoveryConfiguration;
export declare let createJWT: typeof OpenIdRecipeWrapper.createJWT;
export declare let getJWKS: typeof OpenIdRecipeWrapper.getJWKS;
17 changes: 1 addition & 16 deletions lib/build/recipe/openid/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var __importDefault =
return mod && mod.__esModule ? mod : { default: mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getJWKS = exports.createJWT = exports.getOpenIdDiscoveryConfiguration = exports.init = void 0;
exports.getOpenIdDiscoveryConfiguration = exports.init = void 0;
const utils_1 = require("../../utils");
const recipe_1 = __importDefault(require("./recipe"));
class OpenIdRecipeWrapper {
Expand All @@ -14,23 +14,8 @@ class OpenIdRecipeWrapper {
userContext: utils_1.getUserContext(userContext),
});
}
static createJWT(payload, validitySeconds, useStaticSigningKey, userContext) {
return recipe_1.default.getInstanceOrThrowError().jwtRecipe.recipeInterfaceImpl.createJWT({
payload,
validitySeconds,
useStaticSigningKey,
userContext: utils_1.getUserContext(userContext),
});
}
static getJWKS(userContext) {
return recipe_1.default.getInstanceOrThrowError().jwtRecipe.recipeInterfaceImpl.getJWKS({
userContext: utils_1.getUserContext(userContext),
});
}
}
exports.default = OpenIdRecipeWrapper;
OpenIdRecipeWrapper.init = recipe_1.default.init;
exports.init = OpenIdRecipeWrapper.init;
exports.getOpenIdDiscoveryConfiguration = OpenIdRecipeWrapper.getOpenIdDiscoveryConfiguration;
exports.createJWT = OpenIdRecipeWrapper.createJWT;
exports.getJWKS = OpenIdRecipeWrapper.getJWKS;
18 changes: 6 additions & 12 deletions lib/build/recipe/openid/recipe.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,28 @@ import normalisedURLPath from "../../normalisedURLPath";
import RecipeModule from "../../recipeModule";
import { APIHandled, HTTPMethod, NormalisedAppinfo, RecipeListFunction, UserContext } from "../../types";
import { APIInterface, RecipeInterface, TypeInput, TypeNormalisedInput } from "./types";
import JWTRecipe from "../jwt/recipe";
export default class OpenIdRecipe extends RecipeModule {
static RECIPE_ID: string;
private static instance;
config: TypeNormalisedInput;
jwtRecipe: JWTRecipe;
recipeImplementation: RecipeInterface;
apiImpl: APIInterface;
constructor(recipeId: string, appInfo: NormalisedAppinfo, isInServerlessEnv: boolean, config?: TypeInput);
constructor(recipeId: string, appInfo: NormalisedAppinfo, config?: TypeInput);
static getInstanceOrThrowError(): OpenIdRecipe;
static init(config?: TypeInput): RecipeListFunction;
static reset(): void;
static getIssuer(userContext: UserContext): Promise<string>;
getAPIsHandled: () => APIHandled[];
handleAPIRequest: (
id: string,
tenantId: string,
_tenantId: string,
req: BaseRequest,
response: BaseResponse,
path: normalisedURLPath,
method: HTTPMethod,
_path: normalisedURLPath,
_method: HTTPMethod,
userContext: UserContext
) => Promise<boolean>;
handleError: (
error: STError,
request: BaseRequest,
response: BaseResponse,
userContext: UserContext
) => Promise<void>;
handleError: (error: STError) => Promise<void>;
getAllCORSHeaders: () => string[];
isErrorFromThisRecipe: (err: any) => err is STError;
}
42 changes: 16 additions & 26 deletions lib/build/recipe/openid/recipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
const error_1 = __importDefault(require("../../error"));
const recipeModule_1 = __importDefault(require("../../recipeModule"));
const utils_1 = require("./utils");
const recipe_1 = __importDefault(require("../jwt/recipe"));
const supertokens_js_override_1 = __importDefault(require("supertokens-js-override"));
const recipeImplementation_1 = __importDefault(require("./recipeImplementation"));
const implementation_1 = __importDefault(require("./api/implementation"));
Expand All @@ -31,7 +30,7 @@ const constants_1 = require("./constants");
const getOpenIdDiscoveryConfiguration_1 = __importDefault(require("./api/getOpenIdDiscoveryConfiguration"));
const utils_2 = require("../../utils");
class OpenIdRecipe extends recipeModule_1.default {
constructor(recipeId, appInfo, isInServerlessEnv, config) {
constructor(recipeId, appInfo, config) {
super(recipeId, appInfo);
this.getAPIsHandled = () => {
return [
Expand All @@ -41,10 +40,9 @@ class OpenIdRecipe extends recipeModule_1.default {
id: constants_1.GET_DISCOVERY_CONFIG_URL,
disabled: this.apiImpl.getOpenIdDiscoveryConfigurationGET === undefined,
},
...this.jwtRecipe.getAPIsHandled(),
];
};
this.handleAPIRequest = async (id, tenantId, req, response, path, method, userContext) => {
this.handleAPIRequest = async (id, _tenantId, req, response, _path, _method, userContext) => {
let apiOptions = {
recipeImplementation: this.recipeImplementation,
config: this.config,
Expand All @@ -55,33 +53,20 @@ class OpenIdRecipe extends recipeModule_1.default {
if (id === constants_1.GET_DISCOVERY_CONFIG_URL) {
return await getOpenIdDiscoveryConfiguration_1.default(this.apiImpl, apiOptions, userContext);
} else {
return this.jwtRecipe.handleAPIRequest(id, tenantId, req, response, path, method, userContext);
return false;
}
};
this.handleError = async (error, request, response, userContext) => {
if (error.fromRecipe === OpenIdRecipe.RECIPE_ID) {
throw error;
} else {
return await this.jwtRecipe.handleError(error, request, response, userContext);
}
this.handleError = async (error) => {
throw error;
};
this.getAllCORSHeaders = () => {
return [...this.jwtRecipe.getAllCORSHeaders()];
return [];
};
this.isErrorFromThisRecipe = (err) => {
return (
(error_1.default.isErrorFromSuperTokens(err) && err.fromRecipe === OpenIdRecipe.RECIPE_ID) ||
this.jwtRecipe.isErrorFromThisRecipe(err)
);
return error_1.default.isErrorFromSuperTokens(err) && err.fromRecipe === OpenIdRecipe.RECIPE_ID;
};
this.config = utils_1.validateAndNormaliseUserInput(appInfo, config);
this.jwtRecipe = new recipe_1.default(recipeId, appInfo, isInServerlessEnv, {
jwtValiditySeconds: this.config.jwtValiditySeconds,
override: this.config.override.jwtFeature,
});
let builder = new supertokens_js_override_1.default(
recipeImplementation_1.default(this.config, this.jwtRecipe.recipeInterfaceImpl, appInfo)
);
this.config = utils_1.validateAndNormaliseUserInput(config);
let builder = new supertokens_js_override_1.default(recipeImplementation_1.default(appInfo));
this.recipeImplementation = builder.override(this.config.override.functions).build();
let apiBuilder = new supertokens_js_override_1.default(implementation_1.default());
this.apiImpl = apiBuilder.override(this.config.override.apis).build();
Expand All @@ -93,9 +78,9 @@ class OpenIdRecipe extends recipeModule_1.default {
throw new Error("Initialisation not done. Did you forget to call the Openid.init function?");
}
static init(config) {
return (appInfo, isInServerlessEnv) => {
return (appInfo) => {
if (OpenIdRecipe.instance === undefined) {
OpenIdRecipe.instance = new OpenIdRecipe(OpenIdRecipe.RECIPE_ID, appInfo, isInServerlessEnv, config);
OpenIdRecipe.instance = new OpenIdRecipe(OpenIdRecipe.RECIPE_ID, appInfo, config);
return OpenIdRecipe.instance;
} else {
throw new Error("OpenId recipe has already been initialised. Please check your code for bugs.");
Expand All @@ -108,6 +93,11 @@ class OpenIdRecipe extends recipeModule_1.default {
}
OpenIdRecipe.instance = undefined;
}
static async getIssuer(userContext) {
return (
await this.getInstanceOrThrowError().recipeImplementation.getOpenIdDiscoveryConfiguration({ userContext })
).issuer;
}
}
exports.default = OpenIdRecipe;
OpenIdRecipe.RECIPE_ID = "openid";
Expand Down
9 changes: 2 additions & 7 deletions lib/build/recipe/openid/recipeImplementation.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
// @ts-nocheck
import { RecipeInterface, TypeNormalisedInput } from "./types";
import { RecipeInterface as JWTRecipeInterface } from "../jwt/types";
import { RecipeInterface } from "./types";
import { NormalisedAppinfo } from "../../types";
export default function getRecipeInterface(
config: TypeNormalisedInput,
jwtRecipeImplementation: JWTRecipeInterface,
appInfo: NormalisedAppinfo
): RecipeInterface;
export default function getRecipeInterface(appInfo: NormalisedAppinfo): RecipeInterface;
16 changes: 7 additions & 9 deletions lib/build/recipe/openid/recipeImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ var __importDefault =
return mod && mod.__esModule ? mod : { default: mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const recipe_1 = __importDefault(require("../jwt/recipe"));
const normalisedURLPath_1 = __importDefault(require("../../normalisedURLPath"));
const constants_1 = require("../jwt/constants");
const constants_2 = require("../oauth2provider/constants");
function getRecipeInterface(config, jwtRecipeImplementation, appInfo) {
function getRecipeInterface(appInfo) {
return {
getOpenIdDiscoveryConfiguration: async function () {
let issuer = config.issuerDomain.getAsStringDangerous() + config.issuerPath.getAsStringDangerous();
let issuer = appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous();
let jwks_uri =
config.issuerDomain.getAsStringDangerous() +
config.issuerPath
appInfo.apiDomain.getAsStringDangerous() +
appInfo.apiBasePath
.appendPath(new normalisedURLPath_1.default(constants_1.GET_JWKS_API))
.getAsStringDangerous();
const apiBasePath = appInfo.apiDomain.getAsStringDangerous() + appInfo.apiBasePath.getAsStringDangerous();
Expand All @@ -35,17 +36,14 @@ function getRecipeInterface(config, jwtRecipeImplementation, appInfo) {
},
createJWT: async function ({ payload, validitySeconds, useStaticSigningKey, userContext }) {
payload = payload === undefined || payload === null ? {} : payload;
let issuer = config.issuerDomain.getAsStringDangerous() + config.issuerPath.getAsStringDangerous();
return await jwtRecipeImplementation.createJWT({
let issuer = (await this.getOpenIdDiscoveryConfiguration({ userContext })).issuer;
return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createJWT({
payload: Object.assign({ iss: issuer }, payload),
useStaticSigningKey,
validitySeconds,
userContext,
});
},
getJWKS: async function (input) {
return await jwtRecipeImplementation.getJWKS(input);
},
};
}
exports.default = getRecipeInterface;
Loading

0 comments on commit a1517e5

Please sign in to comment.