Skip to content

Commit

Permalink
fix(init): fixes an issue where the init command exited with bad cred…
Browse files Browse the repository at this point in the history
…entials (#3632)

Fixes #3488
  • Loading branch information
lukasholzer authored Nov 15, 2021
1 parent 7058451 commit 5d07d8a
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 19 deletions.
25 changes: 17 additions & 8 deletions src/utils/gh-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ const { openBrowser } = require('./open-browser')

const SERVER_PORT = 3000

/**
* @typedef Token
* @type {object}
* @property {string} user - The username that is associated with the token
* @property {string} token - The actual token value starting with `gho_`
* @property {string} provider - The Provider where the token is associated with ('github').
*/

const promptForAuthMethod = async () => {
const authChoiceNetlify = 'Authorize with GitHub through app.netlify.com'
const authChoiceToken = 'Authorize with a GitHub personal access token'
Expand All @@ -34,14 +42,7 @@ const promptForAuthMethod = async () => {

/**
* Authenticate with the netlify app
* @returns {Promise<Record<string,string>} Returns a Promise with an object of the following shape
* ```
* {
* user: 'spongebob,
* token: 'gho_some-token',
* provider: 'github'
* }
* ```
* @returns {Promise<Token>} Returns a Promise with a token object
*/
const authWithNetlify = async () => {
const port = await getPort({ port: SERVER_PORT })
Expand Down Expand Up @@ -95,6 +96,10 @@ const getPersonalAccessToken = async () => {
return { token }
}

/**
* Authenticate with the netlify app
* @returns {Promise<Token>} Returns a Promise with a token object
*/
const authWithToken = async () => {
const { token } = await getPersonalAccessToken()
if (token) {
Expand All @@ -108,6 +113,10 @@ const authWithToken = async () => {
throw error
}

/**
* Get a github token
* @returns {Promise<Token>} Returns a Promise with a token object
*/
const getGitHubToken = async () => {
log('')

Expand Down
46 changes: 36 additions & 10 deletions src/utils/init/config-github.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,58 @@ const { getGitHubToken: ghauth } = require('../gh-auth')

const { createDeployKey, formatErrorMessage, getBuildSettings, saveNetlifyToml, setupSite } = require('./utils')

/**
* @typedef Token
* @type {object}
* @property {string} user - The username that is associated with the token
* @property {string} token - The actual token value.
* @property {string} provider - The Provider where the token is associated with ('github').
*/

const formatRepoAndOwner = ({ repoName, repoOwner }) => ({
name: chalk.magenta(repoName),
owner: chalk.magenta(repoOwner),
})

const PAGE_SIZE = 100

const isValidToken = (token) => token && token.user && token.token

/**
* Get a valid github token
* @returns {string}
*/
const getGitHubToken = async ({ globalConfig }) => {
const userId = globalConfig.get('userId')

/** @type {Token} */
const githubToken = globalConfig.get(`users.${userId}.auth.github`)

if (isValidToken(githubToken)) {
return githubToken.token
if (githubToken && githubToken.user && githubToken.token) {
try {
const octokit = getGitHubClient(githubToken.token)
const { status } = await octokit.rest.users.getAuthenticated()
if (status < 400) {
return githubToken.token
}
} catch {
log(chalk.yellow('Token is expired or invalid!'))
log('Generating a new Github token...')
}
}

const newToken = await ghauth()
globalConfig.set(`users.${userId}.auth.github`, newToken)
return newToken.token
}

const getGitHubClient = ({ token }) => {
const octokit = new Octokit({
/**
* Retrieves the Github octokit client
* @param {string} token
* @returns {Octokit}
*/
const getGitHubClient = (token) =>
new Octokit({
auth: `token ${token}`,
})
return octokit
}

const addDeployKey = async ({ api, octokit, repoName, repoOwner }) => {
log('Adding deploy key to repository...')
Expand Down Expand Up @@ -172,7 +196,7 @@ const addNotificationHooks = async ({ api, siteId, token }) => {
log(`Netlify Notification Hooks configured!`)
}

module.exports = async function configGithub({ context, repoName, repoOwner, siteId }) {
const configGithub = async ({ context, repoName, repoOwner, siteId }) => {
const { netlify } = context
const {
api,
Expand All @@ -193,7 +217,7 @@ module.exports = async function configGithub({ context, repoName, repoOwner, sit
})
await saveNetlifyToml({ repositoryRoot, config, configPath, baseDir, buildCmd, buildDir, functionsDir })

const octokit = getGitHubClient({ token })
const octokit = getGitHubClient(token)
const [deployKey, githubRepo] = await Promise.all([
addDeployKey({ api, octokit, repoOwner, repoName }),
getGitHubRepo({ octokit, repoOwner, repoName }),
Expand Down Expand Up @@ -223,3 +247,5 @@ module.exports = async function configGithub({ context, repoName, repoOwner, sit
log()
await addNotificationHooks({ siteId, api, token })
}

module.exports = { configGithub, getGitHubToken }
80 changes: 80 additions & 0 deletions src/utils/init/config-github.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const octokit = require('@octokit/rest')
const test = require('ava')
const sinon = require('sinon')

const githubAuth = require('../gh-auth')

let getAuthenticatedResponse

const octokitStub = sinon.stub(octokit, 'Octokit').callsFake(() => ({
rest: {
users: {
getAuthenticated: () => {
if (getAuthenticatedResponse instanceof Error) {
throw getAuthenticatedResponse
}
return Promise.resolve(getAuthenticatedResponse)
},
},
},
}))

// stub the await ghauth() call for a new token
sinon.stub(githubAuth, 'getGitHubToken').callsFake(() =>
Promise.resolve({
provider: 'github',
token: 'new_token',
user: 'spongebob',
}),
)

const { getGitHubToken } = require('./config-github')

// mocked configstore
let globalConfig

test.beforeEach(() => {
const values = new Map()
globalConfig = {
values: new Map(),
get: (key) => values.get(key),
set: (key, value) => {
values.set(key, value)
},
}
globalConfig.set('userId', 'spongebob')
globalConfig.set(`users.spongebob.auth.github`, {
provider: 'github',
token: 'old_token',
user: 'spongebob',
})
})

test.serial('should create a octokit client with the provided token if the token is valid', async (t) => {
getAuthenticatedResponse = { status: 200 }
const token = await getGitHubToken({ globalConfig })
t.is(octokitStub.callCount, 1)
t.deepEqual(octokitStub.getCall(0).args[0], { auth: 'token old_token' })
t.is(token, 'old_token')
t.deepEqual(globalConfig.get(`users.spongebob.auth.github`), {
provider: 'github',
token: 'old_token',
user: 'spongebob',
})
octokitStub.resetHistory()
})

test.serial('should renew the github token when the provided token is not valid', async (t) => {
getAuthenticatedResponse = new Error('Bad Credentials')
getAuthenticatedResponse.status = 401
const token = await getGitHubToken({ globalConfig })
t.is(octokitStub.callCount, 1)
t.is(token, 'new_token')
t.deepEqual(octokitStub.getCall(0).args[0], { auth: 'token old_token' })
t.deepEqual(globalConfig.get(`users.spongebob.auth.github`), {
provider: 'github',
token: 'new_token',
user: 'spongebob',
})
octokitStub.resetHistory()
})
2 changes: 1 addition & 1 deletion src/utils/init/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const chalk = require('chalk')

const { log } = require('../command-helpers')

const configGithub = require('./config-github')
const { configGithub } = require('./config-github')
const configManual = require('./config-manual')

const logSuccess = (repoData) => {
Expand Down

1 comment on commit 5d07d8a

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📊 Benchmark results

Package size: 365 MB

Please sign in to comment.