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

Add Studio Token Auth Flow #4931

Merged
merged 22 commits into from
Nov 13, 2023
Merged

Add Studio Token Auth Flow #4931

merged 22 commits into from
Nov 13, 2023

Conversation

julieg18
Copy link
Contributor

@julieg18 julieg18 commented Oct 30, 2023

@julieg18 julieg18 added the product PR that affects product label Oct 30, 2023
@julieg18 julieg18 self-assigned this Oct 30, 2023
extension/src/setup/studio.ts Outdated Show resolved Hide resolved
@julieg18 julieg18 mentioned this pull request Nov 2, 2023
extension/src/setup/studio.ts Outdated Show resolved Hide resolved
jest.resetAllMocks()
})

describe('openUrl', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went ahead and added a small test for openUrl since we didn't seem to have one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'should handle a message from the webview to open Studio' tested it indirectly (anything that used mockOpenExternal tested the call to the same level as this). It seems like you've deleted all of those tests which means mockOpenExternal can be removed from buildSetup.

extension/src/vscode/external.test.ts Outdated Show resolved Hide resolved
export const waitForUriResponse = (path: string, onResponse: () => unknown) => {
window.registerUriHandler({
handleUri(uri: Uri): ProviderResult<void> {
if (uri.path.startsWith(path)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally, I was going to check for uri.path === path, but there's an issue with Studio redirecting an encoded version of our callback URL.

Instead of getting redirected to vscode://iterative.dvc/studio-complete-auth?windowId=2, Studio will redirect the user to vscode://iterative.dvc/studio-complete-auth%253FwindowId=2, which makes the path studio-complete-auth%253F, failing an equals check.

I'm asking Thomas if Studio can redirect the user to an decoded version... Though now that I think about it, it should be a small change, I can probably open a pr myself 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I'm using uri.path.startsWith, but we can change it to an === check once Studio is updated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decodeURI() does not work here?

Copy link
Contributor Author

@julieg18 julieg18 Nov 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, technically we could use decodeURI here to check the path, but we need the user to be sent to the vscode URL with the windowId query since I believe it helps VSCode to route the user correctly depending on certain circumstances.

We could use decodeURI as a temporary solution instead of .startsWith() though!

@julieg18 julieg18 marked this pull request as ready for review November 9, 2023 16:06
@julieg18 julieg18 changed the title Add Studio Token Auth Flow (wip) Add Studio Token Auth Flow Nov 9, 2023
@julieg18 julieg18 requested a review from shcheklein November 9, 2023 16:07
@shcheklein
Copy link
Member

Looks good to me. @yathomasi @amritghimire we still need to review the messaging on the Studio side (e.g. Device Activation - etc - do we really need those low level terms?). Why do we need to click Continue, then Authorize - we don't need to buttons to my mind there ... Let's please one more time review it.

@julieg18 we need to find a way to get rid of the pause at the end (feels like nothing is happening) or show some spinner (with some timeout). Otherwise it was a bit confusing.

Good stuff.

import { Toast } from '../vscode/toast'

const mockedFetch = jest.mocked(Fetch)
const mockedFetchDefault = jest.fn()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not import fetch from 'node-fetch' and

const mockedFetch = jest.mocked(fetch)
const mockedFetchDefault = jest.fn()
mockedFetch.mockIplementation(() => mockedFetchDefault)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmm, this code gives me an error: mockedFetch.mockImplementation is not a function. Maybe it's because because fetch is a function, not a source object?

extension/src/setup/studio.ts Outdated Show resolved Hide resolved
extension/src/setup/studio.ts Outdated Show resolved Hide resolved
extension/src/setup/studio.ts Outdated Show resolved Hide resolved
const webview = await setup.showWebview()
await webview.isReady()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
stub(Setup.prototype as any, 'getCliCompatible').returns(true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this private in the first place?

private getCliCompatible() {
    return this.cliCompatible
  }

That does not seem to have any use since it's accessing a local variable. Is this code for tests only? Is there no other way to change the value? Why not set it public then? cc. @mattseddon (I think you're the one that added it)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no other way to change the value?

Yes, that is the reason that I remember... https://github.com/iterative/vscode-dvc/pull/3768/files#r1178629676

export const waitForUriResponse = (path: string, onResponse: () => unknown) => {
window.registerUriHandler({
handleUri(uri: Uri): ProviderResult<void> {
if (uri.path.startsWith(path)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decodeURI() does not work here?

@@ -21,26 +18,13 @@ export const Connect: React.FC = () => {
<a href="https://dvc.org/doc/studio/user-guide/projects-and-experiments/live-metrics-and-plots#set-up-an-access-token">
access token
</a>{' '}
generated from your Studio profile page.
generated from your Studio profile page. Request a token below or{' '}
<button className={styles.buttonAsLink} onClick={saveStudioToken}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The style seems different than the previous link (buttonAsLink might need an update, underline vs none). Also, it feels weird to have this link here (before the "Get Token" button that is mentioned before), I feel like it should be a button on the same level as the "Get Token" one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Q] Why do we need this escape hatch? Will the endpoint always return a new token or does it fail if the user has one setup already?

Copy link
Contributor Author

@julieg18 julieg18 Nov 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it feels weird to have this link here (before the "Get Token" button that is mentioned before), I feel like it should be a button on the same level as the "Get Token" one.

Makes sense, I can move it to a secondary button.

[Q] Why do we need this escape hatch? Will the endpoint always return a new token or does it fail if the user has one setup already?

Yes, the endpoint always creates a new one (your studio profile can hold multiple now) which I why I wanted to leave the option to add an already created one. If preferred, I can remove the option though if we want to keep onboarding as simplified as possible.

extension/src/setup/studio.test.ts Outdated Show resolved Hide resolved
return fetchStudioToken(deviceCode, tokenUri, false)
}
return Toast.showError(
'Unable to get token. Please try again later or add an already created token.'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[C] IMO, this should be a modal. The user will have no chance of missing it.

[Q] Is there a reason to retry this? Have you seen the process work on these second try? If we only try to contact the server once, we can (easily) provide details of the failure based on the response we get. If the call is unstable then we should be working with the Studio team, not trying to patch it up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[C] IMO, this should be a modal. The user will have no chance of missing it.

Good point!

[Q] Is there a reason to retry this? Have you seen the process work on these second try? If we only try to contact the server once, we can (easily) provide details of the failure based on the response we get. If the call is unstable then we should be working with the Studio team, not trying to patch it up.

Oh, you're right. When I first reviewed the Studio code I thought there was a chance of the token not being ready by the time the user is redirected, but I now see that this should work on the first try. I'll remove the retry attempt.

extension/src/setup/studio.ts Outdated Show resolved Hide resolved
const webview = await setup.showWebview()
await webview.isReady()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
stub(Setup.prototype as any, 'getCliCompatible').returns(true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no other way to change the value?

Yes, that is the reason that I remember... https://github.com/iterative/vscode-dvc/pull/3768/files#r1178629676

extension/src/vscode/external.test.ts Outdated Show resolved Hide resolved
extension/src/vscode/external.test.ts Outdated Show resolved Hide resolved
jest.resetAllMocks()
})

describe('openUrl', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'should handle a message from the webview to open Studio' tested it indirectly (anything that used mockOpenExternal tested the call to the same level as this). It seems like you've deleted all of those tests which means mockOpenExternal can be removed from buildSetup.

@@ -21,26 +18,13 @@ export const Connect: React.FC = () => {
<a href="https://dvc.org/doc/studio/user-guide/projects-and-experiments/live-metrics-and-plots#set-up-an-access-token">
access token
</a>{' '}
generated from your Studio profile page.
generated from your Studio profile page. Request a token below or{' '}
<button className={styles.buttonAsLink} onClick={saveStudioToken}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Q] Why do we need this escape hatch? Will the endpoint always return a new token or does it fail if the user has one setup already?

@julieg18
Copy link
Contributor Author

Alright, resolved/responded to all comments. What's left:

I'd like to do this one in a followup and merge this a first iteration on Monday :)

@julieg18 julieg18 enabled auto-merge (squash) November 13, 2023 13:34
Copy link

codeclimate bot commented Nov 13, 2023

Code Climate has analyzed commit a4e1baa and detected 0 issues on this pull request.

The test coverage on the diff in this pull request is 100.0% (85% is the threshold).

This pull request will bring the total coverage in the repository to 95.2% (0.0% change).

View more on Code Climate.

@julieg18 julieg18 merged commit b1decdb into main Nov 13, 2023
3 checks passed
@julieg18 julieg18 deleted the add-studio-token-auth-flow branch November 13, 2023 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
product PR that affects product
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants