mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: allow external services to be authable (#9996)
* feat: allow external services to be authable * Refactor external auth config structure for defaults * Add support for new config properties * Change the name of external auth * Move externalauth -> external-auth * Run gen * Fix tests * Fix MW tests * Fix git auth redirect * Fix lint * Fix name * Allow any ID * Fix invalid type test * Fix e2e tests * Fix comments * Fix colors * Allow accepting any type as string * Run gen * Fix href
This commit is contained in:
@@ -57,6 +57,7 @@ export default defineConfig({
|
||||
CODER_GITAUTH_0_ID: gitAuth.deviceProvider,
|
||||
CODER_GITAUTH_0_TYPE: "github",
|
||||
CODER_GITAUTH_0_CLIENT_ID: "client",
|
||||
CODER_GITAUTH_0_CLIENT_SECRET: "secret",
|
||||
CODER_GITAUTH_0_DEVICE_FLOW: "true",
|
||||
CODER_GITAUTH_0_APP_INSTALL_URL:
|
||||
"https://github.com/apps/coder/installations/new",
|
||||
|
||||
@@ -46,7 +46,7 @@ test("external auth device", async ({ page }) => {
|
||||
sentPending.done();
|
||||
});
|
||||
|
||||
await page.goto(`/externalauth/${gitAuth.deviceProvider}`, {
|
||||
await page.goto(`/external-auth/${gitAuth.deviceProvider}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await page.getByText(device.user_code).isVisible();
|
||||
@@ -70,11 +70,11 @@ test("external auth web", async ({ baseURL, page }) => {
|
||||
});
|
||||
srv.use(gitAuth.authPath, (req, res) => {
|
||||
res.redirect(
|
||||
`${baseURL}/externalauth/${gitAuth.webProvider}/callback?code=1234&state=` +
|
||||
`${baseURL}/external-auth/${gitAuth.webProvider}/callback?code=1234&state=` +
|
||||
req.query.state,
|
||||
);
|
||||
});
|
||||
await page.goto(`/externalauth/${gitAuth.webProvider}`, {
|
||||
await page.goto(`/external-auth/${gitAuth.webProvider}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
// This endpoint doesn't have the installations URL set intentionally!
|
||||
|
||||
@@ -110,10 +110,10 @@ const UserAuthSettingsPage = lazy(
|
||||
"./pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPage"
|
||||
),
|
||||
);
|
||||
const GitAuthSettingsPage = lazy(
|
||||
const ExternalAuthSettingsPage = lazy(
|
||||
() =>
|
||||
import(
|
||||
"./pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPage"
|
||||
"./pages/DeploySettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPage"
|
||||
),
|
||||
);
|
||||
const NetworkSettingsPage = lazy(
|
||||
@@ -210,7 +210,7 @@ export const AppRouter: FC = () => {
|
||||
<Route path="health" element={<HealthPage />} />
|
||||
|
||||
<Route
|
||||
path="externalauth/:provider"
|
||||
path="external-auth/:provider"
|
||||
element={<ExternalAuthPage />}
|
||||
/>
|
||||
|
||||
@@ -292,7 +292,10 @@ export const AppRouter: FC = () => {
|
||||
<Route path="appearance" element={<AppearanceSettingsPage />} />
|
||||
<Route path="network" element={<NetworkSettingsPage />} />
|
||||
<Route path="userauth" element={<UserAuthSettingsPage />} />
|
||||
<Route path="gitauth" element={<GitAuthSettingsPage />} />
|
||||
<Route
|
||||
path="external-auth"
|
||||
element={<ExternalAuthSettingsPage />}
|
||||
/>
|
||||
<Route
|
||||
path="workspace-proxies"
|
||||
element={<WorkspaceProxyPage />}
|
||||
|
||||
+7
-4
@@ -338,7 +338,7 @@ export const getTemplateVersionExternalAuth = async (
|
||||
versionId: string,
|
||||
): Promise<TypesGen.TemplateVersionExternalAuth[]> => {
|
||||
const response = await axios.get(
|
||||
`/api/v2/templateversions/${versionId}/externalauth`,
|
||||
`/api/v2/templateversions/${versionId}/external-auth`,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
@@ -861,14 +861,14 @@ export const getExperiments = async (): Promise<TypesGen.Experiment[]> => {
|
||||
export const getExternalAuthProvider = async (
|
||||
provider: string,
|
||||
): Promise<TypesGen.ExternalAuth> => {
|
||||
const resp = await axios.get(`/api/v2/externalauth/${provider}`);
|
||||
const resp = await axios.get(`/api/v2/external-auth/${provider}`);
|
||||
return resp.data;
|
||||
};
|
||||
|
||||
export const getExternalAuthDevice = async (
|
||||
provider: string,
|
||||
): Promise<TypesGen.ExternalAuthDevice> => {
|
||||
const resp = await axios.get(`/api/v2/externalauth/${provider}/device`);
|
||||
const resp = await axios.get(`/api/v2/external-auth/${provider}/device`);
|
||||
return resp.data;
|
||||
};
|
||||
|
||||
@@ -876,7 +876,10 @@ export const exchangeExternalAuthDevice = async (
|
||||
provider: string,
|
||||
req: TypesGen.ExternalAuthDeviceExchange,
|
||||
): Promise<void> => {
|
||||
const resp = await axios.post(`/api/v2/externalauth/${provider}/device`, req);
|
||||
const resp = await axios.post(
|
||||
`/api/v2/external-auth/${provider}/device`,
|
||||
req,
|
||||
);
|
||||
return resp.data;
|
||||
};
|
||||
|
||||
|
||||
Generated
+37
-35
@@ -396,7 +396,7 @@ export interface DeploymentValues {
|
||||
readonly disable_session_expiry_refresh?: boolean;
|
||||
readonly disable_password_auth?: boolean;
|
||||
readonly support?: SupportConfig;
|
||||
readonly git_auth?: GitAuthConfig[];
|
||||
readonly external_auth?: ExternalAuthConfig[];
|
||||
readonly config_ssh?: SSHConfig;
|
||||
readonly wgtunnel_host?: string;
|
||||
readonly disable_owner_workspace_exec?: boolean;
|
||||
@@ -426,7 +426,7 @@ export type Experiments = Experiment[];
|
||||
export interface ExternalAuth {
|
||||
readonly authenticated: boolean;
|
||||
readonly device: boolean;
|
||||
readonly type: string;
|
||||
readonly display_name: string;
|
||||
readonly user?: ExternalAuthUser;
|
||||
readonly app_installable: boolean;
|
||||
readonly installations: ExternalAuthAppInstallation[];
|
||||
@@ -440,6 +440,25 @@ export interface ExternalAuthAppInstallation {
|
||||
readonly configure_url: string;
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface ExternalAuthConfig {
|
||||
readonly type: string;
|
||||
readonly client_id: string;
|
||||
readonly id: string;
|
||||
readonly auth_url: string;
|
||||
readonly token_url: string;
|
||||
readonly validate_url: string;
|
||||
readonly app_install_url: string;
|
||||
readonly app_installations_url: string;
|
||||
readonly no_refresh: boolean;
|
||||
readonly scopes: string[];
|
||||
readonly device_flow: boolean;
|
||||
readonly device_code_url: string;
|
||||
readonly regex: string;
|
||||
readonly display_name: string;
|
||||
readonly display_icon: string;
|
||||
}
|
||||
|
||||
// From codersdk/externalauth.go
|
||||
export interface ExternalAuthDevice {
|
||||
readonly device_code: string;
|
||||
@@ -481,23 +500,6 @@ export interface GetUsersResponse {
|
||||
readonly count: number;
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface GitAuthConfig {
|
||||
readonly id: string;
|
||||
readonly type: string;
|
||||
readonly client_id: string;
|
||||
readonly auth_url: string;
|
||||
readonly token_url: string;
|
||||
readonly validate_url: string;
|
||||
readonly app_install_url: string;
|
||||
readonly app_installations_url: string;
|
||||
readonly regex: string;
|
||||
readonly no_refresh: boolean;
|
||||
readonly scopes: string[];
|
||||
readonly device_flow: boolean;
|
||||
readonly device_code_url: string;
|
||||
}
|
||||
|
||||
// From codersdk/gitsshkey.go
|
||||
export interface GitSSHKey {
|
||||
readonly user_id: string;
|
||||
@@ -1013,7 +1015,9 @@ export interface TemplateVersion {
|
||||
// From codersdk/templateversions.go
|
||||
export interface TemplateVersionExternalAuth {
|
||||
readonly id: string;
|
||||
readonly type: ExternalAuthProvider;
|
||||
readonly type: string;
|
||||
readonly display_name: string;
|
||||
readonly display_icon: string;
|
||||
readonly authenticate_url: string;
|
||||
readonly authenticated: boolean;
|
||||
}
|
||||
@@ -1631,6 +1635,19 @@ export const DisplayApps: DisplayApp[] = [
|
||||
"web_terminal",
|
||||
];
|
||||
|
||||
// From codersdk/externalauth.go
|
||||
export type EnhancedExternalAuthProvider =
|
||||
| "azure-devops"
|
||||
| "bitbucket"
|
||||
| "github"
|
||||
| "gitlab";
|
||||
export const EnhancedExternalAuthProviders: EnhancedExternalAuthProvider[] = [
|
||||
"azure-devops",
|
||||
"bitbucket",
|
||||
"github",
|
||||
"gitlab",
|
||||
];
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export type Entitlement = "entitled" | "grace_period" | "not_entitled";
|
||||
export const Entitlements: Entitlement[] = [
|
||||
@@ -1656,21 +1673,6 @@ export const Experiments: Experiment[] = [
|
||||
"workspace_actions",
|
||||
];
|
||||
|
||||
// From codersdk/workspaceagents.go
|
||||
export type ExternalAuthProvider =
|
||||
| "azure-devops"
|
||||
| "bitbucket"
|
||||
| "github"
|
||||
| "gitlab"
|
||||
| "openid-connect";
|
||||
export const ExternalAuthProviders: ExternalAuthProvider[] = [
|
||||
"azure-devops",
|
||||
"bitbucket",
|
||||
"github",
|
||||
"gitlab",
|
||||
"openid-connect",
|
||||
];
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export type FeatureName =
|
||||
| "advanced_template_scheduling"
|
||||
|
||||
@@ -94,12 +94,16 @@ export const ExternalAuth: Story = {
|
||||
type: "github",
|
||||
authenticated: false,
|
||||
authenticate_url: "",
|
||||
display_icon: "/icon/github.svg",
|
||||
display_name: "GitHub",
|
||||
},
|
||||
{
|
||||
id: "gitlab",
|
||||
type: "gitlab",
|
||||
authenticated: true,
|
||||
authenticate_url: "",
|
||||
display_icon: "/icon/gitlab.svg",
|
||||
display_name: "GitLab",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -163,8 +163,8 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
|
||||
{externalAuth && externalAuth.length > 0 && (
|
||||
<FormSection
|
||||
title="Git Authentication"
|
||||
description="This template requires authentication to automatically perform Git operations on create."
|
||||
title="External Authentication"
|
||||
description="This template requires authentication to external services."
|
||||
>
|
||||
<FormFields>
|
||||
{externalAuth.map((auth) => (
|
||||
@@ -174,7 +174,8 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
authenticated={auth.authenticated}
|
||||
externalAuthPollingState={externalAuthPollingState}
|
||||
startPollingExternalAuth={startPollingExternalAuth}
|
||||
type={auth.type}
|
||||
displayName={auth.display_name}
|
||||
displayIcon={auth.display_icon}
|
||||
error={externalAuthErrors[auth.id]}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -11,56 +11,64 @@ type Story = StoryObj<typeof ExternalAuth>;
|
||||
|
||||
export const GithubNotAuthenticated: Story = {
|
||||
args: {
|
||||
type: "github",
|
||||
displayIcon: "/icon/github.svg",
|
||||
displayName: "GitHub",
|
||||
authenticated: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const GithubAuthenticated: Story = {
|
||||
args: {
|
||||
type: "github",
|
||||
displayIcon: "/icon/github.svg",
|
||||
displayName: "GitHub",
|
||||
authenticated: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const GitlabNotAuthenticated: Story = {
|
||||
args: {
|
||||
type: "gitlab",
|
||||
displayIcon: "/icon/gitlab.svg",
|
||||
displayName: "GitLab",
|
||||
authenticated: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const GitlabAuthenticated: Story = {
|
||||
args: {
|
||||
type: "gitlab",
|
||||
displayIcon: "/icon/gitlab.svg",
|
||||
displayName: "GitLab",
|
||||
authenticated: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const AzureDevOpsNotAuthenticated: Story = {
|
||||
args: {
|
||||
type: "azure-devops",
|
||||
displayIcon: "/icon/azure-devops.svg",
|
||||
displayName: "Azure DevOps",
|
||||
authenticated: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const AzureDevOpsAuthenticated: Story = {
|
||||
args: {
|
||||
type: "azure-devops",
|
||||
displayIcon: "/icon/azure-devops.svg",
|
||||
displayName: "Azure DevOps",
|
||||
authenticated: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const BitbucketNotAuthenticated: Story = {
|
||||
args: {
|
||||
type: "bitbucket",
|
||||
displayIcon: "/icon/bitbucket.svg",
|
||||
displayName: "Bitbucket",
|
||||
authenticated: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const BitbucketAuthenticated: Story = {
|
||||
args: {
|
||||
type: "bitbucket",
|
||||
displayIcon: "/icon/bitbucket.svg",
|
||||
displayName: "Bitbucket",
|
||||
authenticated: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import ReplayIcon from "@mui/icons-material/Replay";
|
||||
import Button from "@mui/material/Button";
|
||||
import FormHelperText from "@mui/material/FormHelperText";
|
||||
import { SvgIconProps } from "@mui/material/SvgIcon";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import GitHub from "@mui/icons-material/GitHub";
|
||||
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 { FC } from "react";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { type ExternalAuthPollingState } from "./CreateWorkspacePage";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import ReplayIcon from "@mui/icons-material/Replay";
|
||||
import { LoadingButton } from "components/LoadingButton/LoadingButton";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { FC } from "react";
|
||||
import { type ExternalAuthPollingState } from "./CreateWorkspacePage";
|
||||
|
||||
export interface ExternalAuthProps {
|
||||
type: TypesGen.ExternalAuthProvider;
|
||||
displayName: string;
|
||||
displayIcon: string;
|
||||
authenticated: boolean;
|
||||
authenticateURL: string;
|
||||
externalAuthPollingState: ExternalAuthPollingState;
|
||||
@@ -25,7 +20,8 @@ export interface ExternalAuthProps {
|
||||
|
||||
export const ExternalAuth: FC<ExternalAuthProps> = (props) => {
|
||||
const {
|
||||
type,
|
||||
displayName,
|
||||
displayIcon,
|
||||
authenticated,
|
||||
authenticateURL,
|
||||
externalAuthPollingState,
|
||||
@@ -37,32 +33,9 @@ export const ExternalAuth: FC<ExternalAuthProps> = (props) => {
|
||||
error: typeof error !== "undefined",
|
||||
});
|
||||
|
||||
let prettyName: string;
|
||||
let Icon: (props: SvgIconProps) => JSX.Element;
|
||||
switch (type) {
|
||||
case "azure-devops":
|
||||
prettyName = "Azure DevOps";
|
||||
Icon = AzureDevOpsIcon;
|
||||
break;
|
||||
case "bitbucket":
|
||||
prettyName = "Bitbucket";
|
||||
Icon = BitbucketIcon;
|
||||
break;
|
||||
case "github":
|
||||
prettyName = "GitHub";
|
||||
Icon = GitHub as (props: SvgIconProps) => JSX.Element;
|
||||
break;
|
||||
case "gitlab":
|
||||
prettyName = "GitLab";
|
||||
Icon = GitlabIcon;
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid git provider: " + type);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={authenticated && `${prettyName} has already been connected.`}
|
||||
title={authenticated && `${displayName} has already been connected.`}
|
||||
>
|
||||
<Stack alignItems="center" spacing={1}>
|
||||
<LoadingButton
|
||||
@@ -70,7 +43,14 @@ export const ExternalAuth: FC<ExternalAuthProps> = (props) => {
|
||||
href={authenticateURL}
|
||||
variant="contained"
|
||||
size="large"
|
||||
startIcon={<Icon />}
|
||||
startIcon={
|
||||
<img
|
||||
src={displayIcon}
|
||||
alt={`${displayName} Icon`}
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
}
|
||||
disabled={authenticated}
|
||||
className={styles.button}
|
||||
color={error ? "error" : undefined}
|
||||
@@ -86,8 +66,8 @@ export const ExternalAuth: FC<ExternalAuthProps> = (props) => {
|
||||
}}
|
||||
>
|
||||
{authenticated
|
||||
? `Authenticated with ${prettyName}`
|
||||
: `Login with ${prettyName}`}
|
||||
? `Authenticated with ${displayName}`
|
||||
: `Login with ${displayName}`}
|
||||
</LoadingButton>
|
||||
|
||||
{externalAuthPollingState === "abandoned" && (
|
||||
|
||||
+5
-5
@@ -2,20 +2,20 @@ import { useDeploySettings } from "components/DeploySettingsLayout/DeploySetting
|
||||
import { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { GitAuthSettingsPageView } from "./GitAuthSettingsPageView";
|
||||
import { ExternalAuthSettingsPageView } from "./ExternalAuthSettingsPageView";
|
||||
|
||||
const GitAuthSettingsPage: FC = () => {
|
||||
const ExternalAuthSettingsPage: FC = () => {
|
||||
const { deploymentValues: deploymentValues } = useDeploySettings();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Git Authentication Settings")}</title>
|
||||
<title>{pageTitle("External Authentication Settings")}</title>
|
||||
</Helmet>
|
||||
|
||||
<GitAuthSettingsPageView config={deploymentValues.config} />
|
||||
<ExternalAuthSettingsPageView config={deploymentValues.config} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GitAuthSettingsPage;
|
||||
export default ExternalAuthSettingsPage;
|
||||
+8
-6
@@ -1,12 +1,12 @@
|
||||
import { GitAuthSettingsPageView } from "./GitAuthSettingsPageView";
|
||||
import { ExternalAuthSettingsPageView } from "./ExternalAuthSettingsPageView";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta: Meta<typeof GitAuthSettingsPageView> = {
|
||||
title: "pages/GitAuthSettingsPageView",
|
||||
component: GitAuthSettingsPageView,
|
||||
const meta: Meta<typeof ExternalAuthSettingsPageView> = {
|
||||
title: "pages/ExternalAuthSettingsPageView",
|
||||
component: ExternalAuthSettingsPageView,
|
||||
args: {
|
||||
config: {
|
||||
git_auth: [
|
||||
external_auth: [
|
||||
{
|
||||
id: "0000-1111",
|
||||
type: "GitHub",
|
||||
@@ -21,6 +21,8 @@ const meta: Meta<typeof GitAuthSettingsPageView> = {
|
||||
scopes: [],
|
||||
device_flow: true,
|
||||
device_code_url: "",
|
||||
display_icon: "",
|
||||
display_name: "GitHub",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -28,6 +30,6 @@ const meta: Meta<typeof GitAuthSettingsPageView> = {
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof GitAuthSettingsPageView>;
|
||||
type Story = StoryObj<typeof ExternalAuthSettingsPageView>;
|
||||
|
||||
export const Page: Story = {};
|
||||
+13
-11
@@ -5,27 +5,27 @@ import TableCell from "@mui/material/TableCell";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import { DeploymentValues, GitAuthConfig } from "api/typesGenerated";
|
||||
import { DeploymentValues, ExternalAuthConfig } from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { EnterpriseBadge } from "components/DeploySettingsLayout/Badges";
|
||||
import { Header } from "components/DeploySettingsLayout/Header";
|
||||
import { docs } from "utils/docs";
|
||||
|
||||
export type GitAuthSettingsPageViewProps = {
|
||||
export type ExternalAuthSettingsPageViewProps = {
|
||||
config: DeploymentValues;
|
||||
};
|
||||
|
||||
export const GitAuthSettingsPageView = ({
|
||||
export const ExternalAuthSettingsPageView = ({
|
||||
config,
|
||||
}: GitAuthSettingsPageViewProps): JSX.Element => {
|
||||
}: ExternalAuthSettingsPageViewProps): JSX.Element => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
title="Git Authentication"
|
||||
description="Coder integrates with GitHub, GitLab, BitBucket, and Azure Repos to authenticate developers with your Git provider."
|
||||
docsHref={docs("/admin/git-providers")}
|
||||
title="External Authentication"
|
||||
description="Coder integrates with GitHub, GitLab, BitBucket, Azure Repos, and OpenID Connect to authenticate developers with external services."
|
||||
docsHref={docs("/admin/external-auth")}
|
||||
/>
|
||||
|
||||
<video
|
||||
@@ -33,7 +33,7 @@ export const GitAuthSettingsPageView = ({
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
src="/gitauth.mp4"
|
||||
src="/external-auth.mp4"
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
borderRadius: 4,
|
||||
@@ -42,7 +42,8 @@ export const GitAuthSettingsPageView = ({
|
||||
|
||||
<div className={styles.description}>
|
||||
<Alert severity="info" actions={<EnterpriseBadge key="enterprise" />}>
|
||||
Integrating with multiple Git providers is an Enterprise feature.
|
||||
Integrating with multiple External authentication providers is an
|
||||
Enterprise feature.
|
||||
</Alert>
|
||||
</div>
|
||||
|
||||
@@ -56,7 +57,8 @@ export const GitAuthSettingsPageView = ({
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{((config.git_auth === null || config.git_auth?.length === 0) && (
|
||||
{((config.external_auth === null ||
|
||||
config.external_auth?.length === 0) && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<div className={styles.empty}>
|
||||
@@ -65,7 +67,7 @@ export const GitAuthSettingsPageView = ({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)) ||
|
||||
config.git_auth?.map((git: GitAuthConfig) => {
|
||||
config.external_auth?.map((git: ExternalAuthConfig) => {
|
||||
const name = git.id || git.type;
|
||||
return (
|
||||
<TableRow key={name}>
|
||||
@@ -72,8 +72,7 @@ const ExternalAuthPage: FC = () => {
|
||||
!getExternalAuthProviderQuery.data.authenticated &&
|
||||
!getExternalAuthProviderQuery.data.device
|
||||
) {
|
||||
window.location.href = `/externalauth/${provider}/callback`;
|
||||
|
||||
window.location.href = `/external-auth/${provider}/callback`;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@ const Template: StoryFn<ExternalAuthPageViewProps> = (args) => (
|
||||
export const WebAuthenticated = Template.bind({});
|
||||
WebAuthenticated.args = {
|
||||
externalAuth: {
|
||||
type: "BitBucket",
|
||||
authenticated: true,
|
||||
device: false,
|
||||
installations: [],
|
||||
app_install_url: "",
|
||||
app_installable: false,
|
||||
display_name: "BitBucket",
|
||||
user: {
|
||||
avatar_url: "",
|
||||
login: "kylecarbs",
|
||||
@@ -33,7 +33,7 @@ WebAuthenticated.args = {
|
||||
export const DeviceUnauthenticated = Template.bind({});
|
||||
DeviceUnauthenticated.args = {
|
||||
externalAuth: {
|
||||
type: "GitHub",
|
||||
display_name: "GitHub",
|
||||
authenticated: false,
|
||||
device: true,
|
||||
installations: [],
|
||||
@@ -52,7 +52,7 @@ DeviceUnauthenticated.args = {
|
||||
export const DeviceUnauthenticatedError = Template.bind({});
|
||||
DeviceUnauthenticatedError.args = {
|
||||
externalAuth: {
|
||||
type: "GitHub",
|
||||
display_name: "GitHub",
|
||||
authenticated: false,
|
||||
device: true,
|
||||
installations: [],
|
||||
@@ -76,7 +76,7 @@ export const DeviceAuthenticatedNotInstalled = Template.bind({});
|
||||
DeviceAuthenticatedNotInstalled.args = {
|
||||
viewExternalAuthConfig: true,
|
||||
externalAuth: {
|
||||
type: "GitHub",
|
||||
display_name: "GitHub",
|
||||
authenticated: true,
|
||||
device: true,
|
||||
installations: [],
|
||||
@@ -94,7 +94,7 @@ DeviceAuthenticatedNotInstalled.args = {
|
||||
export const DeviceAuthenticatedInstalled = Template.bind({});
|
||||
DeviceAuthenticatedInstalled.args = {
|
||||
externalAuth: {
|
||||
type: "GitHub",
|
||||
display_name: "GitHub",
|
||||
authenticated: true,
|
||||
device: true,
|
||||
installations: [
|
||||
|
||||
@@ -35,7 +35,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
|
||||
if (!externalAuth.authenticated) {
|
||||
return (
|
||||
<SignInLayout>
|
||||
<Welcome message={`Authenticate with ${externalAuth.type}`} />
|
||||
<Welcome message={`Authenticate with ${externalAuth.display_name}`} />
|
||||
|
||||
{externalAuth.device && (
|
||||
<GitDeviceAuth
|
||||
@@ -50,9 +50,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
|
||||
const hasInstallations = externalAuth.installations.length > 0;
|
||||
|
||||
// We only want to wrap this with a link if an install URL is available!
|
||||
let installTheApp: JSX.Element = (
|
||||
<>{`install the ${externalAuth.type} App`}</>
|
||||
);
|
||||
let installTheApp: React.ReactNode = `install the ${externalAuth.display_name} App`;
|
||||
if (externalAuth.app_install_url) {
|
||||
installTheApp = (
|
||||
<Link
|
||||
@@ -67,12 +65,14 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
|
||||
|
||||
return (
|
||||
<SignInLayout>
|
||||
<Welcome message={`You've authenticated with ${externalAuth.type}!`} />
|
||||
<Welcome
|
||||
message={`You've authenticated with ${externalAuth.display_name}!`}
|
||||
/>
|
||||
<p className={styles.text}>
|
||||
Hey @{externalAuth.user?.login}! 👋{" "}
|
||||
{externalAuth.user?.login && `Hey @${externalAuth.user?.login}! 👋 `}
|
||||
{(!externalAuth.app_installable ||
|
||||
externalAuth.installations.length > 0) &&
|
||||
"You are now authenticated with Git. Feel free to close this window!"}
|
||||
"You are now authenticated. Feel free to close this window!"}
|
||||
</p>
|
||||
|
||||
{externalAuth.installations.length > 0 && (
|
||||
@@ -126,7 +126,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
|
||||
{externalAuth.installations.length > 0
|
||||
? "Configure"
|
||||
: "Install"}{" "}
|
||||
the {externalAuth.type} App
|
||||
the {externalAuth.display_name} App
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
|
||||
@@ -2188,16 +2188,20 @@ export const MockTemplateVersionExternalAuthGithub: TypesGen.TemplateVersionExte
|
||||
{
|
||||
id: "github",
|
||||
type: "github",
|
||||
authenticate_url: "https://example.com/externalauth/github",
|
||||
authenticate_url: "https://example.com/external-auth/github",
|
||||
authenticated: false,
|
||||
display_icon: "/icon/github.svg",
|
||||
display_name: "GitHub",
|
||||
};
|
||||
|
||||
export const MockTemplateVersionExternalAuthGithubAuthenticated: TypesGen.TemplateVersionExternalAuth =
|
||||
{
|
||||
id: "github",
|
||||
type: "github",
|
||||
authenticate_url: "https://example.com/externalauth/github",
|
||||
authenticate_url: "https://example.com/external-auth/github",
|
||||
authenticated: true,
|
||||
display_icon: "/icon/github.svg",
|
||||
display_name: "GitHub",
|
||||
};
|
||||
|
||||
export const MockDeploymentStats: TypesGen.DeploymentStats = {
|
||||
|
||||
@@ -112,7 +112,7 @@ export const handlers = [
|
||||
},
|
||||
),
|
||||
rest.get(
|
||||
"/api/v2/templateversions/:templateVersionId/externalauth",
|
||||
"/api/v2/templateversions/:templateVersionId/external-auth",
|
||||
async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json([]));
|
||||
},
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import SvgIcon, { SvgIconProps } from "@mui/material/SvgIcon";
|
||||
|
||||
export const AzureDevOpsIcon = (props: SvgIconProps): JSX.Element => (
|
||||
<SvgIcon {...props} viewBox="0 0 111 110">
|
||||
<svg viewBox="0 0 111 110" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_1916_993)">
|
||||
<path
|
||||
d="M83.0365 94.769L0 82.288L83.0365 110L111 98.365V11.2115L83.0365 0V94.769Z"
|
||||
@@ -21,5 +18,4 @@ export const AzureDevOpsIcon = (props: SvgIconProps): JSX.Element => (
|
||||
<rect width="111" height="110" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</SvgIcon>
|
||||
);
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 819 B After Width: | Height: | Size: 695 B |
@@ -1,8 +1,5 @@
|
||||
import SvgIcon, { SvgIconProps } from "@mui/material/SvgIcon";
|
||||
|
||||
export const BitbucketIcon = (props: SvgIconProps): JSX.Element => (
|
||||
<SvgIcon {...props} viewBox="0 0 501 450">
|
||||
<g clipPath="url(#clip0_1917_1001)">
|
||||
<svg viewBox="0 0 501 450" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1917_1001)">
|
||||
<path
|
||||
d="M17.0206 0.0721333C14.6826 0.0419786 12.3663 0.523969 10.2344 1.48427C8.10245 2.44457 6.20658 3.8599 4.67987 5.63088C3.15316 7.40186 2.03262 9.48557 1.39691 11.7357C0.761211 13.9858 0.625758 16.3479 1.00007 18.6559L69.0071 431.504C69.8544 436.556 72.4548 441.148 76.3515 444.474C80.2481 447.799 85.1919 449.645 90.3144 449.688H416.572C420.412 449.737 424.142 448.405 427.082 445.935C430.023 443.465 431.978 440.021 432.592 436.23L500.6 18.736C500.974 16.428 500.838 14.0659 500.203 11.8158C499.567 9.56568 498.446 7.48197 496.92 5.71098C495.393 3.94 493.497 2.52467 491.365 1.56437C489.233 0.604073 486.917 0.122079 484.579 0.152234L17.0206 0.0721333ZM303.387 298.454H199.254L171.058 151.146H328.619L303.387 298.454Z"
|
||||
fill="#2684FF"
|
||||
@@ -21,12 +18,11 @@ export const BitbucketIcon = (props: SvgIconProps): JSX.Element => (
|
||||
y2="386.327"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.18" stopColor="#0052CC" />
|
||||
<stop offset="1" stopColor="#2684FF" />
|
||||
<stop offset="0.18" stop-color="#0052CC" />
|
||||
<stop offset="1" stop-color="#2684FF" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_1917_1001">
|
||||
<rect width="501" height="450" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</SvgIcon>
|
||||
);
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 961 B |
@@ -0,0 +1,25 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 194 186">
|
||||
<g clipPath="url(#clip0_1915_987)">
|
||||
<path
|
||||
d="M189.83 73.7299L189.56 73.0399L163.42 4.81995C162.888 3.48288 161.946 2.34863 160.73 1.57996C159.513 0.82434 158.093 0.460411 156.662 0.537307C155.232 0.614203 153.859 1.12822 152.73 2.00996C151.613 2.91701 150.803 4.14609 150.41 5.52995L132.76 59.53H61.2899L43.6399 5.52995C43.2571 4.13855 42.4453 2.90331 41.3199 1.99995C40.1907 1.11822 38.8181 0.6042 37.3875 0.527305C35.9569 0.450409 34.5371 0.814338 33.3199 1.56995C32.1062 2.34173 31.1653 3.47499 30.6299 4.80995L4.43991 73L4.17991 73.69C0.416933 83.522 -0.0475445 94.3109 2.8565 104.43C5.76055 114.549 11.8757 123.45 20.2799 129.79L20.3699 129.86L20.6099 130.03L60.4299 159.85L80.1299 174.76L92.1299 183.82C93.5336 184.886 95.2475 185.463 97.0099 185.463C98.7723 185.463 100.486 184.886 101.89 183.82L113.89 174.76L133.59 159.85L173.65 129.85L173.75 129.77C182.135 123.429 188.236 114.537 191.136 104.432C194.035 94.3262 193.577 83.5527 189.83 73.7299Z"
|
||||
fill="#E24329"
|
||||
/>
|
||||
<path
|
||||
d="M189.83 73.7299L189.56 73.0399C176.823 75.6543 164.82 81.0495 154.41 88.8399L97 132.25C116.55 147.04 133.57 159.89 133.57 159.89L173.63 129.89L173.73 129.81C182.127 123.469 188.238 114.572 191.141 104.457C194.045 94.3434 193.585 83.5598 189.83 73.7299Z"
|
||||
fill="#FC6D26"
|
||||
/>
|
||||
<path
|
||||
d="M60.4299 159.89L80.1299 174.8L92.1299 183.86C93.5336 184.926 95.2475 185.503 97.0099 185.503C98.7723 185.503 100.486 184.926 101.89 183.86L113.89 174.8L133.59 159.89C133.59 159.89 116.55 147 96.9999 132.25C77.4499 147 60.4299 159.89 60.4299 159.89Z"
|
||||
fill="#FCA326"
|
||||
/>
|
||||
<path
|
||||
d="M39.5799 88.84C29.1778 81.0335 17.1779 75.6243 4.43991 73L4.17991 73.69C0.416933 83.5221 -0.0475445 94.311 2.8565 104.43C5.76055 114.549 11.8757 123.45 20.2799 129.79L20.3699 129.86L20.6099 130.03L60.4299 159.85C60.4299 159.85 77.4299 147 96.9999 132.21L39.5799 88.84Z"
|
||||
fill="#FC6D26"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1915_987">
|
||||
<rect width="194" height="186" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
Reference in New Issue
Block a user