Skip to content

Commit

Permalink
Modify GitHub sign in functionality to work properly
Browse files Browse the repository at this point in the history
* Make Sign In button work as expected.
* Extract a common login wrapper for each plugin
  • Loading branch information
Xantier committed Jan 16, 2025
1 parent 647dbf8 commit 7cd4044
Show file tree
Hide file tree
Showing 25 changed files with 569 additions and 226 deletions.
7 changes: 7 additions & 0 deletions .changeset/good-turtles-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@roadiehq/backstage-plugin-github-pull-requests': patch
'@roadiehq/backstage-plugin-security-insights': patch
'@roadiehq/backstage-plugin-github-insights': patch
---

Modify login functionality to allow logging in using the Sign In button.
46 changes: 45 additions & 1 deletion packages/app/src/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,66 @@ import {
ScmIntegrationsApi,
scmIntegrationsApiRef,
ScmAuth,
scmAuthApiRef,
} from '@backstage/integration-react';
import {
AnyApiFactory,
ApiRef,
configApiRef,
createApiFactory,
createApiRef,
discoveryApiRef,
fetchApiRef,
githubAuthApiRef,
OAuthApi,
oauthRequestApiRef,
ProfileInfoApi,
SessionApi,
} from '@backstage/core-plugin-api';
import fetch from 'cross-fetch';
import { GithubAuth } from '@backstage/core-app-api';

const ghesAuthApiRef: ApiRef<OAuthApi & ProfileInfoApi & SessionApi> =
createApiRef({
id: 'internal.auth.ghe',
});
export const apis: AnyApiFactory[] = [
createApiFactory({
api: scmIntegrationsApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
}),
ScmAuth.createDefaultApiFactory(),
createApiFactory({
api: ghesAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
GithubAuth.create({
configApi,
discoveryApi,
oauthRequestApi,
provider: { id: 'ghes', title: 'GitHub Enterprise', icon: () => null },
defaultScopes: ['read:user'],
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: scmAuthApiRef,
deps: {
gheAuthApi: ghesAuthApiRef,
githubAuthApi: githubAuthApiRef,
},
factory: ({ githubAuthApi, gheAuthApi }) =>
ScmAuth.merge(
ScmAuth.forGithub(githubAuthApi),
ScmAuth.forGithub(gheAuthApi, {
host: 'ghes.enginehouse.io',
}),
),
}),
createApiFactory({
api: fetchApiRef,
deps: {},
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/plugins/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createRouter } from '@backstage/plugin-auth-backend';
import { createRouter, providers } from '@backstage/plugin-auth-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';

Expand All @@ -30,5 +30,8 @@ export default async function createPlugin({
database,
discovery,
tokenManager,
providerFactories: {
ghes: providers.github.create(),
},
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2025 Larder Software Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
scmAuthApiRef,
ScmAuthTokenResponse,
} from '@backstage/integration-react';
import { useApi } from '@backstage/core-plugin-api';
import React, { useCallback, useState } from 'react';
import { Button, Grid, Tooltip, Typography } from '@material-ui/core';
import { InfoCard } from '@backstage/core-components';
import { useGithubLoggedIn } from '../hooks/useGithubLoggedIn';

export const GithubNotAuthorized = ({
hostname = 'github.com',
validateCredentials,
}: {
hostname?: string;
validateCredentials: (credentials: ScmAuthTokenResponse) => void;
}) => {
const scmAuth = useApi(scmAuthApiRef);

const signIn = useCallback(async () => {
const credentials = await scmAuth.getCredentials({
url: `https://${hostname}/`,
additionalScope: {
customScopes: {
github: ['repo'],
},
},
});
validateCredentials(credentials);
}, [scmAuth, hostname, validateCredentials]);

return (
<Grid container>
<Grid item xs={8}>
<Typography>
You are not logged into GitHub. You need to be signed in to see the
content of this card.
</Typography>
</Grid>
<Grid item xs={4} container justifyContent="flex-end">
<Tooltip placement="top" arrow title="Sign in to Github">
<Button variant="outlined" color="primary" onClick={signIn}>
Sign in
</Button>
</Tooltip>
</Grid>
</Grid>
);
};

export const GitHubAuthorizationWrapper = ({
children,
title,
hostname,
}: {
children: React.ReactNode;
title: string;
hostname?: string;
}) => {
const isLoggedIn = useGithubLoggedIn();
const [credentialsValidated, setCredentialsValidated] = useState(false);
const validateCredentials = useCallback(
(credentials: ScmAuthTokenResponse) => {
if (isLoggedIn) {
return;
}

if (credentials.token) {
setCredentialsValidated(true);
}
},
[isLoggedIn],
);
return isLoggedIn || credentialsValidated ? (
children
) : (
<InfoCard title={title}>
<GithubNotAuthorized
validateCredentials={validateCredentials}
hostname={hostname}
/>
</InfoCard>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Larder Software Limited
* Copyright 2025 Larder Software Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,8 +36,10 @@ import {
import WarningIcon from '@material-ui/icons/ErrorOutline';
import { styles as useStyles } from '../../utils/styles';
import { useEntity } from '@backstage/plugin-catalog-react';
import { getHostname } from '../../utils/githubUtils';
import { GitHubAuthorizationWrapper } from '../../GitHubAuthorizationWrapper';

const ComplianceCard = () => {
const ComplianceCardContent = () => {
const { entity } = useEntity();
const { branches, loading, error } = useProtectedBranches(entity);
const {
Expand All @@ -59,7 +61,7 @@ const ComplianceCard = () => {
} else if (error || licenseError) {
return (
<Alert severity="error">
Error occured while fetching data for the compliance card:{' '}
Error occurred while fetching data for the compliance card:{' '}
{error?.message}
</Alert>
);
Expand Down Expand Up @@ -109,4 +111,15 @@ const ComplianceCard = () => {
);
};

const ComplianceCard = () => {
const { entity } = useEntity();
const hostname = getHostname(entity);

return (
<GitHubAuthorizationWrapper title="Compliance" hostname={hostname}>
<ComplianceCardContent />
</GitHubAuthorizationWrapper>
);
};

export default ComplianceCard;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Larder Software Limited
* Copyright 2025 Larder Software Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,22 +19,20 @@ import { makeStyles } from '@material-ui/core/styles';
import Alert from '@material-ui/lab/Alert';
import {
InfoCard,
Progress,
MissingAnnotationEmptyState,
Progress,
} from '@backstage/core-components';
import ContributorsList from './components/ContributorsList';
import { useRequest } from '../../../hooks/useRequest';
import { useEntityGithubScmIntegration } from '../../../hooks/useEntityGithubScmIntegration';
import { useProjectEntity } from '../../../hooks/useProjectEntity';
import {
isGithubInsightsAvailable,
GITHUB_INSIGHTS_ANNOTATION,
isGithubInsightsAvailable,
} from '../../utils/isGithubInsightsAvailable';
import { useEntity } from '@backstage/plugin-catalog-react';
import {
GithubNotAuthorized,
useGithubLoggedIn,
} from '../../../hooks/useGithubLoggedIn';
import { getHostname } from '../../utils/githubUtils';
import { GitHubAuthorizationWrapper } from '../../GitHubAuthorizationWrapper';

const useStyles = makeStyles(theme => ({
infoCard: {
Expand Down Expand Up @@ -89,15 +87,13 @@ const ContributorsCardContent = () => {
};

const ContributorsCard = () => {
const classes = useStyles();
const isLoggedIn = useGithubLoggedIn();
const { entity } = useEntity();
const hostname = getHostname(entity);

return isLoggedIn ? (
<ContributorsCardContent />
) : (
<InfoCard title="Contributors" className={classes.infoCard}>
<GithubNotAuthorized />
</InfoCard>
return (
<GitHubAuthorizationWrapper title="Contributors" hostname={hostname}>
<ContributorsCardContent />
</GitHubAuthorizationWrapper>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Larder Software Limited
* Copyright 2025 Larder Software Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,14 +31,16 @@ import {
} from '../../utils/isGithubInsightsAvailable';
import { useEntity } from '@backstage/plugin-catalog-react';
import { styles as useStyles } from '../../utils/styles';
import { getHostname } from '../../utils/githubUtils';
import { GitHubAuthorizationWrapper } from '../../GitHubAuthorizationWrapper';

type Environment = {
id: number;
html_url: string;
name: string;
};

const EnvironmentsCard = () => {
const EnvironmentsCardContent = () => {
const classes = useStyles();
const { entity } = useEntity();

Expand Down Expand Up @@ -96,4 +98,15 @@ const EnvironmentsCard = () => {
);
};

const EnvironmentsCard = () => {
const { entity } = useEntity();
const hostname = getHostname(entity);

return (
<GitHubAuthorizationWrapper title="Environments" hostname={hostname}>
<EnvironmentsCardContent />
</GitHubAuthorizationWrapper>
);
};

export default EnvironmentsCard;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Larder Software Limited
* Copyright 2025 Larder Software Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,25 +16,22 @@

import React from 'react';
import { Chip, makeStyles, Tooltip } from '@material-ui/core';
// eslint-disable-next-line
import Alert from '@material-ui/lab/Alert';
import {
InfoCard,
Progress,
MissingAnnotationEmptyState,
Progress,
} from '@backstage/core-components';
import { useRequest } from '../../../hooks/useRequest';
import { colors } from './colors';
import { useProjectEntity } from '../../../hooks/useProjectEntity';
import {
GithubNotAuthorized,
useGithubLoggedIn,
} from '../../../hooks/useGithubLoggedIn';
import {
isGithubInsightsAvailable,
GITHUB_INSIGHTS_ANNOTATION,
isGithubInsightsAvailable,
} from '../../utils/isGithubInsightsAvailable';
import { useEntity } from '@backstage/plugin-catalog-react';
import { getHostname } from '../../utils/githubUtils';
import { GitHubAuthorizationWrapper } from '../../GitHubAuthorizationWrapper';

const useStyles = makeStyles(theme => ({
infoCard: {
Expand Down Expand Up @@ -159,15 +156,13 @@ const LanguagesCardContent = () => {
};

const LanguagesCard = () => {
const classes = useStyles();
const isLoggedIn = useGithubLoggedIn();
const { entity } = useEntity();
const hostname = getHostname(entity);

return isLoggedIn ? (
<LanguagesCardContent />
) : (
<InfoCard title="Languages" className={classes.infoCard}>
<GithubNotAuthorized />
</InfoCard>
return (
<GitHubAuthorizationWrapper title="Languages" hostname={hostname}>
<LanguagesCardContent />
</GitHubAuthorizationWrapper>
);
};

Expand Down
Loading

0 comments on commit 7cd4044

Please sign in to comment.