diff --git a/packages/cli/src/commands/admin/auth/files.ts b/packages/cli/src/commands/admin/auth/files.ts index ad9b53a894..166538335f 100644 --- a/packages/cli/src/commands/admin/auth/files.ts +++ b/packages/cli/src/commands/admin/auth/files.ts @@ -18,19 +18,33 @@ import { findPaths } from '@backstage/cli-common'; import * as path from 'path'; /* eslint-disable-next-line no-restricted-syntax */ -const { targetRoot, resolveTargetRoot } = findPaths(__dirname); +const { targetRoot, ownDir, resolveTargetRoot } = findPaths(__dirname); export const APP_CONFIG_FILE = path.join(targetRoot, 'app-config.local.yaml'); export const ENV_CONFIG_FILE = path.join(targetRoot, '.env.local'); export const USER_ENTITY_FILE = path.join(targetRoot, 'user-info.yaml'); -export const APP_TSX_FILE = path.join( +const APP_TSX_FILE = path.join( resolveTargetRoot('packages/app'), 'src', 'App.tsx', ); -export const AUTH_BACKEND_PLUGIN_FILE = path.join( +const AUTH_BACKEND_PLUGIN_FILE = path.join( resolveTargetRoot('packages/backend'), 'src', 'plugins', 'auth.ts', ); + +export const PATCH_FOLDER = path.join( + ownDir, + 'src', + 'commands', + 'admin', + 'auth', + 'patches', +); + +export const patchMap: Record = { + 'App.tsx': APP_TSX_FILE, + 'auth.ts': AUTH_BACKEND_PLUGIN_FILE, +}; diff --git a/packages/cli/src/commands/admin/auth/github/diffs.ts b/packages/cli/src/commands/admin/auth/github/diffs.ts deleted file mode 100644 index 43b6013d4e..0000000000 --- a/packages/cli/src/commands/admin/auth/github/diffs.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * 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. - */ - -export const addSignInPageDiff = - '@@ -32,11 +32,27 @@\n' + - " import { AppRouter, FlatRoutes } from '@backstage/core-app-api';\n" + - " import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';\n" + - " import { RequirePermission } from '@backstage/plugin-permission-react';\n" + - " import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';\n" + - "+import { githubAuthApiRef } from '@backstage/core-plugin-api';\n" + - "+import { SignInPage } from '@backstage/core-components';\n" + - ' \n' + - ' const app = createApp({\n' + - ' apis,\n' + - '+ components: {\n' + - '+ SignInPage: props => (\n' + - '+ \n' + - '+ ),\n' + - '+ },\n' + - ' bindRoutes({ bind }) {\n' + - ' bind(catalogPlugin.externalRoutes, {\n' + - ' createComponent: scaffolderPlugin.routes.root,\n' + - ' viewTechDoc: techdocsPlugin.routes.docRoot,\n'; - -export const replaceSignInResolverDiff = - '@@ -36,18 +36,18 @@\n' + - ' //\n' + - ' // https://backstage.io/docs/auth/identity-resolver\n' + - ' github: providers.github.create({\n' + - ' signIn: {\n' + - '- resolver(_, ctx) {\n' + - "- const userRef = 'user:default/guest'; // Must be a full entity reference\n" + - '- return ctx.issueToken({\n' + - '- claims: {\n' + - "- sub: userRef, // The user's own identity\n" + - '- ent: [userRef], // A list of identities that the user claims ownership through\n' + - '- },\n' + - '- });\n' + - '- },\n' + - '- // resolver: providers.github.resolvers.usernameMatchingUserEntityName(),\n' + - '+ // resolver(_, ctx) {\n' + - "+ // const userRef = 'user:default/guest'; // Must be a full entity reference\n" + - '+ // return ctx.issueToken({\n' + - '+ // claims: {\n' + - "+ // sub: userRef, // The user's own identity\n" + - '+ // ent: [userRef], // A list of identities that the user claims ownership through\n' + - '+ // },\n' + - '+ // });\n' + - '+ // },\n' + - '+ resolver: providers.github.resolvers.usernameMatchingUserEntityName(),\n' + - ' },\n' + - ' }),\n' + - ' },\n' + - ' });\n'; diff --git a/packages/cli/src/commands/admin/auth/github/oauth.ts b/packages/cli/src/commands/admin/auth/github/oauth.ts index 64abf520cf..fe05b6431a 100644 --- a/packages/cli/src/commands/admin/auth/github/oauth.ts +++ b/packages/cli/src/commands/admin/auth/github/oauth.ts @@ -18,17 +18,18 @@ import { OAuthApp } from '@octokit/oauth-app'; import chalk from 'chalk'; import inquirer from 'inquirer'; import fetch from 'node-fetch'; +import * as fs from 'fs-extra'; +import * as path from 'path'; import { Task } from '../../../../lib/tasks'; import { addUserEntity, updateConfigFile, updateEnvFile } from '../config'; import { APP_CONFIG_FILE, - APP_TSX_FILE, - AUTH_BACKEND_PLUGIN_FILE, ENV_CONFIG_FILE, + patchMap, + PATCH_FOLDER, USER_ENTITY_FILE, } from '../files'; import { patch } from '../patch'; -import { addSignInPageDiff, replaceSignInResolverDiff } from './diffs'; const validateCredentials = async (clientId: string, clientSecret: string) => { try { @@ -50,7 +51,7 @@ const validateCredentials = async (clientId: string, clientSecret: string) => { error.response.status !== 200 && error.response.data.error !== 'bad_verification_code' ) { - throw new Error(`Validating Github Credentials failed.`); + throw new Error(`Validating GitHub Credentials failed.`); } } }; @@ -109,13 +110,14 @@ export const oauth = async (useEnvForSecrets: boolean) => { You can find the full documentation page here: ${chalk.blue( 'https://backstage.io/docs/auth/github/provider', - )}`); + )} + `); const answers = await inquirer.prompt([ { type: 'input', name: 'username', - message: 'What is your Github username?', + message: 'What is your GitHub username?', validate: async (input: string) => { const response = await fetch(`https://api.github.com/users/${input}`); if (!response.ok) { @@ -139,30 +141,53 @@ export const oauth = async (useEnvForSecrets: boolean) => { { type: 'confirm', name: 'hasEnterprise', - message: 'Are you using Github Enterprise?', + message: 'Are you using GitHub Enterprise?', }, { type: 'input', name: 'enterpriseInstanceUrl', - message: 'What is your URL for Github Enterprise?', + message: 'What is your URL for GitHub Enterprise?', when: ({ hasEnterprise }) => hasEnterprise, validate: (input: string) => Boolean(new URL(input)), }, ]); const { username, clientId, clientSecret } = answers; - - await validateCredentials(clientId, clientSecret); - const config = getConfig(answers, useEnvForSecrets); - await updateConfigFile(APP_CONFIG_FILE, config); + Task.log('Setting up GitHub Authentication for you...'); + + await Task.forItem( + 'Validating', + 'credentials', + async () => await validateCredentials(clientId, clientSecret), + ); + await Task.forItem( + 'Updating', + APP_CONFIG_FILE, + async () => await updateConfigFile(APP_CONFIG_FILE, config), + ); if (useEnvForSecrets) { - await updateEnvFile(ENV_CONFIG_FILE, clientId, clientSecret); + await Task.forItem( + 'Updating', + ENV_CONFIG_FILE, + async () => await updateEnvFile(ENV_CONFIG_FILE, clientId, clientSecret), + ); } + await Task.forItem( + 'Creating', + USER_ENTITY_FILE, + async () => await addUserEntity(USER_ENTITY_FILE, username), + ); - await addUserEntity(USER_ENTITY_FILE, username); + const patches = await fs.readdir(PATCH_FOLDER); + for (const patchFile of patches) { + const target = patchFile + .replace(/[0-9]+-github\./, '') + .replace('.patch', ''); - await patch(APP_TSX_FILE, addSignInPageDiff); - await patch(AUTH_BACKEND_PLUGIN_FILE, replaceSignInResolverDiff); + await Task.forItem('Pactching', target, async () => { + await patch(patchMap[target], path.join(PATCH_FOLDER, patchFile)); + }); + } }; diff --git a/packages/cli/src/commands/admin/auth/index.ts b/packages/cli/src/commands/admin/auth/index.ts index ce8e55556b..728a2ffc2c 100644 --- a/packages/cli/src/commands/admin/auth/index.ts +++ b/packages/cli/src/commands/admin/auth/index.ts @@ -43,5 +43,7 @@ export async function auth(): Promise { } Task.log(`Done setting up ${provider}!`); - Task.log(`You can now start you app with ${chalk.inverse('yarn dev')}`); + Task.log( + `You can now start you app with ${chalk.inverse(chalk.italic('yarn dev'))}`, + ); } diff --git a/packages/cli/src/commands/admin/auth/patch.ts b/packages/cli/src/commands/admin/auth/patch.ts index 2a4eccdb9e..cf466f9408 100644 --- a/packages/cli/src/commands/admin/auth/patch.ts +++ b/packages/cli/src/commands/admin/auth/patch.ts @@ -17,9 +17,10 @@ import * as fs from 'fs-extra'; import * as differ from 'diff'; -export const patch = async (file: string, diff: string) => { +export const patch = async (file: string, patchFile: string) => { + const patchContent = await fs.readFile(patchFile, 'utf8'); const oldContent = await fs.readFile(file, 'utf8'); - const newContent = differ.applyPatch(oldContent, diff); + const newContent = differ.applyPatch(oldContent, patchContent); return await fs.writeFile(file, newContent, 'utf8'); }; diff --git a/packages/cli/src/commands/admin/auth/patches/01-github.App.tsx.patch b/packages/cli/src/commands/admin/auth/patches/01-github.App.tsx.patch new file mode 100644 index 0000000000..a7e0c150a5 --- /dev/null +++ b/packages/cli/src/commands/admin/auth/patches/01-github.App.tsx.patch @@ -0,0 +1,24 @@ +@@ -30 +30,5 @@ import { Root } from './components/Root'; +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} from '@backstage/core-components'; +@@ -35,0 +40 @@ import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/ ++import { githubAuthApiRef } from '@backstage/core-plugin-api'; +@@ -38,0 +44,14 @@ const app = createApp({ ++ components: { ++ SignInPage: props => ( ++ ++ ), ++ }, \ No newline at end of file diff --git a/packages/cli/src/commands/admin/auth/patches/02-github.auth.ts.patch b/packages/cli/src/commands/admin/auth/patches/02-github.auth.ts.patch new file mode 100644 index 0000000000..e6e65e7b74 --- /dev/null +++ b/packages/cli/src/commands/admin/auth/patches/02-github.auth.ts.patch @@ -0,0 +1,12 @@ +@@ -40,10 +40 @@ export default async function createPlugin( +- resolver(_, ctx) { +- const userRef = 'user:default/guest'; // Must be a full entity reference +- return ctx.issueToken({ +- claims: { +- sub: userRef, // The user's own identity +- ent: [userRef], // A list of identities that the user claims ownership through +- }, +- }); +- }, +- // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ resolver: providers.github.resolvers.usernameMatchingUserEntityName(), \ No newline at end of file