192 lines
6.7 KiB
Vue
192 lines
6.7 KiB
Vue
<script setup>
|
|
|
|
import { ref, onMounted } from 'vue';
|
|
import { marked } from 'marked';
|
|
import { Button, PasswordInput, TextInput, fetcher, FormGroup } from '@cloudron/pankow';
|
|
import { API_ORIGIN } from '../constants.js';
|
|
import PublicPageLayout from '../components/PublicPageLayout.vue';
|
|
|
|
const footer = marked.parse(window.cloudron.footer);
|
|
const cloudronName = window.cloudron.name;
|
|
|
|
const ready = ref(false);
|
|
const busy = ref(false);
|
|
const error = ref({});
|
|
const mode = ref('');
|
|
const resetToken = ref('');
|
|
const passwordResetIdentifier = ref('');
|
|
const newPassword = ref('');
|
|
const newPasswordRepeat = ref('');
|
|
const totpToken = ref('');
|
|
|
|
const MODE = {
|
|
NEW_PASSWORD: 'newPassword',
|
|
NEW_PASSWORD_DONE: 'newPasswordDone',
|
|
RESET_PASSWORD: 'passwordReset',
|
|
RESET_PASSWORD_DONE: 'passwordResetDone',
|
|
};
|
|
|
|
async function onPasswordReset() {
|
|
busy.value = true;
|
|
error.value = {};
|
|
|
|
try {
|
|
await fetcher.post(`${API_ORIGIN}/api/v1/auth/password_reset_request`, { identifier: passwordResetIdentifier.value });
|
|
} catch (error) {
|
|
error.value.generic = error;
|
|
console.error('Failed to reset password.', error);
|
|
}
|
|
|
|
busy.value = 'false';
|
|
mode.value = MODE.RESET_PASSWORD_DONE;
|
|
}
|
|
|
|
async function onNewPassword() {
|
|
busy.value = true;
|
|
error.value = {};
|
|
|
|
const data = {
|
|
resetToken: resetToken.value,
|
|
password: newPassword.value,
|
|
totpToken: totpToken.value
|
|
};
|
|
|
|
try {
|
|
const res = await fetcher.post(`${API_ORIGIN}/api/v1/auth/password_reset`, data);
|
|
if (res.status === 400 || res.status === 401) {
|
|
if (res.body.message.indexOf('totpToken') !== -1) {
|
|
error.value.totpToken = res.body.message;
|
|
totpToken.value = '';
|
|
} else {
|
|
error.value.generic = res.body.message;
|
|
newPasswordRepeat.value = '';
|
|
}
|
|
} else if (res.status === 409) {
|
|
error.value.generic = 'Ask your admin for an invite link first';
|
|
} else if (res.status === 202) {
|
|
// set token to autologin
|
|
localStorage.token = res.body.accessToken;
|
|
mode.value = MODE.NEW_PASSWORD_DONE;
|
|
}
|
|
} catch (error) {
|
|
error.value.generic = 'Internal error';
|
|
console.error('Failed to set new password.', error);
|
|
}
|
|
|
|
busy.value = false;
|
|
}
|
|
|
|
onMounted(async () => {
|
|
const search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.indexOf('=') === -1 ? [item, true] : [item.slice(0, item.indexOf('=')), item.slice(item.indexOf('=')+1)]; }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
|
|
|
// Init into the correct view
|
|
if (search.resetToken) {
|
|
resetToken.value = search.resetToken;
|
|
window.document.title = 'Set New Password';
|
|
mode.value = MODE.NEW_PASSWORD;
|
|
setTimeout(() => document.getElementById('inputNewPassword').focus(), 200);
|
|
} else if (search.accessToken || search.access_token) { // auto-login feature
|
|
localStorage.token = search.accessToken || search.access_token;
|
|
window.location.href = '/';
|
|
} else { // also search.passwordReset
|
|
window.document.title = 'Password Reset Request';
|
|
mode.value = MODE.RESET_PASSWORD;
|
|
passwordResetIdentifier.value = '';
|
|
setTimeout(() => document.getElementById('inputPasswordResetIdentifier').focus(), 200);
|
|
}
|
|
|
|
ready.value = true;
|
|
});
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<PublicPageLayout :footerHtml="footer">
|
|
<div v-if="ready">
|
|
|
|
<div v-if="mode === MODE.RESET_PASSWORD">
|
|
<small>{{ $t('passwordReset.title') }}</small>
|
|
<h1>{{ cloudronName }}</h1>
|
|
|
|
<form name="passwordResetForm" @submit.prevent="onPasswordReset()">
|
|
<input type="submit" style="display: none;"/>
|
|
|
|
<FormGroup>
|
|
<label for="inputPasswordResetIdentifier">{{ $t('passwordReset.usernameOrEmail') }}</label>
|
|
<TextInput id="inputPasswordResetIdentifier" name="passwordResetIdentifier" v-model="passwordResetIdentifier" :disabled="busy" autofocus required />
|
|
</FormGroup>
|
|
|
|
<div class="actions">
|
|
<Button @click="onPasswordReset()" :disabled="busy || !passwordResetIdentifier" :loading="busy">{{ $t('passwordReset.resetAction') }}</Button>
|
|
<a class="login" href="/">{{ $t('passwordReset.backToLoginAction') }}</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div v-if="mode === MODE.RESET_PASSWORD_DONE">
|
|
<h4 v-if="error.generic" class="has-error">{{ error.generic }}</h4>
|
|
<h2 v-else>{{ $t('passwordReset.emailSent.title') }}</h2>
|
|
<Button href="/">{{ $t('passwordReset.backToLoginAction') }}</Button>
|
|
</div>
|
|
|
|
<div v-if="mode === MODE.NEW_PASSWORD">
|
|
<small>{{ $t('passwordReset.newPassword.title') }}</small>
|
|
<h1>{{ cloudronName }}</h1>
|
|
<br/>
|
|
|
|
<p class="has-error" v-if="error.generic">{{ error.generic }}</p>
|
|
|
|
<form name="newPasswordForm" @submit.prevent="onNewPassword()">
|
|
<input type="submit" style="display: none;"/>
|
|
<input type="password" style="display: none;"/>
|
|
|
|
<FormGroup :has-error="(newPasswordRepeat && newPassword !== newPasswordRepeat) ? true : null">
|
|
<label for="inputNewPassword">{{ $t('passwordReset.newPassword.password') }}</label>
|
|
<PasswordInput id="inputNewPassword" v-model="newPassword" autofocus required />
|
|
</FormGroup>
|
|
|
|
<FormGroup :has-error="(newPasswordRepeat && newPassword !== newPasswordRepeat) ? true : null">
|
|
<label for="inputNewPasswordRepeat">{{ $t('passwordReset.newPassword.passwordRepeat') }}</label>
|
|
<PasswordInput id="inputNewPasswordRepeat" v-model="newPasswordRepeat" required />
|
|
</FormGroup>
|
|
|
|
<FormGroup :has-error="error.totpToken">
|
|
<label for="inputPasswordResetTotpToken">{{ $t('login.2faToken') }}</label>
|
|
<TextInput id="inputPasswordResetTotpToken" v-model="totpToken" :disabled="busy" />
|
|
<p class="has-error" v-if="error.totpToken">{{ error.totpToken }}</p>
|
|
</FormGroup>
|
|
|
|
<div class="actions">
|
|
<Button @click="onNewPassword()" :disabled="busy || !newPassword || newPassword !== newPasswordRepeat" :loading="busy">{{ $t('passwordReset.passwordChanged.submitAction') }}</Button>
|
|
<a class="login" href="/">{{ $t('passwordReset.backToLoginAction') }}</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div v-if="mode === MODE.NEW_PASSWORD_DONE">
|
|
<small>{{ $t('passwordReset.success.title') }}</small>
|
|
<h1>{{ cloudronName }}</h1>
|
|
<br/>
|
|
<Button href="/">{{ $t('passwordReset.success.openDashboardAction') }}</Button>
|
|
</div>
|
|
</div>
|
|
</PublicPageLayout>
|
|
</template>
|
|
|
|
|
|
<style scoped>
|
|
|
|
.actions {
|
|
margin-top: 1.5em;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.actions .login {
|
|
margin-top: 1em;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
</style>
|