Files
vault/ui/mirage/handlers/mfa-login.js
Jordan Reimer ca14c1919f MFA Config (#15200)
* adds mirage factories for mfa methods and login enforcement

* adds mirage handler for mfa config endpoints

* adds mirage identity manager for uuids

* updates mfa test to use renamed mfaLogin mirage handler

* updates mfa login workflow for push methods (#15214)

* MFA Login Enforcement Model (#15244)

* adds mfa login enforcement model, adapter and serializer

* updates mfa methods to hasMany realtionship and transforms property names

* updates login enforcement adapter to use urlForQuery over buildURL

* Model for mfa method (#15218)

* Model for mfa method

* Added adapter and serializer for mfa method

- Updated mfa method model
- Basic route to handle list view
- Added MFA to access nav

* Show landing page if methods are not configured

* Updated adapter,serializer

- Backend is adding new endpoint to list all the mfa methods

* Updated landing page

- Added MFA diagram
- Created helper to resolve full path for assets like images

* Remove ember assign

* Fixed failing test

* MFA method and enforcement list view (#15353)

* MFA method and enforcement list view

- Added new route for list views
- List mfa methods along with id, type and icon
- Added client side pagination to list views

* Throw error if method id is not present

* MFA Login Enforcement Form (#15410)

* adds mfa login enforcement form and header components and radio card component

* skips login enforcement form tests for now

* adds jsdoc annotations for mfa-login-enforcement-header component

* adds error handling when fetching identity targets in login enforcement form component

* updates radio-card label elements

* MFA Login Enforcement Create and Edit routes (#15422)

* adds mfa login enforcement form and header components and radio card component

* skips login enforcement form tests for now

* updates to login enforcement form to fix issues hydrating methods and targets from model when editing

* updates to mfa-config mirage handler and login enforcement handler

* fixes issue with login enforcement serializer normalizeItems method throwing error on save

* updates to mfa route structure

* adds login enforcement create and edit routes

* MFA Login Enforcement Read Views (#15462)

* adds login enforcement read views

* skip mfa-method-list-item test for now

* MFA method form (#15432)

* MFA method form

- Updated model for form attributes
- Form for editing, creating mfa methods

* Added comments

* Update model for mfa method

* Refactor buildURL in mfa method adapter

* Update adapter to handle mfa create

* Fixed adapter to handle create mfa response

* Sidebranch: MFA end user setup (#15273)

* initial setup of components and route

* fix navbar

* replace parent component with controller

* use auth service to return entity id

* adapter and some error handling:

* clean up adapter and handle warning

* wip

* use library for qrCode generation

* clear warning and QR code display fix

* flow for restart setup

* add documentation

* clean up

* fix warning issue

* handle root user

* remove comment

* update copy

* fix margin

* address comment

* MFA Guided Setup Route (#15479)

* adds mfa method create route with type selection workflow

* updates mfa method create route links to use DocLink component

* MFA Guided Setup Config View (#15486)

* adds mfa guided setup config view

* resets type query param on mfa method create route exit

* hide next button if type is not selected in mfa method create route

* updates to sure correct state when changing mfa method type in guided setup

* Enforcement view at MFA method level (#15485)

- List enforcements for each mfa method
- Delete MFA method if no enforcements are present
- Moved method, enforcement list item component to mfa folder

* MFA Login Enforcement Validations (#15498)

* adds model and form validations for mfa login enforcements

* updates mfa login enforcement validation messages

* updates validation message for mfa login enforcement targets

* adds transition action to configure mfa button on landing page

* unset enforcement on preference change in mfa guided setup workflow

* Added validations for mfa method model (#15506)

* UI/mfa breadcrumbs and small fixes (#15499)

* add active class when on index

* breadcrumbs

* remove box-shadow to match designs

* fix refresh load mfa-method

* breadcrumb create

* add an empty state the enforcements list view

* change to beforeModel

* UI/mfa small bugs (#15522)

* remove pagintion and fix on methods list view

* fix enforcements

* Fix label for value on radio-card (#15542)

* MFA Login Enforcement Component Tests (#15539)

* adds tests for mfa-login-enforcement-header component

* adds tests for mfa-login-enforcement-form component

* Remove default values from mfa method model (#15540)

- use passcode had a default value, as a result it was being sent
with all the mfa method types during save and edit flows..

* UI/mfa small cleanup (#15549)

* data-test-mleh -> data-test-mfa

* Only one label per radio card

* Remove unnecessary async

* Simplify boolean logic

* Make mutation clear

* Revert "data-test-mleh -> data-test-mfa"

This reverts commit 31430df7bb42580a976d082667cb6ed1f09c3944.

* updates mfa login enforcement form to only display auth method types for current mounts as targets (#15547)

* remove token type (#15548)

* remove token type

* conditional param

* removes type from mfa method payload and fixes bug transitioning to method route on save success

* removes punctuation from mfa form error message string match

* updates qr-code component invocation to angle bracket

* Re-trigger CI jobs with empty commit

Co-authored-by: Arnav Palnitkar <arnav@hashicorp.com>
Co-authored-by: Angel Garbarino <Monkeychip@users.noreply.github.com>
Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
Co-authored-by: Michele Degges <mdeggies@gmail.com>
2022-05-20 18:40:16 -06:00

156 lines
6.6 KiB
JavaScript

import { Response } from 'miragejs';
import Ember from 'ember';
import fetch from 'fetch';
// initial auth response cache -- lookup by mfa_request_id key
const authResponses = {};
// mfa requirement cache -- lookup by mfa_request_id key
const mfaRequirement = {};
// may be imported in tests when the validation request needs to be intercepted to make assertions prior to returning a response
// in that case it may be helpful to still use this validation logic to ensure to payload is as expected
export const validationHandler = (schema, req) => {
try {
const { mfa_request_id, mfa_payload } = JSON.parse(req.requestBody);
const mfaRequest = mfaRequirement[mfa_request_id];
if (!mfaRequest) {
return new Response(404, {}, { errors: ['MFA Request ID not found'] });
}
// validate request body
for (let constraintId in mfa_payload) {
// ensure ids were passed in map
const method = mfaRequest.methods.find(({ id }) => id === constraintId);
if (!method) {
return new Response(400, {}, { errors: [`Invalid MFA constraint id ${constraintId} passed in map`] });
}
// test non-totp validation by rejecting all pingid requests
if (method.type === 'pingid') {
return new Response(403, {}, { errors: ['PingId MFA validation failed'] });
}
// validate totp passcode
const passcode = mfa_payload[constraintId][0];
if (method.uses_passcode) {
if (passcode !== 'test') {
const error =
{
used: 'code already used; new code is available in 30 seconds',
limit:
'maximum TOTP validation attempts 4 exceeded the allowed attempts 3. Please try again in 15 seconds',
}[passcode] || 'failed to validate';
console.log(error);
return new Response(403, {}, { errors: [error] });
}
} else if (passcode) {
// for okta and duo, reject if a passcode was provided
return new Response(400, {}, { errors: ['Passcode should only be provided for TOTP MFA type'] });
}
}
return authResponses[mfa_request_id];
} catch (error) {
console.log(error);
return new Response(500, {}, { errors: ['Mirage Handler Error: /sys/mfa/validate'] });
}
};
export default function (server) {
// generate different constraint scenarios and return mfa_requirement object
const generateMfaRequirement = (req, res) => {
const { user } = req.params;
// uses_passcode automatically set to true in factory for totp type
const m = (type, uses_passcode = false) => server.create('mfa-method', { type, uses_passcode });
let mfa_constraints = {};
let methods = []; // flat array of methods for easy lookup during validation
function generator() {
const methods = [];
const constraintObj = [...arguments].reduce((obj, methodArray, index) => {
obj[`test_${index}`] = { any: methodArray };
methods.push(...methodArray);
return obj;
}, {});
return [constraintObj, methods];
}
if (user === 'mfa-a') {
[mfa_constraints, methods] = generator([m('totp')]); // 1 constraint 1 passcode
} else if (user === 'mfa-b') {
[mfa_constraints, methods] = generator([m('okta')]); // 1 constraint 1 non-passcode
} else if (user === 'mfa-c') {
[mfa_constraints, methods] = generator([m('totp'), m('duo', true)]); // 1 constraint 2 passcodes
} else if (user === 'mfa-d') {
[mfa_constraints, methods] = generator([m('okta'), m('duo')]); // 1 constraint 2 non-passcode
} else if (user === 'mfa-e') {
[mfa_constraints, methods] = generator([m('okta'), m('totp')]); // 1 constraint 1 passcode 1 non-passcode
} else if (user === 'mfa-f') {
[mfa_constraints, methods] = generator([m('totp')], [m('duo', true)]); // 2 constraints 1 passcode for each
} else if (user === 'mfa-g') {
[mfa_constraints, methods] = generator([m('okta')], [m('duo')]); // 2 constraints 1 non-passcode for each
} else if (user === 'mfa-h') {
[mfa_constraints, methods] = generator([m('totp')], [m('okta')]); // 2 constraints 1 passcode 1 non-passcode
} else if (user === 'mfa-i') {
[mfa_constraints, methods] = generator([m('okta'), m('totp')], [m('totp')]); // 2 constraints 1 passcode/1 non-passcode 1 non-passcode
} else if (user === 'mfa-j') {
[mfa_constraints, methods] = generator([m('pingid')]); // use to test push failures
}
const numbers = (length) =>
Math.random()
.toString()
.substring(2, length + 2);
const mfa_request_id = `${numbers(8)}-${numbers(4)}-${numbers(4)}-${numbers(4)}-${numbers(12)}`;
const mfa_requirement = {
mfa_request_id,
mfa_constraints,
};
// cache mfa requests to test different validation scenarios
mfaRequirement[mfa_request_id] = { methods };
// cache auth response to be returned later by sys/mfa/validate
authResponses[mfa_request_id] = { ...res };
return mfa_requirement;
};
// passthrough original request, cache response and return mfa stub
const passthroughLogin = async (schema, req) => {
// test totp not configured scenario
if (req.params.user === 'totp-na') {
return new Response(400, {}, { errors: ['TOTP mfa required but not configured'] });
}
const mock = req.params.user ? req.params.user.includes('mfa') : null;
// bypass mfa for users that do not match type
if (!mock) {
req.passthrough();
} else if (Ember.testing) {
// use root token in test environment
const res = await fetch('/v1/auth/token/lookup-self', { headers: { 'X-Vault-Token': 'root' } });
if (res.status < 300) {
const json = res.json();
if (Ember.testing) {
json.auth = {
...json.data,
policies: [],
metadata: { username: 'foobar' },
};
json.data = null;
}
return { auth: { mfa_requirement: generateMfaRequirement(req, json) } };
}
return new Response(500, {}, { errors: ['Mirage error fetching root token in testing'] });
} else {
const xhr = req.passthrough();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status < 300) {
// XMLHttpRequest response prop only has a getter -- redefine as writable and set value
Object.defineProperty(xhr, 'response', {
writable: true,
value: JSON.stringify({
auth: { mfa_requirement: generateMfaRequirement(req, JSON.parse(xhr.responseText)) },
}),
});
}
};
}
};
server.post('/auth/:method/login/:user', passthroughLogin);
server.post('/sys/mfa/validate', validationHandler);
}