Skip to content
This repository has been archived by the owner on Jun 27, 2019. It is now read-only.

add support for saml logins #420

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 50 additions & 15 deletions src/commands/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ const utils = require('../utils');
const QUESTION_USERNAME =
'What is your Zapier login email address? (Ctrl-C to cancel)';
const QUESTION_PASSWORD = 'What is your Zapier login password?';
const QUESTION_SSO = 'Paste your deploy key here:';
const QUESTION_TOTP = 'What is your current 6-digit 2FA code?';
const DEPLOY_KEY_DASH_URL = `${constants.BASE_ENDPOINT}/developer/dashboard`; // TODO: fix
const SSO_INSTRUCTIONS = `To generate a deploy key, go to ${DEPLOY_KEY_DASH_URL}, create/copy a key, and then paste the result below.`;

const getSsoKey = async context => {
context.line(SSO_INSTRUCTIONS);
return utils.getInput(QUESTION_SSO);
};

const login = async (context, firstTime = true) => {
const checks = [
Expand Down Expand Up @@ -44,23 +52,40 @@ const login = async (context, firstTime = true) => {
)
);
}
const username = await utils.getInput(QUESTION_USERNAME);
const password = await utils.getInput(QUESTION_PASSWORD, { secret: true });

let goodResponse;
try {
goodResponse = await utils.createCredentials(username, password);
} catch ({ errText, json: { errors } }) {
if (errors[0].startsWith('missing totp_code')) {
const code = await utils.getInput(QUESTION_TOTP);
goodResponse = await utils.createCredentials(username, password, code);
let deployKey;

if (context.argOpts.sso) {
deployKey = await getSsoKey(context);
} else {
const email = await utils.getInput(QUESTION_USERNAME);
const isSaml = await utils.isSamlEmail(email);

if (isSaml) {
deployKey = await getSsoKey(context);
} else {
throw new Error(errText);
context.line(
"\nNow you'll enter your Zapier password. If you log into Zapier via the Google button, you may not have a Zapier password. If that's the case, re-run this command with the `--sso` flag and follow the instructions.\n"
);
const password = await utils.getInput(QUESTION_PASSWORD, {
secret: true
});

let goodResponse;
try {
goodResponse = await utils.createCredentials(email, password);
} catch ({ errText, json: { errors } }) {
if (errors[0].startsWith('missing totp_code')) {
const code = await utils.getInput(QUESTION_TOTP);
goodResponse = await utils.createCredentials(email, password, code);
} else {
throw new Error(errText);
}
}

deployKey = goodResponse.key;
}
}

const deployKey = goodResponse.key;

await utils.writeFile(
constants.AUTH_LOCATION,
utils.prettyJSONstringify({
Expand All @@ -79,7 +104,13 @@ const login = async (context, firstTime = true) => {
}
};
login.argsSpec = [];
login.argOptsSpec = {};
login.argOptsSpec = {
sso: {
flag: true,
help:
"Use this flag if you log into Zapier using the Google SSO button and don't have a password to type"
}
};
login.help = `Configure your \`${
constants.AUTH_LOCATION_RAW
}\` with a deploy key.`;
Expand All @@ -91,8 +122,12 @@ This is an interactive prompt which will create, retrieve and store a deploy key
constants.AUTH_LOCATION_RAW
}\` (home directory identifies the deploy key & user).

**Arguments**

${utils.argOptsFragment(login.argOptsSpec)}

${'```'}bash
$ zapier login
# $ zapier login
# ${QUESTION_USERNAME}
# ${QUESTION_PASSWORD}
# <type here>
Expand Down
17 changes: 17 additions & 0 deletions src/tests/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,21 @@ describe('api', () => {

afterEach(mock.restore);
});

describe('SAML checking', () => {
it('should throw for bad emails', async () => {
await api.isSamlEmail('asdf').should.be.rejected();
});

it('should be false for non-saml emails', async () => {
const res = await api.isSamlEmail('bruce@wayneenterprises.com');
res.should.be.false();
});

// TODO: need an actual saml domain for this to work
it.skip('should work for saml emails', async () => {
const res = await api.isSamlEmail('bruce@wayneenterprises.com');
res.should.be.true();
});
});
});
14 changes: 14 additions & 0 deletions src/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ const createCredentials = (username, password, totpCode) => {
);
};

const isSamlEmail = async email => {
const rawResponse = await fetch(
`https://zapier.com/api/v4/idp-discovery/?email=${encodeURIComponent(
email
)}`
);
const { results = [], errors = [] } = await rawResponse.json();
if (errors.length) {
throw new Error(errors[0]);
}
return results.length > 0;
};

// Reads the JSON file in the app directory.
const getLinkedAppConfig = appDir => {
appDir = appDir || '.';
Expand Down Expand Up @@ -314,6 +327,7 @@ module.exports = {
getLinkedApp,
getLinkedAppConfig,
getVersionInfo,
isSamlEmail,
listApps,
listEndpoint,
listEnv,
Expand Down