Browse Source

authenticator verification working

pull/1/head
Jonathan Cobb 4 years ago
parent
commit
701a13ded7
4 changed files with 100 additions and 37 deletions
  1. +12
    -1
      src/_services/user.service.js
  2. +18
    -1
      src/_store/account.module.js
  3. +18
    -2
      src/_store/users.module.js
  4. +52
    -33
      src/account/profile/PolicyPage.vue

+ 12
- 1
src/_services/user.service.js View File

@@ -14,7 +14,8 @@ export const userService = {
update,
delete: _delete,
approveAction,
denyAction
denyAction,
sendAuthenticatorCode
};

function setSessionUser (user) {
@@ -83,6 +84,16 @@ function approveAction(id, code, messages, errors) {
.then(setSessionUser);
}

function sendAuthenticatorCode(id, code, verifyOnly, messages, errors) {
return fetch(`${config.apiUrl}/auth/authenticator`, postWithAuth({
account: id,
token: parseInt(code),
verify: verifyOnly
}))
.then(handleCrudResponse(messages, errors))
.then(setSessionUser);
}

function denyAction(id, code, messages, errors) {
return fetch(`${config.apiUrl}/auth/deny/${code}`, postWithAuth()).then(handleCrudResponse(messages, errors));
}


+ 18
- 1
src/_store/account.module.js View File

@@ -87,7 +87,14 @@ const actions = {
error => commit('denyActionFailure', error)
);
},

sendAuthenticatorCode({ commit }, {uuid, code, verifyOnly, messages, errors}) {
commit('sendAuthenticatorCodeRequest');
userService.sendAuthenticatorCode(uuid, code, verifyOnly, messages, errors)
.then(
policy => commit('sendAuthenticatorCodeSuccess', policy),
error => commit('sendAuthenticatorCodeFailure', error)
);
}
};

const mutations = {
@@ -140,6 +147,16 @@ const mutations = {
},
denyActionFailure(state, error) {
state.actionStatus = { error: error, type: 'deny' };
},
sendAuthenticatorCodeRequest(state) {
state.actionStatus = { requesting: true, type: 'approve' };
},
sendAuthenticatorCodeSuccess(state, user) {
state.actionStatus = { success: true, type: 'approve', result: user };
if (user.token) state.user = user;
},
sendAuthenticatorCodeFailure(state, error) {
state.actionStatus = { error: error, type: 'approve' };
}

};


+ 18
- 2
src/_store/users.module.js View File

@@ -7,9 +7,23 @@ const state = {
policy: {},
policyStatus: {},
contact: null,
authenticator: null
authenticator: {}
};

function setAuthenticator(policy) {
if (policy && policy.accountContacts) {
const contacts = policy.accountContacts;
for (let i=0; i<contacts.length; i++) {
if (contacts[i].type === 'authenticator') {
state.authenticator = JSON.parse(contacts[i].info);
return;
}
}
}
state.authenticator = {};
return state.authenticator;
}

const actions = {
getAll({ commit }, {messages, errors}) {
commit('getAllRequest');
@@ -110,6 +124,7 @@ const mutations = {
},
getPolicyByUuidSuccess(state, policy) {
state.policy = policy;
setAuthenticator(policy);
},
getPolicyByUuidFailure(state, error) {
state.policy = { error };
@@ -121,6 +136,7 @@ const mutations = {
updatePolicyByUuidSuccess(state, policy) {
state.policyStatus = {};
state.policy = policy;
setAuthenticator(policy);
},
updatePolicyByUuidFailure(state, error) {
state.policyStatus = {};
@@ -133,7 +149,7 @@ const mutations = {
addPolicyContactByUuidSuccess(state, contact) {
state.contact = contact;
if (contact.type === 'authenticator') {
state.authenticator = contact.info;
state.authenticator = JSON.parse(contact.info);
}
},
addPolicyContactByUuidFailure(state, error) {


+ 52
- 33
src/account/profile/PolicyPage.vue View File

@@ -110,13 +110,14 @@
<span class="sr-only">{{messages.message_true}}</span>
</td>
<td v-else>
<form v-if="verifyingContact === contact.uuid" @submit.prevent="submitVerification(contact.uuid)">
<form v-if="verifyingContact === contact.uuid || contact.type === 'authenticator'" @submit.prevent="submitVerification(contact)">
<div class="form-group">
<div v-if="contact.type === 'authenticator'">
<canvas :id="'canvas_'+contact.uuid"></canvas>
<div>{{messages.message_verify_authenticator_preamble}}</div>
<canvas id="authenticator_qr_canvas"></canvas>
<hr/>
<span>{{messages.message_verify_authenticator_backupCodes}}<br/>
<span :id="'backupCodes_'+contact.uuid"></span>
<span id="authenticator_backupCodes"></span>
</span>
<hr/>
<span>{{messages.message_verify_authenticator_backupCodes_description}}</span>
@@ -125,21 +126,21 @@
<input :disabled="actionStatus.requesting" :id="'verifyContactCode_'+contact.uuid" v-validate="'required'" name="verifyCode" type="text" size="8"/>
<div v-if="errors.has('token')" class="invalid-feedback d-block">{{ errors.first('token') }}</div>
<button class="btn btn-primary" :disabled="actionStatus.requesting">{{messages.button_label_submit_verify_code}}</button>
<button class="btn btn-primary" :disabled="actionStatus.requesting" @click="cancelVerifyContact()">{{messages.button_label_cancel}}</button>
<button v-if="contact.type !== 'authenticator'" class="btn btn-primary" :disabled="actionStatus.requesting" @click="cancelVerifyContact()">{{messages.button_label_cancel}}</button>
</div>
</form>
<button v-if="verifyingContact !== contact.uuid" @click="startVerifyContact(contact)" class="btn btn-primary">{{messages.button_label_submit_verify_code}}</button>
<button v-if="verifyingContact !== contact.uuid && contact.type !== 'authenticator'" @click="startVerifyContact(contact)" class="btn btn-primary">{{messages.button_label_submit_verify_code}}</button>
</td>

<td v-if="contact.authFactor === 'required'">
<i 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>
</td>
<td v-if="contact.authFactor === 'sufficient'">
<td v-else-if="contact.authFactor === 'sufficient'">
<i aria-hidden="true" :class="messages.field_label_policy_contact_authFactor_name_sufficient_icon" :title="messages.field_label_policy_contact_authFactor_name_sufficient"></i>
<span class="sr-only">{{messages.field_label_policy_contact_authFactor_name_sufficient}}</span>
</td>
<td v-if="contact.authFactor === 'not_required'">
<td v-else-if="contact.authFactor === 'not_required'">
<i aria-hidden="true" :class="messages.field_label_policy_contact_authFactor_name_not_required_icon" :title="messages.field_label_policy_contact_authFactor_name_not_required"></i>
<span class="sr-only">{{messages.field_label_policy_contact_authFactor_name_not_required}}</span>
</td>
@@ -399,7 +400,7 @@
}
},
methods: {
...mapActions('account', ['approveAction', 'denyAction']),
...mapActions('account', ['approveAction', 'denyAction', 'sendAuthenticatorCode']),
...mapActions('users', [
'getPolicyByUuid', 'updatePolicyByUuid', 'addPolicyContactByUuid', 'removePolicyContactByUuid',
]),
@@ -452,22 +453,6 @@
startVerifyContact(contact) {
console.log('startVerifyContact: '+JSON.stringify(contact));
this.verifyingContact = contact.uuid;
if (contact.type === 'authenticator') {
const canvas = document.getElementById('canvas_'+contact.uuid);
QRCode.toCanvas(canvas, this.authenticator.key, function (error) {
if (error) {
console.error('QR generation error: '+error);
} else {
console.log('QR generation success');
}
});
const backupCodes = document.getElementById('backupCodes_'+contact.uuid);
if (backupCodes != null && typeof this.authenticator.backupCodes !== 'undefined' && this.authenticator.backupCodes != null && this.authenticator.backupCodes.length > 0) {
backupCodes.innerText = this.authenticator.backupCodes.join(' ');
} else {
console.log('backupCodes element not found, or no backupCodes defined');
}
}
return false; // do not follow the click
},
cancelVerifyContact() {
@@ -475,19 +460,31 @@
this.errors.clear();
return false; // do not follow the click
},
submitVerification(uuid) {
submitVerification(contact) {
const uuid = contact.uuid;
const type = contact.type;
const codeElementId = 'verifyContactCode_'+uuid;
const codeElement = document.getElementById(codeElementId);
if (codeElement != null) {
const code = codeElement.value;
this.errors.clear();
this.approveAction({
uuid: this.currentUser.uuid,
code: code,
messages: this.messages,
errors: this.errors
});
console.log('submitVerification: would submit: ' + code);
if (type === 'authenticator') {
// console.log('submitVerification: sending authenticator code: '+code);
this.sendAuthenticatorCode({
uuid: this.currentUser.uuid,
code: code,
verifyOnly: true,
messages: this.messages,
errors: this.errors
});
} else {
this.approveAction({
uuid: this.currentUser.uuid,
code: code,
messages: this.messages,
errors: this.errors
});
}
} else {
console.log('submitVerification: DOM element not found: '+codeElementId);
}
@@ -524,10 +521,32 @@
}
},
actionStatus (status) {
console.log('watch.actionStatus: received: '+JSON.stringify(status));
// console.log('watch.actionStatus: received: '+JSON.stringify(status));
if (status.success) {
this.getPolicyByUuid({uuid: this.currentUser.uuid, messages: this.messages, errors: this.errors});
}
},
authenticator (auth) {
// console.log('watch.authenticator: received: '+JSON.stringify(auth));
if (auth.url) {
const checkExist = setInterval(function() {
if (document.getElementById('authenticator_qr_canvas') != null) {
clearInterval(checkExist);
const canvas = document.getElementById('authenticator_qr_canvas');
if (canvas !== null) {
QRCode.toCanvas(canvas, auth.url, function (error) {
if (error) console.error('QR generation error: ' + error);
});
const backupCodes = document.getElementById('authenticator_backupCodes');
if (backupCodes != null && typeof auth.backupCodes !== 'undefined' && auth.backupCodes != null && auth.backupCodes.length > 0) {
backupCodes.innerHTML = auth.backupCodes.join('<br/>');
} else {
console.log('backupCodes element not found, or no backupCodes defined');
}
}
}
}, 200);
}
}
},
created () {


Loading…
Cancel
Save