Start client-side locale support
This commit is contained in:
parent
d2ae9c43e8
commit
c956628c53
@ -7,13 +7,13 @@ const template = `
|
|||||||
<div class="coreid-auth-page-inner">
|
<div class="coreid-auth-page-inner">
|
||||||
<div class="coreid-header font-weight-light">{{ app_name }}</div>
|
<div class="coreid-header font-weight-light">{{ app_name }}</div>
|
||||||
<div class="coreid-message">
|
<div class="coreid-message">
|
||||||
Your account has multi-factor authentication enabled. Please enter the code generated by your authenticator app to continue.
|
{{ t['mfa.challenge_prompt'] }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="2FA Code"
|
:placeholder="t['mfa.mfa_code']"
|
||||||
v-model="verify_code"
|
v-model="verify_code"
|
||||||
@keyup="on_key_up"
|
@keyup="on_key_up"
|
||||||
name="verify_code"
|
name="verify_code"
|
||||||
@ -28,7 +28,7 @@ const template = `
|
|||||||
<div class="coreid-loading-spinner" v-if="loading"><div class="inner"></div></div>
|
<div class="coreid-loading-spinner" v-if="loading"><div class="inner"></div></div>
|
||||||
<small
|
<small
|
||||||
class="mr-3"
|
class="mr-3"
|
||||||
><a href="#" class="text-secondary" @click="on_do_recovery">Lost your MFA device?</a></small>
|
><a href="#" class="text-secondary" @click="on_do_recovery">{{ t['mfa.lost_device'] }}</a></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@ -45,6 +45,19 @@ export default class MFAChallengePage extends Component {
|
|||||||
|
|
||||||
error_message = ''
|
error_message = ''
|
||||||
other_message = ''
|
other_message = ''
|
||||||
|
t = {}
|
||||||
|
|
||||||
|
async vue_on_create() {
|
||||||
|
this.t = await T(
|
||||||
|
'mfa.challenge_prompt',
|
||||||
|
'mfa.mfa_code',
|
||||||
|
'mfa.lost_device',
|
||||||
|
'mfa.invalid_code',
|
||||||
|
'mfa.success'
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(this)
|
||||||
|
}
|
||||||
|
|
||||||
async watch_verify_code(new_verify_code, old_verify_code) {
|
async watch_verify_code(new_verify_code, old_verify_code) {
|
||||||
if ( new_verify_code.length === 6 ) {
|
if ( new_verify_code.length === 6 ) {
|
||||||
@ -53,11 +66,11 @@ export default class MFAChallengePage extends Component {
|
|||||||
this.verify_success = result && result.is_valid
|
this.verify_success = result && result.is_valid
|
||||||
if ( !this.verify_success ) {
|
if ( !this.verify_success ) {
|
||||||
this.other_message = ''
|
this.other_message = ''
|
||||||
this.error_message = `Uh, oh! It looks like that's not the right code. Please try again.`
|
this.error_message = this.t['mfa.invalid_code']
|
||||||
this.loading = false
|
this.loading = false
|
||||||
} else {
|
} else {
|
||||||
this.error_message = ''
|
this.error_message = ''
|
||||||
this.other_message = `Success! Redirecting...`
|
this.other_message = this.t['mfa.success']
|
||||||
await location_service.redirect(result.next_destination, 1500)
|
await location_service.redirect(result.next_destination, 1500)
|
||||||
}
|
}
|
||||||
} else if ( new_verify_code.length > 6 ) {
|
} else if ( new_verify_code.length > 6 ) {
|
||||||
|
@ -9,17 +9,13 @@ const template = `
|
|||||||
<div class="coreid-header font-weight-light">{{ app_name }}</div>
|
<div class="coreid-header font-weight-light">{{ app_name }}</div>
|
||||||
<span v-if="step === 0">
|
<span v-if="step === 0">
|
||||||
<div class="coreid-message">
|
<div class="coreid-message">
|
||||||
This process will disable multi-factor authentication on your account.
|
<span v-html="t['mfa.disable_prompt']"></span>
|
||||||
<br><br>
|
|
||||||
For security reasons, this will sign you out of all devices. It will also deactivate any existing app passwords you have generated.
|
|
||||||
<br><br>
|
|
||||||
Are you sure you want to continue?
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="error_message" class="error-message">{{ error_message }}</div>
|
<div v-if="error_message" class="error-message">{{ error_message }}</div>
|
||||||
<div v-if="other_message" class="other-message">{{ other_message }}</div>
|
<div v-if="other_message" class="other-message">{{ other_message }}</div>
|
||||||
<div class="buttons text-right pad-top">
|
<div class="buttons text-right pad-top">
|
||||||
<button type="button" class="btn btn-primary" @click="back_click">Cancel</button>
|
<button type="button" class="btn btn-primary" @click="back_click">{{ t['common.cancel'] }}</button>
|
||||||
<button type="button" class="btn btn-primary" @click="continue_click">Disable MFA</button>
|
<button type="button" class="btn btn-primary" @click="continue_click">{{ t['mfa.disable'] }}</button>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div class="coreid-loading-spinner" v-if="loading"><div class="inner"></div></div>
|
<div class="coreid-loading-spinner" v-if="loading"><div class="inner"></div></div>
|
||||||
@ -37,9 +33,18 @@ export default class MFADisableComponent extends Component {
|
|||||||
loading = false
|
loading = false
|
||||||
error_message = ''
|
error_message = ''
|
||||||
other_message = ''
|
other_message = ''
|
||||||
|
t = {}
|
||||||
|
|
||||||
vue_on_create() {
|
async vue_on_create() {
|
||||||
this.app_name = session.get('app.name')
|
this.app_name = session.get('app.name')
|
||||||
|
|
||||||
|
this.t = await T(
|
||||||
|
'common.cancel',
|
||||||
|
'common.unknown_error',
|
||||||
|
'mfa.disable_success',
|
||||||
|
'mfa.disable',
|
||||||
|
'mfa.disable_prompt'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async back_click() {
|
async back_click() {
|
||||||
@ -51,10 +56,10 @@ export default class MFADisableComponent extends Component {
|
|||||||
this.loading = true
|
this.loading = true
|
||||||
const success = await auth_api.mfa_disable()
|
const success = await auth_api.mfa_disable()
|
||||||
if ( success ) {
|
if ( success ) {
|
||||||
this.other_message = 'MFA was successfully disabled. You\'ll now sign-in normally.'
|
this.other_message = this.t['mfa.disable_success']
|
||||||
await location_service.redirect('/dash/profile', 3000)
|
await location_service.redirect('/dash/profile', 3000)
|
||||||
} else {
|
} else {
|
||||||
this.error_message = 'An unknown error occurred while trying to disable MFA. Let\'s try again...'
|
this.error_message = this.t['common.unknown_error']
|
||||||
await location_service.reload(4000)
|
await location_service.reload(4000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,13 @@ const template = `
|
|||||||
<div class="coreid-auth-page-inner">
|
<div class="coreid-auth-page-inner">
|
||||||
<div class="coreid-header font-weight-light">{{ app_name }}</div>
|
<div class="coreid-header font-weight-light">{{ app_name }}</div>
|
||||||
<div class="coreid-message">
|
<div class="coreid-message">
|
||||||
To recover access to your account, you can enter one of the generated MFA recovery codes:
|
{{ t['mfa.recover_prompt'] }}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Recovery Code"
|
:placeholder="t['mfa.recovery_code']"
|
||||||
v-model="recovery_code"
|
v-model="recovery_code"
|
||||||
@keyup="on_key_up"
|
@keyup="on_key_up"
|
||||||
name="recovery_code"
|
name="recovery_code"
|
||||||
@ -27,7 +27,7 @@ const template = `
|
|||||||
<div v-if="other_message" class="other-message">{{ other_message }}</div>
|
<div v-if="other_message" class="other-message">{{ other_message }}</div>
|
||||||
<small
|
<small
|
||||||
class="mr-3" v-if="!loading"
|
class="mr-3" v-if="!loading"
|
||||||
><a href="#" class="text-secondary" @click="on_do_challenge">Have a normal MFA code?</a></small>
|
><a href="#" class="text-secondary" @click="on_do_challenge">{{ t['mfa.normal_code'] }}</a></small>
|
||||||
<div class="coreid-loading-spinner" v-if="loading"><div class="inner"></div></div>
|
<div class="coreid-loading-spinner" v-if="loading"><div class="inner"></div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -43,8 +43,17 @@ export default class MFARecoveryComponent extends Component {
|
|||||||
recovery_code = ''
|
recovery_code = ''
|
||||||
error_message = ''
|
error_message = ''
|
||||||
other_message = ''
|
other_message = ''
|
||||||
|
t = {}
|
||||||
|
|
||||||
async vue_on_create() {
|
async vue_on_create() {
|
||||||
|
this.t = await T(
|
||||||
|
'mfa.recover_prompt',
|
||||||
|
'mfa.recovery_code',
|
||||||
|
'mfa.normal_code',
|
||||||
|
'mfa.recover_success',
|
||||||
|
'mfa.invalid_code'
|
||||||
|
)
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.verify_input.focus()
|
this.$refs.verify_input.focus()
|
||||||
})
|
})
|
||||||
@ -57,11 +66,11 @@ export default class MFARecoveryComponent extends Component {
|
|||||||
this.loading = true
|
this.loading = true
|
||||||
const result = await auth_api.attempt_mfa_recovery(this.recovery_code)
|
const result = await auth_api.attempt_mfa_recovery(this.recovery_code)
|
||||||
if ( result && result.success ) {
|
if ( result && result.success ) {
|
||||||
this.other_message = `Success! There are only ${result.remaining_codes} recovery codes remaining. Let's get you on your way...`
|
this.other_message = this.t['mfa.recover_success'].replace('NUM_CODES', result.remaining_codes)
|
||||||
await location_service.redirect(result.next_destination, 5000)
|
await location_service.redirect(result.next_destination, 5000)
|
||||||
} else {
|
} else {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.error_message = 'Hm. It doesn\'t look like that code is valid.'
|
this.error_message = this.t['mfa.invalid_code']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ const template = `
|
|||||||
id="coreid-login-form-username"
|
id="coreid-login-form-username"
|
||||||
name="username"
|
name="username"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="Username"
|
:placeholder="t['login.username']"
|
||||||
v-model="username"
|
v-model="username"
|
||||||
autofocus
|
autofocus
|
||||||
@keyup="on_key_up"
|
@keyup="on_key_up"
|
||||||
@ -29,7 +29,7 @@ const template = `
|
|||||||
id="coreid-login-form-password"
|
id="coreid-login-form-password"
|
||||||
name="password"
|
name="password"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="Password"
|
:placeholder="t['login.password']"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
@keyup="on_key_up"
|
@keyup="on_key_up"
|
||||||
@ -42,12 +42,12 @@ const template = `
|
|||||||
<small
|
<small
|
||||||
class="mr-3"
|
class="mr-3"
|
||||||
v-if="!step_two && !loading && registration_enabled"
|
v-if="!step_two && !loading && registration_enabled"
|
||||||
><a href="#" class="text-secondary" @click="on_register_click">Need an account?</a></small>
|
><a href="#" class="text-secondary" @click="on_register_click">{{ t['login.need_account'] }}</a></small>
|
||||||
<small
|
<small
|
||||||
class="mr-3"
|
class="mr-3"
|
||||||
v-if="!auth_user && !loading"
|
v-if="!auth_user && !loading"
|
||||||
><a href="#" class="text-secondary" @click="on_forgot_password">Forgot password?</a></small>
|
><a href="#" class="text-secondary" @click="on_forgot_password">{{ t['login.forgot_password'] }}</a></small>
|
||||||
<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" v-if="step_two" v-on:click="back_click">{{ t['common.back'] }}</button>
|
||||||
<button type="button" class="btn btn-primary" :disabled="loading || btn_disabled" v-on:click="step_click">{{ button_text }}</button>
|
<button type="button" class="btn btn-primary" :disabled="loading || btn_disabled" v-on:click="step_click">{{ button_text }}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -65,7 +65,7 @@ export default class AuthLoginForm extends Component {
|
|||||||
|
|
||||||
username = ''
|
username = ''
|
||||||
password = ''
|
password = ''
|
||||||
button_text = 'Next'
|
button_text = ''
|
||||||
step_two = false
|
step_two = false
|
||||||
btn_disabled = true
|
btn_disabled = true
|
||||||
loading = false
|
loading = false
|
||||||
@ -74,6 +74,8 @@ export default class AuthLoginForm extends Component {
|
|||||||
allow_back = true
|
allow_back = true
|
||||||
auth_user = false
|
auth_user = false
|
||||||
|
|
||||||
|
t = {}
|
||||||
|
|
||||||
watch_username(new_username, old_username) {
|
watch_username(new_username, old_username) {
|
||||||
this.btn_disabled = !new_username
|
this.btn_disabled = !new_username
|
||||||
}
|
}
|
||||||
@ -81,7 +83,7 @@ export default class AuthLoginForm extends Component {
|
|||||||
back_click() {
|
back_click() {
|
||||||
if ( !this.allow_back ) return;
|
if ( !this.allow_back ) return;
|
||||||
this.step_two = false
|
this.step_two = false
|
||||||
this.button_text = 'Next'
|
this.button_text = this.t['common.next']
|
||||||
}
|
}
|
||||||
|
|
||||||
async vue_on_create() {
|
async vue_on_create() {
|
||||||
@ -92,6 +94,28 @@ export default class AuthLoginForm extends Component {
|
|||||||
this.username = auth_user
|
this.username = auth_user
|
||||||
await this.step_click()
|
await this.step_click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Batch-load translations all at once to make fewer requests
|
||||||
|
this.t = await T(
|
||||||
|
'common.continue',
|
||||||
|
'common.unknown_error',
|
||||||
|
'common.cancel',
|
||||||
|
'common.request',
|
||||||
|
'common.back',
|
||||||
|
'common.next',
|
||||||
|
'login.username',
|
||||||
|
'login.password',
|
||||||
|
'login.need_account',
|
||||||
|
'login.forgot_password',
|
||||||
|
'login.username_invalid',
|
||||||
|
'login.reset_password',
|
||||||
|
'login.reset_password_prompt',
|
||||||
|
'login.reset_password_success',
|
||||||
|
'login.success',
|
||||||
|
'login.get_started'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.button_text = this.t['common.next']
|
||||||
}
|
}
|
||||||
|
|
||||||
async on_key_up(event) {
|
async on_key_up(event) {
|
||||||
@ -110,17 +134,17 @@ export default class AuthLoginForm extends Component {
|
|||||||
try {
|
try {
|
||||||
const is_valid = await auth_api.validate_username(this.username)
|
const is_valid = await auth_api.validate_username(this.username)
|
||||||
if ( !is_valid ) {
|
if ( !is_valid ) {
|
||||||
this.error_message = 'That username is invalid. Please try again.'
|
this.error_message = this.t['login.username_invalid']
|
||||||
} else {
|
} else {
|
||||||
this.step_two = true
|
this.step_two = true
|
||||||
this.button_text = 'Continue'
|
this.button_text = this.t['common.continue']
|
||||||
this.error_message = ''
|
this.error_message = ''
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.password_input.focus()
|
this.$refs.password_input.focus()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.error_message = 'Sorry, an unknown error has occurred and we are unable to continue at this time.'
|
this.error_message = this.t['common.unknown_error']
|
||||||
}
|
}
|
||||||
this.loading = false
|
this.loading = false
|
||||||
} else {
|
} else {
|
||||||
@ -139,27 +163,27 @@ export default class AuthLoginForm extends Component {
|
|||||||
|
|
||||||
const result = await auth_api.attempt(attempt_vars)
|
const result = await auth_api.attempt(attempt_vars)
|
||||||
if ( !result.success ) {
|
if ( !result.success ) {
|
||||||
this.error_message = result.message || 'Sorry, an unknown error has occurred and we are unable to continue at this time.'
|
this.error_message = result.message || this.t['common.unknown_error']
|
||||||
this.loading = false
|
this.loading = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.other_message = 'Success! Let\'s get you on your way...'
|
this.other_message = this.t['login.success']
|
||||||
await location_service.redirect(result.next, 1500)
|
await location_service.redirect(result.next, 1500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
on_register_click() {
|
on_register_click() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.other_message = 'Okay! Let\'s get started...'
|
this.other_message = this.t['login.get_started']
|
||||||
location_service.redirect('/auth/register', 1500) // TODO get this dynamically
|
location_service.redirect('/auth/register', 1500) // TODO get this dynamically
|
||||||
}
|
}
|
||||||
|
|
||||||
async on_forgot_password() {
|
async on_forgot_password() {
|
||||||
console.log(message_service)
|
console.log(message_service)
|
||||||
await message_service.modal({
|
await message_service.modal({
|
||||||
title: 'Reset Password',
|
title: this.t['login.reset_password'],
|
||||||
message: 'If you have forgotten your password, you can request a reset e-mail to be sent to your account. Enter your e-mail address:',
|
message: this.t['login.reset_password_prompt'],
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
type: 'email',
|
type: 'email',
|
||||||
@ -170,17 +194,17 @@ export default class AuthLoginForm extends Component {
|
|||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
type: 'close',
|
type: 'close',
|
||||||
text: 'Cancel',
|
text: this.t['common.cancel'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'close',
|
type: 'close',
|
||||||
class: ['btn', 'btn-primary'],
|
class: ['btn', 'btn-primary'],
|
||||||
text: 'Request',
|
text: this.t['common.request'],
|
||||||
on_click: async ($event, { email }) => {
|
on_click: async ($event, { email }) => {
|
||||||
await password_service.request_reset(email)
|
await password_service.request_reset(email)
|
||||||
await message_service.alert({
|
await message_service.alert({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: 'Success! If that e-mail address is associated with a valid ' + this.app_name + ' account, it will receive an e-mail with more instructions shortly.',
|
message: this.t['login.reset_password_success'].replace('APP_NAME', this.app_name),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -15,7 +15,7 @@ const template = `
|
|||||||
id="coreid-registration-form-first"
|
id="coreid-registration-form-first"
|
||||||
name="first_name"
|
name="first_name"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="First Name"
|
:placeholder="t['register.first_name']"
|
||||||
v-model="first_name"
|
v-model="first_name"
|
||||||
autofocus
|
autofocus
|
||||||
@keyup="on_key_up"
|
@keyup="on_key_up"
|
||||||
@ -29,7 +29,7 @@ const template = `
|
|||||||
id="coreid-registration-form-last"
|
id="coreid-registration-form-last"
|
||||||
name="last_name"
|
name="last_name"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="Last Name"
|
:placeholder="t['register.last_name']"
|
||||||
v-model="last_name"
|
v-model="last_name"
|
||||||
autofocus
|
autofocus
|
||||||
@keyup="on_key_up"
|
@keyup="on_key_up"
|
||||||
@ -43,7 +43,7 @@ const template = `
|
|||||||
id="coreid-registration-form-username"
|
id="coreid-registration-form-username"
|
||||||
name="username"
|
name="username"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="Username"
|
:placeholder="t['login.username']"
|
||||||
v-model="username"
|
v-model="username"
|
||||||
autofocus
|
autofocus
|
||||||
@keyup="on_key_up"
|
@keyup="on_key_up"
|
||||||
@ -58,7 +58,7 @@ const template = `
|
|||||||
id="coreid-registration-form-email"
|
id="coreid-registration-form-email"
|
||||||
name="email"
|
name="email"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="E-Mail"
|
:placeholder="t['register.email']"
|
||||||
v-model="email"
|
v-model="email"
|
||||||
autofocus
|
autofocus
|
||||||
@keyup="on_key_up"
|
@keyup="on_key_up"
|
||||||
@ -72,14 +72,14 @@ const template = `
|
|||||||
<small
|
<small
|
||||||
class="mr-3"
|
class="mr-3"
|
||||||
v-if="step === 1 && !loading"
|
v-if="step === 1 && !loading"
|
||||||
><a href="#" class="text-secondary" @click="on_login_click">Already have an account?</a></small>
|
><a href="#" class="text-secondary" @click="on_login_click">{{ t['register.have_account'] }}</a></small>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
v-if="step > 1"
|
v-if="step > 1"
|
||||||
v-on:click="back_click"
|
v-on:click="back_click"
|
||||||
>Back</button>
|
>{{ t['common.back'] }}</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
@ -104,15 +104,37 @@ export default class RegistrationFormComponent extends Component {
|
|||||||
error_message = ''
|
error_message = ''
|
||||||
message = ''
|
message = ''
|
||||||
btn_disabled = true
|
btn_disabled = true
|
||||||
button_text = 'Continue'
|
button_text = ''
|
||||||
|
|
||||||
first_name = ''
|
first_name = ''
|
||||||
last_name = ''
|
last_name = ''
|
||||||
username = ''
|
username = ''
|
||||||
email = ''
|
email = ''
|
||||||
|
t = {}
|
||||||
|
|
||||||
async vue_on_create() {
|
async vue_on_create() {
|
||||||
this.message = 'Create an account to continue:'
|
// Batch-load translated phrases
|
||||||
|
this.t = await T(
|
||||||
|
'common.back',
|
||||||
|
'common.continue',
|
||||||
|
'common.unknown_error',
|
||||||
|
'login.username',
|
||||||
|
'register.first_name',
|
||||||
|
'register.last_name',
|
||||||
|
'register.email',
|
||||||
|
'register.have_account',
|
||||||
|
'register.create_to_continue',
|
||||||
|
'register.provide_first_last',
|
||||||
|
'register.hi_choose_username',
|
||||||
|
'register.username_in_use',
|
||||||
|
'register.invalid_email',
|
||||||
|
'register.email_in_use',
|
||||||
|
'register.success_password',
|
||||||
|
'register.login_instead'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.button_text = this.t['common.continue']
|
||||||
|
this.message = this.t['register.create_to_continue']
|
||||||
}
|
}
|
||||||
|
|
||||||
async step_click() {
|
async step_click() {
|
||||||
@ -121,18 +143,18 @@ export default class RegistrationFormComponent extends Component {
|
|||||||
this.other_message = ''
|
this.other_message = ''
|
||||||
if ( this.step === 1 ) {
|
if ( this.step === 1 ) {
|
||||||
if ( !this.first_name.trim() || !this.last_name.trim() ) {
|
if ( !this.first_name.trim() || !this.last_name.trim() ) {
|
||||||
this.error_message = 'Please provide your first and last name.'
|
this.error_message = this.t['register.provide_first_last']
|
||||||
this.loading = false
|
this.loading = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.message = `Hi, ${this.first_name.trim()}. Now, you need to choose a username:`
|
this.message = this.t['register.hi_choose_username'].replace('FIRST_NAME', this.first_name.trim())
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.input_username.focus()
|
this.$refs.input_username.focus()
|
||||||
})
|
})
|
||||||
} else if ( this.step === 2 ) {
|
} else if ( this.step === 2 ) {
|
||||||
if ( await auth_api.username_taken(this.username) ) {
|
if ( await auth_api.username_taken(this.username) ) {
|
||||||
this.error_message = 'That username is already taken.'
|
this.error_message = this.t['register.username_in_use']
|
||||||
this.loading = false
|
this.loading = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -144,13 +166,13 @@ export default class RegistrationFormComponent extends Component {
|
|||||||
})
|
})
|
||||||
} else if ( this.step === 3 ) {
|
} else if ( this.step === 3 ) {
|
||||||
if ( !(await auth_api.validate_email(this.email)) ) {
|
if ( !(await auth_api.validate_email(this.email)) ) {
|
||||||
this.error_message = 'Please provide a valid e-mail address.'
|
this.error_message = this.t['register.invalid_email']
|
||||||
this.loading = false
|
this.loading = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( await auth_api.email_taken(this.email) ) {
|
if ( await auth_api.email_taken(this.email) ) {
|
||||||
this.error_message = 'That e-mail address is already taken.'
|
this.error_message = this.t['register.email_in_use']
|
||||||
this.loading = false
|
this.loading = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -163,12 +185,12 @@ export default class RegistrationFormComponent extends Component {
|
|||||||
email: this.email,
|
email: this.email,
|
||||||
})
|
})
|
||||||
|
|
||||||
if ( !user ) this.error_message = 'Sorry, an unknown error has occurred and we are unable to continue at this time.'
|
if ( !user ) this.error_message = this.t['common.unknown_error']
|
||||||
else this.other_message = 'Welcome! Let\'s get your password set up...'
|
else this.other_message = this.t['register.success_password']
|
||||||
this.btn_disabled = true
|
this.btn_disabled = true
|
||||||
return location_service.redirect('/dash', 2000)
|
return location_service.redirect('/dash', 2000)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.error_message = e.message || 'Sorry, an unknown error has occurred and we are unable to continue at this time.'
|
this.error_message = e.message || this.t['common.unknown_error']
|
||||||
this.loading = false
|
this.loading = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -186,7 +208,7 @@ export default class RegistrationFormComponent extends Component {
|
|||||||
this.on_key_up()
|
this.on_key_up()
|
||||||
|
|
||||||
if ( this.step === 1 ) {
|
if ( this.step === 1 ) {
|
||||||
this.message = 'Create an account to continue:'
|
this.message = this.t['register.create_to_continue']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +231,7 @@ export default class RegistrationFormComponent extends Component {
|
|||||||
|
|
||||||
on_login_click() {
|
on_login_click() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.other_message = 'Okay! We\'ll have you login instead...'
|
this.other_message = this.t['register.login_instead']
|
||||||
location_service.redirect('/auth/login', 1500)
|
location_service.redirect('/auth/login', 1500)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,9 @@ import RegistrationFormComponent from './auth/register/Form.component.js'
|
|||||||
import MessageContainerComponent from './dash/message/MessageContainer.component.js'
|
import MessageContainerComponent from './dash/message/MessageContainer.component.js'
|
||||||
import MFARecoveryComponent from './auth/MFARecovery.component.js'
|
import MFARecoveryComponent from './auth/MFARecovery.component.js'
|
||||||
|
|
||||||
|
// Create the global
|
||||||
|
import { T } from './service/Translate.service.js'
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
AuthLoginForm,
|
AuthLoginForm,
|
||||||
AuthPage,
|
AuthPage,
|
||||||
|
57
app/assets/app/service/Translate.service.js
Normal file
57
app/assets/app/service/Translate.service.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
class TranslateService {
|
||||||
|
_cache = {}
|
||||||
|
|
||||||
|
check_cache(...keys) {
|
||||||
|
const obj = {}
|
||||||
|
for ( const key of keys ) {
|
||||||
|
if ( this._cache[key] )
|
||||||
|
obj[key] = this._cache[key]
|
||||||
|
else return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolve(key) {
|
||||||
|
const cache_hit = this.check_cache(key)
|
||||||
|
if ( cache_hit ) return cache_hit[key]
|
||||||
|
const result = await axios.get('/api/v1/locale/resolve/'+key)
|
||||||
|
if ( result && result.data && result.data.data ) {
|
||||||
|
this._cache[key] = result.data.data
|
||||||
|
return result.data.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async load_module(key) {
|
||||||
|
const result = await axios.get('/api/v1/locale/module/'+key)
|
||||||
|
if ( result && result.data && result.data.data ) {
|
||||||
|
for ( const key in result.data.data ) {
|
||||||
|
if ( !this._cache[key] ) this._cache[key] = result.data.data[key]
|
||||||
|
}
|
||||||
|
return result.data.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async batch_resolve(...keys) {
|
||||||
|
const cache_hit = this.check_cache(...keys)
|
||||||
|
if ( cache_hit ) return cache_hit
|
||||||
|
const result = await axios.post('/api/v1/locale/batch', { resolvers: keys })
|
||||||
|
if ( result && result.data && result.data.data ) {
|
||||||
|
for ( const key in result.data.data ) {
|
||||||
|
if ( !this._cache[key] ) this._cache[key] = result.data.data[key]
|
||||||
|
}
|
||||||
|
return result.data.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const translate_service = new TranslateService()
|
||||||
|
|
||||||
|
const T = (...keys) => {
|
||||||
|
if ( keys.length === 1 ) return translate_service.resolve(keys[0])
|
||||||
|
else return translate_service.batch_resolve(...keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.T = T
|
||||||
|
|
||||||
|
export { translate_service, T }
|
44
app/controllers/api/v1/Locale.controller.js
Normal file
44
app/controllers/api/v1/Locale.controller.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
const { Controller } = require('libflitter')
|
||||||
|
|
||||||
|
class LocaleController extends Controller {
|
||||||
|
static get services() {
|
||||||
|
return [...super.services, 'locale']
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolve(req, res, next) {
|
||||||
|
try {
|
||||||
|
return res.api(req.T(req.params.resolver))
|
||||||
|
} catch (e) {
|
||||||
|
return res.status(400)
|
||||||
|
.message(req.T('common.invalid_resolver'))
|
||||||
|
.api()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async load_module(req, res, next) {
|
||||||
|
const resolver = `${req.i18n.region()}:${req.params.resolver}`
|
||||||
|
try {
|
||||||
|
return res.api(this.locale.get(resolver))
|
||||||
|
} catch (e) {
|
||||||
|
return res.status(400)
|
||||||
|
.message(req.T('common.invalid_resolver'))
|
||||||
|
.api()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async batch_resolve(req, res, next) {
|
||||||
|
if ( !req.body.resolvers || !Array.isArray(req.body.resolvers) )
|
||||||
|
return res.status(400)
|
||||||
|
.message(`${req.T('api.improper_field')} resolvers ${req.T('api.array')}`)
|
||||||
|
.api()
|
||||||
|
|
||||||
|
const translation_map = {}
|
||||||
|
for ( const resolve of req.body.resolvers ) {
|
||||||
|
translation_map[resolve] = req.T(resolve)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.api(translation_map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports = LocaleController
|
22
app/routing/routers/api/v1/locale.routes.js
Normal file
22
app/routing/routers/api/v1/locale.routes.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
const locale_routes = {
|
||||||
|
prefix: '/api/v1/locale',
|
||||||
|
|
||||||
|
middleware: [],
|
||||||
|
|
||||||
|
get: {
|
||||||
|
'/resolve/:resolver': [
|
||||||
|
'controller::api:v1:Locale.resolve'
|
||||||
|
],
|
||||||
|
'/module/:resolver': [
|
||||||
|
'controller::api:v1:Locale.load_module',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
post: {
|
||||||
|
'/batch': [
|
||||||
|
'controller::api:v1:Locale.batch_resolve',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports = locale_routes
|
@ -7,6 +7,7 @@ const traps_config = {
|
|||||||
'/password/reset',
|
'/password/reset',
|
||||||
'/api/v1/password/resets',
|
'/api/v1/password/resets',
|
||||||
'/auth/logout',
|
'/auth/logout',
|
||||||
|
'/api/v1/locale/batch',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
mfa_challenge: {
|
mfa_challenge: {
|
||||||
@ -17,6 +18,7 @@ const traps_config = {
|
|||||||
'/api/v1/auth/mfa/attempt',
|
'/api/v1/auth/mfa/attempt',
|
||||||
'/auth/logout',
|
'/auth/logout',
|
||||||
'/api/v1/auth/mfa/recovery/attempt',
|
'/api/v1/auth/mfa/recovery/attempt',
|
||||||
|
'/api/v1/locale/batch',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -10,4 +10,12 @@ module.exports = exports = {
|
|||||||
|
|
||||||
deny: 'Deny',
|
deny: 'Deny',
|
||||||
grant: 'Grant Access',
|
grant: 'Grant Access',
|
||||||
|
back: 'Back',
|
||||||
|
next: 'Next',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
request: 'Request',
|
||||||
|
continue: 'Continue',
|
||||||
|
unknown_error: 'An unknown error has occurred, and we are unable to continue at this time.',
|
||||||
|
|
||||||
|
invalid_resolver: 'Invalid locale resolver.',
|
||||||
}
|
}
|
||||||
|
12
locale/en_US/login.locale.js
Normal file
12
locale/en_US/login.locale.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module.exports = exports = {
|
||||||
|
username: 'Username',
|
||||||
|
password: 'Password',
|
||||||
|
need_account: 'Need an account?',
|
||||||
|
forgot_password: 'Forgot password?',
|
||||||
|
username_invalid: 'That username is invalid. Please try again.',
|
||||||
|
success: 'Success! Let\'s get you on your way...',
|
||||||
|
get_started: 'Okay! Let\'s get you started...',
|
||||||
|
reset_password: 'Reset Password',
|
||||||
|
reset_password_prompt: 'If you have forgotten your password, you can request a reset e-mail to be sent to your account. Enter your e-mail address:',
|
||||||
|
reset_password_success: 'Success! If that e-mail address is associated with a valid APP_NAME account, it will receive an e-mail with more instructions shortly.',
|
||||||
|
}
|
23
locale/en_US/mfa.locale.js
Normal file
23
locale/en_US/mfa.locale.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module.exports = exports = {
|
||||||
|
challenge_prompt: 'Your account has multi-factor authentication enabled. Please enter the code generated by your authenticator app to continue.',
|
||||||
|
mfa_code: { one: 'MFA Code', many: 'MFA Codes' },
|
||||||
|
lost_device: 'Lost your MFA device?',
|
||||||
|
invalid_code: 'Uh, oh! It looks like that\'s not the right code. Please try again.',
|
||||||
|
success: 'Success! Redirecting...',
|
||||||
|
|
||||||
|
disable_prompt: `
|
||||||
|
This process will disable multi-factor authentication on your account.
|
||||||
|
<br><br>
|
||||||
|
For security reasons, this will sign you out of all devices. It will also deactivate any existing app passwords you have generated.
|
||||||
|
<br><br>
|
||||||
|
Are you sure you want to continue?
|
||||||
|
`,
|
||||||
|
disable: 'Disable MFA',
|
||||||
|
disable_success: 'MFA was successfully disabled. You\'ll now sign-in normally.',
|
||||||
|
|
||||||
|
recover_prompt: 'To recover access to your account, you can enter one of the generated MFA recovery codes:',
|
||||||
|
recovery_code: 'Recovery Code',
|
||||||
|
normal_code: 'Have a normal MFA code?',
|
||||||
|
recover_success: 'Success! There are only NUM_CODES recovery codes remaining. Let\'s get you on your way...',
|
||||||
|
|
||||||
|
}
|
14
locale/en_US/register.locale.js
Normal file
14
locale/en_US/register.locale.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module.exports = exports = {
|
||||||
|
first_name: 'First Name',
|
||||||
|
last_name: 'Last Name',
|
||||||
|
email: 'E-Mail Address',
|
||||||
|
have_account: 'Have an account?',
|
||||||
|
create_to_continue: 'Create an account to continue:',
|
||||||
|
provide_first_last: 'Please provide your first and last name.',
|
||||||
|
hi_choose_username: 'Hi, FIRST_NAME. Now, you need to choose a username:',
|
||||||
|
username_in_use: 'That username is already in use.',
|
||||||
|
invalid_email: 'Please provide a valid e-mail address.',
|
||||||
|
email_in_use: 'That e-mail address is already taken.',
|
||||||
|
success_password: 'Welcome! Let\'s get your password set up...',
|
||||||
|
login_instead: 'Okay! We\'ll have you login instead...',
|
||||||
|
}
|
@ -23,7 +23,7 @@
|
|||||||
"flitter-di": "^0.5.0",
|
"flitter-di": "^0.5.0",
|
||||||
"flitter-flap": "^0.5.2",
|
"flitter-flap": "^0.5.2",
|
||||||
"flitter-forms": "^0.8.1",
|
"flitter-forms": "^0.8.1",
|
||||||
"flitter-i18n": "^0.1.0",
|
"flitter-i18n": "^0.1.1",
|
||||||
"flitter-jobs": "^0.1.2",
|
"flitter-jobs": "^0.1.2",
|
||||||
"flitter-less": "^0.5.3",
|
"flitter-less": "^0.5.3",
|
||||||
"flitter-orm": "^0.4.0",
|
"flitter-orm": "^0.4.0",
|
||||||
@ -32,7 +32,7 @@
|
|||||||
"ioredis": "^4.17.1",
|
"ioredis": "^4.17.1",
|
||||||
"is-absolute-url": "^3.0.3",
|
"is-absolute-url": "^3.0.3",
|
||||||
"ldapjs": "^1.0.2",
|
"ldapjs": "^1.0.2",
|
||||||
"libflitter": "^0.53.0",
|
"libflitter": "^0.53.1",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"mongodb": "^3.5.6",
|
"mongodb": "^3.5.6",
|
||||||
"nodemailer": "^6.4.6",
|
"nodemailer": "^6.4.6",
|
||||||
|
16
yarn.lock
16
yarn.lock
@ -1888,10 +1888,10 @@ flitter-forms@^0.8.1:
|
|||||||
recursive-readdir "^2.2.2"
|
recursive-readdir "^2.2.2"
|
||||||
validator "^10.11.0"
|
validator "^10.11.0"
|
||||||
|
|
||||||
flitter-i18n@^0.1.0:
|
flitter-i18n@^0.1.1:
|
||||||
version "0.1.0"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/flitter-i18n/-/flitter-i18n-0.1.0.tgz#9670f9ebf5f120e794f147ebeb1fe99537ac4779"
|
resolved "https://registry.yarnpkg.com/flitter-i18n/-/flitter-i18n-0.1.1.tgz#852f916fc643e47c355fcef63d3761edf5bf4f22"
|
||||||
integrity sha512-vdEgcDNtA4EEjwJ0QqIIdPwR4FI67XMr4dD5U2l/u/xrnwzUrQD2O3avc5EK4RAPXTgZpK5twotTD6julJlwoA==
|
integrity sha512-n1z0Ijs5p98osooC6jdegbnPf1rgKM7ufeo1uqBKXYFrtIfZHJ9X/+QFUPvcKtchnvWk8m7UkWxXE85GLLeIig==
|
||||||
dependencies:
|
dependencies:
|
||||||
ncp "^2.0.0"
|
ncp "^2.0.0"
|
||||||
pluralize "^8.0.0"
|
pluralize "^8.0.0"
|
||||||
@ -2833,10 +2833,10 @@ leven@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/leven/-/leven-1.0.2.tgz#9144b6eebca5f1d0680169f1a6770dcea60b75c3"
|
resolved "https://registry.yarnpkg.com/leven/-/leven-1.0.2.tgz#9144b6eebca5f1d0680169f1a6770dcea60b75c3"
|
||||||
integrity sha1-kUS27ryl8dBoAWnxpncNzqYLdcM=
|
integrity sha1-kUS27ryl8dBoAWnxpncNzqYLdcM=
|
||||||
|
|
||||||
libflitter@^0.53.0:
|
libflitter@^0.53.1:
|
||||||
version "0.53.0"
|
version "0.53.1"
|
||||||
resolved "https://registry.yarnpkg.com/libflitter/-/libflitter-0.53.0.tgz#f1e2250597916dd7b7b7b8f0af04118ed1f30b41"
|
resolved "https://registry.yarnpkg.com/libflitter/-/libflitter-0.53.1.tgz#30b1838763a228fba8b9c820d2cad501c3aa0117"
|
||||||
integrity sha512-i8otSTzNwMFJEa585bw+xfaNSuXm6c/kQBYXsyB8jzFU9PBFmvrEp/QzRqQD/lDgiQChYgjRs2TJofqDYvAOHg==
|
integrity sha512-EK3okZyt0pmnpsZNx2lYOIcwgtmSOEPh4a5xE3pXM9RVc3dtXXscgJ5h9OvLTIN9WfRc7T5VTdpOjeAK6Xmysg==
|
||||||
dependencies:
|
dependencies:
|
||||||
colors "^1.3.3"
|
colors "^1.3.3"
|
||||||
connect-mongodb-session "^2.2.0"
|
connect-mongodb-session "^2.2.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user