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 < / b u t t o n >
< / d i v >
< / d i v >
< 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 } } < / h 3 > < / d i v >
< div class = "card-subtitle mb-2 text-muted" > { { profile _tagline } } < / d i v >
< / d i v >
< / d i v >
< / d i v >
< ul class = "list-group list-group-flush" >
< li class = "list-group-item" >
< h4 > Basic Profile < / h 4 >
< div class = "row" >
< div class = "col-12 col-md-6 form-group" >
< label for = "coreid-profile-first-input" > First Name < / l a b e l >
< input
type = "text"
class = "form-control"
id = "coreid-profile-first-input"
placeholder = "John"
v - model = "profile_first"
@ keyup = "on_key_up($event)"
>
< / d i v >
< div class = "col-12 col-md-6 form-group" >
< label for = "coreid-profile-last-input" > Last Name < / l a b e l >
< input
type = "text"
class = "form-control"
id = "coreid-profile-last-input"
placeholder = "Doe"
v - model = "profile_last"
@ keyup = "on_key_up($event)"
>
< / d i v >
< / d i v >
< div class = "row" >
< div class = "col-12 form-group" >
< label for = "coreid-profile-email-input" > E - Mail Address < / l a b e l >
< 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)"
>
< / d i v >
< / d i v >
< div class = "row" >
< div class = "col-12 form-group" >
< label for = "coreid-profile-tag-input" > Tagline < / l a b e l >
< input
type = "text"
class = "form-control"
id = "coreid-profile-tag-input"
v - model = "profile_tagline"
@ keyup = "on_key_up($event)"
>
< / d i v >
< / d i v >
< / l i >
< li class = "list-group-item text-right font-italic text-muted" >
{ { form _message } }
< / l i >
< li class = "list-group-item" v - if = "!user_id || user_id === 'me'" >
< h4 > Password < / h 4 >
< 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 < / b u t t o n >
< / l i >
< li class = "list-group-item" v - if = "!has_mfa && (!user_id || user_id === 'me')" >
< h4 > Multi - factor Authentication < / h 4 >
< 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 < / b u t t o n >
< / l i >
< li class = "list-group-item" v - if = "has_mfa && (!user_id || user_id === 'me')" >
< h4 > Multi - factor Authentication < / h 4 >
< 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 < / b u t t o n >
< h6 class = "pad-top" > App Passwords < / h 6 >
< 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 } } < / s p a n >
< / d i v >
< div class = "col-3 my-auto" >
< button
class = "btn btn-sm btn-danger"
type = "button"
@ click = "deactivate_app_password($event, pw)"
> Deactivate < / b u t t o n >
< / d i v >
< / d i v >
< / l i >
< / u l >
< button class = "btn btn-sm btn-primary" @ click = "on_click_generate_app_password" > Generate New < / b u t t o n >
< h6 class = "pad-top" > Recovery Codes < / h 6 >
< 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 >
< span v - if = "!has_mfa_recovery" >
< p class = "font-italic" > No recovery codes have been generated for your account . < / p >
< button class = "btn btn-sm btn-success" @ click = "on_mfa_recovery_generate" > Generate Recovery Codes < / b u t t o n >
< / s p a n >
< span v - if = "has_mfa_recovery" >
< p class = "font-italic" > Recovery codes were generate for your account on { { mfa _recovery _date } } . < span v - if = "mfa_recovery_codes === 1" > There is only 1 recovery code remaining . < / s p a n > < s p a n v - i f = " m f a _ r e c o v e r y _ c o d e s ! = = 1 " > T h e r e a r e { { m f a _ r e c o v e r y _ c o d e s } } r e c o v e r y c o d e s r e m a i n i n g . < / s p a n > < / p >
< button class = "btn btn-sm btn-success" @ click = "on_mfa_recovery_generate" > Re - generate Recovery Codes < / b u t t o n >
< / s p a n >
< / l i >
< / u l >
< / d i v >
< coreid - form - app - password
v - if = "!user_id || user_id === 'me'"
ref = "app_password_modal"
@ modal - success = "load_app_passwords"
> < / c o r e i d - f o r m - a p p - p a s s w o r d >
< coreid - profile - photo - uploader
ref = "profile_photo_uploader"
: user - id = "user_id"
@ upload = "on_profile_photo_upload"
> < / c o r e i d - p r o f i l e - p h o t o - u p l o a d e r >
< / d i v >
`
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 = ''
has _mfa _recovery = false
mfa _recovery _date = ''
mfa _recovery _codes = 0
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 ( )
const result = await auth _api . has _mfa _recovery ( )
if ( result && result . has _recovery ) {
this . has _mfa _recovery = true
this . mfa _recovery _date = ( new Date ( result . generated ) ) . toLocaleDateString ( )
this . mfa _recovery _codes = result . remaining _codes
}
}
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 ( )
} ,
} ,
] ,
} )
}
async on _mfa _recovery _generate ( $event ) {
if ( ! this . has _mfa ) return
if ( ! this . has _mfa _recovery ) {
await this . generate _mfa _recovery ( )
} else {
message _service . modal ( {
title : 'Are you sure?' ,
message : 'There are already MFA recovery codes associated with your account. If you re-generate them, you will be unable to use the old ones. Continue?' ,
buttons : [
{
text : 'Cancel' ,
type : 'close' ,
} ,
{
text : 'Re-generate' ,
type : 'close' ,
class : [ 'btn' , 'btn-warning' ] ,
on _click : async ( ) => {
await this . generate _mfa _recovery ( )
} ,
} ,
] ,
} )
}
}
async generate _mfa _recovery ( ) {
const codes = await auth _api . generate _mfa _recovery ( )
if ( codes ) {
this . display _mfa _recovery _modal ( codes )
} else {
message _service . alert ( {
type : 'error' ,
message : 'An unknown error occurred while attempting to generate MFA recovery codes.'
} )
}
await this . load ( )
}
display _mfa _recovery _modal ( codes ) {
const code _display = codes . map ( x => ` <li><code> ${ x } </code></li> ` ) . join ( '\n' )
message _service . modal ( {
title : 'MFA Recovery Codes' ,
message : ` We've generated recovery codes for your account. You can use these to recover access to your account in the event that you lose your MFA device.
< br > < br >
Be sure to put these somewhere safe . After you close this modal , they will disappear :
< br > < br >
< ul >
$ { code _display }
< / u l > ` ,
buttons : [
{
text : 'Close' ,
type : 'close' ,
} ,
] ,
} )
}
}