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

Env add with authorization on portal website #528

Merged
merged 2 commits into from
Nov 18, 2023
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
111 changes: 37 additions & 74 deletions bin/pos-cli-env-add.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,16 @@
#!/usr/bin/env node

const program = require('commander'),
fs = require('fs'),
rl = require('readline'),
logger = require('../lib/logger'),
validate = require('../lib/validators'),
files = require('../lib/files'),
Portal = require('../lib/portal');

// turn to promise
const getPassword = () => {
return new Promise((resolve, reject) => {
const reader = rl.createInterface({ input: process.stdin, output: process.stdout });
reader.stdoutMuted = true;
reader.question('Password: ', password => {
reader.close();
logger.Info('');
resolve(password);
});

reader._writeToOutput = stringToWrite => {
(reader.stdoutMuted && reader.output.write('*')) || reader.output.write(stringToWrite);
};
});
};

const storeEnvironment = settings => {
logger.Debug(`[storeEnvironment] ${JSON.stringify(settings, null, 2)}`);

const environmentSettings = {
[settings.endpoint]: {
url: settings.url,
token: settings.token,
email: settings.email
}
};

const configPath = files.getConfigPath();
logger.Debug(`[storeEnvironment] Current config path: ${configPath}`);

const newSettings = Object.assign({}, files.getConfig(), environmentSettings);
fs.writeFileSync(configPath, JSON.stringify(newSettings, null, 2));
const program = require('commander');
const logger = require('../lib/logger');
const validate = require('../lib/validators');
const Portal = require('../lib/portal');
const waitForStatus = require('../lib/data/waitForStatus');
const { readPassword } = require('../lib/utils/password');
const { storeEnvironment, deviceAuthorizationFlow } = require('../lib/environments');

const saveToken = (settings, token) => {
storeEnvironment(Object.assign(settings, { token: token }));
logger.Success(`Environment ${settings.url} as ${settings.environment} has been added successfuly.`);
};

const help = () => {
Expand All @@ -49,18 +19,26 @@ const help = () => {
}

const checkParams = params => {
validate.existence({ argumentValue: params.email, argumentName: 'email', fail: help });
validate.existence({ argumentValue: params.url, argumentName: 'URL', fail: help });
// validate.existence({ argumentValue: params.email, argumentName: 'email', fail: help });
if (params.email) validate.email(params.email);

validate.existence({ argumentValue: program.args[0], argumentName: 'environment', fail: help });
validate.email(params.email);

validate.existence({ argumentValue: params.url, argumentName: 'URL', fail: help });
if (params.url.slice(-1) != '/') {
params.url = params.url + '/';
}

validate.url(params.url);
};


const login = async (email, password, url) => {
return Portal.login(email, password, url)
.then(response => {
if (response) Promise.resolve(response[0].token);
})
}

program
.name('pos-cli env add')
.arguments('[environment]', 'name of environment. Example: staging')
Expand All @@ -70,42 +48,27 @@ program
'--token <token>',
'if you have a token you can add it directly to pos-cli configuration without connecting to portal'
)
.action((environment, params) => {
.action(async (environment, params) => {
checkParams(params);
const settings = { url: params.url, endpoint: environment, email: params.email };

if (params.token) {
storeEnvironment(Object.assign(settings, { token: params.token }));
logger.Success(`Environment ${params.url} as ${environment} has been added successfuly.`);
process.exit(0);
}

logger.Info(
`Please make sure that you have a permission to deploy. \n
You can verify it here: ${Portal.HOST}/me/permissions`,
{ hideTimestamp: true }
);

getPassword().then(password => {
token = params.token;
} else if (!params.email){
token = await deviceAuthorizationFlow(params.url);
} else {
logger.Info(
`Please make sure that you have a permission to deploy. \n You can verify it here: ${Portal.HOST}/me/permissions`,
{ hideTimestamp: true }
);

const password = await readPassword();
logger.Info(`Asking ${Portal.HOST} for access token...`);

Portal.login(params.email, password, params.url)
.then(response => {
const token = response[0].token;
token = await login(params.email, password, params.url);
}

if (token) {
storeEnvironment(Object.assign(settings, { token }));
logger.Success(`Environment ${params.url} as ${environment} has been added successfuly.`);
}
})
.catch(e => {
if (e.statusCode === 401) {
logger.Error('Either email or password is incorrect.');
} else {
logger.Error(e.message);
}
});
});
if (token) saveToken(settings, token);
});

program.parse(process.argv);
55 changes: 54 additions & 1 deletion lib/apiRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,57 @@ const apiRequest = ({ method = 'GET', uri, body, headers, formData, json = true,
});
}

module.exports = apiRequest;
const apiRequestRaw = ({ method = 'GET', uri, body, headers, formData, json = true, forever, request = requestPromise }) => {
logger.Debug(`[${method}] ${uri}`);
return request({
method,
uri,
body,
headers,
formData,
json,
forever
})
.catch(errors.StatusCodeError, request => {
switch (request.statusCode) {
case 504:
ServerError.gatewayTimeout(request);
break;
case 502:
ServerError.badGateway(request);
break;
case 500:
ServerError.internal(request);
break;
case 413:
ServerError.entityTooLarge(request);
break;
case 422:
ServerError.unprocessableEntity(request);
break;
default:
return request;
}
})
.catch(errors.RequestError, (reason) => {
switch (reason.cause.code) {
case 'ENOTFOUND':
ServerError.addressNotFound(reason);
break;

case 'ENETDOWN':
ServerError.netDown(reason);
logger.Error(`${reason.cause}`, { exit: true });
break;

default:
logger.Error('Request to the server failed.', { exit: false });
logger.Error(`${reason.cause}`, { exit: false });
}
});
}

module.exports = {
apiRequest: apiRequest,
apiRequestRaw: apiRequestRaw
}
6 changes: 2 additions & 4 deletions lib/data/waitForStatus.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
const logger = require('../logger');

const waitForStatus = (statusCheck, pendingStatus, successStatus) => {
const waitForStatus = (statusCheck, pendingStatus, successStatus, interval = 1500) => {
return new Promise((resolve, reject) => {
(getStatus = () => {
statusCheck().then(response => {
const status = response.status;
if (status) {
// if (status === 'pending' || status === 'ready_for_export') {
if (status === pendingStatus) {
setTimeout(getStatus, 1500);
// } else if (status === 'done' || status === 'success') {
setTimeout(getStatus, interval);
} else if (status === successStatus) {
resolve(response);
} else {
Expand Down
75 changes: 75 additions & 0 deletions lib/environments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const fs = require('fs');
const open = require('open');
const files = require('../lib/files');
const Portal = require('../lib/portal');
const logger = require('../lib/logger');
const waitForStatus = require('../lib/data/waitForStatus');

const storeEnvironment = settings => {
logger.Debug(`[storeEnvironment] ${JSON.stringify(settings, null, 2)}`);

const environmentSettings = {
[settings.endpoint]: {
url: settings.url,
token: settings.token,
email: settings.email
}
};

const configPath = files.getConfigPath();
logger.Debug(`[storeEnvironment] Current config path: ${configPath}`);

const newSettings = Object.assign({}, files.getConfig(), environmentSettings);
fs.writeFileSync(configPath, JSON.stringify(newSettings, null, 2));
};

const waitForAccessToken = async (deviceCode, interval) => {
const tokenResponse = await waitForStatus(
() => {
return Portal.fetchDeviceAccessToken(deviceCode).then(response => {
let token;
if (response['access_token']) {
token = { ...response, status: 'success' };
} else {
// TODO: use node-fetch instead of request-promise
const responseBody = response.response.body;
switch(response.response.statusCode){
case 400:
token = { status: responseBody.error };
break;
case 200:
token = { ...responseBody, status: 'success' };
break;
default:
throw `Unhandled response: ${response.statusCode}`
}
}

return Promise.resolve(token);
})
}, 'authorization_pending', 'success', interval
);

return tokenResponse['access_token'];
};

const deviceAuthorizationFlow = async (instanceUrl) => {
const instanceDomain = (new URL(instanceUrl)).hostname;
const deviceAuthorizationResponse = await Portal.requestDeviceAuthorization(instanceDomain);
logger.Debug('deviceAuthorizationResponse', deviceAuthorizationResponse);

const deviceAuthorization = JSON.parse(deviceAuthorizationResponse);
const verificationUrl = deviceAuthorization['verification_uri_complete'];
const deviceCode = deviceAuthorization['device_code']
const interval = (deviceAuthorization['interval'] || 5) * 1000;

await open(verificationUrl);

const accessToken = await waitForAccessToken(deviceCode, interval);
return accessToken;
};

module.exports = {
deviceAuthorizationFlow: deviceAuthorizationFlow,
storeEnvironment: storeEnvironment
}
1 change: 1 addition & 0 deletions lib/modules/dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const resolveDependencies = async (modules, getVersions) => {

const findModuleVersion = async (moduleName, moduleVersion, getVersions) => {
const modules = await getVersions([moduleName]);
logger.Debug(`modulesVersions: ${JSON.stringify(modules)}`);
const versions = Object.keys(modules.find(m => m.module === moduleName)?.versions || {});
let version;
if(moduleVersion){
Expand Down
27 changes: 21 additions & 6 deletions lib/portal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const apiRequest = require('./apiRequest');
const { apiRequest, apiRequestRaw } = require('./apiRequest');
const logger = require('./logger');

const HOST = process.env.PARTNER_PORTAL_HOST || 'https://partners.platformos.com';
Expand All @@ -10,29 +10,25 @@ const Portal = {
return apiRequest({
uri: `${HOST}/api/user_tokens`,
headers: { UserAuthorization: `${email}:${password}`, InstanceDomain: url },
json: true
});
},
jwtToken: (email, password) => {
return apiRequest({
method: 'POST',
uri: `${HOST}/api/authenticate`,
formData: { email: email, password: password },
json: true
});
},
findModules: (token, name) => {
return apiRequest({
method: 'GET',
uri: `${HOST}/api/pos_modules/?modules=${name}`,
headers: { Authorization: `Bearer ${token}` },
json: true
});
},
moduleVersions(modules) {
return apiRequest({
uri: `${HOST}/api/pos_modules?modules=${modules.join(',')}`,
json: true
});
},
createVersion: (token, url, name, posModuleId) => {
Expand All @@ -41,14 +37,33 @@ const Portal = {
uri: `${HOST}/api/pos_modules/${posModuleId}/pos_module_versions`,
body: { pos_module_version: { archive: url, name: name } },
headers: { Authorization: `Bearer ${token}` },
json: true
});
},
moduleVersionStatus: (token, posModuleId, moduleVersionId) => {
return apiRequest({
method: 'GET',
uri: `${HOST}/api/pos_modules/${posModuleId}/pos_module_versions/${moduleVersionId}`,
headers: { Authorization: `Bearer ${token}` },
});
},
requestDeviceAuthorization: (instanceDomain) => {
return apiRequest({
method: 'POST',
uri: `${HOST}/oauth/authorize_device`,
formData: {
domain: instanceDomain
},
json: false
});
},
fetchDeviceAccessToken: (deviceCode) => {
return apiRequestRaw({
method: 'POST',
uri: `${HOST}/oauth/device_token`,
formData: {
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
device_code: deviceCode
},
json: true
});
},
Expand Down
2 changes: 1 addition & 1 deletion lib/proxy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const requestPromise = require('request-promise');

const apiRequest = require('./apiRequest');
const { apiRequest } = require('./apiRequest');
const logger = require('./logger');
const version = require('../package.json').version;
const Portal = require('./portal');
Expand Down
Loading