diff --git a/src/_components/TotpModal.vue b/src/_components/TotpModal.vue new file mode 100644 index 0000000..626959a --- /dev/null +++ b/src/_components/TotpModal.vue @@ -0,0 +1,70 @@ + + + \ No newline at end of file diff --git a/src/_helpers/util.js b/src/_helpers/util.js index 44b3b6b..770f302 100644 --- a/src/_helpers/util.js +++ b/src/_helpers/util.js @@ -134,13 +134,17 @@ export const util = { setValidationErrors: function(data, messages, errors) { for (let i=0; i>>>> field '+field+' added error: '+message+', errors='+JSON.stringify(errors)); // } else { // console.log('>>>>> data item did not contain a valid error: '+JSON.stringify(data[i])); diff --git a/src/_services/user.service.js b/src/_services/user.service.js index 5277d63..0668bce 100644 --- a/src/_services/user.service.js +++ b/src/_services/user.service.js @@ -11,7 +11,7 @@ export const userService = { getPolicyByUserId, updatePolicyByUserId, addPolicyContactById, - removePolicyContactByUuid, + removePolicyContactByUserId, setLocale, addSshKeyByUserId, removeSshKeyByUserId, @@ -84,7 +84,7 @@ function addPolicyContactById(userId, contact, messages, errors) { return fetch(`${config.apiUrl}/users/${userId}/policy/contacts`, util.postWithAuth(contact)).then(util.handleCrudResponse(messages, errors)); } -function removePolicyContactByUuid(userId, contactUuid, messages, errors) { +function removePolicyContactByUserId(userId, contactUuid, messages, errors) { return fetch(`${config.apiUrl}/users/${userId}/policy/contacts/${contactUuid}`, util.deleteWithAuth()).then(util.handleCrudResponse(messages, errors)); } @@ -94,12 +94,14 @@ function approveAction(userId, code, messages, errors) { .then(setSessionUser); } -function sendAuthenticatorCode(userId, code, verifyOnly, messages, errors) { - return fetch(`${config.apiUrl}/auth/authenticator`, util.postWithAuth({ +function sendAuthenticatorCode(userId, code, authOnly, verifyOnly, messages, errors) { + const auth = { account: userId, - token: code, - verify: verifyOnly - })) + token: code + }; + if (typeof authOnly !== 'undefined' && authOnly !== null) auth.authenticate = authOnly; + if (typeof verifyOnly !== 'undefined' && verifyOnly !== null) auth.verify = verifyOnly; + return fetch(`${config.apiUrl}/auth/authenticator`, util.postWithAuth(auth)) .then(util.handleCrudResponse(messages, errors)) .then(setSessionUser); } diff --git a/src/_store/account.module.js b/src/_store/account.module.js index 4da1b74..429f2dd 100644 --- a/src/_store/account.module.js +++ b/src/_store/account.module.js @@ -126,11 +126,11 @@ const actions = { error => commit('denyActionFailure', error) ); }, - sendAuthenticatorCode({ commit }, {userId, code, verifyOnly, messages, errors}) { + sendAuthenticatorCode({ commit }, {userId, code, authOnly, verifyOnly, messages, errors}) { commit('sendAuthenticatorCodeRequest'); - userService.sendAuthenticatorCode(userId, code, verifyOnly, messages, errors) + userService.sendAuthenticatorCode(userId, code, authOnly, verifyOnly, messages, errors) .then( - user => commit('sendAuthenticatorCodeSuccess', user), + user => commit('sendAuthenticatorCodeSuccess', {user, messages}), error => commit('sendAuthenticatorCodeFailure', error) ); }, @@ -275,11 +275,16 @@ const mutations = { state.status.authenticating = true; state.actionStatus = { requesting: true, type: 'approve' }; }, - sendAuthenticatorCodeSuccess(state, user) { + sendAuthenticatorCodeSuccess(state, user, messages) { state.status.authenticating = false; state.actionStatus = { success: true, type: 'approve', result: user }; + console.log('auth successful -- setting window.showTotpModal = false'); + window.showTotpModal = false; + if (user.token) { state.user = user; + localStorage.setItem(util.USER_KEY, JSON.stringify(user)); + } else if (user.multifactorAuth) { state.user.multifactorAuth = user.multifactorAuth; localStorage.setItem(util.USER_KEY, JSON.stringify(user)); diff --git a/src/_store/system.module.js b/src/_store/system.module.js index f5538d8..115b58d 100644 --- a/src/_store/system.module.js +++ b/src/_store/system.module.js @@ -19,6 +19,7 @@ const state = { error: null, messages: { durationToMillis: function(count, units) { + if (typeof count === 'undefined' || count === null || count === '') return null; return parseInt(count) * parseInt(state.messages['time_duration_'+units+'_factor']); }, millisToDuration: function (ms) { diff --git a/src/_store/users.module.js b/src/_store/users.module.js index d97f1b7..b4befc2 100644 --- a/src/_store/users.module.js +++ b/src/_store/users.module.js @@ -202,7 +202,7 @@ const mutations = { }, updatePolicyByUserIdFailure(state, error) { state.loading.updatingPolicy = false; - state.policy = { error }; + state.errors.policy = { error }; }, addPolicyContactByUserIdRequest(state) { diff --git a/src/account/profile/PolicyPage.vue b/src/account/profile/PolicyPage.vue index 742e62d..5e3fc18 100644 --- a/src/account/profile/PolicyPage.vue +++ b/src/account/profile/PolicyPage.vue @@ -20,6 +20,7 @@
+
@@ -30,7 +31,6 @@ {{messages.field_label_policy_account_operation_timeout_description}}
-
@@ -41,6 +41,16 @@ {{messages.field_label_policy_node_operation_timeout_description}}
+
+ + + +
{{ errors.first('authenticatorTimeout') }}
+ {{messages.field_label_policy_authenticator_timeout_description}} +
+
@@ -51,6 +61,7 @@

{{messages.form_title_account_contacts}}

+
{{ errors.first('contact') }}
@@ -65,12 +76,8 @@ - + + + + + + + - - - - - + + + + + +
- - {{messages.field_label_policy_contact_requiredForNetworkUnlock}} - - - {{messages.field_label_policy_contact_requiredForNodeOperations}} + + {{messages.field_label_policy_contact_requiredForNetworkOperations}} @@ -101,10 +108,16 @@
{{contact.nick}} {{messages['field_label_policy_contact_type_'+contact.type]}} {{contact.info}} {{messages.message_true}} @@ -137,6 +150,7 @@ {{messages.field_label_policy_contact_authFactor_name_required}} @@ -154,33 +168,27 @@ {{messages.field_label_policy_contact_value_not_applicable_name}} - - {{messages.message_true}} - - - {{messages.message_false}} - - + + + {{messages.message_true}} - + {{messages.message_false}} - + + + {{messages.message_true}} - + {{messages.message_false}} {{messages.field_label_policy_contact_value_not_applicable_name}} @@ -194,6 +202,7 @@ {{messages.message_false}} {{messages.field_label_policy_contact_value_not_applicable_name}} @@ -207,6 +216,7 @@ {{messages.message_false}} {{messages.field_label_policy_contact_value_not_applicable_name}} @@ -220,6 +230,7 @@ {{messages.message_false}} {{messages.field_label_policy_contact_value_not_applicable_name}} @@ -233,6 +244,7 @@ {{messages.message_false}} {{messages.field_label_policy_contact_value_not_applicable_name}} @@ -246,6 +258,7 @@ {{messages.message_false}} {{messages.button_label_remove_contact}} @@ -275,19 +288,15 @@
-
- - -
{{ errors.first('requiredForNetworkUnlock') }}
-
-
- - -
+
+ + +
{{ errors.first('requiredForNetworkOperations') }}
+
@@ -339,8 +348,7 @@ function initNewContact () { return { uuid: '', type: '', info: '', - requiredForNetworkUnlock: true, - requiredForNodeOperations: true, + requiredForNetworkOperations: true, requiredForAccountOperations: true, receiveVerifyNotifications: true, receiveLoginNotifications: true, @@ -364,12 +372,15 @@ accountOperationTimeoutUnits: '', nodeOperationTimeout: '', nodeOperationTimeoutUnits: '', + authenticatorTimeout: '', + authenticatorTimeoutUnits: '', contacts: [], contactSubmitted: false, newContactSmsCountry: '', newContact: initNewContact(), verifyingContact: null, - inboundAction: null + inboundAction: null, + watchedPolicy: null } }, computed: { @@ -413,19 +424,21 @@ methods: { ...mapActions('account', ['approveAction', 'denyAction', 'sendAuthenticatorCode', 'resendVerificationCode']), ...mapActions('users', [ - 'getPolicyByUserId', 'updatePolicyByUserId', 'addPolicyContactByUserId', 'removePolicyContactByUuid', + 'getPolicyByUserId', 'updatePolicyByUserId', 'addPolicyContactByUserId', 'removePolicyContactByUserId', ]), ...mapGetters('users', ['loading']), isAuthenticator(val) { return window.isAuthenticator(val); }, isNotAuthenticator(val) { return window.isNotAuthenticator(val); }, updatePolicy(e) { + this.errors.clear(); this.submitted = true; this.updatePolicyByUserId({ userId: this.currentUser.uuid, policy: { deletionPolicy: this.deletionPolicy, accountOperationTimeout: this.messages.durationToMillis(this.accountOperationTimeout, this.accountOperationTimeoutUnits), - nodeOperationTimeout: this.messages.durationToMillis(this.nodeOperationTimeout, this.nodeOperationTimeoutUnits) + nodeOperationTimeout: this.messages.durationToMillis(this.nodeOperationTimeout, this.nodeOperationTimeoutUnits), + authenticatorTimeout: this.messages.durationToMillis(this.authenticatorTimeout, this.authenticatorTimeoutUnits), }, messages: this.messages, errors: this.errors @@ -438,7 +451,7 @@ } this.contactSubmitted = true; this.errors.clear(); - console.log('addContact: adding: '+JSON.stringify(contactToAdd)); + // console.log('addContact: adding: '+JSON.stringify(contactToAdd)); this.addPolicyContactByUserId({ userId: this.currentUser.uuid, contact: contactToAdd, @@ -447,25 +460,45 @@ }); }, removeContact(uuid) { - this.removePolicyContactByUuid({ + this.errors.clear(); + this.removePolicyContactByUserId({ userId: this.currentUser.uuid, contactUuid: uuid, messages: this.messages, errors: this.errors }); }, + prepContact(contact) { + // remove 'info' property before sending to API + const c = Object.assign({}, contact); + if (c.hasOwnProperty('info')) delete c['info']; + return c; + }, contactFlag(contact, flag, val) { + this.errors.clear(); + if (!contact.verified) { + this.errors.add({field: 'contact', msg: this.messages['err_contact_unverified']}); + return; + } contact[flag] = val; - console.log('contactFlag: update: '+JSON.stringify(contact)); + const c = this.prepContact(contact); + // console.log('contactFlag: update: '+JSON.stringify(c)); this.addPolicyContactByUserId({ userId: this.currentUser.uuid, - contact: contact, + contact: c, messages: this.messages, errors: this.errors }); }, toggleAuthFactor(contact) { - if (isAuthenticator(contact)) return; + if (!contact.verified) { + this.errors.add({field: 'contact', msg: this.messages['err_contact_unverified']}); + return; + } + if (isAuthenticator(contact)) { + this.errors.add({field: 'contact', msg: this.messages['err_contact_authenticatorAuthFactorCannotBeChanged']}); + return; + } switch (contact.authFactor) { case 'required': this.setAuthFactor(contact, 'sufficient'); break; case 'sufficient': this.setAuthFactor(contact, 'not_required'); break; @@ -473,10 +506,12 @@ } }, setAuthFactor(contact, factor) { - contact.authFactor = factor; + this.errors.clear(); + const c = this.prepContact(contact); + c.authFactor = factor; this.addPolicyContactByUserId({ userId: this.currentUser.uuid, - contact: contact, + contact: c, messages: this.messages, errors: this.errors }); @@ -487,6 +522,7 @@ return false; // do not follow the click }, resendVerification(contact) { + this.errors.clear(); this.resendVerificationCode({ userId: this.currentUser.uuid, contact: contact, @@ -500,6 +536,23 @@ this.errors.clear(); return false; // do not follow the click }, + updatePolicyTimeouts(p) { + if (typeof p.accountOperationTimeout !== 'undefined' && p.accountOperationTimeout !== null) { + const parts = this.messages.millisToDuration(p.accountOperationTimeout); + this.accountOperationTimeout = parts.count; + this.accountOperationTimeoutUnits = parts.units; + } + if (typeof p.nodeOperationTimeout !== 'undefined' && p.nodeOperationTimeout !== null) { + const parts = this.messages.millisToDuration(p.nodeOperationTimeout); + this.nodeOperationTimeout = parts.count; + this.nodeOperationTimeoutUnits = parts.units; + } + if (typeof p.authenticatorTimeout !== 'undefined' && p.authenticatorTimeout !== null) { + const parts = this.messages.millisToDuration(p.authenticatorTimeout); + this.authenticatorTimeout = parts.count; + this.authenticatorTimeoutUnits = parts.units; + } + }, submitVerification(contact) { const uuid = contact.uuid; const type = contact.type; @@ -514,6 +567,7 @@ this.sendAuthenticatorCode({ userId: this.currentUser.uuid, code: code, + authOnly: null, verifyOnly: true, messages: this.messages, errors: this.errors @@ -532,21 +586,18 @@ } }, watch: { + messages (msgs) { + if (this.accountOperationTimeoutUnits === '' && this.watchedPolicy) { + this.updatePolicyTimeouts(this.watchedPolicy); + } + }, policy (p) { // console.log('watch.policy: received: '+JSON.stringify(p)); + if (p) this.watchedPolicy = p; if (typeof p.deletionPolicy !== 'undefined' && p.deletionPolicy !== null) { this.deletionPolicy = p.deletionPolicy; } - if (typeof p.accountOperationTimeout !== 'undefined' && p.accountOperationTimeout !== null) { - const parts = this.messages.millisToDuration(p.accountOperationTimeout); - this.accountOperationTimeout = parts.count; - this.accountOperationTimeoutUnits = parts.units; - } - if (typeof p.nodeOperationTimeout !== 'undefined' && p.nodeOperationTimeout !== null) { - const parts = this.messages.millisToDuration(p.nodeOperationTimeout); - this.nodeOperationTimeout = parts.count; - this.nodeOperationTimeoutUnits = parts.units; - } + this.updatePolicyTimeouts(p); if (typeof p.accountContacts !== 'undefined' && p.accountContacts !== null && p.accountContacts.length > 0) { this.contacts = p.accountContacts; } else { diff --git a/src/app/App.vue b/src/app/App.vue index a191c76..879dc50 100644 --- a/src/app/App.vue +++ b/src/app/App.vue @@ -1,5 +1,6 @@