-
Notifications
You must be signed in to change notification settings - Fork 734
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: stop wrangler spamming console after login (#695)
If a user hasn't logged in and then they run a command that needs a login they'll get bounced to the login flow. The login flow (if completed) would write their shiny new OAuth2 credentials to disk, but wouldn't reload the in-memory state. This led to issues like #693, where even though the user was logged in on-disk, wrangler wouldn't be aware of it. We now update the in-memory login state each time new credentials are written to disk.
- Loading branch information
Cass
authored
Mar 30, 2022
1 parent
c4e5dc3
commit 48fa89b
Showing
8 changed files
with
381 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
"wrangler": patch | ||
--- | ||
|
||
fix: stop wrangler spamming console after login | ||
|
||
If a user hasn't logged in and then they run a command that needs a login they'll get bounced to the login flow. | ||
The login flow (if completed) would write their shiny new OAuth2 credentials to disk, but wouldn't reload the | ||
in-memory state. This led to issues like #693, where even though the user was logged in on-disk, wrangler | ||
wouldn't be aware of it. | ||
|
||
We now update the in-memory login state each time new credentials are written to disk. |
178 changes: 178 additions & 0 deletions
178
packages/wrangler/src/__tests__/helpers/mock-oauth-flow.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import { ChildProcess } from "child_process"; | ||
import fetchMock from "jest-fetch-mock"; | ||
import { Request } from "undici"; | ||
const { fetch } = jest.requireActual("undici") as { | ||
fetch: (input: string) => Promise<unknown>; | ||
}; | ||
|
||
// the response to send when wrangler wants an oauth grant | ||
let oauthGrantResponse: GrantResponseOptions | "timeout" = {}; | ||
|
||
/** | ||
* A mock implementation for `openInBrowser` that sends an oauth grant response | ||
* to wrangler. Use it like this: | ||
* | ||
* ```js | ||
* jest.mock("../open-in-browser"); | ||
* (openInBrowser as jest.Mock).mockImplementation(mockOpenInBrowser); | ||
* ``` | ||
*/ | ||
export const mockOpenInBrowser = async (url: string, ..._args: unknown[]) => { | ||
const { searchParams } = new URL(url); | ||
if (oauthGrantResponse === "timeout") { | ||
throw "unimplemented"; | ||
} else { | ||
const queryParams = toQueryParams(oauthGrantResponse, searchParams); | ||
// don't await this -- it will block the rest of the login flow | ||
fetch(`${searchParams.get("redirect_uri")}?${queryParams}`).catch((e) => { | ||
throw new Error( | ||
"Failed to send OAuth Grant to wrangler, maybe the server was closed?", | ||
e as Error | ||
); | ||
}); | ||
return new ChildProcess(); | ||
} | ||
}; | ||
|
||
/** | ||
* Functions to help with mocking various parts of the OAuth Flow | ||
*/ | ||
export const mockOAuthFlow = () => { | ||
afterEach(() => { | ||
fetchMock.resetMocks(); | ||
}); | ||
|
||
const mockGrantAuthorization = ({ | ||
respondWith, | ||
}: { | ||
respondWith: "timeout" | "success" | "failure" | GrantResponseOptions; | ||
}) => { | ||
if (respondWith === "failure") { | ||
oauthGrantResponse = { | ||
error: "access_denied", | ||
}; | ||
} else if (respondWith === "success") { | ||
oauthGrantResponse = { | ||
code: "test-oauth-code", | ||
}; | ||
} else if (respondWith === "timeout") { | ||
oauthGrantResponse = "timeout"; | ||
} else { | ||
oauthGrantResponse = respondWith; | ||
} | ||
}; | ||
|
||
const mockRevokeAuthorization = () => { | ||
const outcome = { | ||
actual: new Request("https://example.org"), | ||
expected: new Request("https://dash.cloudflare.com/oauth2/revoke", { | ||
method: "POST", | ||
}), | ||
}; | ||
|
||
fetchMock.mockIf(outcome.expected.url, async (req) => { | ||
outcome.actual = req; | ||
return ""; | ||
}); | ||
|
||
return outcome; | ||
}; | ||
|
||
const mockGrantAccessToken = ({ | ||
respondWith, | ||
}: { | ||
respondWith: MockTokenResponse; | ||
}) => { | ||
const outcome = { | ||
actual: new Request("https://example.org"), | ||
expected: new Request("https://dash.cloudflare.com/oauth2/token", { | ||
method: "POST", | ||
}), | ||
}; | ||
|
||
fetchMock.mockOnceIf(outcome.expected.url, async (req) => { | ||
outcome.actual = req; | ||
return makeTokenResponse(respondWith); | ||
}); | ||
|
||
return outcome; | ||
}; | ||
|
||
return { | ||
mockGrantAuthorization, | ||
mockRevokeAuthorization, | ||
mockGrantAccessToken, | ||
}; | ||
}; | ||
|
||
type GrantResponseOptions = { | ||
code?: string; | ||
error?: ErrorType | ErrorType[]; | ||
}; | ||
|
||
const toQueryParams = ( | ||
{ code, error }: GrantResponseOptions, | ||
wranglerRequestParams: URLSearchParams | ||
): string => { | ||
const queryParams = []; | ||
if (code) { | ||
queryParams.push(`code=${code}`); | ||
} | ||
if (error) { | ||
const stringifiedErr = Array.isArray(error) ? error.join(",") : error; | ||
queryParams.push(`error=${stringifiedErr}`); | ||
} | ||
|
||
queryParams.push(`state=${wranglerRequestParams.get("state")}`); | ||
|
||
return queryParams.join("&"); | ||
}; | ||
|
||
type ErrorType = | ||
| "invalid_request" | ||
| "invalid_grant" | ||
| "unauthorized_client" | ||
| "access_denied" | ||
| "unsupported_response_type" | ||
| "invalid_scope" | ||
| "server_error" | ||
| "temporarily_unavailable" | ||
| "invalid_client" | ||
| "unsupported_grant_type" | ||
| "invalid_json" | ||
| "invalid_token" | ||
| "test-token-grant-error"; | ||
|
||
type MockTokenResponse = | ||
| "ok" | ||
| "error" | ||
| { | ||
access_token: string; | ||
expires_in: number; | ||
refresh_token: string; | ||
scope: string; | ||
} | ||
| { | ||
error: ErrorType; | ||
}; | ||
|
||
const makeTokenResponse = (partialResponse: MockTokenResponse): string => { | ||
let fullResponse: MockTokenResponse; | ||
|
||
if (partialResponse === "ok") { | ||
fullResponse = { | ||
access_token: "test-access-token", | ||
expires_in: 100000, | ||
refresh_token: "test-refresh-token", | ||
scope: "account:read", | ||
}; | ||
} else if (partialResponse === "error") { | ||
fullResponse = { | ||
error: "test-token-grant-error", | ||
}; | ||
} else { | ||
fullResponse = partialResponse; | ||
} | ||
|
||
return JSON.stringify(fullResponse); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.