fleet/website/api/controllers/create-vanta-authorization-request.js
Eric ace1fa0d37
Website: Add Vanta integration (#9072)
* stubing out required pages

* Add Vanta authorization action, create externalAuthorization(name will most likely change) Model

* rebuild cloud sdk

* Draft script

* update importer, update connect-vanta page

* layout updates

* update action

* change model name

* update model name

* update script

* Update vanta callback page

* add new model to lint config

* update model attributes

* update vanta connection page to require url protocol, fix lint error in routes

* rename page, vanta-callback » vanta-authorization

* Update errors, handle incomplete connections

* Update model attribute names

* Remove console.log(), add error message for non-Fleet Premium users

* update importer, fix lint error

* update send-data-to-vanta script

* Update create-vanta-authorization-request.js

* update page name

* update comments and error messages

* layout changes

* update status codes used in exits

* Add comment

* Update .eslintrc

* Update create-vanta-authorization-request.js

* Update view-vanta-authorization.js

* Update VantaConnection.js

* Update send-data-to-vanta.js

* Update create-vanta-authorization-request.js

* Update mobile styles

* update text

* update error text, show sucess message if account has already been authorized

* lint fix

Co-authored-by: Mike McNeil <mikermcneil@users.noreply.github.com>
2022-12-21 23:39:39 -06:00

142 lines
6.3 KiB
JavaScript
Vendored

module.exports = {
friendlyName: 'Create Vanta authorization request',
description: 'Returns a URL used to authorize requests to the user\'s Vanta account from fleetdm.com on behalf of the user.',
inputs: {
emailAddress: {
type: 'string',
required: true,
},
fleetInstanceUrl: {
type: 'string',
required: true,
},
fleetApiKey: {
type: 'string',
required: true,
}
},
exits: {
success: {
outputType: 'string'
},
connectionAlreadyExists: {
description: 'The Fleet instance url provided is already connected to a Vanta account.',
statusCode: 409,
},
fleetInstanceNotResponding: {
description: 'A http request to the user\'s Fleet instance failed.',
statusCode: 404,
},
invalidToken: {
description: 'The provided token for the api-only user could not be used to authorize requests from fleetdm.com',
statusCode: 403,
},
invalidLicense: {
description: 'The Fleet instance provided is using a Free license.',
statusCode: 400,
},
nonApiOnlyUser: {
description: 'The provided API token for this Fleet instance is not associated with an api-only user.',
statusCode: 400,
},
insufficientPermissions:{
description: 'The api-only user associated with the provided token does not have the propper permissions to query the users endpoint.',
statusCode: 403,
},
},
fn: async function (inputs) {
let url = require('url');
// Look for any existing VantaConnection records that use this fleet instance URL.
let existingConnectionRecord = await VantaConnection.findOne({fleetInstanceUrl: inputs.fleetInstanceUrl});
// Generate the `state` string for this request.
let generatedStateForThisRequest = await sails.helpers.strings.random.with({len: 10});
// Generate a sourceId for this user. This value will be used as the indentifier of ther user's vanta connection
let generatedSourceIdSuffix = await sails.helpers.strings.random.with({len: 20, style: 'url-friendly'});
let sourceIDForThisRequest = 'fleet_'+generatedSourceIdSuffix;
if(existingConnectionRecord) {
// If an active Vanta connection exists for the provided Fleet instance url, we'll throw a 'connectionAlreadyExists' exit, and the user will be asked to contact us to make changes to the existing vanta connection.
if(existingConnectionRecord.isConnectedToVanta) {
throw 'connectionAlreadyExists';
} else if(existingConnectionRecord.fleetApiKey !== inputs.fleetApiKey && existingConnectionRecord.emailAddress !== inputs.emailAddress) {
// If an incomplete connection exists, and the API token and email address provided do not match. The user will be asked to contact us to make changes to their connection.
throw 'connectionAlreadyExists';
} else {
// If an inactive and incomplete Vanta connection exists that uses the same API token and email address, we'll use the sourceId from that record for this request.
sourceIDForThisRequest = existingConnectionRecord.vantaSourceId;
}
}
// Check the fleet instance url and API key provided
let responseFromFleetInstance = await sails.helpers.http.get(inputs.fleetInstanceUrl+'/api/v1/fleet/me',{},{'Authorization': 'Bearer ' +inputs.fleetApiKey})
.intercept('requestFailed', 'fleetInstanceNotResponding')
.intercept('non200Response', 'invalidToken')
.intercept((error)=>{
return new Error(`When sending a request to a Fleet instance's /me endpoint to verify that a token meets the requirements for a Vanta connection, an error occurred: ${error}`);
});
// Throw an error if the provided API token is not an API-only user.
if(!responseFromFleetInstance.user.api_only) {
throw 'nonApiOnlyUser';
}
// If the API-only user associated with the token provided does not have the admin role, we'll throw an error.
// We require an admin token so we can send Vanta data about all of the active user accounts on the requesting user's Fleet instance
if(responseFromFleetInstance.user.global_role !== 'admin') {
throw 'insufficientPermissions';
}
// Send a request to the provided Fleet instance's /config endpoint to check their license tier.
let configResponse = await sails.helpers.http.get(inputs.fleetInstanceUrl+'/api/v1/fleet/config', {}, {'Authorization': 'Bearer ' +inputs.fleetApiKey})
.intercept('requestFailed','fleetInstanceNotResponding')
.intercept('non200Response', 'invalidToken')
.intercept((error)=>{
return new Error(`When sending a request to a Fleet instance's /config API endpoint for a Vanta connection, an error occurred: ${error}`);
});
// If the user's Fleet instance has a free license, we'll throw the 'invalidLicense' exit and let the user know that this is only available for Fleet Premium subscribers.
if(configResponse.license.tier === 'free') {
throw 'invalidLicense';
}
// If we're not using an existing vantaConnection record for this request, we'll create a new one.
if(!existingConnectionRecord) {
// Create the VantaConnection record for this request.
await VantaConnection.create({
emailAddress: inputs.emailAddress,
vantaSourceId: sourceIDForThisRequest,
fleetInstanceUrl: inputs.fleetInstanceUrl,
fleetApiKey: inputs.fleetApiKey,
});
}
// Build the authorization URL for this request.
let vantaAuthorizationRequestURL = `https://app.vanta.com/oauth/authorize?client_id=${encodeURIComponent(sails.config.custom.vantaAuthorizationClientId)}&scope=connectors.self:write-resource connectors.self:read-resource&state=${encodeURIComponent(generatedStateForThisRequest)}&source_id=${encodeURIComponent(sourceIDForThisRequest)}&redirect_uri=${encodeURIComponent(url.resolve(sails.config.custom.baseUrl, '/vanta-authorization'))}&response_type=code`;
// Set a `state` cookie on the user's browser. This value will be checked against a query parameter when the user returns to fleetdm.com.
this.res.cookie('state', generatedStateForThisRequest, {signed: true});
// Set the sourceId to a cookie, we'll use this value to find the database record we created for this request when the user returns to fleetdm.com.
this.res.cookie('vantaSourceId', sourceIDForThisRequest, {signed: true});
return vantaAuthorizationRequestURL;
}
};