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 offset-0 col-md-8 offset-md-2 col-xl-6 offset-xl-3" >
< 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 = "t['profile.profile_photo']" ref = "photo" class = "img-fluid" >
< div class = "overlay" >
< button class = "btn btn-outline-light" @ click = "on_profile_change_click" > { { t [ 'common.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 > { { t [ 'profile.basic_profile' ] } } < / h 4 >
< div class = "row" >
< div class = "col-12 col-md-6 form-group" >
< label for = "coreid-profile-first-input" > { { t [ 'register.first_name' ] } } < / l a b e l >
< input
type = "text"
class = "form-control"
id = "coreid-profile-first-input"
: placeholder = "t['profile.placeholder_first']"
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" > { { t [ 'register.last_name' ] } } < / l a b e l >
< input
type = "text"
class = "form-control"
id = "coreid-profile-last-input"
: placeholder = "t['profile.placeholder_last']"
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" > { { t [ 'register.email' ] } } < / l a b e l >
< input
type = "email"
class = "form-control"
id = "coreid-profile-email-input"
: placeholder = "t['profile.placeholder_email'] "
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" > { { t [ 'profile.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 >
< div class = "row" >
< h4 style = "margin-left: 15px" > { { t [ 'profile.advanced_header' ] } } < / h 4 >
< div class = "col-12 form-group" >
< label for = "coreid-profile-shell-input" > { { t [ 'profile.advanced_shell' ] } } < / l a b e l >
< input
type = "text"
class = "form-control"
id = "coreid-profile-shell-input"
v - model = "profile_shell"
@ keyup = "on_key_up($event)"
placeholder = "/bin/bash"
>
< / 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 > { { t [ 'password.password' ] } } < / h 4 >
< p class = "font-italic" v - if = "last_reset" > { { t [ 'profile.pw_last_reset' ] . replace ( 'LAST_RESET' , last _reset ) } } < / p >
< button
type = "button"
class = "btn btn-primary btn-sm"
@ click = "change_password"
> { { t [ 'password.change' ] } } < / b u t t o n >
< / l i >
< li class = "list-group-item" >
< h4 > { { t [ 'authn.authn' ] } } < / h 4 >
< p > { { t [ 'authn.desc' ] . replace ( /APP_NAME/g , app _name ) } } < / p >
< button class = "btn btn-success btn-sm" type = "button" > { { t [ 'authn.enable' ] } } < / b u t t o n >
< / l i >
< li class = "list-group-item" v - if = "ready && !has_mfa && (!user_id || user_id === 'me')" >
< h4 > { { t [ 'mfa.mfa' ] } } < / h 4 >
< p > { { t [ 'profile.mfa_1' ] . replace ( /APP_NAME/g , app _name ) } } < / p >
< p > { { t [ 'profile.mfa_2' ] . replace ( /APP_NAME/g , app _name ) } } < / p >
< button class = "btn btn-success btn-sm" type = "button" @ click = "enable_mfa" > { { t [ 'mfa.enable' ] } } < / b u t t o n >
< / l i >
< li class = "list-group-item" v - if = "has_mfa && (!user_id || user_id === 'me')" >
< h4 > { { t [ 'mfa.mfa' ] } } < / h 4 >
< p class = "font-italic" > { { t [ 'profile.mfa_enabled_on' ] . replace ( 'MFA_ENABLED' , mfa _enable _date ) } } < / p >
< button
class = "btn btn-danger btn-sm"
type = "button"
@ click = "disable_mfa"
> { { t [ 'mfa.disable' ] } } < / b u t t o n >
< h6 class = "pad-top" > { { t [ 'profile.app_pws' ] } } < / h 6 >
< p > { { t [ 'profile.app_pw_1' ] . replace ( 'APP_NAME' , app _name ) } } < / p >
< p > { { t [ 'profile.app_pw_2' ] } } < / p >
< p > { { t [ 'profile.app_pw_3' ] } } < / p >
< p class = "font-italic text-muted" v - if = "app_passwords.length > 0" > { { t [ 'profile.app_pw_remaining.' + ( app _passwords . length === 1 ? 'one' : 'many' ) ] . replace ( 'NUM_PWS' , app _passwords . length ) } } < / 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" > { { t [ 'profile.issued' ] } } { { pw . created } } < / s p a n >
< span class = "text-muted font-italic" > & nbsp ; & nbsp ; | & nbsp ; & nbsp ; & nbsp ; { { t [ 'profile.accessed' ] } } { { pw . accessed || t [ 'common.never' ] } } < / 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)"
> { { t [ 'common.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" > { { t [ 'profile.gen_new' ] } } < / b u t t o n >
< h6 class = "pad-top" > { { t [ 'profile.recovery_codes' ] } } < / h 6 >
< p > { { t [ 'profile.recovery_1' ] } } < / p >
< span v - if = "!has_mfa_recovery" >
< p class = "font-italic" > { { t [ 'profile.no_recovery' ] } } < / p >
< button class = "btn btn-sm btn-success" @ click = "on_mfa_recovery_generate" > { { t [ 'profile.generate_recovery' ] } } < / b u t t o n >
< / s p a n >
< span v - if = "has_mfa_recovery" >
< p class = "font-italic" > { { t [ 'profile.recovery_gen_on' ] . replace ( 'MFA_RECOVERY' , mfa _recovery _date ) } } { { t [ 'profile.codes_remaining.' + ( mfa _recovery _codes === 1 ? 'one' : 'many' ) ] . replace ( 'NUM_CODES' , mfa _recovery _codes ) } } < / p >
< button class = "btn btn-sm btn-success" @ click = "on_mfa_recovery_generate" > { { t [ 'profile.regenerate_recovery' ] } } < / b u t t o n >
< / s p a n >
< / l i >
< li class = "list-group-item" v - if = "notify_loaded" >
< h4 > { { t [ 'profile.notifications' ] } } < / h 4 >
< p v - html = "t['profile.notify_explainer_1'].replace(/APP_NAME/g, app_name)" > < / p >
< p > { { t [ 'profile.notify_explainer_2' ] . replace ( /APP_NAME/g , app _name ) } } < / p >
< div class = "row" >
< div class = "col-12 form-group" >
< input
type = "text"
class = "form-control"
: placeholder = "t['profile.example_gateway_url']"
id = "coreid-profile-notify-gateway-url"
v - model = "notify_gateway_url"
>
< / d i v >
< div class = "col-12 form-group" >
< input
type = "text"
class = "form-control"
: placeholder = "t['profile.app_key']"
id = "coreid-profile-notify-app-key"
v - model = "notify_app_key"
>
< / d i v >
< div class = "col-12 mt-2" >
< button class = "btn btn-sm btn-primary" @ click = "on_notifications_save" > { { t [ 'profile.save_notify' ] } } < / b u t t o n >
< button
class = "btn btn-sm btn-secondary"
v - if = "notify_app_key && notify_gateway_url && notify_enabled"
@ click = "send_test_notification"
> { { t [ 'profile.test_notify' ] } } < / b u t t o n >
< / d i v >
< / d i v >
< / 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' ] }
constructor ( ) {
super ( )
this . profile _first = ''
this . profile _last = ''
this . profile _email = ''
this . profile _tagline = ''
this . profile _shell = ''
this . last _reset = ''
this . mfa _enable _date = ''
this . has _mfa _recovery = false
this . mfa _recovery _date = ''
this . mfa _recovery _codes = 0
this . form _message = 'No changes.'
this . has _mfa = false
this . ready = false
this . notify _gateway _url = ''
this . notify _app _key = ''
this . notify _enabled = false
this . notify _created _on = ''
this . notify _loaded = false
this . app _passwords = [ ]
this . app _name = ''
this . t = { }
}
on _key _up = ( $event ) => { }
async vue _on _create ( ) {
this . t = await T (
'profile.profile_photo' ,
'common.change' ,
'profile.basic_profile' ,
'register.first_name' ,
'profile.placeholder_first' ,
'register.last_name' ,
'profile.placeholder_last' ,
'register.email' ,
'profile.placeholder_email' ,
'profile.tagline' ,
'password.password' ,
'password.change' ,
'profile.pw_last_reset' ,
'mfa.mfa' ,
'profile.mfa_enabled_on' ,
'mfa.disable' ,
'profile.app_pws' ,
'profile.issued' ,
'common.deactivate' ,
'profile.app_pw_remaining.one' ,
'profile.app_pw_remaining.many' ,
'profile.gen_new' ,
'profile.recovery_codes' ,
'profile.recovery_1' ,
'profile.no_recovery' ,
'profile.generate_recovery' ,
'profile.recovery_gen_on' ,
'profile.codes_remaining.one' ,
'profile.codes_remaining.many' ,
'profile.regenerate_recovery' ,
'profile.app_pw_1' ,
'profile.app_pw_2' ,
'profile.app_pw_3' ,
'profile.mfa_1' ,
'profile.mfa_2' ,
'mfa.enable' ,
'profile.notifications' ,
'profile.notify_explainer_1' ,
'profile.notify_explainer_2' ,
'profile.app_key' ,
'profile.example_gateway_url' ,
'profile.save_notify' ,
'profile.test_notify' ,
'profile.advanced_header' ,
'profile.advanced_shell' ,
'profile.accessed' ,
'common.never' ,
'authn.authn' ,
'authn.desc' ,
'authn.enable' ,
)
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 ,
login _shell : this . profile _shell ,
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
this . profile _shell = result . login _shell
const notify _config = await profile _service . get _notify ( this . user _id || 'me' )
if ( ! notify _config || ! notify _config . has _config ) {
this . notify _app _key = ''
this . notify _enabled = false
this . notify _created _on = ''
this . notify _gateway _url = ''
} else if ( notify _config && notify _config . has _config ) {
this . notify _app _key = notify _config . config . application _key
this . notify _enabled = notify _config . config . active
this . notify _created _on = notify _config . config . created _on
this . notify _gateway _url = notify _config . config . gateway _url
}
this . notify _loaded = true
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 ( )
if ( x . accessed ) x . accessed = ( new Date ( x . accessed ) ) . 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' ,
} ,
] ,
} )
}
async on _notifications _save ( ) {
const data = {
user _id : this . user _id || 'me' ,
app _key : this . notify _app _key ,
gateway _url : this . notify _gateway _url ,
}
await profile _service . update _notify ( data )
await this . load ( )
}
async send _test _notification ( ) {
await profile _service . test _notify ( { user _id : this . user _id || 'me' } )
}
}