Skip to content

Commit

Permalink
fix: PR changes
Browse files Browse the repository at this point in the history
  • Loading branch information
anku255 committed Jul 24, 2024
1 parent e4506de commit 08183ce
Show file tree
Hide file tree
Showing 18 changed files with 162 additions and 40 deletions.
3 changes: 2 additions & 1 deletion lib/build/recipe/oauth2/api/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,12 @@ function getAPIImplementation() {
},
};
},
userInfoGET: async ({ accessTokenPayload, user, scopes, options, userContext }) => {
userInfoGET: async ({ accessTokenPayload, user, scopes, tenantId, options, userContext }) => {
const userInfo = await options.recipeImplementation.buildUserInfo({
user,
accessTokenPayload,
scopes,
tenantId,
userContext,
});
return {
Expand Down
1 change: 1 addition & 0 deletions lib/build/recipe/oauth2/api/userInfo.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { APIInterface, APIOptions } from "..";
import { UserContext } from "../../../types";
export default function userInfoGET(
apiImplementation: APIInterface,
tenantId: string,
options: APIOptions,
userContext: UserContext
): Promise<boolean>;
26 changes: 20 additions & 6 deletions lib/build/recipe/oauth2/api/userInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,38 +27,52 @@ async function validateOAuth2AccessToken(accessToken) {
});
return await resp.json();
}
async function userInfoGET(apiImplementation, options, userContext) {
var _a;
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 ")) {
utils_1.sendNon200ResponseWithMessage(options.res, "Missing or invalid Authorization header", 401);
// 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) {
utils_1.sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token!", 401);
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",
401
400
);
return true;
}
const response = await apiImplementation.userInfoGET({
accessTokenPayload,
user,
scopes: ((_a = accessTokenPayload.scope) !== null && _a !== void 0 ? _a : "").split(" "),
tenantId,
scopes: accessTokenPayload.scope.split(" "),
options,
userContext,
});
Expand Down
3 changes: 2 additions & 1 deletion lib/build/recipe/oauth2/recipe.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default class Recipe extends RecipeModule {
getAPIsHandled(): APIHandled[];
handleAPIRequest: (
id: string,
_tenantId: string | undefined,
tenantId: string,
req: BaseRequest,
res: BaseResponse,
_path: NormalisedURLPath,
Expand All @@ -46,6 +46,7 @@ export default class Recipe extends RecipeModule {
user: User,
accessTokenPayload: JSONObject,
scopes: string[],
tenantId: string,
userContext: UserContext
): Promise<UserInfo>;
}
8 changes: 4 additions & 4 deletions lib/build/recipe/oauth2/recipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class Recipe extends recipeModule_1.default {
this.addUserInfoBuilderFromOtherRecipe = (userInfoBuilderFn) => {
this.userInfoBuilders.push(userInfoBuilderFn);
};
this.handleAPIRequest = async (id, _tenantId, req, res, _path, _method, userContext) => {
this.handleAPIRequest = async (id, tenantId, req, res, _path, _method, userContext) => {
let options = {
config: this.config,
recipeId: this.getRecipeId(),
Expand Down Expand Up @@ -71,7 +71,7 @@ class Recipe extends recipeModule_1.default {
return loginInfo_1.default(this.apiImpl, options, userContext);
}
if (id === constants_1.USER_INFO_PATH) {
return userInfo_1.default(this.apiImpl, options, userContext);
return userInfo_1.default(this.apiImpl, tenantId, options, userContext);
}
throw new Error("Should never come here: handleAPIRequest called with unknown id");
};
Expand Down Expand Up @@ -215,7 +215,7 @@ class Recipe extends recipeModule_1.default {
}
return payload;
}
async getDefaultUserInfoPayload(user, accessTokenPayload, scopes, userContext) {
async getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext) {
let payload = {
sub: accessTokenPayload.sub,
};
Expand All @@ -236,7 +236,7 @@ class Recipe extends recipeModule_1.default {
for (const fn of this.userInfoBuilders) {
payload = Object.assign(
Object.assign({}, payload),
await fn(user, accessTokenPayload, scopes, userContext)
await fn(user, accessTokenPayload, scopes, tenantId, userContext)
);
}
return payload;
Expand Down
4 changes: 2 additions & 2 deletions lib/build/recipe/oauth2/recipeImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,8 @@ function getRecipeInterface(querier, _config, _appInfo, getDefaultIdTokenPayload
buildIdTokenPayload: async function (input) {
return input.defaultPayload;
},
buildUserInfo: async function ({ user, accessTokenPayload, scopes, userContext }) {
return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, userContext);
buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) {
return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext);
},
};
}
Expand Down
9 changes: 6 additions & 3 deletions lib/build/recipe/oauth2/types.d.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -93,7 +93,7 @@ export declare type UserInfo = {
email_verified?: boolean;
phoneNumber?: string;
phoneNumber_verified?: boolean;
[key: string]: any;
[key: string]: JSONValue;
};
export declare type RecipeInterface = {
authorization(input: {
Expand Down Expand Up @@ -232,6 +232,7 @@ export declare type RecipeInterface = {
user: User;
accessTokenPayload: JSONObject;
scopes: string[];
tenantId: string;
userContext: UserContext;
}): Promise<JSONObject>;
};
Expand Down Expand Up @@ -357,6 +358,7 @@ export declare type APIInterface = {
accessTokenPayload: JSONObject;
user: User;
scopes: string[];
tenantId: string;
options: APIOptions;
userContext: UserContext;
}) => Promise<
Expand Down Expand Up @@ -471,5 +473,6 @@ export declare type UserInfoBuilderFunction = (
user: User,
accessTokenPayload: JSONObject,
scopes: string[],
tenantId: string,
userContext: UserContext
) => Promise<UserInfo>;
) => Promise<JSONObject>;
7 changes: 3 additions & 4 deletions lib/build/recipe/oauth2client/recipeImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
Expand Down
1 change: 1 addition & 0 deletions lib/build/recipe/openid/recipeImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
34 changes: 34 additions & 0 deletions lib/build/recipe/userroles/recipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -51,6 +52,39 @@ 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 = {};
if (scopes.includes("roles")) {
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");
}
userInfo.roles = res.roles;
if (scopes.includes("permissions")) {
const userPermissions = new Set();
for (const role of userInfo.roles) {
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 */
Expand Down
3 changes: 2 additions & 1 deletion lib/ts/recipe/oauth2/api/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,12 @@ export default function getAPIImplementation(): APIInterface {
},
};
},
userInfoGET: async ({ accessTokenPayload, user, scopes, options, userContext }) => {
userInfoGET: async ({ accessTokenPayload, user, scopes, tenantId, options, userContext }) => {
const userInfo = await options.recipeImplementation.buildUserInfo({
user,
accessTokenPayload,
scopes,
tenantId,
userContext,
});

Expand Down
27 changes: 22 additions & 5 deletions lib/ts/recipe/oauth2/api/userInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async function validateOAuth2AccessToken(accessToken: string) {

export default async function userInfoGET(
apiImplementation: APIInterface,
tenantId: string,
options: APIOptions,
userContext: UserContext
): Promise<boolean> {
Expand All @@ -42,7 +43,9 @@ export default async function userInfoGET(
const authHeader = options.req.getHeaderValue("authorization") || options.req.getHeaderValue("Authorization");

if (authHeader === undefined || !authHeader.startsWith("Bearer ")) {
sendNon200ResponseWithMessage(options.res, "Missing or invalid Authorization header", 401);
// 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;
}

Expand All @@ -53,23 +56,37 @@ export default async function userInfoGET(
try {
accessTokenPayload = await validateOAuth2AccessToken(accessToken);
} catch (error) {
sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token!", 401);
options.res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"', false);
sendNon200ResponseWithMessage(options.res, "Invalid or expired OAuth2 access token", 400);
return true;
}

const userId = accessTokenPayload.sub as string;
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) {
sendNon200ResponseWithMessage(options.res, "Couldn't find any user associated with the access token", 401);
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,
scopes: ((accessTokenPayload.scope as string) ?? "").split(" "),
tenantId,
scopes: accessTokenPayload.scope.split(" "),
options,
userContext,
});
Expand Down
7 changes: 4 additions & 3 deletions lib/ts/recipe/oauth2/recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export default class Recipe extends RecipeModule {

handleAPIRequest = async (
id: string,
_tenantId: string | undefined,
tenantId: string,
req: BaseRequest,
res: BaseResponse,
_path: NormalisedURLPath,
Expand Down Expand Up @@ -223,7 +223,7 @@ export default class Recipe extends RecipeModule {
return loginInfoGET(this.apiImpl, options, userContext);
}
if (id === USER_INFO_PATH) {
return userInfoGET(this.apiImpl, options, userContext);
return userInfoGET(this.apiImpl, tenantId, options, userContext);
}
throw new Error("Should never come here: handleAPIRequest called with unknown id");
};
Expand Down Expand Up @@ -267,6 +267,7 @@ export default class Recipe extends RecipeModule {
user: User,
accessTokenPayload: JSONObject,
scopes: string[],
tenantId: string,
userContext: UserContext
) {
let payload: JSONObject = {
Expand All @@ -286,7 +287,7 @@ export default class Recipe extends RecipeModule {
for (const fn of this.userInfoBuilders) {
payload = {
...payload,
...(await fn(user, accessTokenPayload, scopes, userContext)),
...(await fn(user, accessTokenPayload, scopes, tenantId, userContext)),
};
}

Expand Down
4 changes: 2 additions & 2 deletions lib/ts/recipe/oauth2/recipeImplementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,8 @@ export default function getRecipeInterface(
buildIdTokenPayload: async function (input) {
return input.defaultPayload;
},
buildUserInfo: async function ({ user, accessTokenPayload, scopes, userContext }) {
return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, userContext);
buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) {
return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext);
},
};
}
Loading

0 comments on commit 08183ce

Please sign in to comment.