Skip to content

Commit

Permalink
Merge pull request #238 from zendesk/rdemeterio/VEG-2359
Browse files Browse the repository at this point in the history
[VEG-2359] - drop support for password based basic auth
  • Loading branch information
romeodemeteriojr authored Jul 16, 2024
2 parents b663c77 + 5672642 commit 6f9d18a
Show file tree
Hide file tree
Showing 15 changed files with 196 additions and 195 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ yarn-error.log
packages/zcli-apps/tests/functional/mocks/*/tmp
packages/**/dist
.DS_Store
.idea
36 changes: 10 additions & 26 deletions packages/zcli-apps/tests/functional/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as createAppUtils from '../../src/utils/createApp'
import * as appConfig from '../../src/utils/appConfig'
import * as requestUtils from '../../../zcli-core/src/lib/requestUtils'
import * as packageUtil from '../../src/lib/package'
import env from './env'

describe('apps', function () {
const singleProductApp = path.join(__dirname, 'mocks/single_product_app')
Expand All @@ -25,13 +26,10 @@ describe('apps', function () {
test
.stub(packageUtil, 'createAppPkg', () => createAppPkgStub)
.stub(createAppUtils, 'getManifestAppName', () => 'importantAppName')
.stub(requestUtils, 'getSubdomain', () => Promise.resolve('z3ntest'))
.stub(requestUtils, 'getSubdomain', () => Promise.resolve(undefined))
.stub(requestUtils, 'getDomain', () => Promise.resolve(undefined))
.stub(appConfig, 'setConfig', () => Promise.resolve())
.env({
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_PASSWORD: '123456' // the universal password
})
.env(env)
.do(() => {
createAppPkgStub.onFirstCall().resolves('thePathLessFrequentlyTravelled')
uploadAppPkgStub.onFirstCall().resolves({ id: 817 })
Expand Down Expand Up @@ -67,11 +65,9 @@ describe('apps', function () {
describe('with single app', () => {
test
.stub(packageUtil, 'createAppPkg', () => createAppPkgStub)
.env({
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_PASSWORD: '123456' // the universal password
})
.stub(requestUtils, 'getSubdomain', () => Promise.resolve(undefined))
.stub(requestUtils, 'getDomain', () => Promise.resolve(undefined))
.env(env)
.do(() => {
createAppPkgStub.onFirstCall().resolves('thePathLessFrequentlyTravelled')
uploadAppPkgStub.onFirstCall().resolves({ id: 819 })
Expand All @@ -97,11 +93,7 @@ describe('apps', function () {
describe('with requirements-only app', () => {
test
.stub(packageUtil, 'createAppPkg', () => createAppPkgStub)
.env({
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_PASSWORD: '123456' // the universal password
})
.env(env)
.do(() => {
createAppPkgStub.onFirstCall().resolves('thePathLessFrequentlyTravelled')
uploadAppPkgStub.onFirstCall().resolves({ id: 819 })
Expand All @@ -126,11 +118,7 @@ describe('apps', function () {
describe('with single app', () => {
test
.stub(packageUtil, 'createAppPkg', () => createAppPkgStub)
.env({
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_PASSWORD: '123456' // the universal password
})
.env(env)
.do(() => {
createAppPkgStub.onFirstCall().resolves('thePathLessFrequentlyTravelled')
uploadAppPkgStub.onFirstCall().resolves({ id: 819 })
Expand All @@ -153,11 +141,7 @@ describe('apps', function () {
describe('with requirements-only app', () => {
test
.stub(packageUtil, 'createAppPkg', () => createAppPkgStub)
.env({
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_PASSWORD: '123456' // the universal password
})
.env(env)
.do(() => {
createAppPkgStub.onFirstCall().resolves('thePathLessFrequentlyTravelled')
uploadAppPkgStub.onFirstCall().resolves({ id: 819 })
Expand Down
5 changes: 5 additions & 0 deletions packages/zcli-apps/tests/functional/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_API_TOKEN: '123456'
}
26 changes: 11 additions & 15 deletions packages/zcli-apps/tests/functional/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import * as path from 'path'
import * as fs from 'fs'
import * as readline from 'readline'
import * as AdmZip from 'adm-zip'
import env from './env'
import * as requestUtils from '../../../zcli-core/src/lib/requestUtils'

describe('package', function () {
const appPath = path.join(__dirname, 'mocks/single_product_app')
test
.env({
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_PASSWORD: '123456' // the universal password
})
.stub(requestUtils, 'getSubdomain', () => Promise.resolve(undefined))
.stub(requestUtils, 'getDomain', () => Promise.resolve(undefined))
.env(env)
.nock('https://z3ntest.zendesk.com', api => {
api
.post('/api/v2/apps/validate')
Expand All @@ -25,11 +25,9 @@ describe('package', function () {
})

test
.env({
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_PASSWORD: '123456' // the universal password
})
.stub(requestUtils, 'getSubdomain', () => Promise.resolve(undefined))
.stub(requestUtils, 'getDomain', () => Promise.resolve(undefined))
.env(env)
.nock('https://z3ntest.zendesk.com', api => {
api
.post('/api/v2/apps/validate')
Expand Down Expand Up @@ -69,11 +67,9 @@ describe('zcliignore', function () {
})

test
.env({
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_PASSWORD: '123456' // the universal password
})
.stub(requestUtils, 'getSubdomain', () => Promise.resolve(undefined))
.stub(requestUtils, 'getDomain', () => Promise.resolve(undefined))
.env(env)
.nock('https://z3ntest.zendesk.com', api => {
api
.post('/api/v2/apps/validate')
Expand Down
18 changes: 8 additions & 10 deletions packages/zcli-apps/tests/functional/validate.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { expect, test } from '@oclif/test'
import * as path from 'path'
import env from './env'
import * as requestUtils from '../../../zcli-core/src/lib/requestUtils'

describe('validate', function () {
test
.env({
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_PASSWORD: '123456' // the universal password
})
.stub(requestUtils, 'getSubdomain', () => Promise.resolve(undefined))
.stub(requestUtils, 'getDomain', () => Promise.resolve(undefined))
.env(env)
.nock('https://z3ntest.zendesk.com', api => {
api
.post('/api/v2/apps/validate')
Expand All @@ -20,11 +20,9 @@ describe('validate', function () {
})

test
.env({
ZENDESK_SUBDOMAIN: 'z3ntest',
ZENDESK_EMAIL: 'admin@z3ntest.com',
ZENDESK_PASSWORD: '123456' // the universal password
})
.stub(requestUtils, 'getSubdomain', () => Promise.resolve(undefined))
.stub(requestUtils, 'getDomain', () => Promise.resolve(undefined))
.env(env)
.nock('https://z3ntest.zendesk.com', api => {
api
.post('/api/v2/apps/validate')
Expand Down
54 changes: 23 additions & 31 deletions packages/zcli-core/src/lib/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,14 @@ import Auth from './auth'
import SecureStore from './secureStore'
import { Profile } from '../types'

const mockCreateBasicAuthToken = (...args: any[]) => {
return `Basic ${args[0]}_${args[1]}_base64`
}

describe('Auth', () => {
describe('createBasicAuthToken', () => {
test
.it('should create basic auth token', async () => {
const auth = new Auth()
expect(
await auth.createBasicAuthToken('test@zendesk.com', '123456')
).to.equal('Basic dGVzdEB6ZW5kZXNrLmNvbToxMjM0NTY=')
).to.equal('Basic dGVzdEB6ZW5kZXNrLmNvbS90b2tlbjoxMjM0NTY=')
})
})

Expand All @@ -35,24 +31,13 @@ describe('Auth', () => {
ZENDESK_EMAIL: 'test@zendesk.com',
ZENDESK_API_TOKEN: 'test_api_token'
})
.stub(auth, 'createBasicAuthToken', mockCreateBasicAuthToken)
.it('should return basic token if ZENDESK_EMAIL and ZENDESK_API_TOKEN is set', async () => {
expect(await auth.getAuthorizationToken()).to.equal('Basic test@zendesk.com/token_test_api_token_base64')
})

test
.env({
ZENDESK_EMAIL: 'test@zendesk.com',
ZENDESK_PASSWORD: '123456'
})
.stub(auth, 'createBasicAuthToken', mockCreateBasicAuthToken)
.it('should return basic token if ZENDESK_EMAIL and ZENDESK_PASSWORD is set', async () => {
expect(await auth.getAuthorizationToken()).to.equal('Basic test@zendesk.com_123456_base64')
expect(await auth.getAuthorizationToken()).to.equal('Basic dGVzdEB6ZW5kZXNrLmNvbS90b2tlbjp0ZXN0X2FwaV90b2tlbg==')
})

test
.stub(auth, 'getLoggedInProfile', () => ({ subdomain: 'z3ntest' }))
.stub(auth.secureStore, 'getPassword', () => 'Basic test_token')
.stub(auth.secureStore, 'getSecret', () => 'Basic test_token')
.it('should return token stored in secure store if no env vars are set', async () => {
expect(await auth.getAuthorizationToken()).to.equal('Basic test_token')
})
Expand All @@ -74,10 +59,20 @@ describe('Auth', () => {
ZENDESK_API_TOKEN: 'test_api_token',
ZENDESK_PASSWORD: '123456'
})
.stub(auth, 'createBasicAuthToken', mockCreateBasicAuthToken)
.it('should give precedence to ZENDESK_EMAIL and ZENDESK_API_TOKEN when ZENDESK_OAUTH_TOKEN is not defined', async () => {
expect(await auth.getAuthorizationToken()).to.equal('Basic test@zendesk.com/token_test_api_token_base64')
expect(await auth.getAuthorizationToken()).to.equal('Basic dGVzdEB6ZW5kZXNrLmNvbS90b2tlbjp0ZXN0X2FwaV90b2tlbg==')
})

test
.env({
ZENDESK_EMAIL: 'test@zendesk.com',
ZENDESK_PASSWORD: '123456'
})
.do(async () => {
await auth.getAuthorizationToken()
})
.catch(chalk.red('Basic authentication of type \'password\' is not supported.'))
.it('should throw an error if only ZENDESK_EMAIL and ZENDESK_PASSWORD are set - basic auth with password not supported')
})

describe('loginInteractively', () => {
Expand All @@ -91,14 +86,13 @@ describe('Auth', () => {
promptStub.onThirdCall().resolves('123456')
})
.stub(CliUx.ux, 'prompt', () => promptStub)
.stub(auth.secureStore, 'setPassword', () => Promise.resolve())
.stub(auth.secureStore, 'setSecret', () => Promise.resolve())
.stub(auth, 'setLoggedInProfile', () => Promise.resolve())
.stub(auth, 'createBasicAuthToken', mockCreateBasicAuthToken)
.nock('https://z3ntest.zendesk.com', api => {
api
.get('/api/v2/account/settings.json')
.reply(function () {
expect(this.req.headers.authorization).to.equal('Basic test@zendesk.com_123456_base64')
expect(this.req.headers.authorization).to.equal('Basic dGVzdEB6ZW5kZXNrLmNvbS90b2tlbjoxMjM0NTY=')
return [200]
})
})
Expand All @@ -114,14 +108,13 @@ describe('Auth', () => {
promptStub.onThirdCall().resolves('123456')
})
.stub(CliUx.ux, 'prompt', () => promptStub)
.stub(auth.secureStore, 'setPassword', () => Promise.resolve())
.stub(auth.secureStore, 'setSecret', () => Promise.resolve())
.stub(auth, 'setLoggedInProfile', () => Promise.resolve())
.stub(auth, 'createBasicAuthToken', mockCreateBasicAuthToken)
.nock('https://z3ntest.example.com', api => {
api
.get('/api/v2/account/settings.json')
.reply(function () {
expect(this.req.headers.authorization).to.equal('Basic test@zendesk.com_123456_base64')
expect(this.req.headers.authorization).to.equal('Basic dGVzdEB6ZW5kZXNrLmNvbS90b2tlbjoxMjM0NTY=')
return [200]
})
})
Expand All @@ -136,14 +129,13 @@ describe('Auth', () => {
promptStub.onSecondCall().resolves('123456')
})
.stub(CliUx.ux, 'prompt', () => promptStub)
.stub(auth.secureStore, 'setPassword', () => Promise.resolve())
.stub(auth.secureStore, 'setSecret', () => Promise.resolve())
.stub(auth, 'setLoggedInProfile', () => Promise.resolve())
.stub(auth, 'createBasicAuthToken', mockCreateBasicAuthToken)
.nock('https://z3ntest.example.com', api => {
api
.get('/api/v2/account/settings.json')
.reply(function () {
expect(this.req.headers.authorization).to.equal('Basic test@zendesk.com_123456_base64')
expect(this.req.headers.authorization).to.equal('Basic dGVzdEB6ZW5kZXNrLmNvbS90b2tlbjoxMjM0NTY=')
return [200]
})
})
Expand Down Expand Up @@ -172,7 +164,7 @@ describe('Auth', () => {

test
.stub(auth, 'getLoggedInProfile', () => ({ subdomain: 'z3ntest' }))
.stub(auth.secureStore, 'deletePassword', () => Promise.resolve(true))
.stub(auth.secureStore, 'deleteSecret', () => Promise.resolve(true))
.stub(auth.config, 'removeConfig', () => Promise.resolve())
.it('should return true on logout success', async () => {
expect(await auth.logout()).to.equal(true)
Expand All @@ -188,7 +180,7 @@ describe('Auth', () => {

test
.stub(auth, 'getLoggedInProfile', () => ({ subdomain: 'z3ntest' }))
.stub(auth.secureStore, 'deletePassword', () => Promise.resolve(false))
.stub(auth.secureStore, 'deleteSecret', () => Promise.resolve(false))
.stub(auth.config, 'removeConfig', () => Promise.resolve())
.do(async () => {
await auth.logout()
Expand Down
25 changes: 14 additions & 11 deletions packages/zcli-core/src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import SecureStore from './secureStore'
import { Profile } from '../types'
import { getAccount, parseSubdomain } from './authUtils'
import { getBaseUrl } from './requestUtils'
import { SecretType } from './secretType'

export interface AuthOptions {
secureStore: SecureStore;
Expand All @@ -28,23 +29,26 @@ export default class Auth {
if (ZENDESK_OAUTH_TOKEN) {
return `Bearer ${ZENDESK_OAUTH_TOKEN}`
} else if (ZENDESK_EMAIL && ZENDESK_API_TOKEN) {
return this.createBasicAuthToken(`${ZENDESK_EMAIL}/token`, ZENDESK_API_TOKEN)
return this.createBasicAuthToken(`${ZENDESK_EMAIL}`, ZENDESK_API_TOKEN)
} else if (ZENDESK_EMAIL && ZENDESK_PASSWORD) {
return this.createBasicAuthToken(ZENDESK_EMAIL, ZENDESK_PASSWORD)
return this.createBasicAuthToken(ZENDESK_EMAIL, ZENDESK_PASSWORD, SecretType.PASSWORD)
} else {
const profile = await this.getLoggedInProfile()
if (profile && this.secureStore) {
const authToken = await this.secureStore.getPassword(getAccount(profile.subdomain, profile.domain))
const authToken = await this.secureStore.getSecret(getAccount(profile.subdomain, profile.domain))
return authToken
}

return undefined
}
}

createBasicAuthToken (email: string, passwordOrToken: string) {
const plainToken = Buffer.from(`${email}:${passwordOrToken}`)
return `Basic ${plainToken.toString('base64')}`
createBasicAuthToken (user: string, secret: string, secretType: SecretType = SecretType.TOKEN) {
const basicBase64 = (str: string) => `Basic ${Buffer.from(str).toString('base64')}`
if (secretType === SecretType.TOKEN) {
return basicBase64(`${user}/token:${secret}`)
}
throw new CLIError(chalk.red(`Basic authentication of type '${secretType}' is not supported.`))
}

getLoggedInProfile () {
Expand All @@ -61,9 +65,8 @@ export default class Auth {
const account = getAccount(subdomain, domain)
const baseUrl = getBaseUrl(subdomain, domain)
const email = await CliUx.ux.prompt('Email')
const password = await CliUx.ux.prompt('Password', { type: 'hide' })

const authToken = this.createBasicAuthToken(email, password)
const token = await CliUx.ux.prompt('API Token', { type: 'hide' })
const authToken = this.createBasicAuthToken(email, token)
const testAuth = await axios.get(
`${baseUrl}/api/v2/account/settings.json`,
{
Expand All @@ -72,7 +75,7 @@ export default class Auth {
})

if (testAuth.status === 200 && this.secureStore) {
await this.secureStore.setPassword(account, authToken)
await this.secureStore.setSecret(account, authToken)
await this.setLoggedInProfile(subdomain, domain)

return true
Expand All @@ -89,7 +92,7 @@ export default class Auth {
const profile = await this.getLoggedInProfile()
if (!profile?.subdomain) throw new CLIError(chalk.red('Failed to log out: no active profile found.'))
await this.config.removeConfig('activeProfile')
const deleted = await this.secureStore.deletePassword(getAccount(profile.subdomain, profile.domain))
const deleted = await this.secureStore.deleteSecret(getAccount(profile.subdomain, profile.domain))
if (!deleted) throw new CLIError(chalk.red('Failed to log out: Account, Service not found.'))

return true
Expand Down
Loading

0 comments on commit 6f9d18a

Please sign in to comment.