Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Identity] Hotfix 1.2.5: Removing express #14521

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions common/config/rush/pnpm-lock.yaml

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

4 changes: 4 additions & 0 deletions sdk/identity/identity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release History

## 1.2.5 (2021-03-25)

- For the `InteractiveBrowserCredential` for Node.js, we've replaced the use of the `express` module with a native HTTP server, shrinking the resulting `@azure/identity` module considerably.

## 1.2.4 (2021-03-08)

- Bug fix: Now if the `managedIdentityClientId` optional parameter is provided to `DefaultAzureCredential`, it will be properly passed through to the underlying `ManagedIdentityCredential`. Related to customer issue: [13872](https://github.com/Azure/azure-sdk-for-js/issues/13872).
Expand Down
6 changes: 3 additions & 3 deletions sdk/identity/identity/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@azure/identity",
"sdk-type": "client",
"version": "1.2.4",
"version": "1.2.5",
"description": "Provides credential implementations for Azure SDK libraries that can authenticate with Azure Active Directory",
"main": "dist/index.js",
"module": "dist-esm/src/index.js",
Expand Down Expand Up @@ -85,12 +85,14 @@
"@azure/logger": "^1.0.0",
"@azure/msal-node": "1.0.0-beta.6",
"@opentelemetry/api": "^0.10.2",
"@types/stoppable": "^1.1.0",
"axios": "^0.21.1",
"events": "^3.0.0",
"jws": "^4.0.0",
"msal": "^1.0.2",
"open": "^7.0.0",
"qs": "^6.7.0",
"stoppable": "^1.1.0",
"tslib": "^2.0.0",
"uuid": "^8.3.0"
},
Expand All @@ -107,7 +109,6 @@
"@rollup/plugin-multi-entry": "^3.0.0",
"@rollup/plugin-node-resolve": "^8.0.0",
"@rollup/plugin-replace": "^2.2.0",
"@types/express": "^4.16.0",
"@types/jws": "^3.2.2",
"@types/mocha": "^7.0.2",
"@types/node": "^8.0.0",
Expand All @@ -116,7 +117,6 @@
"assert": "^1.4.1",
"cross-env": "^7.0.2",
"eslint": "^7.15.0",
"express": "^4.16.3",
"inherits": "^2.0.3",
"karma": "^5.1.0",
"karma-chrome-launcher": "^3.0.0",
Expand Down
145 changes: 93 additions & 52 deletions sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import { Socket } from "net";
import { AuthenticationRequired, MsalClient } from "../client/msalClient";
import { AuthorizationCodeRequest } from "@azure/msal-node";

import express from "express";
import open from "open";
import http from "http";
import stoppable from "stoppable";

import { checkTenantId } from "../util/checkTenantId";

const logger = credentialLogger("InteractiveBrowserCredential");
Expand All @@ -26,6 +27,7 @@ const logger = credentialLogger("InteractiveBrowserCredential");
export class InteractiveBrowserCredential implements TokenCredential {
private redirectUri: string;
private port: number;
private hostname: string;
private msalClient: MsalClient;

constructor(options?: InteractiveBrowserCredentialOptions) {
Expand Down Expand Up @@ -53,6 +55,8 @@ export class InteractiveBrowserCredential implements TokenCredential {
this.port = 80;
}

this.hostname = url.hostname;

let authorityHost;
if (options && options.authorityHost) {
if (options.authorityHost.endsWith("/")) {
Expand Down Expand Up @@ -112,74 +116,111 @@ export class InteractiveBrowserCredential implements TokenCredential {
await open(response);
}

private async acquireTokenFromBrowser(scopeArray: string[]): Promise<AccessToken | null> {
// eslint-disable-next-line
return new Promise<AccessToken | null>(async (resolve, reject) => {
// eslint-disable-next-line
let listen: http.Server | undefined;
let socketToDestroy: Socket | undefined;
private acquireTokenFromBrowser(scopeArray: string[]): Promise<AccessToken | null> {
return new Promise<AccessToken | null>((resolve, reject) => {
const socketToDestroy: Socket[] = [];

function cleanup(): void {
if (listen) {
listen.close();
const requestListener = (req: http.IncomingMessage, res: http.ServerResponse) => {
if (!req.url) {
reject(
new Error(
`Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
)
);
return;
}
if (socketToDestroy) {
socketToDestroy.destroy();
let url: URL;
try {
url = new URL(req.url, this.redirectUri);
} catch (e) {
reject(
new Error(
`Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
)
);
return;
}
}

// Create Express App and Routes
const app = express();

app.get("/", async (req, res) => {
const tokenRequest: AuthorizationCodeRequest = {
code: req.query.code as string,
code: url.searchParams.get("code")!,
redirectUri: this.redirectUri,
scopes: scopeArray
};

try {
const authResponse = await this.msalClient.acquireTokenByCode(tokenRequest);
const successMessage = `Authentication Complete. You can close the browser and return to the application.`;
if (authResponse && authResponse.expiresOn) {
const expiresOnTimestamp = authResponse?.expiresOn.valueOf();
res.status(200).send(successMessage);
logger.getToken.info(formatSuccess(scopeArray));

resolve({
expiresOnTimestamp,
token: authResponse.accessToken
});
} else {
this.msalClient
.acquireTokenByCode(tokenRequest)
.then((authResponse) => {
const successMessage = `Authentication Complete. You can close the browser and return to the application.`;
if (authResponse && authResponse.expiresOn) {
const expiresOnTimestamp = authResponse?.expiresOn.valueOf();
res.writeHead(200);
res.end(successMessage);
logger.getToken.info(formatSuccess(scopeArray));

resolve({
expiresOnTimestamp,
token: authResponse.accessToken
});
} else {
const errorMessage = formatError(
scopeArray,
`${url.searchParams.get("error")}. ${url.searchParams.get("error_description")}`
);
res.writeHead(500);
res.end(errorMessage);
logger.getToken.info(errorMessage);

reject(
new Error(
`Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
)
);
}
cleanup();
return;
})
.catch(() => {
const errorMessage = formatError(
scopeArray,
`${url.searchParams.get("error")}. ${url.searchParams.get("error_description")}`
);
res.writeHead(500);
res.end(errorMessage);
logger.getToken.info(errorMessage);

reject(
new Error(
`Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
)
);
}
} catch (error) {
const errorMessage = formatError(
scopeArray,
`${req.query["error"]}. ${req.query["error_description"]}`
);
res.status(500).send(errorMessage);
logger.getToken.info(errorMessage);
reject(new Error(errorMessage));
} finally {
cleanup();
}
});
cleanup();
});
};
const app = http.createServer(requestListener);

listen = app.listen(this.port, () =>
logger.info(`Msal Node Auth Code Sample app listening on port ${this.port}!`)
const listen = app.listen(this.port, this.hostname, () =>
logger.info(`InteractiveBrowerCredential listening on port ${this.port}!`)
);
listen.on("connection", (socket) => (socketToDestroy = socket));
app.on("connection", (socket) => socketToDestroy.push(socket));
const server = stoppable(app);

try {
await this.openAuthCodeUrl(scopeArray);
} catch (e) {
this.openAuthCodeUrl(scopeArray).catch((e) => {
cleanup();
throw e;
reject(e);
});

function cleanup(): void {
if (listen) {
listen.close();
}

for (const socket of socketToDestroy) {
socket.destroy();
}

if (server) {
server.close();
server.stop();
}
}
});
}
Expand Down