Skip to content

Commit 2cb579b

Browse files
committed
update wrapper
1 parent fe4ad02 commit 2cb579b

File tree

8 files changed

+101
-23
lines changed

8 files changed

+101
-23
lines changed

Common/msal-node-wrapper/src/config/ConfigurationHelper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class ConfigurationHelper {
1919
return {
2020
auth: {
2121
...authConfig.auth,
22+
authority: authConfig.auth?.authority ? authConfig.auth.authority : "https://login.microsoftonline.com/common",
2223
},
2324
system: {
2425
...authConfig.system,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
import { AuthError, AccountInfo } from "@azure/msal-node";
7+
8+
/**
9+
* Contains string constants used by error codes and messages.
10+
*/
11+
export const AccessDeniedErrorMessage = {
12+
unauthorizedAccessError: {
13+
code: "401",
14+
desc: "Unauthorized"
15+
},
16+
forbiddenAccessError: {
17+
code: "403",
18+
desc: "Forbidden"
19+
}
20+
};
21+
22+
/**
23+
* Error thrown when the user is not authorized to access a route
24+
*/
25+
export class AccessDeniedError extends AuthError {
26+
route?: string;
27+
account?: AccountInfo;
28+
29+
constructor(errorCode: string, errorMessage?: string, route?: string, account?: AccountInfo) {
30+
super(errorCode, errorMessage);
31+
this.name = "AccessDeniedError";
32+
this.route = route;
33+
this.account = account;
34+
35+
Object.setPrototypeOf(this, AccessDeniedError.prototype);
36+
}
37+
38+
/**
39+
* Creates an error when access is unauthorized
40+
*
41+
* @returns {AccessDeniedError} Empty issuer error
42+
*/
43+
static createUnauthorizedAccessError(route?: string, account?: AccountInfo): AccessDeniedError {
44+
return new AccessDeniedError(
45+
AccessDeniedErrorMessage.unauthorizedAccessError.code,
46+
AccessDeniedErrorMessage.unauthorizedAccessError.desc,
47+
route,
48+
account
49+
);
50+
}
51+
52+
/**
53+
* Creates an error when the access is forbidden
54+
*
55+
* @returns {AccessDeniedError} Empty issuer error
56+
*/
57+
static createForbiddenAccessError(route?: string, account?: AccountInfo): AccessDeniedError {
58+
return new AccessDeniedError(
59+
AccessDeniedErrorMessage.forbiddenAccessError.code,
60+
AccessDeniedErrorMessage.forbiddenAccessError.desc,
61+
route,
62+
account
63+
);
64+
}
65+
}

Common/msal-node-wrapper/src/error/InteractionRequiredError.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { InteractionRequiredAuthError } from "@azure/msal-node";
77
import { LoginOptions, TokenRequestOptions } from "../middleware/MiddlewareOptions";
88

99
/**
10-
* Token Validation library error class thrown for configuration errors
10+
* Error thrown when user interaction is required.
1111
*/
1212
export class InteractionRequiredError extends InteractionRequiredAuthError {
1313
requestOptions: LoginOptions;

Common/msal-node-wrapper/src/middleware/context/AuthContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ export class AuthContext {
6767
* Returns the current user account from session
6868
* @returns {AccountInfo} account object
6969
*/
70-
getAccount(): AccountInfo {
71-
return this.context.req.session.account!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
70+
getAccount(): AccountInfo | undefined {
71+
return this.context.req.session.account || undefined; // eslint-disable-line @typescript-eslint/no-non-null-assertion
7272
}
7373

7474
/**

Common/msal-node-wrapper/src/middleware/guardMiddleware.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { Request, Response, NextFunction, RequestHandler } from "express";
77
import { WebAppAuthProvider } from "../provider/WebAppAuthProvider";
88
import { RouteGuardOptions } from "./MiddlewareOptions";
9+
import { AccessDeniedError } from "../error/AccessDeniedError";
910

1011
function guardMiddleware(
1112
this: WebAppAuthProvider,
@@ -20,7 +21,7 @@ function guardMiddleware(
2021
})(req, res, next);
2122
}
2223

23-
return res.status(401).send("Unauthorized");
24+
return next(AccessDeniedError.createUnauthorizedAccessError(req.originalUrl, req.authContext.getAccount()));
2425
}
2526

2627
if (options.idTokenClaims) {
@@ -53,7 +54,7 @@ function guardMiddleware(
5354
});
5455

5556
if (!hasClaims) {
56-
return res.status(403).send("Forbidden");
57+
return next(AccessDeniedError.createForbiddenAccessError(req.originalUrl, req.authContext.getAccount()));
5758
}
5859
}
5960

Common/msal-node-wrapper/src/middleware/handlers/logoutHandler.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,47 +9,49 @@ import { LogoutOptions } from "../MiddlewareOptions";
99
import { UrlUtils } from "../../utils/UrlUtils";
1010

1111
function logoutHandler(
12-
this: WebAppAuthProvider,
12+
this: WebAppAuthProvider,
1313
options: LogoutOptions
1414
): RequestHandler {
1515
return async (req: Request, res: Response): Promise<void> => {
1616
this.getLogger().trace("logoutHandler called");
17-
17+
1818
const shouldLogoutFromIdp = options.idpLogout ? options.idpLogout : true;
1919
let logoutUri = options.postLogoutRedirectUri || "/";
2020

21-
try {
22-
const tokenCache = this.getMsalClient().getTokenCache();
23-
const cachedAccount = await tokenCache.getAccountByHomeId(req.authContext.getAccount().homeAccountId);
21+
const account = req.authContext.getAccount();
22+
23+
if (account) {
24+
try {
25+
const tokenCache = this.getMsalClient().getTokenCache();
26+
const cachedAccount = await tokenCache.getAccountByHomeId(account.homeAccountId);
2427

25-
if (cachedAccount) {
26-
await tokenCache.removeAccount(cachedAccount);
28+
if (cachedAccount) {
29+
await tokenCache.removeAccount(cachedAccount);
30+
}
31+
} catch (error) {
32+
this.logger.error(`Error occurred while clearing cache for user: ${JSON.stringify(error)}`);
2733
}
28-
} catch (error) {
29-
this.logger.error(`Error occurred while clearing cache for user: ${JSON.stringify(error)}`);
3034
}
3135

3236
if (shouldLogoutFromIdp) {
3337
/**
3438
* Construct a logout URI and redirect the user to end the
3539
* session with Azure AD. For more information, visit:
3640
* (AAD) https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request
41+
* (B2C) https://docs.microsoft.com/azure/active-directory-b2c/openid-connect#send-a-sign-out-request
3742
*/
3843

3944
const postLogoutRedirectUri = UrlUtils.ensureAbsoluteUrl(
4045
options.postLogoutRedirectUri || "/",
4146
req.protocol,
4247
req.get("host") || req.hostname
4348
);
44-
45-
// TODO: need to make use of endSessionRequest options
46-
47-
// FIXME: need the canonical uri (ending with slash) && esnure absolute url
48-
logoutUri = `${this.getMsalConfig().auth.authority}/oauth2/v2.0/logout?post_logout_redirect_uri=${postLogoutRedirectUri}`;
49+
50+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
51+
logoutUri = `${UrlUtils.enforceTrailingSlash(this.getMsalConfig().auth.authority!)}/oauth2/v2.0/logout?post_logout_redirect_uri=${postLogoutRedirectUri}`;
4952
}
5053

5154
req.session.destroy(() => {
52-
// TODO: remove/expire cookie?
5355
res.redirect(logoutUri);
5456
});
5557
};

Common/msal-node-wrapper/src/network/FetchManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class FetchManager {
3030
* @param {string} accessToken: Raw access token
3131
* @returns {Promise<any>}
3232
*/
33-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
33+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3434
static callApiEndpointWithToken = async (endpoint: string, accessToken: string): Promise<any> => {
3535
if (StringUtils.isEmpty(accessToken)) {
3636
throw new Error(ErrorMessages.TOKEN_NOT_FOUND);
@@ -89,7 +89,7 @@ export class FetchManager {
8989
static handlePagination = async (accessToken: string, nextPage: string, data: string[] = []): Promise<string[]> => {
9090
try {
9191
const graphResponse = await (await FetchManager.callApiEndpointWithToken(nextPage, accessToken)).data;
92-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
92+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9393
graphResponse["value"].map((v: any) => data.push(v.id));
9494

9595
if (graphResponse[AccessControlConstants.PAGINATION_LINK]) {

Common/msal-node-wrapper/src/utils/UrlUtils.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,20 @@ export class UrlUtils {
6565
};
6666

6767
/**
68-
* Ensures that the URL contains a trailing slash at the end
68+
* Ensures that the path contains a leading slash at the start
6969
* @param {string} path: a given path
7070
* @returns {string}
7171
*/
7272
static enforceLeadingSlash = (path: string): string => {
7373
return path.split("")[0] === "/" ? path : "/" + path;
7474
};
75+
76+
/**
77+
* Ensures that the URL contains a trailing slash at the end
78+
* @param {string} url: a given path
79+
* @returns {string}
80+
*/
81+
static enforceTrailingSlash = (url: string): string => {
82+
return url.endsWith("/") ? url : url + "/";
83+
};
7584
}

0 commit comments

Comments
 (0)