pm2/lib/API/PM2/CliAuth.js
2018-05-30 14:48:21 +02:00

286 lines
8.0 KiB
JavaScript

'use strict'
const AuthStrategy = require('@pm2/js-api/src/auth_strategies/strategy')
const http = require('http')
const fs = require('fs')
const url = require('url')
const exec = require('child_process').exec
const async = require('async')
const path = require('path')
const os = require('os')
const needle = require('needle');
const chalk = require('chalk')
const cst = require('../../../constants.js');
module.exports = class CustomStrategy extends AuthStrategy {
// the client will try to call this but we handle this part ourselves
retrieveTokens (km, cb) {
this.authenticated = false
this.callback = cb
this.km = km
this.BASE_URI = 'https://app.keymetrics.io';
}
// so the cli know if we need to tell user to login/register
isAuthenticated () {
return new Promise((resolve, reject) => {
if (this.authenticated) return resolve(true)
let tokensPath = cst.PM2_IO_ACCESS_TOKEN
fs.readFile(tokensPath, (err, tokens) => {
if (err && err.code === 'ENOENT') return resolve(false)
if (err) return reject(err)
// verify that the token is valid
try {
tokens = JSON.parse(tokens || '{}')
} catch (err) {
fs.unlinkSync(tokensPath)
return resolve(false)
}
// if the refresh tokens is here, the user could be automatically authenticated
return resolve(typeof tokens.refresh_token === 'string')
})
})
}
verifyToken (refresh) {
return this.km.auth.retrieveToken({
client_id: this.client_id,
refresh_token: refresh
})
}
// called when we are sure the user asked to be logged in
_retrieveTokens (optionalCallback) {
const km = this.km
const cb = this.callback
async.tryEach([
// try to find the token via the environement
(next) => {
if (!process.env.KM_TOKEN) {
return next(new Error('No token in env'))
}
this.verifyToken(process.env.KM_TOKEN)
.then((res) => {
return next(null, res.data)
}).catch(next)
},
// try to find it in the file system
(next) => {
return next(new Error('nope'))
fs.readFile(cst.PM2_IO_ACCESS_TOKEN, (err, tokens) => {
if (err) return next(err)
// verify that the token is valid
tokens = JSON.parse(tokens || '{}')
if (new Date(tokens.expire_at) > new Date(new Date().toISOString())) {
return next(null, tokens)
}
this.verifyToken(tokens.refresh_token)
.then((res) => {
return next(null, res.data)
}).catch(next)
})
},
// otherwise make the whole flow
(next) => {
return this.loginViaCLI((data) => {
// verify that the token is valid
this.verifyToken(data.refresh_token)
.then((res) => {
return next(null, res.data)
}).catch(next)
})
}
], (err, result) => {
// if present run the optional callback
if (typeof optionalCallback === 'function') {
optionalCallback(err, result)
}
if (result.refresh_token) {
this.authenticated = true
let file = cst.PM2_IO_ACCESS_TOKEN
fs.writeFile(file, JSON.stringify(result), () => {
return cb(err, result)
})
} else {
return cb(err, result)
}
})
}
loginViaCLI (cb) {
var promptly = require('promptly');
let retry = () => {
promptly.prompt('Username or Email: ', (err, username) => {
if (err) return retry();
promptly.password('Password: ', { replace : '*' }, (err, password) => {
if (err) return retry();
this._loginUser({
username: username,
password: password
}, (err, data) => {
if (err) return retry()
cb(data)
})
})
})
}
retry()
}
_loginUser (user_info, cb) {
const querystring = require('querystring');
const AUTH_URI = 'https://id.keymetrics.io'
const URL_AUTH = '/api/oauth/authorize?response_type=token&scope=all&client_id=' +
this.client_id + '&redirect_uri=https://app.keymetrics.io';
console.log(chalk.bold('[-] Logging to pm2.io'))
needle.get(AUTH_URI + URL_AUTH, (err, res) => {
if (err) return cb(err);
var cookie = res.cookies;
needle.post(AUTH_URI + '/api/oauth/login', user_info, {
cookies : cookie
}, (err, resp, body) => {
if (err) return cb(err);
if (resp.statusCode != 200) return cb('Wrong credentials');
var location = resp.headers['x-redirect'];
var redirect = AUTH_URI + location;
needle.get(redirect, {
cookies : cookie
}, (err, res) => {
if (err) return cb(err);
var refresh_token = querystring.parse(url.parse(res.headers.location).query).access_token;
needle.post(AUTH_URI + '/api/oauth/token', {
client_id : this.client_id,
grant_type : 'refresh_token',
refresh_token : refresh_token,
scope : 'all'
}, (err, res, body) => {
if (err) return cb(err);
console.log(chalk.bold.green('[+] Logged in!'))
return cb(null, body);
})
});
});
});
}
registerViaCLI (cb) {
var promptly = require('promptly');
console.log(chalk.bold('[-] Registering to pm2.io'));
var retry = () => {
promptly.prompt('Username: ', {
validator : this._validateUsername,
retry : true
}, (err, username) => {
promptly.prompt('Email: ', {
validator : this._validateEmail,
retry : true
},(err, email) => {
promptly.password('Password: ', { replace : '*' }, (err, password) => {
process.stdout.write('Creating account on pm2.io...');
var inter = setInterval(function() {
process.stdout.write('.');
}, 200);
this._registerUser({
email : email,
password : password,
username : username
}, (err, data) => {
clearInterval(inter)
if (err) {
console.error()
console.error(chalk.bold.red(err));
return retry()
}
console.log(chalk.green.bold('\n[+] Account created!'))
this._loginUser({
username: username,
password: password
}, (err, data) => {
this.callback(err, data)
return cb(err, data)
})
})
});
});
})
}
retry();
}
/**
* Register function
* @param user_info.username
* @param user_info.password
* @param user_info.email
*/
_registerUser (user_info, cb) {
needle.post(this.BASE_URI + '/api/oauth/register', user_info, {
json: true,
headers: {
'X-Register-Provider': 'pm2-register'
}
}, function (err, res, body) {
if (err) return cb(err);
if (body.email && body.email.message) return cb(body.email.message);
if (body.username && body.username.message) return cb(body.username.message);
if (!body.access_token) return cb(body.msg)
cb(null, {
token : body.refresh_token.token
})
});
}
_validateEmail (email) {
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (re.test(email) == false)
throw new Error('Not an email');
return email;
}
_validateUsername (value) {
if (value.length < 6) {
throw new Error('Min length of 6');
}
return value;
};
deleteTokens (km) {
return new Promise((resolve, reject) => {
// revoke the refreshToken
km.auth.revoke()
.then(res => {
// remove the token from the filesystem
let file = cst.PM2_IO_ACCESS_TOKEN
fs.unlinkSync(file)
return resolve(res)
}).catch(reject)
})
}
}