浏览代码

add TOTP interceptor component. fix policy bugs.

pull/1/head
Jonathan Cobb 4 年前
父节点
当前提交
7fe91bd61d
共有 12 个文件被更改,包括 216 次插入70 次删除
  1. +70
    -0
      src/_components/TotpModal.vue
  2. +7
    -3
      src/_helpers/util.js
  3. +9
    -7
      src/_services/user.service.js
  4. +9
    -4
      src/_store/account.module.js
  5. +1
    -0
      src/_store/system.module.js
  6. +1
    -1
      src/_store/users.module.js
  7. +104
    -53
      src/account/profile/PolicyPage.vue
  8. +1
    -0
      src/app/App.vue
  9. +1
    -0
      src/auth/MultifactorAuthPage.vue
  10. +11
    -0
      src/index.html
  11. +2
    -0
      src/index.js
  12. +0
    -2
      webpack.config.js

+ 70
- 0
src/_components/TotpModal.vue 查看文件

@@ -0,0 +1,70 @@
<template>
<div v-if="showTotpModal === true" class="totp-modal">
<h3>{{messages.form_title_totp_modal}}</h3>
<form @submit.prevent="sendTotpToken()">
<h5>{{messages.field_description_totp_code}}</h5>
<div class="form-group">
<label htmlFor="token">{{messages.field_label_totp_code}}</label>
<input v-model="token" name="token" class="form-control" width="10"/>
<div v-if="submitted && errors.has('totpToken')" class="invalid-feedback d-block">{{ errors.first('totpToken') }}</div>
</div>
<div class="form-group">
<button class="btn btn-primary" :disabled="status.authenticating || token === null || token.length < 6">
{{messages.button_label_submit_totp_code}}
</button>
<img v-show="status.authenticating" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
</div>
</form>
</div>
</template>

<script>
import { mapState, mapActions } from 'vuex';
import { util } from '../_helpers'

export default {
data() {
return {
user: util.currentUser(),
submitted: false,
token: '',
refresher: null,
showTotpModal: false
};
},
created() {
this.refreshTotpModal(this);
this.refresher = setInterval(() => this.refreshTotpModal(this), 1000);
},
beforeDestroy () {
clearInterval(this.refresher);
},
computed: {
...mapState('system', ['messages']),
...mapState('account', ['status']),
},
methods: {
...mapActions('account', ['sendAuthenticatorCode']),
refreshTotpModal(vue) {
if (typeof window.showTotpModal !== 'undefined') {
if (window.showTotpModal === true && vue.showTotpModal !== true) {
vue.showTotpModal = true;
} else if (window.showTotpModal === false && vue.showTotpModal !== false) {
vue.showTotpModal = false;
delete window['showTotpModal'];
}
}
},
sendTotpToken () {
this.submitted = true;
this.sendAuthenticatorCode({
userId: this.user.uuid,
code: this.token,
authOnly: true,
messages: this.messages,
errors: this.errors
});
}
}
}
</script>

+ 7
- 3
src/_helpers/util.js 查看文件

@@ -134,13 +134,17 @@ export const util = {

setValidationErrors: function(data, messages, errors) {
for (let i=0; i<data.length; i++) {
if (data[i].messageTemplate) {
const parts = data[i].messageTemplate.split(/[._]+/);
const messageTemplate = data[i].messageTemplate.replace(/\./g, '_');
if (messageTemplate) {
const parts = messageTemplate.split(/[_]+/);
if (parts.length === 3 && parts[0] === 'err') {
const field = parts[1];
const messageTemplate = data[i].messageTemplate.replace(/\./g, '_');
const message = messages[messageTemplate];
errors.add({field: field, msg: message});
if (messageTemplate === 'err_totpToken_invalid') {
// console.log('received '+messageTemplate+' -- setting window.showTotpModal = true');
window.showTotpModal = true;
}
// console.log('>>>>> field '+field+' added error: '+message+', errors='+JSON.stringify(errors));
// } else {
// console.log('>>>>> data item did not contain a valid error: '+JSON.stringify(data[i]));


+ 9
- 7
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);
}


+ 9
- 4
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));


+ 1
- 0
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) {


+ 1
- 1
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) {


+ 104
- 53
src/account/profile/PolicyPage.vue 查看文件

@@ -20,6 +20,7 @@
</div>

<hr/>

<div class="form-group">
<label htmlFor="accountOperationTimeout">{{messages.field_label_policy_account_operation_timeout}}</label>
<input type="number" v-model="accountOperationTimeout" name="accountOperationTimeout" class="form-control"/>
@@ -30,7 +31,6 @@
<span>{{messages.field_label_policy_account_operation_timeout_description}}</span>
</div>

<hr/>
<div class="form-group">
<label htmlFor="nodeOperationTimeout">{{messages.field_label_policy_node_operation_timeout}}</label>
<input type="number" v-model="nodeOperationTimeout" name="nodeOperationTimeout" class="form-control"/>
@@ -41,6 +41,16 @@
<span>{{messages.field_label_policy_node_operation_timeout_description}}</span>
</div>

<div class="form-group" v-if="hasAuthenticator">
<label htmlFor="authenticatorTimeout">{{messages.field_label_policy_authenticator_timeout}}</label>
<input type="number" v-model="authenticatorTimeout" name="authenticatorTimeout" class="form-control"/>
<select v-model="authenticatorTimeoutUnits" name="authenticatorTimeoutUnits" class="form-control">
<option v-for="opt in timeDurationOptions" v-bind:value="opt">{{messages['time_duration_'+opt]}}</option>
</select>
<div v-if="submitted && errors.has('authenticatorTimeout')" class="invalid-feedback d-block">{{ errors.first('authenticatorTimeout') }}</div>
<span>{{messages.field_label_policy_authenticator_timeout_description}}</span>
</div>

<hr/>
<div class="form-group">
<button class="btn btn-primary" :disabled="loading()">{{messages.button_label_update_policy}}</button>
@@ -51,6 +61,7 @@

<hr/>
<h2>{{messages.form_title_account_contacts}}</h2>
<div v-if="errors.has('contact')" class="invalid-feedback d-block">{{ errors.first('contact') }}</div>
<table v-if="contacts && contacts.length > 0" border="1">
<thead>
<tr>
@@ -65,12 +76,8 @@
</td>

<td>
<i aria-hidden="true" :class="messages.field_label_policy_contact_requiredForNetworkUnlock_icon" :title="messages.field_label_policy_contact_requiredForNetworkUnlock"></i>
<span class="sr-only">{{messages.field_label_policy_contact_requiredForNetworkUnlock}}</span>
</td>
<td>
<i aria-hidden="true" :class="messages.field_label_policy_contact_requiredForNodeOperations_icon" :title="messages.field_label_policy_contact_requiredForNodeOperations"></i>
<span class="sr-only">{{messages.field_label_policy_contact_requiredForNodeOperations}}</span>
<i aria-hidden="true" :class="messages.field_label_policy_contact_requiredForNetworkOperations_icon" :title="messages.field_label_policy_contact_requiredForNetworkOperations"></i>
<span class="sr-only">{{messages.field_label_policy_contact_requiredForNetworkOperations}}</span>
</td>
<td>
<i aria-hidden="true" :class="messages.field_label_policy_contact_requiredForAccountOperations_icon" :title="messages.field_label_policy_contact_requiredForAccountOperations"></i>
@@ -101,10 +108,16 @@
</thead>
<tbody>
<tr v-for="contact in contacts">
<!-- nickname -->
<td>{{contact.nick}}</td>

<!-- contact type -->
<td>{{messages['field_label_policy_contact_type_'+contact.type]}}</td>

<!-- masked contact info -->
<td><span v-if="isNotAuthenticator(contact)">{{contact.info}}</span></td>

<!-- verified, or show verification form -->
<td v-if="contact.verified">
<i aria-hidden="true" :class="messages.field_label_policy_contact_value_enabled_icon" :title="messages.message_true"></i>
<span class="sr-only">{{messages.message_true}}</span>
@@ -137,6 +150,7 @@
<button v-if="verifyingContact !== contact.uuid && isNotAuthenticator(contact)" @click="startVerifyContact(contact)" class="btn btn-primary">{{messages.button_label_submit_verify_code}}</button>
</td>

<!-- auth factor: required, sufficient, or not_required -->
<td v-if="contact.authFactor === 'required'">
<i @click="toggleAuthFactor(contact)" aria-hidden="true" :class="messages.field_label_policy_contact_authFactor_name_required_icon" :title="messages.field_label_policy_contact_authFactor_name_required"></i>
<span class="sr-only">{{messages.field_label_policy_contact_authFactor_name_required}}</span>
@@ -154,33 +168,27 @@
<span class="sr-only">{{messages.field_label_policy_contact_value_not_applicable_name}}</span>
</td>

<td v-if="contact.requiredForNetworkUnlock">
<i @click="contactFlag(contact, 'requiredForNetworkUnlock', false)" aria-hidden="true" :class="messages.field_label_policy_contact_value_enabled_icon" :title="messages.message_true"></i>
<span class="sr-only">{{messages.message_true}}</span>
</td>
<td v-else>
<i @click="contactFlag(contact, 'requiredForNetworkUnlock', true)" aria-hidden="true" :class="messages.field_label_policy_contact_value_disabled_icon" :title="messages.message_false"></i>
<span class="sr-only">{{messages.message_false}}</span>
</td>

<td v-if="contact.requiredForNodeOperations">
<i @click="contactFlag(contact, 'requiredForNodeOperations', false)" aria-hidden="true" :class="messages.field_label_policy_contact_value_enabled_icon" :title="messages.message_true"></i>
<!-- required for account operations? -->
<td v-if="contact.requiredForAccountOperations">
<i @click="contactFlag(contact, 'requiredForAccountOperations', false)" aria-hidden="true" :class="messages.field_label_policy_contact_value_enabled_icon" :title="messages.message_true"></i>
<span class="sr-only">{{messages.message_true}}</span>
</td>
<td v-else>
<i @click="contactFlag(contact, 'requiredForNodeOperations', true)" aria-hidden="true" :class="messages.field_label_policy_contact_value_disabled_icon" :title="messages.message_false"></i>
<i @click="contactFlag(contact, 'requiredForAccountOperations', true)" aria-hidden="true" :class="messages.field_label_policy_contact_value_disabled_icon" :title="messages.message_false"></i>
<span class="sr-only">{{messages.message_false}}</span>
</td>

<td v-if="contact.requiredForAccountOperations">
<i @click="contactFlag(contact, 'requiredForAccountOperations', false)" aria-hidden="true" :class="messages.field_label_policy_contact_value_enabled_icon" :title="messages.message_true"></i>
<!-- required for network operations? -->
<td v-if="contact.requiredForNetworkOperations">
<i @click="contactFlag(contact, 'requiredForNetworkOperations', false)" aria-hidden="true" :class="messages.field_label_policy_contact_value_enabled_icon" :title="messages.message_true"></i>
<span class="sr-only">{{messages.message_true}}</span>
</td>
<td v-else>
<i @click="contactFlag(contact, 'requiredForAccountOperations', true)" aria-hidden="true" :class="messages.field_label_policy_contact_value_disabled_icon" :title="messages.message_false"></i>
<i @click="contactFlag(contact, 'requiredForNetworkOperations', true)" aria-hidden="true" :class="messages.field_label_policy_contact_value_disabled_icon" :title="messages.message_false"></i>
<span class="sr-only">{{messages.message_false}}</span>
</td>

<!-- receive verify notifications? -->
<td v-if="isAuthenticator(contact)">
<i aria-hidden="true" :class="messages.field_label_policy_contact_value_not_applicable_icon" :title="messages.field_label_policy_contact_value_not_applicable_name"></i>
<span class="sr-only">{{messages.field_label_policy_contact_value_not_applicable_name}}</span>
@@ -194,6 +202,7 @@
<span class="sr-only">{{messages.message_false}}</span>
</td>

<!-- receive login notifications? -->
<td v-if="isAuthenticator(contact)">
<i aria-hidden="true" :class="messages.field_label_policy_contact_value_not_applicable_icon" :title="messages.field_label_policy_contact_value_not_applicable_name"></i>
<span class="sr-only">{{messages.field_label_policy_contact_value_not_applicable_name}}</span>
@@ -207,6 +216,7 @@
<span class="sr-only">{{messages.message_false}}</span>
</td>

<!-- receive change password notifications? -->
<td v-if="isAuthenticator(contact)">
<i aria-hidden="true" :class="messages.field_label_policy_contact_value_not_applicable_icon" :title="messages.field_label_policy_contact_value_not_applicable_name"></i>
<span class="sr-only">{{messages.field_label_policy_contact_value_not_applicable_name}}</span>
@@ -220,6 +230,7 @@
<span class="sr-only">{{messages.message_false}}</span>
</td>

<!-- receive informational messages? -->
<td v-if="isAuthenticator(contact)">
<i aria-hidden="true" :class="messages.field_label_policy_contact_value_not_applicable_icon" :title="messages.field_label_policy_contact_value_not_applicable_name"></i>
<span class="sr-only">{{messages.field_label_policy_contact_value_not_applicable_name}}</span>
@@ -233,6 +244,7 @@
<span class="sr-only">{{messages.message_false}}</span>
</td>

<!-- receive promotional messages? -->
<td v-if="isAuthenticator(contact)">
<i aria-hidden="true" :class="messages.field_label_policy_contact_value_not_applicable_icon" :title="messages.field_label_policy_contact_value_not_applicable_name"></i>
<span class="sr-only">{{messages.field_label_policy_contact_value_not_applicable_name}}</span>
@@ -246,6 +258,7 @@
<span class="sr-only">{{messages.message_false}}</span>
</td>

<!-- remove contact -->
<td>
<i @click="removeContact(contact.uuid)" aria-hidden="true" :class="messages.button_label_remove_contact_icon" :title="messages.button_label_remove_contact"></i>
<span class="sr-only">{{messages.button_label_remove_contact}}</span>
@@ -275,19 +288,15 @@
</div>

<hr/>
<div class="form-group">
<label for="requiredForNetworkUnlock">{{messages.field_label_policy_contact_requiredForNetworkUnlock}}</label>
<input type="checkbox" id="requiredForNetworkUnlock" v-model="newContact.requiredForNetworkUnlock">
<div v-if="contactSubmitted && errors.has('requiredForNetworkUnlock')" class="invalid-feedback d-block">{{ errors.first('requiredForNetworkUnlock') }}</div>
</div>
<div class="form-group">
<label for="requiredForNodeOperations">{{messages.field_label_policy_contact_requiredForNodeOperations}}</label>
<input type="checkbox" id="requiredForNodeOperations" v-model="newContact.requiredForNodeOperations">
</div>
<div class="form-group">
<label for="requiredForAccountOperations">{{messages.field_label_policy_contact_requiredForAccountOperations}}</label>
<input type="checkbox" id="requiredForAccountOperations" v-model="newContact.requiredForAccountOperations">
</div>
<div class="form-group">
<label for="requiredForNetworkOperations">{{messages.field_label_policy_contact_requiredForNetworkOperations}}</label>
<input type="checkbox" id="requiredForNetworkOperations" v-model="newContact.requiredForNetworkOperations">
<div v-if="contactSubmitted && errors.has('requiredForNetworkOperations')" class="invalid-feedback d-block">{{ errors.first('requiredForNetworkOperations') }}</div>
</div>
<div v-if="newContact.type !== '' && isNotAuthenticator(newContact)" class="form-group">
<label for="receiveVerifyNotifications">{{messages.field_label_policy_contact_receiveVerifyNotifications}}</label>
<input type="checkbox" id="receiveVerifyNotifications" v-model="newContact.receiveVerifyNotifications">
@@ -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 {


+ 1
- 0
src/app/App.vue 查看文件

@@ -1,5 +1,6 @@
<template>
<div class="jumbotron">
<totp-modal/>
<sidebar-menu :hide-toggle="true" :menu="menu" v-if="status.loggedIn"/>
<div class="container">
<div class="row">


+ 1
- 0
src/auth/MultifactorAuthPage.vue 查看文件

@@ -72,6 +72,7 @@
this.sendAuthenticatorCode({
userId: this.username,
code: code,
authOnly: null,
verifyOnly: false,
messages: this.messages,
errors: this.errors


+ 11
- 0
src/index.html 查看文件

@@ -33,6 +33,17 @@
width: 100%;
text-align: center;
}

.totp-modal {
position:fixed;
width:100%;
height:100%;
opacity:1;
background: #b9bbbe;
top:0;
left:0;
z-index:1000;
}
</style>

<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">


+ 2
- 0
src/index.js 查看文件

@@ -12,6 +12,7 @@ import { Datetime } from 'vue-datetime';

import { store } from './_store';
import { router } from './_helpers';
import TotpModal from './_components/TotpModal';
import App from './app/App';

// why can't i import this?
@@ -21,6 +22,7 @@ Vue.use(VeeValidate);
Vue.use(VueSidebarMenu);
Vue.component('v-select', vSelect);
Vue.component('datetime', Datetime);
Vue.component('totp-modal', TotpModal);
Vue.config.productionTip = false;

// not sure what the best way is to include these icons, we reference them programmatically via string resource/messages


+ 0
- 2
webpack.config.js 查看文件

@@ -1,4 +1,3 @@
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');

@@ -35,7 +34,6 @@ module.exports = {
externals: {
// global app config object
config: JSON.stringify({
// apiUrl: 'http://localhost:4000'
apiUrl: '/api'
})
}

正在加载...
取消
保存