184 lines
5.1 KiB
Vue
184 lines
5.1 KiB
Vue
<script>
|
|
|
|
import { Button, PasswordInput, TextInput, fetcher } from 'pankow';
|
|
|
|
export default {
|
|
name: 'Login',
|
|
components: {
|
|
Button,
|
|
PasswordInput,
|
|
TextInput
|
|
},
|
|
data() {
|
|
return {
|
|
ready: false,
|
|
busy: false,
|
|
passwordError: null,
|
|
totpError: null,
|
|
internalError: null,
|
|
username: '',
|
|
password: '',
|
|
totpToken: '',
|
|
totpTokenRequired: false,
|
|
// coming from login.html template
|
|
name: window.cloudron.name,
|
|
note: window.cloudron.note,
|
|
iconUrl: window.cloudron.iconUrl,
|
|
footer: window.cloudron.footer,
|
|
submitUrl: window.cloudron.submitUrl
|
|
};
|
|
},
|
|
async mounted() {
|
|
// placed optionally in local storage by setupaccount.js
|
|
const autoLoginToken = localStorage.cloudronFirstTimeToken;
|
|
if (autoLoginToken) {
|
|
try {
|
|
const res = await fetch.post(this.submitUrl, { autoLoginToken });
|
|
localStorage.removeItem('cloudronFirstTimeToken');
|
|
|
|
if (res.body.redirectTo) window.location.href = res.body.redirectTo;
|
|
else console.log('login success but missing redirectTo in data:', res.body);
|
|
} catch (error) {
|
|
console.error('Failed to use autologin token', error);
|
|
}
|
|
}
|
|
|
|
this.ready = true;
|
|
},
|
|
methods: {
|
|
async onSubmit() {
|
|
this.busy = true;
|
|
|
|
this.passwordError = false;
|
|
this.totpError = false;
|
|
this.internalError = false;
|
|
|
|
const body = {
|
|
username: this.username,
|
|
password: this.password,
|
|
totpToken: this.totpToken
|
|
};
|
|
|
|
try {
|
|
const res = await fetcher.post(this.submitUrl, body);
|
|
if (res.status === 410) {
|
|
// the oidc login session is old
|
|
window.location.reload();
|
|
} else if (res.status === 401) {
|
|
if (res.body.message.indexOf('totpToken') !== -1) {
|
|
this.totpError = this.totpTokenRequired; // only set on second try coming from login
|
|
this.totpTokenRequired = true;
|
|
this.totpToken = '';
|
|
setTimeout(() => document.getElementById('inputTotpToken').focus(), 0);
|
|
} else {
|
|
this.password = '';
|
|
this.passwordError = true;
|
|
document.getElementById('inputPassword').focus();
|
|
}
|
|
}
|
|
|
|
if (res.body.redirectTo) window.location.href = res.body.redirectTo;
|
|
else console.log('login success but missing redirectTo in data:', res.body);
|
|
} catch (error) {
|
|
this.internalError = true;
|
|
console.error('Login failed:', error);
|
|
}
|
|
|
|
this.busy = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div class="layout-root" v-if="ready">
|
|
<div class="layout-left" :style="{ 'background-image': `url('${API_ORIGIN}/api/v1/cloudron/background')` }">
|
|
<img width="128" height="128" class="icon" :src="`${API_ORIGIN}/api/v1/cloudron/avatar`"/>
|
|
</div>
|
|
|
|
<div class="layout-right">
|
|
<small>{{ $t('login.loginTo') }}</small>
|
|
<h1>{{ name }}</h1>
|
|
<br/>
|
|
<div :html="note"></div>
|
|
|
|
<p class="has-error" v-show="passwordError">{{ $t('login.errorIncorrectCredentials') }}</p>
|
|
<p class="has-error" v-show="internalError">{{ $t('login.errorInternal') }}</p>
|
|
|
|
<form @submit.prevent="onSubmit" v-show="!totpTokenRequired">
|
|
<input type="submit" style="display: none;"/>
|
|
|
|
<div class="form-element">
|
|
<label for="inputUsername">{{ $t('login.username') }}</label>
|
|
<TextInput id="inputUsername" v-model="username" autofocus required/>
|
|
</div>
|
|
|
|
<div class="form-element">
|
|
<label for="inputPassword">{{ $t('login.password') }}</label>
|
|
<PasswordInput id="inputPassword" v-model="password" required/>
|
|
</div>
|
|
|
|
<Button id="loginSubmitButton" style="margin-top: 12px" @click.prevent="onSubmit" :loading="busy">{{ $t('login.signInAction') }}</Button>
|
|
<a href="/passwordreset.html" style="margin-left: 10px;">{{ $t('login.resetPasswordAction') }}</a>
|
|
</form>
|
|
|
|
<form @submit.prevent="onSubmit" v-show="totpTokenRequired">
|
|
<input type="submit" style="display: none;"/>
|
|
|
|
<div class="form-element">
|
|
<label for="inputTotpToken">{{ $t('login.2faToken') }}</label>
|
|
<TextInput id="inputTotpToken" v-model="totpToken"/>
|
|
<p class="has-error" v-show="totpError">{{ $t('login.errorIncorrect2FAToken') }}</p>
|
|
</div>
|
|
|
|
<Button id="totpTokenSubmitButton" style="margin-top: 12px" type="submit" @click.prevent="onSubmit" :loading="busy">{{ $t('login.signInAction') }}</Button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<footer v-show="footer" v-html="footer"></footer>
|
|
</template>
|
|
|
|
<style>
|
|
|
|
.icon {
|
|
margin-bottom: 20%;
|
|
}
|
|
|
|
.layout-root {
|
|
display: flex;
|
|
flex-grow: 1;
|
|
overflow: hidden;
|
|
height: 100%;
|
|
}
|
|
|
|
.layout-left {
|
|
background-color: rgba(0,0,0,0.1);
|
|
background-size: cover;
|
|
background-position: center;
|
|
flex-basis: 30%;
|
|
justify-content: center;
|
|
flex-direction: column;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.layout-right {
|
|
padding-left: 20px;
|
|
flex-basis: 70%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: auto;
|
|
justify-content: center;
|
|
}
|
|
|
|
.form-element {
|
|
max-width: 300px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
</style>
|
|
|