From 4e4a0507ca83cb4f309152e7265a14b48506d416 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 21 Feb 2023 15:22:27 +0100 Subject: [PATCH] cli: Consolidate oauth.ts into index.ts Signed-off-by: Marcus Eide --- .../src/commands/admin/auth/github/index.ts | 158 +++++++++++++++- .../src/commands/admin/auth/github/oauth.ts | 173 ------------------ 2 files changed, 156 insertions(+), 175 deletions(-) delete mode 100644 packages/cli/src/commands/admin/auth/github/oauth.ts diff --git a/packages/cli/src/commands/admin/auth/github/index.ts b/packages/cli/src/commands/admin/auth/github/index.ts index cc2653bc05..def97bf619 100644 --- a/packages/cli/src/commands/admin/auth/github/index.ts +++ b/packages/cli/src/commands/admin/auth/github/index.ts @@ -14,6 +14,160 @@ * limitations under the License. */ -import { oauth } from './oauth'; +import { OAuthApp } from '@octokit/oauth-app'; +import chalk from 'chalk'; +import * as fs from 'fs-extra'; +import inquirer from 'inquirer'; +import fetch from 'node-fetch'; +import { Task } from '../../../../lib/tasks'; +import { addUserEntity, updateConfigFile } from '../config'; +import { APP_CONFIG_FILE, PATCH_FOLDER, USER_ENTITY_FILE } from '../files'; +import { patch } from '../patch'; -export const github = async () => await oauth(); +const validateCredentials = async (clientId: string, clientSecret: string) => { + try { + const app = new OAuthApp({ + clientId, + clientSecret, + }); + await app.createToken({ + code: '%NOT-VALID-CODE%', + }); + } catch (error) { + // @octokit/request returns a error.response object when a request is rejected. + // We can check it to see what kind of error we received. + + // If error.response is successful we can double-check that the error itself was due to the bad code. + // If that's the case then we can assume that the client id and secret exists as we otherwise would + // have gotten a 400/404. + if ( + error.response.status !== 200 && + error.response.data.error !== 'bad_verification_code' + ) { + throw new Error(`Validating GitHub Credentials failed.`); + } + } +}; + +const getConfig = (answers: Answers) => { + const { clientId, clientSecret, hasEnterprise, enterpriseInstanceUrl } = + answers; + + return { + auth: { + providers: { + github: { + development: { + clientId, + clientSecret, + ...(hasEnterprise && { + 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 github = async () => { + Task.log(` + To add GitHub authentication, you must create an OAuth App from the GitHub developer settings: ${chalk.blue( + 'https://github.com/settings/developers', + )} + The Homepage URL should point to Backstage's frontend, while the Authorization callback URL will point to the auth backend. + + Settings for local development: + ${chalk.cyan(` + Homepage URL: http://localhost:3000 + Authorization callback URL: http://localhost:7007/api/auth/github/handler/frame`)} + + 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?', + 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', + message: 'What is your Client Id?', + validate: (input: string) => (input.length ? true : false), + }, + { + type: 'input', + name: 'clientSecret', + message: 'What is your Client Secret?', + validate: (input: string) => (input.length ? true : false), + }, + { + type: 'confirm', + name: 'hasEnterprise', + message: 'Are you using GitHub Enterprise?', + }, + { + type: 'input', + name: 'enterpriseInstanceUrl', + message: 'What is your URL for GitHub Enterprise?', + when: ({ hasEnterprise }) => hasEnterprise, + validate: (input: string) => Boolean(new URL(input)), + }, + ]); + + const { username, clientId, clientSecret } = answers; + const config = getConfig(answers); + + 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), + ); + await Task.forItem( + 'Creating', + USER_ENTITY_FILE, + async () => await addUserEntity(USER_ENTITY_FILE, username), + ); + + const patches = await fs.readdir(PATCH_FOLDER); + for (const patchFile of patches) { + await Task.forItem('Patching', patchFile, async () => { + await patch(patchFile); + }); + } +}; diff --git a/packages/cli/src/commands/admin/auth/github/oauth.ts b/packages/cli/src/commands/admin/auth/github/oauth.ts deleted file mode 100644 index 1de6b2a4c4..0000000000 --- a/packages/cli/src/commands/admin/auth/github/oauth.ts +++ /dev/null @@ -1,173 +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. - */ - -import { OAuthApp } from '@octokit/oauth-app'; -import chalk from 'chalk'; -import * as fs from 'fs-extra'; -import inquirer from 'inquirer'; -import fetch from 'node-fetch'; -import { Task } from '../../../../lib/tasks'; -import { addUserEntity, updateConfigFile } from '../config'; -import { APP_CONFIG_FILE, PATCH_FOLDER, USER_ENTITY_FILE } from '../files'; -import { patch } from '../patch'; - -const validateCredentials = async (clientId: string, clientSecret: string) => { - try { - const app = new OAuthApp({ - clientId, - clientSecret, - }); - await app.createToken({ - code: '%NOT-VALID-CODE%', - }); - } catch (error) { - // @octokit/request returns a error.response object when a request is rejected. - // We can check it to see what kind of error we received. - - // If error.response is successful we can double-check that the error itself was due to the bad code. - // If that's the case then we can assume that the client id and secret exists as we otherwise would - // have gotten a 400/404. - if ( - error.response.status !== 200 && - error.response.data.error !== 'bad_verification_code' - ) { - throw new Error(`Validating GitHub Credentials failed.`); - } - } -}; - -const getConfig = (answers: Answers) => { - const { clientId, clientSecret, hasEnterprise, enterpriseInstanceUrl } = - answers; - - return { - auth: { - providers: { - github: { - development: { - clientId, - clientSecret, - ...(hasEnterprise && { - 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 () => { - Task.log(` - To add GitHub authentication, you must create an OAuth App from the GitHub developer settings: ${chalk.blue( - 'https://github.com/settings/developers', - )} - The Homepage URL should point to Backstage's frontend, while the Authorization callback URL will point to the auth backend. - - Settings for local development: - ${chalk.cyan(` - Homepage URL: http://localhost:3000 - Authorization callback URL: http://localhost:7007/api/auth/github/handler/frame`)} - - 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?', - 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', - message: 'What is your Client Id?', - validate: (input: string) => (input.length ? true : false), - }, - { - type: 'input', - name: 'clientSecret', - message: 'What is your Client Secret?', - validate: (input: string) => (input.length ? true : false), - }, - { - type: 'confirm', - name: 'hasEnterprise', - message: 'Are you using GitHub Enterprise?', - }, - { - type: 'input', - name: 'enterpriseInstanceUrl', - message: 'What is your URL for GitHub Enterprise?', - when: ({ hasEnterprise }) => hasEnterprise, - validate: (input: string) => Boolean(new URL(input)), - }, - ]); - - const { username, clientId, clientSecret } = answers; - const config = getConfig(answers); - - 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), - ); - await Task.forItem( - 'Creating', - USER_ENTITY_FILE, - async () => await addUserEntity(USER_ENTITY_FILE, username), - ); - - const patches = await fs.readdir(PATCH_FOLDER); - for (const patchFile of patches) { - await Task.forItem('Patching', patchFile, async () => { - await patch(patchFile); - }); - } -};