Skip to content

Commit

Permalink
fix(projects): handle GitHub token revocation
Browse files Browse the repository at this point in the history
When a GitHub request is made with a revoked token, the end user is
informed and asked to sign in again.
  • Loading branch information
e18r committed Jan 7, 2020
1 parent 8395187 commit b5e8b4a
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 25 deletions.
13 changes: 10 additions & 3 deletions apps/projects/app/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import {
REQUESTED_GITHUB_TOKEN_FAILURE,
} from './store/eventTypes'

import { initApolloClient } from './utils/apollo-client'
import { useApolloClient } from './utils/apollo-client'
import { getToken, githubPopup, STATUS } from './utils/github'
import Unauthorized from './components/Content/Unauthorized'
import { LoadingAnimation } from './components/Shared'
import { EmptyWrapper } from './components/Shared'
import { Error } from './components/Card'
import { Error, Revoked } from './components/Card'
import { DecoratedReposProvider } from './context/DecoratedRepos'
import usePathSegments from './hooks/usePathSegments'

Expand All @@ -40,6 +40,7 @@ const App = () => {
const [ githubLoading, setGithubLoading ] = useState(false)
const [ panel, setPanel ] = useState(null)
const [ panelProps, setPanelProps ] = useState(null)
const initApolloClient = useApolloClient()

const {
repos = [],
Expand All @@ -56,7 +57,7 @@ const App = () => {
selectIssue: setSelectedIssue,
} = usePathSegments()

const client = github.token ? initApolloClient(github.token) : null
const client = initApolloClient(github.token)

useEffect(() => {
setSelectedIssue(selectedIssueId)
Expand Down Expand Up @@ -140,6 +141,12 @@ const App = () => {
<Error action={noop} />
</Main>
)
} else if (github.status === STATUS.REVOKED) {
return (
<Main>
<Revoked action={handleGithubSignIn} />
</Main>
)
}

// Tabs are not fixed
Expand Down
24 changes: 24 additions & 0 deletions apps/projects/app/assets/revoked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions apps/projects/app/components/Card/Revoked.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Button, EmptyStateCard, GU, Text, useTheme } from '@aragon/ui'
import { EmptyWrapper } from '../Shared'
import revoked from '../../assets/revoked.svg'

const Illustration = () => <img src={revoked} alt="" />


const Revoked = ({ action }) => {
const theme = useTheme()
return (
<EmptyWrapper>
<EmptyStateCard
text={
<>
<Text css={`margin: ${GU}px`}>
Reconnect to GitHub
</Text>
<Text.Block
size="small"
color={`${theme.surfaceContentSecondary}`}
>
It seems that your connection to GitHub has been revoked; please
reconnect.
</Text.Block>
</>
}
illustration={<Illustration />}
action={<Button label="Sign in" onClick={action} />}
/>
</EmptyWrapper>
)
}

Revoked.propTypes = {
action: PropTypes.func.isRequired,
}

export default Revoked
3 changes: 2 additions & 1 deletion apps/projects/app/components/Card/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import Empty from './Empty'
import Project from './Project'
import Issue from './Issue'
import Error from './Error'
export { Empty, Project, Issue, Error }
import Revoked from './Revoked'
export { Empty, Project, Issue, Error, Revoked }
4 changes: 2 additions & 2 deletions apps/projects/app/components/Content/IssueDetail/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import { useLayout } from '@aragon/ui'
import { useQuery } from '@apollo/react-hooks'
import useShapedIssue from '../../../hooks/useShapedIssue'
import { GET_ISSUE } from '../../../utils/gql-queries.js'
import { initApolloClient } from '../../../utils/apollo-client'
import { useApolloClient } from '../../../utils/apollo-client'
import EventsCard from './EventsCard'
import DetailsCard from './DetailsCard'
import BountyCard from './BountyCard'

const IssueDetail = ({ issueId }) => {
const { appState: { github } } = useAragonApi()
const initApolloClient = useApolloClient()
const client = useMemo(() => initApolloClient(github.token), [])
const { layoutName } = useLayout()
const shapeIssue = useShapedIssue()
const { loading, error, data } = useQuery(GET_ISSUE, {
client,
onError: console.error,
variables: { id: issueId },
})

Expand Down
8 changes: 5 additions & 3 deletions apps/projects/app/components/Content/Issues.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useAragonApi } from '../../api-react'
import { Button, GU, Text } from '@aragon/ui'
import { compareAsc, compareDesc } from 'date-fns'

import { initApolloClient } from '../../utils/apollo-client'
import { useApolloClient } from '../../utils/apollo-client'
import useShapedIssue from '../../hooks/useShapedIssue'
import usePathSegments from '../../hooks/usePathSegments'
import { STATUS } from '../../utils/github'
Expand Down Expand Up @@ -365,7 +365,7 @@ class Issues extends React.PureComponent {
}

const IssuesQuery = ({ client, query, ...props }) => {
const graphqlQuery = useQuery(query, { client, onError: console.error })
const graphqlQuery = useQuery(query, { client })
return <Issues graphqlQuery={graphqlQuery} {...props} />
}

Expand All @@ -375,6 +375,7 @@ IssuesQuery.propTypes = {
}

const IssuesWrap = props => {
const initApolloClient = useApolloClient()
const { appState: { github, repos } } = useAragonApi()
const shapeIssue = useShapedIssue()
const { query: { repoId } } = usePathSegments()
Expand Down Expand Up @@ -423,7 +424,8 @@ const IssuesWrap = props => {
}, [ downloadedRepos, filters, repos ])

useEffect(() => {
setClient(github.token ? initApolloClient(github.token) : null)
const apolloClient = initApolloClient(github.token)
setClient(apolloClient)
}, [github.token])

if (!query) return 'Loading...'
Expand Down
1 change: 0 additions & 1 deletion apps/projects/app/components/Panel/NewIssue/NewIssue.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ class NewIssue extends React.PureComponent {
<Mutation
mutation={NEW_ISSUE}
variables={{ title, description, id }}
onError={console.error}
>
{(newIssue, result) => {
const { data, loading, error, called } = result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ const NewProject = () => {
<Query
fetchPolicy="cache-first"
query={GET_REPOSITORIES}
onError={console.error}
>
{({ data, loading, error, refetch }) => {
if (data && data.viewer) {
Expand Down
3 changes: 2 additions & 1 deletion apps/projects/app/hooks/useGithubAuth.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useEffect, useState } from 'react'
import { useAragonApi } from '../api-react'
import { initApolloClient } from '../utils/apollo-client'
import { useApolloClient } from '../utils/apollo-client'
import { CURRENT_USER } from '../utils/gql-queries'

const useGithubAuth = () => {
const { appState } = useAragonApi()
const initApolloClient = useApolloClient()
const token = appState.github && appState.github.token

const [ githubCurrentUser, setGithubCurrentUser ] = useState({})
Expand Down
1 change: 1 addition & 0 deletions apps/projects/app/store/eventTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const REQUESTING_GITHUB_TOKEN = 'Requesting_GitHub_Token'
export const REQUESTED_GITHUB_TOKEN_SUCCESS = 'Requesting_GitHub_Token_Success'
export const REQUESTED_GITHUB_TOKEN_FAILURE = 'Requesting_GitHub_Token_Failure'
export const REQUESTED_GITHUB_DISCONNECT = 'Requested_GitHub_Disconnect'
export const GITHUB_TOKEN_REVOKED = 'Github_Token_Revoked'

/* Projects.sol events */
export const REPO_ADDED = 'RepoAdded'
Expand Down
9 changes: 9 additions & 0 deletions apps/projects/app/store/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
REQUESTED_GITHUB_TOKEN_SUCCESS,
REQUESTED_GITHUB_TOKEN_FAILURE,
REQUESTED_GITHUB_DISCONNECT,
GITHUB_TOKEN_REVOKED,
REPO_ADDED,
REPO_REMOVED,
BOUNTY_ADDED,
Expand Down Expand Up @@ -77,6 +78,14 @@ export const handleEvent = async (state, action, vaultAddress, vaultContract, se
app.cache('github', state.github).toPromise()
return state
}
case GITHUB_TOKEN_REVOKED: {
state.github = {
...INITIAL_STATE.github,
status: STATUS.REVOKED,
}
app.cache('github', state.github).toPromise()
return state
}
case REPO_ADDED: {
return await syncRepos(state, returnValues)
}
Expand Down
44 changes: 31 additions & 13 deletions apps/projects/app/utils/apollo-client.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
import ApolloClient from 'apollo-boost'
import { useAragonApi } from '../api-react'
import { GITHUB_TOKEN_REVOKED } from '../store/eventTypes'
import { STATUS } from '../utils/github'

export const initApolloClient = token =>
new ApolloClient({
uri: 'https://api.github.com/graphql',
request: operation => {
if (token) {
operation.setContext({
headers: {
accept: 'application/vnd.github.starfire-preview+json', // needed to create issues
authorization: `bearer ${token}`,
},
})
export const useApolloClient = () => {
const { api } = useAragonApi()
const initApolloClient = token => {
if (token === null) return null
return new ApolloClient({
uri: 'https://api.github.com/graphql',
request: operation => {
if (token) {
operation.setContext({
headers: {
accept: 'application/vnd.github.starfire-preview+json', // needed to create issues
authorization: `bearer ${token}`,
},
})
}
},
onError: error => {
if (error.networkError && error.networkError.statusCode === 401) {
api.emitTrigger(GITHUB_TOKEN_REVOKED, {
status: STATUS.REVOKED,
scope: null,
token: null,
})
}
}
},
})
})
}
return initApolloClient
}
1 change: 1 addition & 0 deletions apps/projects/app/utils/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const STATUS = {
AUTHENTICATED: 'authenticated',
FAILED: 'failed',
INITIAL: 'initial',
REVOKED: 'revoked',
}
// TODO: STATUS.loading with a smooth transition component

Expand Down

0 comments on commit b5e8b4a

Please sign in to comment.