Rework login page to be AJAX/Vue.js based
This commit is contained in:
125
app/assets/app/auth/login/Form.component.js
Normal file
125
app/assets/app/auth/login/Form.component.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import { Component } from '../../../lib/vues6/vues6.js'
|
||||
import { auth_api } from '../../service/AuthApi.service.js'
|
||||
import { location_service } from '../../service/Location.service.js'
|
||||
|
||||
const template = `
|
||||
<div class="coreid-login-form col-lg-6 col-md-8 col-sm-10 col-xs-12 offset-lg-3 offset-md-2 offset-sm-1 offset-xs-0 text-left">
|
||||
<div class="coreid-login-form-inner">
|
||||
<div class="coreid-login-form-header font-weight-light">{{ app_name }}</div>
|
||||
<div class="coreid-login-form-message">{{ login_message }}</div>
|
||||
<form class="coreid-form" v-on:submit.prevent="do_nothing">
|
||||
<div class="form-group">
|
||||
<input
|
||||
type="text"
|
||||
id="coreid-login-form-username"
|
||||
name="username"
|
||||
class="form-control"
|
||||
placeholder="Username"
|
||||
v-model="username"
|
||||
autofocus
|
||||
@keyup="on_key_up"
|
||||
:disabled="loading || step_two"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group" v-if="step_two">
|
||||
<input
|
||||
type="password"
|
||||
id="coreid-login-form-password"
|
||||
name="password"
|
||||
class="form-control"
|
||||
placeholder="Password"
|
||||
v-model="password"
|
||||
:disabled="loading"
|
||||
@keyup="on_key_up"
|
||||
ref="password_input"
|
||||
>
|
||||
</div>
|
||||
<div v-if="error_message" class="error-message">{{ error_message }}</div>
|
||||
<div v-if="other_message" class="other-message">{{ other_message }}</div>
|
||||
<div class="buttons text-right">
|
||||
<button type="button" class="btn btn-primary" :disabled="loading" v-if="step_two" v-on:click="back_click">Back</button>
|
||||
<button type="button" class="btn btn-primary" :disabled="loading || btn_disabled" v-on:click="step_click">{{ button_text }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="coreid-loading-spinner" v-if="loading"><div class="inner"></div></div>
|
||||
</div>
|
||||
`
|
||||
|
||||
export default class AuthLoginForm extends Component {
|
||||
static get selector() { return 'coreid-login-form' }
|
||||
static get props() { return ['app_name', 'login_message'] }
|
||||
static get template() { return template }
|
||||
|
||||
username = ''
|
||||
password = ''
|
||||
button_text = 'Next'
|
||||
step_two = false
|
||||
btn_disabled = true
|
||||
loading = false
|
||||
error_message = ''
|
||||
other_message = ''
|
||||
|
||||
watch_username(new_username, old_username) {
|
||||
this.btn_disabled = !new_username
|
||||
}
|
||||
|
||||
back_click() {
|
||||
this.step_two = false
|
||||
this.button_text = 'Next'
|
||||
}
|
||||
|
||||
async on_key_up(event) {
|
||||
if ( event.keyCode === 13 ) {
|
||||
// Enter was pressed
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if ( !this.step_two && this.username ) return this.step_click(event)
|
||||
else if ( this.step_two && this.username && this.password ) return this.step_click(event)
|
||||
}
|
||||
}
|
||||
|
||||
async step_click(event) {
|
||||
if ( !this.step_two ) {
|
||||
this.loading = true
|
||||
try {
|
||||
const is_valid = await auth_api.validate_username(this.username)
|
||||
if ( !is_valid ) {
|
||||
this.error_message = 'That username is invalid. Please try again.'
|
||||
} else {
|
||||
this.step_two = true
|
||||
this.button_text = 'Continue'
|
||||
this.error_message = ''
|
||||
this.$nextTick(() => {
|
||||
this.$refs.password_input.focus()
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
this.error_message = 'Sorry, an unknown error has occurred and we are unable to continue at this time.'
|
||||
}
|
||||
this.loading = false
|
||||
} else {
|
||||
this.loading = true
|
||||
this.error_message = ''
|
||||
|
||||
const result = await auth_api.attempt({
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
create_session: true, // TODO support this being passed in
|
||||
})
|
||||
|
||||
if ( !result.success ) {
|
||||
this.error_message = result.message || 'Sorry, an unknown error has occurred and we are unable to continue at this time.'
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
|
||||
// TODO handle 2FA
|
||||
|
||||
this.other_message = 'Success! Let\'s get you on your way...'
|
||||
await location_service.redirect(result.next, 1500)
|
||||
}
|
||||
}
|
||||
|
||||
do_nothing() {}
|
||||
}
|
||||
7
app/assets/app/components.js
Normal file
7
app/assets/app/components.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import AuthLoginForm from "./auth/login/Form.component.js"
|
||||
|
||||
const components = {
|
||||
AuthLoginForm
|
||||
}
|
||||
|
||||
export { components }
|
||||
23
app/assets/app/service/AuthApi.service.js
Normal file
23
app/assets/app/service/AuthApi.service.js
Normal file
@@ -0,0 +1,23 @@
|
||||
class AuthAPI {
|
||||
async validate_username(username) {
|
||||
const result = await axios.post('/api/v1/auth/validate/username', { username })
|
||||
return result && result.data && result.data.data && result.data.data.is_valid
|
||||
}
|
||||
|
||||
async attempt({ username, password, create_session }) {
|
||||
try {
|
||||
const result = await axios.post('/api/v1/auth/attempt', {
|
||||
username, password, create_session
|
||||
})
|
||||
|
||||
if ( result && result.data && result.data.data && result.data.data ) {
|
||||
return result.data.data
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return { success: false }
|
||||
}
|
||||
}
|
||||
|
||||
const auth_api = new AuthAPI()
|
||||
export { auth_api }
|
||||
13
app/assets/app/service/Location.service.js
Normal file
13
app/assets/app/service/Location.service.js
Normal file
@@ -0,0 +1,13 @@
|
||||
class LocationService {
|
||||
async redirect(to, delay = 0) {
|
||||
return new Promise(res => {
|
||||
setTimeout(() => {
|
||||
window.location = to
|
||||
res()
|
||||
}, delay)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const location_service = new LocationService()
|
||||
export { location_service }
|
||||
Reference in New Issue
Block a user