From 5663de4a3737d51615ceb5d2b5a9802fe21355a0 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Thu, 16 Feb 2023 16:18:22 +0100 Subject: [PATCH] cli: Add user entity to local catalog in auth flow Signed-off-by: Marcus Eide --- .../cli/src/commands/admin/auth/config.ts | 31 +++++++ packages/cli/src/commands/admin/auth/files.ts | 2 + .../src/commands/admin/auth/github/oauth.ts | 86 +++++++++++++------ packages/cli/src/commands/admin/auth/index.ts | 2 + 4 files changed, 96 insertions(+), 25 deletions(-) diff --git a/packages/cli/src/commands/admin/auth/config.ts b/packages/cli/src/commands/admin/auth/config.ts index d358e8e89d..659c356360 100644 --- a/packages/cli/src/commands/admin/auth/config.ts +++ b/packages/cli/src/commands/admin/auth/config.ts @@ -29,6 +29,13 @@ type GithubAuthConfig = { }; }; }; + catalog?: { + locations: Array<{ + type: string; + target: string; + rules: Array<{ allow: Array }>; + }>; + }; }; const readYaml = async (file: string) => { @@ -67,3 +74,27 @@ AUTH_GITHUB_CLIENT_SECRET=${clientSecret}`; return await fs.writeFile(file, content, 'utf8'); }; + +export const addUserEntity = async (file: string, username: string) => { + const content = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: username, + annotations: { + 'github.com/user-login': username, + }, + }, + spec: { + memberOf: [], + }, + }; + + return await fs.writeFile( + file, + yaml.stringify(content, { + indent: 2, + }), + 'utf8', + ); +}; diff --git a/packages/cli/src/commands/admin/auth/files.ts b/packages/cli/src/commands/admin/auth/files.ts index 479cda0472..ad9b53a894 100644 --- a/packages/cli/src/commands/admin/auth/files.ts +++ b/packages/cli/src/commands/admin/auth/files.ts @@ -21,6 +21,8 @@ import * as path from 'path'; const { targetRoot, 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( resolveTargetRoot('packages/app'), 'src', diff --git a/packages/cli/src/commands/admin/auth/github/oauth.ts b/packages/cli/src/commands/admin/auth/github/oauth.ts index 27dbe24f74..64abf520cf 100644 --- a/packages/cli/src/commands/admin/auth/github/oauth.ts +++ b/packages/cli/src/commands/admin/auth/github/oauth.ts @@ -17,13 +17,15 @@ import { OAuthApp } from '@octokit/oauth-app'; import chalk from 'chalk'; import inquirer from 'inquirer'; +import fetch from 'node-fetch'; import { Task } from '../../../../lib/tasks'; -import { updateConfigFile, updateEnvFile } from '../config'; +import { addUserEntity, updateConfigFile, updateEnvFile } from '../config'; import { APP_CONFIG_FILE, APP_TSX_FILE, AUTH_BACKEND_PLUGIN_FILE, ENV_CONFIG_FILE, + USER_ENTITY_FILE, } from '../files'; import { patch } from '../patch'; import { addSignInPageDiff, replaceSignInResolverDiff } from './diffs'; @@ -53,6 +55,46 @@ const validateCredentials = async (clientId: string, clientSecret: string) => { } }; +const getConfig = (answers: Answers, useEnvForSecrets: boolean) => { + const { clientId, clientSecret, hasEnterprise, enterpriseInstanceUrl } = + answers; + + return { + auth: { + providers: { + github: { + development: { + clientId: useEnvForSecrets ? '${AUTH_GITHUB_CLIENT_ID}' : clientId, + clientSecret: useEnvForSecrets + ? '${AUTH_GITHUB_CLIENT_SECRET}' + : clientSecret, + ...(hasEnterprise && { + enterpriseInstanceUrl: enterpriseInstanceUrl, + }), + }, + }, + }, + }, + catalog: { + locations: [ + { + type: 'file', + target: '../../user-info.yaml', + rules: [{ allow: ['User'] }], + }, + ], + }, + }; +}; + +type Answers = { + username: string; + clientSecret: string; + clientId: string; + hasEnterprise: boolean; + enterpriseInstanceUrl?: string; +}; + export const oauth = async (useEnvForSecrets: boolean) => { Task.log(` To add GitHub authentication, you must create an OAuth App from the GitHub developer settings: ${chalk.blue( @@ -69,12 +111,19 @@ export const oauth = async (useEnvForSecrets: boolean) => { 'https://backstage.io/docs/auth/github/provider', )}`); - const answers = await inquirer.prompt<{ - clientSecret: string; - clientId: string; - hasEnterprise: boolean; - enterpriseInstanceUrl?: string; - }>([ + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'username', + message: 'What is your Github username?', + validate: async (input: string) => { + const response = await fetch(`https://api.github.com/users/${input}`); + if (!response.ok) { + return chalk.red('Unknown user. Please try again.'); + } + return true; + }, + }, { type: 'input', name: 'clientId', @@ -101,32 +150,19 @@ export const oauth = async (useEnvForSecrets: boolean) => { }, ]); - const { clientId, clientSecret, hasEnterprise, enterpriseInstanceUrl } = - answers; + const { username, clientId, clientSecret } = answers; await validateCredentials(clientId, clientSecret); - const auth = { - providers: { - github: { - development: { - clientId: useEnvForSecrets ? '${AUTH_GITHUB_CLIENT_ID}' : clientId, - clientSecret: useEnvForSecrets - ? '${AUTH_GITHUB_CLIENT_SECRET}' - : clientSecret, - ...(hasEnterprise && { - enterpriseInstanceUrl: enterpriseInstanceUrl, - }), - }, - }, - }, - }; + const config = getConfig(answers, useEnvForSecrets); + await updateConfigFile(APP_CONFIG_FILE, config); - await updateConfigFile(APP_CONFIG_FILE, { auth }); if (useEnvForSecrets) { await updateEnvFile(ENV_CONFIG_FILE, clientId, clientSecret); } + await addUserEntity(USER_ENTITY_FILE, username); + await patch(APP_TSX_FILE, addSignInPageDiff); await patch(AUTH_BACKEND_PLUGIN_FILE, replaceSignInResolverDiff); }; diff --git a/packages/cli/src/commands/admin/auth/index.ts b/packages/cli/src/commands/admin/auth/index.ts index 9af145dcf3..ce39748b09 100644 --- a/packages/cli/src/commands/admin/auth/index.ts +++ b/packages/cli/src/commands/admin/auth/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import chalk from 'chalk'; import inquirer from 'inquirer'; import { Task } from '../../../lib/tasks'; import { github } from './github'; @@ -42,4 +43,5 @@ export async function auth(): Promise { } Task.log(`Done setting up ${provider}!`); + Task.log(`You can now start you app with ${chalk.inverse('yarn dev')}`); }