From d844a31fc414bf0b8ff06aa5da9fbfeab3f66cd4 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Fri, 10 Feb 2023 14:08:43 +0100 Subject: [PATCH] cli: Add validation for github credentials Signed-off-by: Marcus Eide --- packages/cli/package.json | 1 + packages/cli/src/commands/admin/command.ts | 5 +- packages/cli/src/commands/admin/github.ts | 83 +++++++++++++++------- yarn.lock | 40 +++-------- 4 files changed, 66 insertions(+), 63 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 8c76c7888b..8a0848a243 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -39,6 +39,7 @@ "@backstage/types": "workspace:^", "@esbuild-kit/cjs-loader": "^2.4.1", "@manypkg/get-packages": "^1.1.3", + "@octokit/oauth-app": "^4.2.0", "@octokit/request": "^6.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@rollup/plugin-commonjs": "^23.0.0", diff --git a/packages/cli/src/commands/admin/command.ts b/packages/cli/src/commands/admin/command.ts index d7ea348571..eb0ba0b512 100644 --- a/packages/cli/src/commands/admin/command.ts +++ b/packages/cli/src/commands/admin/command.ts @@ -30,9 +30,7 @@ export async function command(): Promise { { type: 'confirm', name: 'shouldSetupAuth', - message: chalk.blue( - 'Do you want to set up Authentication for this project?', - ), + message: 'Do you want to set up Authentication for this project?', }, { type: 'confirm', @@ -51,7 +49,6 @@ export async function command(): Promise { ]); if (!answers.shouldSetupAuth) { - // TODO(eide): Can we add a Task.warning() method? console.log( chalk.yellow( 'If you change your mind, feel free to re-run this command.', diff --git a/packages/cli/src/commands/admin/github.ts b/packages/cli/src/commands/admin/github.ts index 5b359ecb33..0cb900c85d 100644 --- a/packages/cli/src/commands/admin/github.ts +++ b/packages/cli/src/commands/admin/github.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { OAuthApp } from '@octokit/oauth-app'; import chalk from 'chalk'; import inquirer from 'inquirer'; import { Task } from '../../lib/tasks'; @@ -22,14 +23,41 @@ export type GithubAuthConfig = { auth: { providers: { github: { - clientId: string; - clientSecret: string; - enterpriseInstanceUrl?: string; + development: { + clientId: string; + clientSecret: string; + enterpriseInstanceUrl?: string; + }; }; }; }; }; +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.`); + } + } +}; + export const github = async ( useEnvForSecrets?: boolean, ): Promise => { @@ -39,6 +67,11 @@ export const github = async ( )} 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', )} @@ -52,21 +85,15 @@ export const github = async ( }>([ { type: 'input', - name: 'clientSecret', - message: 'What is your Client Secret?', - // TODO(eide): Is there another way to validate? - // validate(input) { - // if (/([a-f0-9]{40})/g.test(input)) { - // return true; - // } - - // throw Error('Please provide a valid client secret.'); - // }, + name: 'clientId', + message: 'What is your Client Id?', + validate: (input: string) => (input.length ? true : false), }, { type: 'input', - name: 'clientId', - message: 'What is your Client Id?', + name: 'clientSecret', + message: 'What is your Client Secret?', + validate: (input: string) => (input.length ? true : false), }, { type: 'confirm', @@ -78,25 +105,27 @@ export const github = async ( name: 'enterpriseInstanceUrl', message: 'What is your URL for Github Enterprise?', when: ({ hasGithubEnterprise }) => hasGithubEnterprise, - validate(input: string) { - return Boolean(new URL(input)); - }, + validate: (input: string) => Boolean(new URL(input)), }, ]); + await validateCredentials(answers.clientId, answers.clientSecret); + return { auth: { providers: { github: { - clientId: useEnvForSecrets - ? '${AUTH_GITHUB_CLIENT_ID}' - : answers.clientId, - clientSecret: useEnvForSecrets - ? '${AUTH_GITHUB_CLIENT_SECRET}' - : answers.clientSecret, - ...(answers.hasGithubEnterprise && { - enterpriseInstanceUrl: answers.enterpriseInstanceUrl, - }), + development: { + clientId: useEnvForSecrets + ? '${AUTH_GITHUB_CLIENT_ID}' + : answers.clientId, + clientSecret: useEnvForSecrets + ? '${AUTH_GITHUB_CLIENT_SECRET}' + : answers.clientSecret, + ...(answers.hasGithubEnterprise && { + enterpriseInstanceUrl: answers.enterpriseInstanceUrl, + }), + }, }, }, }, diff --git a/yarn.lock b/yarn.lock index cd69bd547b..f867cadce5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3678,6 +3678,7 @@ __metadata: "@backstage/types": "workspace:^" "@esbuild-kit/cjs-loader": ^2.4.1 "@manypkg/get-packages": ^1.1.3 + "@octokit/oauth-app": ^4.2.0 "@octokit/request": ^6.0.0 "@pmmmwh/react-refresh-webpack-plugin": ^0.5.7 "@rollup/plugin-commonjs": ^23.0.0 @@ -12534,9 +12535,9 @@ __metadata: languageName: node linkType: hard -"@octokit/oauth-app@npm:^4.0.4, @octokit/oauth-app@npm:^4.0.6": - version: 4.0.6 - resolution: "@octokit/oauth-app@npm:4.0.6" +"@octokit/oauth-app@npm:^4.0.4, @octokit/oauth-app@npm:^4.0.6, @octokit/oauth-app@npm:^4.2.0": + version: 4.2.0 + resolution: "@octokit/oauth-app@npm:4.2.0" dependencies: "@octokit/auth-oauth-app": ^5.0.0 "@octokit/auth-oauth-user": ^2.0.0 @@ -12545,13 +12546,9 @@ __metadata: "@octokit/oauth-authorization-url": ^5.0.0 "@octokit/oauth-methods": ^2.0.0 "@types/aws-lambda": ^8.10.83 - aws-lambda: ^1.0.7 fromentries: ^1.3.1 universal-user-agent: ^6.0.0 - dependenciesMeta: - aws-lambda: - optional: true - checksum: 0d7ceadda4668748a436f52f6c4ae3160a4a5c81a92592c0a6cfc75b0a065ed480880d8861efcabc6ca41b3ccf753d1c1044782a6c8d5fb481d6e8a0acf6d32e + checksum: 6cf5638d27e589daa577653177f2b135c0c346c8f74cc420d9fd0dc0f559a84040073eee147bd555a55bb5756bd0940e6dc4c3a6b3a508a818e3d5bee3837095 languageName: node linkType: hard @@ -17536,20 +17533,6 @@ __metadata: languageName: node linkType: hard -"aws-lambda@npm:^1.0.7": - version: 1.0.7 - resolution: "aws-lambda@npm:1.0.7" - dependencies: - aws-sdk: ^2.814.0 - commander: ^3.0.2 - js-yaml: ^3.14.1 - watchpack: ^2.0.0-beta.10 - bin: - lambda: bin/lambda - checksum: 11316e87b5c4fc36e6bd0495742a3c0ed13befc9527a7b251a58180d141d9afd68b684f37aeb3b53d117d3c2f96747eace31826b683543f1edddc03f392865fd - languageName: node - linkType: hard - "aws-os-connection@npm:^0.2.0": version: 0.2.0 resolution: "aws-os-connection@npm:0.2.0" @@ -17596,7 +17579,7 @@ __metadata: languageName: node linkType: hard -"aws-sdk@npm:^2.1231.0, aws-sdk@npm:^2.814.0, aws-sdk@npm:^2.840.0, aws-sdk@npm:^2.948.0": +"aws-sdk@npm:^2.1231.0, aws-sdk@npm:^2.840.0, aws-sdk@npm:^2.948.0": version: 2.1279.0 resolution: "aws-sdk@npm:2.1279.0" dependencies: @@ -19475,13 +19458,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^3.0.2": - version: 3.0.2 - resolution: "commander@npm:3.0.2" - checksum: 6d14ad030d1904428139487ed31febcb04c1604db2b8d9fae711f60ee6718828dc0e11602249e91c8a97b0e721e9c6d53edbc166bad3cde1596851d59a8f824d - languageName: node - linkType: hard - "commander@npm:^4.0.0": version: 4.1.1 resolution: "commander@npm:4.1.1" @@ -27320,7 +27296,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^3.10.0, js-yaml@npm:^3.13.0, js-yaml@npm:^3.13.1, js-yaml@npm:^3.14.1, js-yaml@npm:^3.6.1, js-yaml@npm:^3.8.3": +"js-yaml@npm:^3.10.0, js-yaml@npm:^3.13.0, js-yaml@npm:^3.13.1, js-yaml@npm:^3.6.1, js-yaml@npm:^3.8.3": version: 3.14.1 resolution: "js-yaml@npm:3.14.1" dependencies: @@ -38736,7 +38712,7 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.0.0-beta.10, watchpack@npm:^2.4.0": +"watchpack@npm:^2.4.0": version: 2.4.0 resolution: "watchpack@npm:2.4.0" dependencies: