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.
287 lines
12 KiB
287 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="/assets/profile.jpg" alt="Profile Image" class="img-fluid">
|
|
</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>
|
|
</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',
|
|
}
|
|
}
|
|
|
|
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()
|
|
},
|
|
},
|
|
],
|
|
})
|
|
}
|
|
}
|
|
|