Skip to content

Commit

Permalink
feat: integrate with OAuth2 core impl (#926)
Browse files Browse the repository at this point in the history
* WIP

* WIP

* feat: clean up earlier debugging changes

* feat: expose new revoke functions + update tests

* feat: make the frontend redirection urls overrideable

* feat: update how oauth token payloads work
  • Loading branch information
porcellus authored Sep 24, 2024
1 parent d8b0c40 commit 4100705
Show file tree
Hide file tree
Showing 54 changed files with 1,217 additions and 988 deletions.
4 changes: 3 additions & 1 deletion lib/build/combinedRemoteJWKSet.d.ts

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

18 changes: 2 additions & 16 deletions lib/build/combinedRemoteJWKSet.js

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

1 change: 0 additions & 1 deletion lib/build/querier.d.ts

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

67 changes: 3 additions & 64 deletions lib/build/querier.js

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

9 changes: 0 additions & 9 deletions lib/build/recipe/jwt/api/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,6 @@ function getAPIImplementation() {
if (resp.validityInSeconds !== undefined) {
options.res.setHeader("Cache-Control", `max-age=${resp.validityInSeconds}, must-revalidate`, false);
}
const oauth2Provider = require("../../oauth2provider/recipe").default.getInstance();
// TODO: dirty hack until we get core support
if (oauth2Provider !== 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,
};
Expand Down
16 changes: 14 additions & 2 deletions lib/build/recipe/oauth2client/api/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,20 @@ const session_1 = __importDefault(require("../../session"));
function getAPIInterface() {
return {
signInPOST: async function (input) {
const { options, tenantId, userContext } = input;
const providerConfig = await options.recipeImplementation.getProviderConfig({ userContext });
const { options, tenantId, userContext, clientId } = input;
let normalisedClientId = clientId;
if (normalisedClientId === undefined) {
if (options.config.providerConfigs.length > 1) {
throw new Error(
"Should never come here: clientId is undefined and there are multiple providerConfigs"
);
}
normalisedClientId = options.config.providerConfigs[0].clientId;
}
const providerConfig = await options.recipeImplementation.getProviderConfig({
clientId: normalisedClientId,
userContext,
});
let oAuthTokensToUse = {};
if ("redirectURIInfo" in input && input.redirectURIInfo !== undefined) {
oAuthTokensToUse = await options.recipeImplementation.exchangeAuthCodeForOAuthTokens({
Expand Down
7 changes: 7 additions & 0 deletions lib/build/recipe/oauth2client/api/signin.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) {
const bodyParams = await options.req.getJSONBody();
let redirectURIInfo;
let oAuthTokens;
if (bodyParams.clientId === undefined && options.config.providerConfigs.length > 1) {
throw new error_1.default({
type: error_1.default.BAD_INPUT_ERROR,
message: "Please provide the clientId in request body",
});
}
if (bodyParams.redirectURIInfo !== undefined) {
if (bodyParams.redirectURIInfo.redirectURI === undefined) {
throw new error_1.default({
Expand Down Expand Up @@ -59,6 +65,7 @@ async function signInAPI(apiImplementation, tenantId, options, userContext) {
}
let result = await apiImplementation.signInPOST({
tenantId,
clientId: bodyParams.clientId,
redirectURIInfo,
oAuthTokens,
options,
Expand Down
1 change: 1 addition & 0 deletions lib/build/recipe/oauth2client/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default class Wrapper {
redirectURIQueryParams: any;
pkceCodeVerifier?: string | undefined;
},
clientId?: string,
userContext?: Record<string, any>
): Promise<import("./types").OAuthTokenResponse>;
static getUserInfo(
Expand Down
19 changes: 17 additions & 2 deletions lib/build/recipe/oauth2client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,22 @@ var __importDefault =
Object.defineProperty(exports, "__esModule", { value: true });
exports.getUserInfo = exports.exchangeAuthCodeForOAuthTokens = exports.init = void 0;
const utils_1 = require("../../utils");
const jwt_1 = require("../session/jwt");
const recipe_1 = __importDefault(require("./recipe"));
class Wrapper {
static async exchangeAuthCodeForOAuthTokens(redirectURIInfo, userContext) {
const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl;
static async exchangeAuthCodeForOAuthTokens(redirectURIInfo, clientId, userContext) {
let normalisedClientId = clientId;
const instance = recipe_1.default.getInstanceOrThrowError();
const recipeInterfaceImpl = instance.recipeInterfaceImpl;
const normalisedUserContext = utils_1.getUserContext(userContext);
if (normalisedClientId === undefined) {
if (instance.config.providerConfigs.length > 1) {
throw new Error("clientId is required if there are more than one provider configs defined");
}
normalisedClientId = instance.config.providerConfigs[0].clientId;
}
const providerConfig = await recipeInterfaceImpl.getProviderConfig({
clientId: normalisedClientId,
userContext: normalisedUserContext,
});
return await recipeInterfaceImpl.exchangeAuthCodeForOAuthTokens({
Expand All @@ -38,7 +48,12 @@ class Wrapper {
static async getUserInfo(oAuthTokens, userContext) {
const recipeInterfaceImpl = recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl;
const normalisedUserContext = utils_1.getUserContext(userContext);
if (oAuthTokens.access_token === undefined) {
throw new Error("access_token is required to get user info");
}
const preparseJWTInfo = jwt_1.parseJWTWithoutSignatureVerification(oAuthTokens.access_token);
const providerConfig = await recipeInterfaceImpl.getProviderConfig({
clientId: preparseJWTInfo.payload.client_id,
userContext: normalisedUserContext,
});
return await recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getUserInfo({
Expand Down
17 changes: 10 additions & 7 deletions lib/build/recipe/oauth2client/recipeImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const __1 = require("../..");
const logger_1 = require("../../logger");
const jose_1 = require("jose");
function getRecipeImplementation(_querier, config) {
let providerConfigWithOIDCInfo = null;
let providerConfigsWithOIDCInfo = {};
return {
signIn: async function ({ userId, tenantId, userContext, oAuthTokens, rawUserInfo }) {
const user = await __1.getUser(userId, userContext);
Expand All @@ -26,11 +26,14 @@ function getRecipeImplementation(_querier, config) {
rawUserInfo,
};
},
getProviderConfig: async function () {
if (providerConfigWithOIDCInfo !== null) {
return providerConfigWithOIDCInfo;
getProviderConfig: async function ({ clientId }) {
if (providerConfigsWithOIDCInfo[clientId] !== undefined) {
return providerConfigsWithOIDCInfo[clientId];
}
const oidcInfo = await thirdpartyUtils_1.getOIDCDiscoveryInfo(config.providerConfig.oidcDiscoveryEndpoint);
const providerConfig = config.providerConfigs.find(
(providerConfig) => providerConfig.clientId === clientId
);
const oidcInfo = await thirdpartyUtils_1.getOIDCDiscoveryInfo(providerConfig.oidcDiscoveryEndpoint);
if (oidcInfo.authorization_endpoint === undefined) {
throw new Error("Failed to authorization_endpoint from the oidcDiscoveryEndpoint.");
}
Expand All @@ -43,13 +46,13 @@ function getRecipeImplementation(_querier, config) {
if (oidcInfo.jwks_uri === undefined) {
throw new Error("Failed to jwks_uri from the oidcDiscoveryEndpoint.");
}
providerConfigWithOIDCInfo = Object.assign(Object.assign({}, config.providerConfig), {
providerConfigsWithOIDCInfo[clientId] = Object.assign(Object.assign({}, providerConfig), {
authorizationEndpoint: oidcInfo.authorization_endpoint,
tokenEndpoint: oidcInfo.token_endpoint,
userInfoEndpoint: oidcInfo.userinfo_endpoint,
jwksURI: oidcInfo.jwks_uri,
});
return providerConfigWithOIDCInfo;
return providerConfigsWithOIDCInfo[clientId];
},
exchangeAuthCodeForOAuthTokens: async function ({ providerConfig, redirectURIInfo }) {
if (providerConfig.tokenEndpoint === undefined) {
Expand Down
7 changes: 4 additions & 3 deletions lib/build/recipe/oauth2client/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export declare type OAuthTokenResponse = {
token_type: string;
};
export declare type TypeInput = {
providerConfig: ProviderConfigInput;
providerConfigs: ProviderConfigInput[];
override?: {
functions?: (
originalImplementation: RecipeInterface,
Expand All @@ -50,7 +50,7 @@ export declare type TypeInput = {
};
};
export declare type TypeNormalisedInput = {
providerConfig: ProviderConfigInput;
providerConfigs: ProviderConfigInput[];
override: {
functions: (
originalImplementation: RecipeInterface,
Expand All @@ -60,7 +60,7 @@ export declare type TypeNormalisedInput = {
};
};
export declare type RecipeInterface = {
getProviderConfig(input: { userContext: UserContext }): Promise<ProviderConfigWithOIDCInfo>;
getProviderConfig(input: { clientId: string; userContext: UserContext }): Promise<ProviderConfigWithOIDCInfo>;
signIn(input: {
userId: string;
oAuthTokens: OAuthTokens;
Expand Down Expand Up @@ -116,6 +116,7 @@ export declare type APIInterface = {
signInPOST: (
input: {
tenantId: string;
clientId?: string;
options: APIOptions;
userContext: UserContext;
} & (
Expand Down
Loading

0 comments on commit 4100705

Please sign in to comment.