Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: MFA support for CLI #807

Merged
merged 2 commits into from
Apr 15, 2024
Merged
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
2 changes: 1 addition & 1 deletion example.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function getSSLPage($url) {
$platform = 'console';
// $platform = 'server';

$spec = getSSLPage("https://raw.githubusercontent.com/appwrite/appwrite/feat-rc-sdks/app/config/specs/swagger2-latest-{$platform}.json");
$spec = getSSLPage("https://raw.githubusercontent.com/appwrite/appwrite/1.5.x/app/config/specs/swagger2-latest-{$platform}.json");

if(empty($spec)) {
throw new Exception('Failed to fetch spec from Appwrite server');
Expand Down
43 changes: 40 additions & 3 deletions templates/cli/lib/commands/generic.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const { sdkForConsole } = require("../sdks");
const { globalConfig, localConfig } = require("../config");
const { actionRunner, success, parseBool, commandDescriptions, log, parse } = require("../parser");
{% if sdk.test != "true" %}
const { questionsLogin } = require("../questions");
const { accountCreateEmailPasswordSession, accountDeleteSession } = require("./account");
const { questionsLogin, questionsListFactors, questionsMfaChallenge } = require("../questions");
const { accountUpdateMfaChallenge, accountCreateMfaChallenge, accountGet, accountCreateEmailPasswordSession, accountDeleteSession } = require("./account");

const login = new Command("login")
.description(commandDescriptions['login'])
Expand All @@ -25,7 +25,44 @@ const login = new Command("login")
sdk: client
})

success()
client.setCookie(globalConfig.getCookie());

let account;

try {
account = await accountGet({
sdk: client,
parseOutput: false
});
} catch(error) {
if (error.response === 'user_more_factors_required') {
const { factor } = await inquirer.prompt(questionsListFactors);

const challenge = await accountCreateMfaChallenge({
factor,
parseOutput: false,
sdk: client
});

const { otp } = await inquirer.prompt(questionsMfaChallenge);

await accountUpdateMfaChallenge({
challengeId: challenge.$id,
otp,
parseOutput: false,
sdk: client
});

account = await accountGet({
sdk: client,
parseOutput: false
});
} else {
throw error;
}
}

success("Signed in as user with ID: " + account.$id);
}));

const logout = new Command("logout")
Expand Down
59 changes: 57 additions & 2 deletions templates/cli/lib/questions.js.twig
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const { localConfig } = require('./config');
const { projectsList } = require('./commands/projects');
const { functionsListRuntimes } = require('./commands/functions');
const { accountListMfaFactors } = require("./commands/account");
const { sdkForConsole } = require("./sdks");

const { databasesList } = require('./commands/databases');
const JSONbig = require("json-bigint")({ storeAsString: false });

Expand Down Expand Up @@ -387,7 +390,57 @@ const questionsDeployTeams = [
name: "override",
message: 'Are you sure you want to override this team? This can lead to loss of data! Type "YES" to confirm.'
},
]
];

const questionsListFactors = [
{
type: "list",
name: "factor",
message: "Your account is protected by multiple factors. Which factor would you like to use to authenticate?",
choices: async () => {
let client = await sdkForConsole(false);
const factors = await accountListMfaFactors({
sdk: client,
parseOutput: false
});

const choices = [
{
name: `TOTP (Time-based One-time Password)`,
value: 'totp'
},
{
name: `E-mail`,
value: 'email'
},
{
name: `Phone (SMS)`,
value: 'phone'
},
{
name: `Recovery code`,
value: 'recoveryCode'
}
].filter((ch) => factors[ch.value] === true);

return choices;
}
}
];

const questionsMfaChallenge = [
{
type: "input",
name: "otp",
message: "Enter OTP",
validate(value) {
if (!value) {
return "Please enter OTP";
}
return true;
},
}
];

module.exports = {
questionsInitProject,
Expand All @@ -398,5 +451,7 @@ module.exports = {
questionsDeployCollections,
questionsDeployBuckets,
questionsDeployTeams,
questionsGetEntrypoint
questionsGetEntrypoint,
questionsListFactors,
questionsMfaChallenge
};