@okta-dfuhriman/okta-auth-js
Version:
The Okta Auth SDK
293 lines (261 loc) • 10 kB
JavaScript
/*!
* 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;
}
},
});