You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CoreID/app/assets/app/dash/profile/EditProfile.component.js

306 lines
12 KiB

import { Component } from '../../../lib/vues6/vues6.js'
import { session } from '../../service/Session.service.js'
import { password_service } from '../../service/Password.service.js'
import { auth_api } from '../../service/AuthApi.service.js'
import { location_service } from '../../service/Location.service.js'
import { message_service } from '../../service/Message.service.js'
import { utility } from '../../service/Utility.service.js'
import { profile_service } from '../../service/Profile.service.js'
const template = `
<div class="coreid-profile-container mb-5">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-8 offset-2 col-sm-4 offset-sm-0">
<img src="/api/v1/profile/me/photo" alt="Profile Image" ref="photo" class="img-fluid">
<div class="overlay">
<button class="btn btn-outline-light" @click="on_profile_change_click">Change</button>
</div>
</div>
<div class="col-sm-8 offset-sm-0 col-12 text-sm-left text-center pad-top">
<div class="card-title"><h3>{{ profile_first }} {{ profile_last }}</h3></div>
<div class="card-subtitle mb-2 text-muted">{{ profile_tagline }}</div>
</div>
</div>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<h4>Basic Profile</h4>
<div class="row">
<div class="col-12 col-md-6 form-group">
<label for="coreid-profile-first-input">First Name</label>
<input
type="text"
class="form-control"
id="coreid-profile-first-input"
placeholder="John"
v-model="profile_first"
@keyup="on_key_up($event)"
>
</div>
<div class="col-12 col-md-6 form-group">
<label for="coreid-profile-last-input">Last Name</label>
<input
type="text"
class="form-control"
id="coreid-profile-last-input"
placeholder="Doe"
v-model="profile_last"
@keyup="on_key_up($event)"
>
</div>
</div>
<div class="row">
<div class="col-12 form-group">
<label for="coreid-profile-email-input">E-Mail Address</label>
<input
type="email"
class="form-control"
id="coreid-profile-email-input"
placeholder="john.doe@contoso.com"
v-model="profile_email"
ref="email_input"
@keyup="on_key_up($event)"
>
</div>
</div>
<div class="row">
<div class="col-12 form-group">
<label for="coreid-profile-tag-input">Tagline</label>
<input
type="text"
class="form-control"
id="coreid-profile-tag-input"
v-model="profile_tagline"
@keyup="on_key_up($event)"
>
</div>
</div>
</li>
<li class="list-group-item text-right font-italic text-muted">
{{ form_message }}
</li>
<li class="list-group-item" v-if="!user_id || user_id === 'me'">
<h4>Password</h4>
<p class="font-italic" v-if="last_reset">Your password was last changed on {{ last_reset }}.</p>
<button
type="button"
class="btn btn-primary btn-sm"
@click="change_password"
>Change Password</button>
</li>
<li class="list-group-item" v-if="!has_mfa && (!user_id || user_id === 'me')">
<h4>Multi-factor Authentication</h4>
<p>MFA is a good-practice security measure that requires you to provide a second factor of identification when you sign in from a service or device that makes use of {{ app_name }}.</p>
<p>Once enabled, {{ app_name }} will prompt you to enter a code when you sign-in with the {{ app_name }} web interface from a new device. It will also require you to append the code to your password when signing in to a service that uses {{ app_name }} as a backend.</p>
<button class="btn btn-success btn-sm" type="button" @click="enable_mfa">Enable MFA</button>
</li>
<li class="list-group-item" v-if="has_mfa && (!user_id || user_id === 'me')">
<h4>Multi-factor Authentication</h4>
<p class="font-italic">MFA was enabled for your account on {{ mfa_enable_date }}.</p>
<button
class="btn btn-danger btn-sm"
type="button"
@click="disable_mfa"
>Disable MFA</button>
<h6 class="pad-top">App Passwords</h6>
<p>App passwords are specially generated passwords that allow you to sign into legacy services with your {{ app_name }} account.</p>
<p>You should only use this to authenticate against a service that needs to repeatedly use your password on your behalf (e.g. e-mail clients).</p>
<p>Use these with caution, as they can bypass your multi-factor authentication.</p>
<p class="font-italic text-muted" v-if="app_passwords.length > 0">You have {{ app_passwords.length }} app {{ app_passwords.length === 1 ? 'password' : 'passwords' }} associated with your account.</p>
<ul class="list-group mb-4" v-if="app_passwords.length > 0">
<li class="list-group-item" v-for="pw of app_passwords">
<div class="row">
<div class="col-9">
{{ pw.name }}
<br><span class="text-muted font-italic">Issued: {{ pw.created }}</span>
</div>
<div class="col-3 my-auto">
<button
class="btn btn-sm btn-danger"
type="button"
@click="deactivate_app_password($event, pw)"
>Deactivate</button>
</div>
</div>
</li>
</ul>
<button class="btn btn-sm btn-primary" @click="on_click_generate_app_password">Generate New</button>
<h6 class="pad-top">Recovery Codes</h6>
<p>Recovery codes can be used to regain access to your account in the event that you lose access to the device that generates your MFA codes.</p>
<p class="font-italic">No recovery codes have been generated for your account.</p>
<button class="btn btn-sm btn-success">Generate Recovery Codes</button>
</li>
</ul>
</div>
<coreid-form-app-password
v-if="!user_id || user_id === 'me'"
ref="app_password_modal"
@modal-success="load_app_passwords"
></coreid-form-app-password>
<coreid-profile-photo-uploader
ref="profile_photo_uploader"
:user-id="user_id"
@upload="on_profile_photo_upload"
></coreid-profile-photo-uploader>
</div>
`
export default class EditProfileComponent extends Component {
static get selector() { return 'coreid-profile-edit' }
static get template() { return template }
static get props() { return ['user_id'] }
profile_first = ''
profile_last = ''
profile_email = ''
profile_tagline = ''
last_reset = ''
mfa_enable_date = ''
form_message = 'No changes.'
has_mfa = false
ready = false
app_passwords = []
on_key_up = ($event) => {}
vue_on_create() {
this.app_name = session.get('app.name')
this.load().then(() => {
this.ready = true
})
const save = utility.debounce(this.save_form.bind(this))
this.on_key_up = () => {
this.form_message = 'Saving...'
save()
}
}
get_submit_data() {
return {
first_name: this.profile_first,
last_name: this.profile_last,
email: this.profile_email,
tagline: this.profile_tagline,
user_id: this.user_id || 'me',
}
}
on_profile_change_click() {
this.$refs.profile_photo_uploader.show()
}
async on_profile_photo_upload() {
this.$refs.profile_photo_uploader.close()
let src = this.$refs.photo.src
if ( src.indexOf('?') > 0 ) src = src.split('?')[0]
this.$refs.photo.src = `${src}?i=${(new Date).getTime()}`
}
valid_email() {
return this.$refs.email_input.validity.valid
}
async save_form($event) {
const submit_data = this.get_submit_data()
try {
if ( !this.valid_email() ) {
this.form_message = 'Invalid e-mail address.'
} else {
await profile_service.update_profile(submit_data)
this.form_message = 'All changes saved.'
}
} catch(e) {
this.form_message = 'Unknown error occurred while saving.'
}
}
populate_from_session() {
this.profile_first = session.get('user.first_name')
this.profile_last = session.get('user.last_name')
this.profile_email = session.get('user.email')
this.profile_tagline = session.get('user.tagline')
}
async load() {
const result = await profile_service.get_profile(this.user_id || 'me')
if ( !result ) throw new Error('Unable to load profile!')
this.profile_first = result.first_name
this.profile_last = result.last_name
this.profile_email = result.email
this.profile_tagline = result.tagline
if ( !this.user_id || this.user_id === 'me' ) {
const reset = (await password_service.get_resets()).reverse()[0]
if (reset && reset.reset_on) {
this.last_reset = (new Date(reset.reset_on)).toLocaleDateString()
}
const mfa = await auth_api.has_mfa()
this.has_mfa = mfa && mfa.mfa_enabled
if (this.has_mfa) this.mfa_enable_date = (new Date(mfa.mfa_enable_date)).toLocaleDateString()
await this.load_app_passwords()
}
}
async load_app_passwords() {
let app_pws = await auth_api.app_passwords()
if ( !Array.isArray(app_pws) ) app_pws = []
this.app_passwords = app_pws.map(x => {
if ( x.expires ) x.expires = (new Date(x.expires)).toLocaleDateString()
if ( x.created ) x.created = (new Date(x.created)).toLocaleDateString()
return x
})
}
disable_mfa() {
location_service.redirect('/auth/mfa/disable')
}
enable_mfa() {
location_service.redirect('/auth/mfa/setup')
}
change_password() {
location_service.redirect('/password/reset')
}
on_click_generate_app_password() {
this.$refs.app_password_modal.trigger()
}
async deactivate_app_password($event, pw) {
message_service.modal({
title: 'Deactivate app password?',
message: `You are about to deactivate the app password for ${pw.name}. If you do this, ${pw.name} will no longer be able to sign-in on your behalf. Continue?`,
buttons: [
{
text: 'Cancel',
type: 'close',
},
{
text: 'Deactivate',
type: 'close',
class: ['btn', 'btn-danger'],
on_click: async () => {
await auth_api.delete_app_password(pw.uuid)
await this.load_app_passwords()
},
},
],
})
}
}