UNPKG

@okta-dfuhriman/okta-auth-js

Version:
293 lines (261 loc) 10 kB
/*! * Copyright (c) 2015-2016, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") * * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and limitations under the License. */ import { _, loc, View } from 'okta'; import hbs from 'handlebars-inline-precompile'; import CountryUtil from 'util/CountryUtil'; import FactorUtil from 'util/FactorUtil'; import FormController from 'v1/util/FormController'; import FormType from 'v1/util/FormType'; import RouterUtil from 'v1/util/RouterUtil'; import Footer from 'v1/views/enroll-factors/ManualSetupPushFooter'; import PhoneTextBox from 'v1/views/enroll-factors/PhoneTextBox'; function goToFactorActivation(view, step) { const url = RouterUtil.createActivateFactorUrl( view.options.appState.get('activatedFactorProvider'), view.options.appState.get('activatedFactorType'), step ); view.options.appState.trigger('navigate', url); } function setStateValues(view) { let userPhoneNumber; let userCountryCode; if (view.model.get('activationType') === 'SMS') { userCountryCode = view.model.get('countryCode'); userPhoneNumber = view.model.get('phoneNumber'); } view.options.appState.set({ factorActivationType: view.model.get('activationType'), userCountryCode: userCountryCode, userPhoneNumber: userPhoneNumber, }); } function enrollFactor(view, factorType) { return view.model.doTransaction(function(transaction) { return transaction .prev() .then(function(trans) { const factor = _.findWhere(trans.factors, { factorType: factorType, provider: 'OKTA', }); return factor.enroll(); }) .then(function(trans) { let textActivationLinkUrl; let emailActivationLinkUrl; let sharedSecret; const res = trans.data; if ( res && res._embedded && res._embedded.factor && res._embedded.factor._embedded && res._embedded.factor._embedded.activation ) { const factor = res._embedded.factor; // Shared secret sharedSecret = factor._embedded.activation.sharedSecret; if (factor._embedded.activation._links && factor._embedded.activation._links.send) { const activationSendLinks = factor._embedded.activation._links.send; const smsItem = _.findWhere(activationSendLinks, { name: 'sms' }); // SMS activation url textActivationLinkUrl = smsItem ? smsItem.href : null; // Email activation url const emailItem = _.findWhere(activationSendLinks, { name: 'email' }); emailActivationLinkUrl = emailItem ? emailItem.href : null; } } view.model.set({ SMS: textActivationLinkUrl, EMAIL: emailActivationLinkUrl, sharedSecret: sharedSecret, }); return trans; }); }); } export default FormController.extend({ className: 'enroll-manual-push', Model: function() { return { local: { activationType: ['string', true, this.options.appState.get('factorActivationType') || 'SMS'], countryCode: ['string', false, this.options.appState.get('userCountryCode')], phoneNumber: 'string', SMS: ['string', false, this.options.appState.get('textActivationLinkUrl')], EMAIL: ['string', false, this.options.appState.get('emailActivationLinkUrl')], sharedSecret: ['string', false, this.options.appState.get('sharedSecret')], __factorType__: ['string', false, this.options.factorType], __provider__: ['string', false, this.options.provider], }, derived: { countryCallingCode: { deps: ['countryCode'], fn: function(countryCode) { return '+' + CountryUtil.getCallingCodeForCountry(countryCode); }, }, fullPhoneNumber: { deps: ['countryCallingCode', 'phoneNumber'], fn: function(countryCallingCode, phoneNumber) { return countryCallingCode + phoneNumber; }, }, }, }; }, Form: { title: function() { const factorName = FactorUtil.getFactorLabel(this.model.get('__provider__'), this.model.get('__factorType__')); return loc('enroll.totp.title', 'login', [factorName]); }, subtitle: _.partial(loc, 'enroll.totp.cannotScanBarcode', 'login'), noButtonBar: true, attributes: { 'data-se': 'step-manual-setup' }, formChildren: function() { const instructions = this.settings.get('brandName') ? loc('enroll.totp.sharedSecretInstructions.specific', 'login', [this.settings.get('brandName')]) : loc('enroll.totp.sharedSecretInstructions.generic', 'login'); const children = [ FormType.Input({ name: 'activationType', label:loc('mfa.setupOptions', 'login'), type: 'select', wide: true, options: { SMS: loc('enroll.totp.sendSms', 'login'), EMAIL: loc('enroll.totp.sendEmail', 'login'), MANUAL: loc('enroll.totp.setupManually', 'login'), }, }), FormType.Input({ label:loc('mfa.country', 'login'), name: 'countryCode', type: 'select', wide: true, options: CountryUtil.getCountries(), showWhen: { activationType: 'SMS' }, }), FormType.Input({ label: loc('mfa.phoneNumber.placeholder', 'login'), 'label-top': true, className: 'enroll-sms-phone', name: 'phoneNumber', input: PhoneTextBox, type: 'text', showWhen: { activationType: 'SMS' }, }), FormType.View({ View: View.extend({ className: 'secret-key-instructions', attributes: { 'data-se': 'secret-key-instructions'}, template: hbs` <section aria-live="assertive"> <p class="okta-form-subtitle o-form-explain text-align-c"> {{instructions}} </p> <p class="shared-key margin-top-10" tabindex=0 aria-label="{{i18n code="enroll.totp.sharedSecretInstructions.aria.secretKey" bundle="login" arguments="sharedSecretKey"}}">{{sharedSecretKey}}</p> </section> `, initialize: function(){ this.listenTo(this.model, 'change:sharedSecret', this.render); }, getTemplateData: function() { return { instructions: instructions, sharedSecretKey: this.model.get('sharedSecret') }; }, }), showWhen: { activationType: 'MANUAL' }, }), FormType.View({ View: View.extend({ template: hbs('<div data-type="next-button-wrap"></div>'), }), showWhen: { activationType: 'MANUAL' }, }), FormType.Button( { title: loc('oform.next', 'login'), className: 'button button-primary button-wide button-next', attributes: { 'data-se': 'next-button' }, click: () => { setStateValues(this); goToFactorActivation(this, 'passcode'); }, }, '[data-type="next-button-wrap"]' ), FormType.Toolbar({ noCancelButton: true, save: loc('oform.send', 'login'), showWhen: { activationType: function(val) { return val === 'SMS' || val === 'EMAIL'; }, }, }), ]; return children; }, }, Footer: Footer, initialize: function() { this.setInitialModel(); // Move this logic to a model when AuthClient supports sending email and sms this.listenTo(this.form, 'save', function() { const self = this; this.model.doTransaction(function(transaction) { const activationType = this.get('activationType').toLowerCase(); const opts = {}; if (activationType === 'sms') { opts.profile = { phoneNumber: this.get('fullPhoneNumber') }; } return transaction.factor.activation.send(activationType, opts).then(function(trans) { setStateValues(self); // Note: Need to defer because OktaAuth calls our router success // handler on the next tick - if we immediately called, appState would // still be populated with the last response _.defer(function() { goToFactorActivation(self, 'sent'); }); return trans; }); }); }); this.listenTo(this.model, 'change:activationType', function(model, value) { this.form.clearErrors(); if (value === 'MANUAL' && this.options.appState.get('activatedFactorType') !== 'token:software:totp') { enrollFactor(this, 'token:software:totp'); } else if (this.options.appState.get('activatedFactorType') !== 'push') { enrollFactor(this, 'push'); } }); }, setInitialModel: function() { if (this.options.appState.get('factorActivationType') === 'SMS') { this.model.set({ countryCode: this.options.appState.get('userCountryCode') || 'US', phoneNumber: this.options.appState.get('userPhoneNumber'), }); } }, trapAuthResponse: function() { if (this.options.appState.get('isMfaEnrollActivate') || this.options.appState.get('isMfaEnroll')) { return true; } }, });