Files
cloudron-box/dashboard/src/views/LoginView.vue
Girish Ramakrishnan fe8d5b0d3e Login -> Log in
2025-10-17 11:13:10 +02:00

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>