Skip to content

feat: add support for coder_git_auth data source #6334

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

Merged
merged 21 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Use BroadcastChannel to automatically authenticate with Git
  • Loading branch information
kylecarbs committed Feb 23, 2023
commit e4188921d7f81b49d2aca18e78510a296441d96a
2 changes: 1 addition & 1 deletion site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,6 @@
"chrome 66",
"firefox 63",
"edge 79",
"safari 13.1"
"safari 15.4"
]
}
14 changes: 7 additions & 7 deletions site/src/components/GitAuth/GitAuth.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,43 @@ GithubNotAuthenticated.args = {
}

export const GithubAuthenticated = Template.bind({})
GithubNotAuthenticated.args = {
GithubAuthenticated.args = {
type: "github",
authenticated: true,
}

export const GitlabNotAuthenticated = Template.bind({})
GithubNotAuthenticated.args = {
GitlabNotAuthenticated.args = {
type: "gitlab",
authenticated: false,
}

export const GitlabAuthenticated = Template.bind({})
GithubNotAuthenticated.args = {
GitlabAuthenticated.args = {
type: "gitlab",
authenticated: true,
}

export const AzureDevOpsNotAuthenticated = Template.bind({})
GithubNotAuthenticated.args = {
AzureDevOpsNotAuthenticated.args = {
type: "azure-devops",
authenticated: false,
}

export const AzureDevOpsAuthenticated = Template.bind({})
GithubNotAuthenticated.args = {
AzureDevOpsAuthenticated.args = {
type: "azure-devops",
authenticated: true,
}

export const BitbucketNotAuthenticated = Template.bind({})
GithubNotAuthenticated.args = {
BitbucketNotAuthenticated.args = {
type: "bitbucket",
authenticated: false,
}

export const BitbucketAuthenticated = Template.bind({})
GithubNotAuthenticated.args = {
BitbucketAuthenticated.args = {
type: "bitbucket",
authenticated: true,
}
96 changes: 68 additions & 28 deletions site/src/components/GitAuth/GitAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { FC } from "react"
import * as TypesGen from "api/typesGenerated"
import Button from "@material-ui/core/Button"
import { makeStyles } from "@material-ui/core/styles"
import { useLocation } from "react-router-dom"
import { SvgIconProps } from "@material-ui/core/SvgIcon"
import Tooltip from "@material-ui/core/Tooltip"
import GitHub from "@material-ui/icons/GitHub"
import { GitlabIcon } from "components/Icons/GitlabIcon"
import * as TypesGen from "api/typesGenerated"
import { AzureDevOpsIcon } from "components/Icons/AzureDevOpsIcon"
import { BitbucketIcon } from "components/Icons/BitbucketIcon"
import { GitlabIcon } from "components/Icons/GitlabIcon"
import { Typography } from "components/Typography/Typography"
import Link from "@material-ui/core/Link"
import Button from "@material-ui/core/Button"
import { Tooltip } from "@material-ui/core"
import { FC } from "react"

export interface GitAuthProps {
type: TypesGen.GitProvider
Expand All @@ -25,43 +24,70 @@ export const GitAuth: FC<GitAuthProps> = ({
const styles = useStyles()

let prettyName: string
let icon: JSX.Element
let Icon: (props: SvgIconProps) => JSX.Element
switch (type) {
case "azure-devops":
prettyName = "Azure DevOps"
icon = <AzureDevOpsIcon />
Icon = AzureDevOpsIcon
break
case "bitbucket":
prettyName = "Bitbucket"
icon = <BitbucketIcon />
Icon = BitbucketIcon
break
case "github":
prettyName = "GitHub"
icon = <GitHub />
Icon = GitHub
break
case "gitlab":
prettyName = "GitLab"
icon = <GitlabIcon />
Icon = GitlabIcon
break
default:
throw new Error("invalid git provider: " + type)
}
const location = useLocation()
const redirectURL = `${authenticateURL}?redirect=${location.pathname}`
// The Git auth page uses a BroadcastChannel to notify listening
// pages for Git auth updates if the "notify" query parameter is specified.
const redirectURL = `${authenticateURL}?redirect=${encodeURIComponent(
`/gitauth?notify`,
)}`

return (
<Tooltip title={authenticated ? "You're already authenticated!" : ""}>
<a href={redirectURL} className={styles.link}>
<Button disabled={authenticated}>
<div className={styles.root}>
{icon}
{authenticated && <Typography variant="body2">Authenticated with {prettyName}</Typography>}
{!authenticated && (
<Typography variant="body2">Authenticate with {prettyName}</Typography>
)}
</div>
</Button>
</a>
<Tooltip
title={
authenticated ? "You're already authenticated! No action needed." : ``
}
>
<a
href={redirectURL}
className={styles.link}
onClick={(event) => {
event.preventDefault()
// If the user is already authenticated, we don't want to redirect them
if (authenticated || authenticateURL === "") {
return
}
window.open(redirectURL, "_blank", "width=900,height=600")
}}
>
<Button className={styles.button} disabled={authenticated} fullWidth>
<div className={styles.root}>
<Icon className={styles.icon} />
<div className={styles.text}>
<Typography variant="body2">
{authenticated
? `You're authenticated with ${prettyName}!`
: `Click to login with ${prettyName}!`}
</Typography>
{!authenticated && (
<Typography variant="caption" color="textSecondary">
{"You'll be redirected back here once authenticated."}
</Typography>
)}
</div>
</div>
</Button>
</a>
</Tooltip>

)
}

Expand All @@ -70,8 +96,22 @@ const useStyles = makeStyles(() => ({
textDecoration: "none",
},
root: {
padding: 4,
display: "flex",
gap: 16,
gap: 12,
alignItems: "center",
textAlign: "left",
},
button: {
height: "unset",
},
text: {
display: "flex",
flexDirection: "column",
gap: 4,
},
icon: {
width: 32,
height: 32,
},
}))
3 changes: 2 additions & 1 deletion site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ const CreateWorkspacePage: FC = () => {
[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]:
getTemplateSchemaError,
[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]: createWorkspaceError,
[CreateWorkspaceErrors.GET_TEMPLATE_GITAUTH_ERROR]: getTemplateGitAuthError,
[CreateWorkspaceErrors.GET_TEMPLATE_GITAUTH_ERROR]:
getTemplateGitAuthError,
}}
canCreateForUser={permissions?.createWorkspaceForUser}
owner={owner}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,25 @@ RichParameters.args = {
],
createWorkspaceErrors: {},
}

export const GitAuth = Template.bind({})
GitAuth.args = {
templates: [MockTemplate],
selectedTemplate: MockTemplate,
createWorkspaceErrors: {},
templateParameters: [],
templateGitAuth: [
{
id: "github",
type: "github",
authenticated: false,
authenticate_url: "",
},
{
id: "gitlab",
type: "gitlab",
authenticated: true,
authenticate_url: "",
},
],
}
Original file line number Diff line number Diff line change
Expand Up @@ -231,14 +231,14 @@ export const CreateWorkspacePageView: FC<
Git Authentication
</h2>
<p className={styles.formSectionInfoDescription}>
This workspace requires authenticating with Git providers to
create a workspace.
This template requires authentication to automatically
perform Git operations on create.
</p>
</div>

<Stack
direction="column"
spacing={4}
spacing={2}
className={styles.formSectionFields}
>
{props.templateGitAuth.map((auth, index) => (
Expand Down
19 changes: 17 additions & 2 deletions site/src/pages/GitAuthPage/GitAuthPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,26 @@ import Button from "@material-ui/core/Button"
import { makeStyles } from "@material-ui/core/styles"
import { SignInLayout } from "components/SignInLayout/SignInLayout"
import { Welcome } from "components/Welcome/Welcome"
import { FC } from "react"
import { Link as RouterLink } from "react-router-dom"
import { FC, useEffect } from "react"
import { Link as RouterLink, useSearchParams } from "react-router-dom"
import { REFRESH_GITAUTH_BROADCAST_CHANNEL } from "xServices/createWorkspace/createWorkspaceXService"

const GitAuthPage: FC = () => {
const styles = useStyles()
const [searchParams] = useSearchParams()
const shouldNotify = searchParams.has("notify")
useEffect(() => {
if (!shouldNotify) {
return
}

// This is used to notify the parent window that the Git auth token has been refreshed.
// It's critical in the create workspace flow!
const bc = new BroadcastChannel(REFRESH_GITAUTH_BROADCAST_CHANNEL)
// The message doesn't matter, any message refreshes the page!
bc.postMessage("noop")
window.close()
}, [shouldNotify])

return (
<SignInLayout>
Expand Down
Loading