163 lines
5.1 KiB
Vue
163 lines
5.1 KiB
Vue
<script setup>
|
|
|
|
import { ref, onMounted } from 'vue';
|
|
import { Button, PasswordInput, TextInput, fetcher, FormGroup } from '@cloudron/pankow';
|
|
import PublicPageLayout from '../components/PublicPageLayout.vue';
|
|
|
|
const ready = ref(false);
|
|
const busy = ref(false);
|
|
const passwordError = ref(null);
|
|
const totpError = ref(null);
|
|
const internalError = ref(null);
|
|
const oidcError = ref('');
|
|
const username = ref('');
|
|
const password = ref('');
|
|
const totpToken = ref('');
|
|
const totpTokenRequired = ref(false);
|
|
|
|
// coming from oidc_login.html template
|
|
const name = window.cloudron.name;
|
|
const note = window.cloudron.note;
|
|
const iconUrl = window.cloudron.iconUrl;
|
|
const footer = window.cloudron.footer;
|
|
const submitUrl = window.cloudron.submitUrl;
|
|
|
|
async function onSubmit() {
|
|
busy.value = true;
|
|
passwordError.value = false;
|
|
totpError.value = false;
|
|
internalError.value = false;
|
|
oidcError.value = '';
|
|
|
|
const body = {
|
|
username: username.value,
|
|
password: password.value,
|
|
totpToken: totpToken.value
|
|
};
|
|
|
|
try {
|
|
const res = await fetcher.post(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) {
|
|
totpError.value = totpTokenRequired.value; // only set on second try coming from login
|
|
totpTokenRequired.value = true;
|
|
totpToken.value = '';
|
|
setTimeout(() => document.getElementById('inputTotpToken').focus(), 0);
|
|
} else {
|
|
password.value = '';
|
|
passwordError.value = true;
|
|
document.getElementById('inputPassword').focus();
|
|
}
|
|
} else if (res.status === 200 ) {
|
|
if (res.body.redirectTo) return window.location.href = res.body.redirectTo;
|
|
console.error('login success but missing redirectTo in data:', res.body);
|
|
internalError.value = true;
|
|
} else if (res.status >= 400 && res.status < 500) {
|
|
oidcError.value = 'OpenID Error: ' + (res.body.message || '') + '. Will reload in 5 seconds';
|
|
setTimeout(() => window.location.href = '/', 5000);
|
|
} else {
|
|
internalError.value = true;
|
|
}
|
|
} catch (error) {
|
|
internalError.value = true;
|
|
console.error(error);
|
|
}
|
|
|
|
busy.value = false;
|
|
}
|
|
|
|
onMounted(async () => {
|
|
// placed optionally in local storage by setupaccount.js
|
|
const autoLoginToken = localStorage.cloudronFirstTimeToken;
|
|
if (autoLoginToken) {
|
|
localStorage.removeItem('cloudronFirstTimeToken');
|
|
try {
|
|
const res = await fetcher.post(submitUrl, { autoLoginToken });
|
|
window.location.href = res.body.redirectTo || '/';
|
|
return;
|
|
} catch (error) {
|
|
console.error('Failed to use autologin token', error);
|
|
}
|
|
}
|
|
|
|
ready.value = true;
|
|
});
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<PublicPageLayout v-if="ready" :footerHtml="footer" :icon-url="iconUrl" :cloudron-name="name">
|
|
<div>
|
|
<h2>{{ $t('login.loginAction') }}</h2>
|
|
<p v-html="note" style="margin-bottom: 8px"></p>
|
|
|
|
<form @submit.prevent="onSubmit" v-if="!totpTokenRequired">
|
|
<fieldset :disabled="busy">
|
|
<input type="submit" style="display: none;"/>
|
|
|
|
<FormGroup>
|
|
<label for="inputUsername">{{ $t('login.username') }}</label>
|
|
<TextInput id="inputUsername" v-model="username" autofocus required/>
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
<label for="inputPassword">{{ $t('login.password') }}</label>
|
|
<PasswordInput id="inputPassword" v-model="password" required/>
|
|
</FormGroup>
|
|
|
|
<div class="error-label" v-if="passwordError">{{ $t('login.errorIncorrectCredentials') }}</div>
|
|
<div class="error-label" v-if="internalError">{{ $t('login.errorInternal') }}</div>
|
|
<div class="error-label" v-if="oidcError">{{ oidcError }}</div>
|
|
</fieldset>
|
|
|
|
<div class="actions">
|
|
<Button id="loginSubmitButton" @click="onSubmit" :disabled="busy || (!username || !password)" :loading="busy">{{ $t('login.loginAction') }}</Button>
|
|
<a href="/passwordreset.html">{{ $t('login.resetPasswordAction') }}</a>
|
|
</div>
|
|
</form>
|
|
|
|
<form @submit.prevent="onSubmit" v-if="totpTokenRequired" autocomplete="off">
|
|
<fieldset :disabled="busy">
|
|
<input type="submit" style="display: none;"/>
|
|
|
|
<FormGroup :has-error="totpError">
|
|
<label for="inputTotpToken">{{ $t('login.2faToken') }}</label>
|
|
<TextInput id="inputTotpToken" v-model="totpToken" required/>
|
|
</FormGroup>
|
|
|
|
<div class="error-label" v-if="totpError">{{ $t('login.errorIncorrect2FAToken') }}</div>
|
|
</fieldset>
|
|
|
|
<div class="actions">
|
|
<Button id="totpTokenSubmitButton" @click="onSubmit" :disabled="busy || !totpToken" :loading="busy">{{ $t('login.signInAction') }}</Button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</PublicPageLayout>
|
|
</template>
|
|
|
|
<style scoped>
|
|
|
|
.actions {
|
|
margin-top: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.actions a {
|
|
margin-top: 12px;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
@media (max-width: 576px) {
|
|
.actions {
|
|
align-items: unset;
|
|
}
|
|
}
|
|
|
|
</style>
|