Skip to content

Commit

Permalink
fix(apply:shema)!: require authentication (#100)
Browse files Browse the repository at this point in the history
* fix(apply:shema)!: require authentication

Linked to CU-6jpyjv

Command schema:apply now requires authentication, to avoid 401 errors.

* test(apply:schema): ✅ add tests for authentication on apply:schema

* refactor: 🎨 move tests to reflect the common user scenario

* refactor: 🎨 rename variables and change parameters order
  • Loading branch information
ghusse authored Jul 30, 2020
1 parent fb17fbe commit bbbb912
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 84 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"Arnaud Valensi <[email protected]>",
"Vincent Molinié <[email protected]>",
"David Routhieau <[email protected]>",
"Arnaud Besnier <[email protected]>"
"Arnaud Besnier <[email protected]>",
"Guillaume Gautreau <[email protected]>"
],
"bin": {
"forest": "./bin/run"
Expand Down
16 changes: 12 additions & 4 deletions src/commands/schema/apply.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
const { Command, flags } = require('@oclif/command');
const { flags } = require('@oclif/command');
const fs = require('fs');
const path = require('path');
const Joi = require('joi');
const SchemaSerializer = require('../../serializers/schema');
const SchemaSender = require('../../services/schema-sender');
const JobStateChecker = require('../../services/job-state-checker');
const AbstractAuthenticatedCommand = require('../../abstract-authenticated-command');
const logger = require('../../services/logger');
const authenticator = require('../../services/authenticator');

class ApplyCommand extends Command {
async run() {
class ApplyCommand extends AbstractAuthenticatedCommand {
async runIfAuthenticated() {
const oclifExit = this.exit.bind(this);
const { flags: parsedFlags } = this.parse(ApplyCommand);
const serializedSchema = this.readSchema();
const secret = this.getEnvironmentSecret(parsedFlags);
const authenticationToken = authenticator.getAuthToken();

this.log('Sending "./.forestadmin-schema.json"...');
const jobId = await new SchemaSender(serializedSchema, secret, oclifExit).perform();
const jobId = await new SchemaSender(
serializedSchema,
secret,
authenticationToken,
oclifExit,
).perform();

if (jobId) {
await new JobStateChecker('Processing schema', oclifExit).check(jobId);
Expand Down
7 changes: 7 additions & 0 deletions src/services/authenticator.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ const api = require('./api');
const logger = require('./logger');
const { ERROR_UNEXPECTED } = require('../utils/messages');

/**
* @class
*/
function Authenticator() {
/**
* @param {string?} path
* @returns {string}
*/
this.getAuthToken = (path = process.env.TOKEN_PATH || os.homedir()) => {
const forestrcToken = this.getVerifiedToken(`${path}/.forestrc`);
return forestrcToken || this.getVerifiedToken(`${path}/.lumberrc`);
Expand Down
14 changes: 13 additions & 1 deletion src/services/schema-sender.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@ const agent = require('superagent-promise')(require('superagent'), P);
const config = require('../config');
const logger = require('../services/logger');

function SchemaSender(serializedSchema, secret, oclifExit) {
/**
* @class
* @param {string} serializedSchema
* @param {string} secret
* @param {string} authenticationToken
* @param {(code: number) => void} oclifExit
*/
function SchemaSender(serializedSchema, secret, authenticationToken, oclifExit) {
/**
* @function
* @returns {Promise<number | undefined>}
*/
this.perform = () =>
agent
.post(`${config.serverHost()}/forest/apimaps`)
.set('forest-secret-key', secret)
.set('Authorization', `Bearer ${authenticationToken}`)
.send(serializedSchema)
.then(({ body }) => {
if (body && body.meta) {
Expand Down
187 changes: 109 additions & 78 deletions test/commands/schema/apply.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,132 +2,163 @@ const testCli = require('./../test-cli');
const ApplySchemaCommand = require('../../../src/commands/schema/apply');
const { testEnv, testEnv2 } = require('../../fixtures/env');
const {
loginValid,
postSchema,
postSchema404,
postSchema500,
postSchema503,
} = require('../../fixtures/api');
const { loginPasswordDialog } = require('../../fixtures/std');

const {
forestadminSchema,
forestadminSchemaSnake,
} = require('../../fixtures/files');

function postSchemaMatch(body) {
expect(body).toMatchObject({
meta: {
liana: 'forest-express-sequelize',
orm_version: '3.24.8',
database_type: 'postgres',
liana_version: '2.16.9',
},
data: [
{
type: 'collections',
id: 'Users',
attributes: {
name: 'Users',
},
},
],
});
return true;
}

describe('schema:apply', () => {
describe('with no environment secret', () => {
it('should exist with code 2', () => testCli({
describe('when the user is not logged in', () => {
it('should ask for the login/password and then send the schema', () => testCli({
file: {
chdir: '/tmp',
name: './.forestadmin-schema.json',
content: forestadminSchema,
},
env: testEnv,
token: 'any',
env: testEnv2,
api: [
loginValid(),
postSchema(postSchemaMatch),
],
command: () => ApplySchemaCommand.run([]),
std: [{
err: 'Cannot find your forest environment secret in the environment variable "FOREST_ENV_SECRET".\n'
+ 'Please set the "FOREST_ENV_SECRET" variable or pass the secret in parameter using --secret.',
}],
exitCode: 2,
std: [
...loginPasswordDialog,
{ out: 'Reading "./.forestadmin-schema.json"...' },
{
out: 'Using the forest environment secret found in the environment variable "FOREST_ENV_SECRET"',
},
{ out: 'Sending "./.forestadmin-schema.json"...' },
{ out: 'The schema is the same as before, nothing changed.' },
],
}));
});

describe('with an environment secret set in "FOREST_ENV_SECRET" environment variable', () => {
describe('with forest server returning 404', () => {
it('should exit with exit code 4', () => testCli({
describe('when the user is logged in', () => {
describe('with no environment secret', () => {
it('should exist with code 2', () => testCli({
file: {
chdir: '/tmp',
name: './.forestadmin-schema.json',
content: forestadminSchema,
},
env: testEnv2,
env: testEnv,
token: 'any',
command: () => ApplySchemaCommand.run([]),
api: [postSchema404()],
std: [{ err: 'Cannot find the project related to the environment secret you configured.' }],
exitCode: 4,
}));
});

describe('with forest server returning 503', () => {
it('should exit with exit code 5', () => testCli({
file: {
chdir: '/tmp',
name: './.forestadmin-schema.json',
content: forestadminSchema,
},
env: testEnv2,
api: [postSchema503()],
command: () => ApplySchemaCommand.run([]),
std: [{ err: 'Forest is in maintenance for a few minutes. We are upgrading your experience in the forest. We just need a few more minutes to get it right.' }],
exitCode: 5,
std: [{
err: 'Cannot find your forest environment secret in the environment variable "FOREST_ENV_SECRET".\n'
+ 'Please set the "FOREST_ENV_SECRET" variable or pass the secret in parameter using --secret.',
}],
exitCode: 2,
}));
});

describe('with forest server returning 200', () => {
const postSchemaMatch = (body) => {
expect(body).toMatchObject({
meta: {
liana: 'forest-express-sequelize',
orm_version: '3.24.8',
database_type: 'postgres',
liana_version: '2.16.9',
},
data: [
{
type: 'collections',
id: 'Users',
attributes: {
name: 'Users',
},
},
],
});
return true;
};

describe('with a schema with camelcased keys', () => {
it('should send the schema', () => testCli({
describe('with an environment secret set in "FOREST_ENV_SECRET" environment variable', () => {
describe('with forest server returning 404', () => {
it('should exit with exit code 4', () => testCli({
file: {
chdir: '/tmp',
name: './.forestadmin-schema.json',
content: forestadminSchema,
},
env: testEnv2,
token: 'any',
api: [postSchema(postSchemaMatch)],
env: testEnv2,
command: () => ApplySchemaCommand.run([]),
std: [
{ out: 'Reading "./.forestadmin-schema.json"...' },
{
out: 'Using the forest environment secret found in the environment variable "FOREST_ENV_SECRET"',
},
{ out: 'Sending "./.forestadmin-schema.json"...' },
{ out: 'The schema is the same as before, nothing changed.' },
],
api: [postSchema404()],
std: [{ err: 'Cannot find the project related to the environment secret you configured.' }],
exitCode: 4,
}));
});

describe('with a schema with snakecased keys', () => {
it('should send the schema', () => testCli({
describe('with forest server returning 503', () => {
it('should exit with exit code 5', () => testCli({
file: {
chdir: '/tmp',
name: './.forestadmin-schema.json',
content: forestadminSchemaSnake,
content: forestadminSchema,
},
env: testEnv2,
token: 'any',
api: [postSchema(postSchemaMatch)],
api: [postSchema503()],
command: () => ApplySchemaCommand.run([]),
std: [
{ out: 'Reading "./.forestadmin-schema.json"...' },
{
out: 'Using the forest environment secret found in the environment variable "FOREST_ENV_SECRET"',
},
{ out: 'Sending "./.forestadmin-schema.json"...' },
{ out: 'The schema is the same as before, nothing changed.' },
],
std: [{ err: 'Forest is in maintenance for a few minutes. We are upgrading your experience in the forest. We just need a few more minutes to get it right.' }],
exitCode: 5,
token: 'any',
}));
});

describe('with forest server returning 200', () => {
describe('with a schema with camelcased keys', () => {
it('should send the schema', () => testCli({
file: {
chdir: '/tmp',
name: './.forestadmin-schema.json',
content: forestadminSchema,
},
env: testEnv2,
token: 'any',
api: [postSchema(postSchemaMatch)],
command: () => ApplySchemaCommand.run([]),
std: [
{ out: 'Reading "./.forestadmin-schema.json"...' },
{
out: 'Using the forest environment secret found in the environment variable "FOREST_ENV_SECRET"',
},
{ out: 'Sending "./.forestadmin-schema.json"...' },
{ out: 'The schema is the same as before, nothing changed.' },
],
}));
});

describe('with a schema with snakecased keys', () => {
it('should send the schema', () => testCli({
file: {
chdir: '/tmp',
name: './.forestadmin-schema.json',
content: forestadminSchemaSnake,
},
env: testEnv2,
token: 'any',
api: [postSchema(postSchemaMatch)],
command: () => ApplySchemaCommand.run([]),
std: [
{ out: 'Reading "./.forestadmin-schema.json"...' },
{
out: 'Using the forest environment secret found in the environment variable "FOREST_ENV_SECRET"',
},
{ out: 'Sending "./.forestadmin-schema.json"...' },
{ out: 'The schema is the same as before, nothing changed.' },
],
}));
});
});
});
});
});
Expand Down

0 comments on commit bbbb912

Please sign in to comment.