Skip to content

Commit

Permalink
feat(login): user login/auth through API Bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
davlgd committed Mar 8, 2025
1 parent db86b57 commit 364cc56
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 59 deletions.
13 changes: 12 additions & 1 deletion bin/clever.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as Application from '../src/models/application.js';
import { AVAILABLE_ZONES } from '../src/models/application.js';
import { EXPERIMENTAL_FEATURES } from '../src/experimental-features.js';
import { getExitOnOption, getOutputFormatOption, getSameCommitPolicyOption } from '../src/command-options.js';
import { getHostAndTokens } from '../src/models/send-to-api.js';
import { getFeatures, conf } from '../src/models/configuration.js';

import * as Addon from '../src/models/addon.js';
Expand Down Expand Up @@ -1005,12 +1006,22 @@ async function run () {
description: 'Revoke an API token',
args: [args.apiTokenId],
}, tokens.revoke);
const tokensCommands = cliparse.command('tokens', {
let tokensCommands = cliparse.command('tokens', {
description: `Manage API tokens to query Clever Cloud API from ${conf.AUTH_BRIDGE_HOST}`,
commands: [apiTokenCreateCommand, apiTokenRevokeCommand],
privateOptions: [opts.humanJsonOutputFormat],
}, tokens.list);

// If the user is logged in through the API bridge, we remove list/revoke tokens commands
const data = await getHostAndTokens();
if (data.tokens.apiToken != null) {
tokensCommands = cliparse.command('tokens', {
description: `Manage API tokens to query Clever Cloud API from ${conf.AUTH_BRIDGE_HOST}`,
commands: [apiTokenCreateCommand],
privateOptions: [opts.humanJsonOutputFormat],
}, tokens.showApiTokenUser);
}

// UNLINK COMMAND
const appUnlinkCommand = cliparse.command('unlink', {
description: 'Unlink this repo from an existing application',
Expand Down
90 changes: 32 additions & 58 deletions src/commands/login.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,40 @@
import crypto from 'node:crypto';
import util from 'node:util';

import colors from 'colors/safe.js';
import open from 'open';
import superagent from 'superagent';

import { Logger } from '../logger.js';
import * as User from '../models/user.js';
import { conf, writeOAuthConf } from '../models/configuration.js';

import { getPackageJson } from '../load-package-json.cjs';

const delay = util.promisify(setTimeout);
const pkg = getPackageJson();

// 20 random bytes as Base64URL
function randomToken () {
return crypto.randomBytes(20).toString('base64').replace(/\//g, '-').replace(/\+/g, '_').replace(/=/g, '');
}

const POLLING_INTERVAL = 2000;
const POLLING_MAX_TRY_COUNT = 60;

function pollOauthData (url, tryCount = 0) {

if (tryCount >= POLLING_MAX_TRY_COUNT) {
throw new Error('Something went wrong while trying to log you in.');
}
if (tryCount > 1 && tryCount % 10 === 0) {
Logger.println("We're still waiting for the login process (in your browser) to be completed…");
}

return superagent
.get(url)
.send()
.then(({ body }) => body)
.catch(async (e) => {
if (e.status === 404) {
await delay(POLLING_INTERVAL);
return pollOauthData(url, tryCount + 1);
}
throw new Error('Something went wrong while trying to log you in.');
});
}
import { Logger } from '../logger.js';
import { promptEmail, promptPassword } from '../prompt.js';
import { sendToAuthBridge } from '../models/send-to-api.js';
import { writeOAuthConf } from '../models/configuration.js';
import { createApiToken } from '../clever-client/auth-bridge.js';

async function loginViaConsole () {

const cliToken = randomToken();

const consoleUrl = new URL(conf.CONSOLE_TOKEN_URL);
consoleUrl.searchParams.set('cli_version', pkg.version);
consoleUrl.searchParams.set('cli_token', cliToken);

const cliPollUrl = new URL(conf.API_HOST);
cliPollUrl.pathname = '/v2/self/cli_tokens';
cliPollUrl.searchParams.set('cli_token', cliToken);

Logger.debug('Try to login to Clever Cloud…');
Logger.println(`Opening ${colors.green(consoleUrl.toString())} in your browser to log you in…`);
await open(consoleUrl.toString(), { wait: false });

return pollOauthData(cliPollUrl.toString());
const dateObject = new Date();
dateObject.setFullYear(dateObject.getFullYear() + 1);
const expirationDate = dateObject;

const name = `Clever Tools - ${dateObject.getTime()}`;
const email = await promptEmail('Enter your email:');
const password = await promptPassword('Enter your password:');
const mfaCode = await promptPassword('Enter your 2FA code (press Enter if none):');

const tokenData = {
email,
password,
mfaCode,
name,
expirationDate: expirationDate.toISOString(),
};

return createApiToken(tokenData).then(sendToAuthBridge).catch((error) => {
const errorCode = error?.cause?.responseBody?.code;
if (errorCode === 'invalid-credential') {
throw new Error('Invalid credentials, check your password');
}
if (errorCode === 'invalid-mfa-code') {
throw new Error('Invalid credentials, check your 2FA code');
}
throw error;
});
}

export async function login (params) {
Expand Down
4 changes: 4 additions & 0 deletions src/commands/tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,7 @@ export async function revoke (params) {
function formatDate (dateInput) {
return new Date(dateInput).toISOString().substring(0, 16).replace('T', ' ');
}

export async function showApiTokenUser () {
Logger.println(`You're logged in with an API Token, you can just create a new one with ${colors.blue('clever tokens create')} command`);
}

0 comments on commit 364cc56

Please sign in to comment.