Add support for changing GitHub OAuth permissions (#4)
This commit is contained in:
parent
f308aeb929
commit
a6c44e0e69
|
@ -1,9 +1,9 @@
|
||||||
import { DefaultButton, IButtonProps, PrimaryButton } from "office-ui-fabric-react";
|
import { DefaultButton, IButtonProps, Link, PrimaryButton } from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient";
|
import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||||
import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent";
|
import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent";
|
||||||
import { AuthorizeAccessComponent, AuthorizeAccessComponentProps } from "./AuthorizeAccessComponent";
|
import { AuthorizeAccessComponent, AuthorizeAccessComponentProps } from "./AuthorizeAccessComponent";
|
||||||
import { ChildrenMargin, ButtonsFooterStyle, ContentFooterStyle } from "./GitHubStyleConstants";
|
import { ButtonsFooterStyle, ChildrenMargin, ContentFooterStyle } from "./GitHubStyleConstants";
|
||||||
import { ReposListComponent, ReposListComponentProps } from "./ReposListComponent";
|
import { ReposListComponent, ReposListComponentProps } from "./ReposListComponent";
|
||||||
|
|
||||||
export interface GitHubReposComponentProps {
|
export interface GitHubReposComponentProps {
|
||||||
|
@ -45,6 +45,9 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p>{GitHubReposComponent.ManageGitHubRepoDescription}</p>
|
<p>{GitHubReposComponent.ManageGitHubRepoDescription}</p>
|
||||||
|
<Link style={{ marginTop: ChildrenMargin }} onClick={this.props.resetConnection}>
|
||||||
|
{GitHubReposComponent.ManageGitHubRepoResetConnection}
|
||||||
|
</Link>
|
||||||
<ReposListComponent {...this.props.reposListProps} />
|
<ReposListComponent {...this.props.reposListProps} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -60,20 +60,20 @@ describe("GitHubOAuthService", () => {
|
||||||
expect(gitHubOAuthService.getTokenObservable()()).toBeUndefined();
|
expect(gitHubOAuthService.getTokenObservable()()).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("startOAuth resets OAuth state", () => {
|
it("startOAuth resets OAuth state", async () => {
|
||||||
let url: string;
|
let url: string;
|
||||||
const windowOpenCallback = jest.fn().mockImplementation((value: string) => {
|
const windowOpenCallback = jest.fn().mockImplementation((value: string) => {
|
||||||
url = value;
|
url = value;
|
||||||
});
|
});
|
||||||
window.open = windowOpenCallback;
|
window.open = windowOpenCallback;
|
||||||
|
|
||||||
gitHubOAuthService.startOAuth("scope");
|
await gitHubOAuthService.startOAuth("scope");
|
||||||
expect(windowOpenCallback).toBeCalled();
|
expect(windowOpenCallback).toBeCalled();
|
||||||
|
|
||||||
const initialParams = new URLSearchParams(new URL(url).search);
|
const initialParams = new URLSearchParams(new URL(url).search);
|
||||||
expect(initialParams.get("state")).toBeDefined();
|
expect(initialParams.get("state")).toBeDefined();
|
||||||
|
|
||||||
gitHubOAuthService.startOAuth("another scope");
|
await gitHubOAuthService.startOAuth("another scope");
|
||||||
expect(windowOpenCallback).toBeCalled();
|
expect(windowOpenCallback).toBeCalled();
|
||||||
|
|
||||||
const newParams = new URLSearchParams(new URL(url).search);
|
const newParams = new URLSearchParams(new URL(url).search);
|
||||||
|
@ -106,7 +106,7 @@ describe("GitHubOAuthService", () => {
|
||||||
junoClient.getGitHubToken = getGitHubTokenCallback;
|
junoClient.getGitHubToken = getGitHubTokenCallback;
|
||||||
|
|
||||||
const initialToken = gitHubOAuthService.getTokenObservable()();
|
const initialToken = gitHubOAuthService.getTokenObservable()();
|
||||||
const state = gitHubOAuthService.startOAuth("scope");
|
const state = await gitHubOAuthService.startOAuth("scope");
|
||||||
|
|
||||||
const params: IGitHubConnectorParams = {
|
const params: IGitHubConnectorParams = {
|
||||||
state,
|
state,
|
||||||
|
@ -120,7 +120,7 @@ describe("GitHubOAuthService", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("finishOAuth updates token to error if state doesn't match", async () => {
|
it("finishOAuth updates token to error if state doesn't match", async () => {
|
||||||
gitHubOAuthService.startOAuth("scope");
|
await gitHubOAuthService.startOAuth("scope");
|
||||||
|
|
||||||
const params: IGitHubConnectorParams = {
|
const params: IGitHubConnectorParams = {
|
||||||
state: "state",
|
state: "state",
|
||||||
|
@ -135,7 +135,7 @@ describe("GitHubOAuthService", () => {
|
||||||
const getGitHubTokenCallback = jest.fn().mockReturnValue({ status: HttpStatusCodes.NotFound });
|
const getGitHubTokenCallback = jest.fn().mockReturnValue({ status: HttpStatusCodes.NotFound });
|
||||||
junoClient.getGitHubToken = getGitHubTokenCallback;
|
junoClient.getGitHubToken = getGitHubTokenCallback;
|
||||||
|
|
||||||
const state = gitHubOAuthService.startOAuth("scope");
|
const state = await gitHubOAuthService.startOAuth("scope");
|
||||||
|
|
||||||
const params: IGitHubConnectorParams = {
|
const params: IGitHubConnectorParams = {
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import ko from "knockout";
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
import { HttpStatusCodes } from "../Common/Constants";
|
||||||
import { Logger } from "../Common/Logger";
|
import { Logger } from "../Common/Logger";
|
||||||
import { config } from "../Config";
|
import { config } from "../Config";
|
||||||
|
import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent";
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { JunoClient } from "../Juno/JunoClient";
|
import { JunoClient } from "../Juno/JunoClient";
|
||||||
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
|
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
|
||||||
|
@ -39,7 +40,19 @@ export class GitHubOAuthService {
|
||||||
this.token = ko.observable<IGitHubOAuthToken>();
|
this.token = ko.observable<IGitHubOAuthToken>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public startOAuth(scope: string): string {
|
public async startOAuth(scope: string): Promise<string> {
|
||||||
|
// If attempting to change scope from "Public & private repos" to "Public only" we need to delete app authorization.
|
||||||
|
// Otherwise OAuth app still retains the "public & private repos" permissions.
|
||||||
|
if (
|
||||||
|
this.token()?.scope === AuthorizeAccessComponent.Scopes.PublicAndPrivate.key &&
|
||||||
|
scope === AuthorizeAccessComponent.Scopes.Public.key
|
||||||
|
) {
|
||||||
|
const logoutSuccessful = await this.logout();
|
||||||
|
if (!logoutSuccessful) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
scope,
|
scope,
|
||||||
client_id: config.GITHUB_CLIENT_ID,
|
client_id: config.GITHUB_CLIENT_ID,
|
||||||
|
@ -76,7 +89,7 @@ export class GitHubOAuthService {
|
||||||
return this.token;
|
return this.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async logout() {
|
public async logout(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await this.junoClient.deleteAppAuthorization(this.token()?.access_token);
|
const response = await this.junoClient.deleteAppAuthorization(this.token()?.access_token);
|
||||||
if (response.status !== HttpStatusCodes.NoContent) {
|
if (response.status !== HttpStatusCodes.NoContent) {
|
||||||
|
@ -84,10 +97,12 @@ export class GitHubOAuthService {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resetToken();
|
this.resetToken();
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to delete app authorization: ${error}`;
|
const message = `Failed to delete app authorization: ${error}`;
|
||||||
Logger.logError(message, "GitHubOAuthService/logout");
|
Logger.logError(message, "GitHubOAuthService/logout");
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue