Skip to content

Commit

Permalink
feat: add security page (#476)
Browse files Browse the repository at this point in the history
* Add password field

* Add access page

* Add security page
  • Loading branch information
RealHinome authored Jun 23, 2024
1 parent 666f1bc commit 209c75a
Show file tree
Hide file tree
Showing 7 changed files with 478 additions and 5 deletions.
6 changes: 3 additions & 3 deletions front/components/SideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ async function logout() {
></path>
</svg>

<span ml-3>{{ $t("about") }}</span>
<span ml-3>{{ $t("about.title") }}</span>
</NuxtLink>

<NuxtLink
Expand Down Expand Up @@ -189,7 +189,7 @@ async function logout() {
></path>
</svg>

<span ml-3>{{ $t("security") }}</span>
<span ml-3>{{ $t("security.title") }}</span>
</NuxtLink>

<NuxtLink
Expand Down Expand Up @@ -334,7 +334,7 @@ async function logout() {
dark:bg-violet-300
dark:text-violet-900
>
{{ $t("security") }}
{{ $t("security.title") }}
</span>
</div>

Expand Down
22 changes: 21 additions & 1 deletion front/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"optional_information": "Optional information",
"required_information": "Required information",

"security": "Security",
"data": "Data",
"logout": "Logout",
"account": "Account",
Expand Down Expand Up @@ -84,10 +83,31 @@
"profile_picture": {
"title": "Update profile picture",
"import": "Import a new photo"
},
"email": {
"title": "Update e-mail"
},
"password": {
"title": "Update password"
}
},
"save": "Save",

"logged_as": "Logged in as {'@'}{vanity}",
"access": {
"title": "Confirm access",
"warn": "You'll be able to change the most crucial information in your account!"
},
"confirm": "Confirm",

"connection": "Connection and recovery",
"mfa": "Multi-factor authentication",
"mfa_soon": "This section will be available soon! It will allow you to add an extra security step during the connection.",
"security": {
"title": "Security",
"description": "Change your account security details."
},

"p_policy": "Privacy policy",
"terms_of_use": "Terms of Use",
"terms_of_service": "Terms of Service",
Expand Down
22 changes: 21 additions & 1 deletion front/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"optional_information": "Informations facultatives",
"required_information": "Informations requises",

"security": "Sécurité",
"data": "Données",
"logout": "Se déconnecter",
"account": "Compte",
Expand Down Expand Up @@ -84,10 +83,31 @@
"profile_picture": {
"title": "Modifier la photo de profil",
"import": "Importer une nouvelle photo"
},
"email": {
"title": "Modifier l'adresse e-mail"
},
"password": {
"title": "Modifier le mot de passe"
}
},
"save": "Sauvegarder",

"logged_as": "Connecté en tant que {'@'}{vanity}",
"access": {
"title": "Confirmer l'accès",
"warn": "Vous allez pouvoir modifier les informations les plus cruciales de votre compte !"
},
"confirm": "Confirmer",

"connection": "Connexion et récupération",
"mfa": "Authentification à plusieurs étapes",
"mfa_soon": "Cette section sera bientôt disponible ! Elle vous permettra d'ajouter une étape de sécurité supplémentaire lors de la connexion.",
"security": {
"title": "Sécurité",
"description": "Modifiez les données relatives à la sécurité de votre compte."
},

"p_policy": "Confidentialité",
"terms_of_use": "Conditions Générales d'Utilisation",
"terms_of_service": "Conditions d'usage",
Expand Down
206 changes: 206 additions & 0 deletions front/pages/access.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
<script setup lang="ts">
import type { Error, TokenResponse } from "../types/index";
// Define reactive refs for error handling.
const isError: Record<string, Ref<boolean>> = {
invalidToken: ref(false),
invalidPassword: ref(false),
missingPassword: ref(false),
rateLimited: ref(false),
internalServerError: ref(false),
};
// Define reactive refs for user input.
const token = ref();
const password = ref("");
const isButtonDisable = ref(false);
const user = useUser();
user.fetchUser();
// Redirect to the login page if user is not connected.
if (useCookie("session").value === "" || user.vanity === "")
await navigateTo("/signin");
// Redirect if user have already a password.
if (typeof user.password === "string" && user.password.length !== 0)
await navigateTo("/security");
async function signin() {
// Disable button until the end.
isButtonDisable.value = true;
// Set all errors to false before processing the sign-in.
for (const key in isError) {
isError[key].value = false;
}
// Check if password is missing.
if (password.value === "") {
isError.missingPassword.value = true;
isButtonDisable.value = false;
return;
}
// Create headers.
const headers: Headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append("CF-Turnstile-Token", token.value);
// Make request.
const json: Error | TokenResponse = await fetch(
`${useRuntimeConfig().public.API_URL}/login`,
{
method: "post",
headers,
body: JSON.stringify({
email: user.email,
password: password.value,
}),
},
)
.then((response) => response.json())
.catch((_) => (isError.internalServerError.value = true));
// Re-activate button.
isButtonDisable.value = false;
// Handle error process.
if ("error" in json) {
if (json.message === "Invalid turnstile token")
isError.invalidToken.value = true;
else if (json.message === "Invalid password")
isError.invalidPassword.value = true;
else if (json.message === "You are being rate limited.")
isError.rateLimited.value = true;
else {
/* eslint-disable no-console */
console.error(json.message);
isError.internalServerError.value = true;
}
// Re-create Turnstile token.
token.value?.reset();
} else {
useCookie("session", {
maxAge: 1200000, // Around two weeks.
sameSite: "strict",
secure: true,
}).value = json.token;
useI18n().setLocale(json.user_settings.locale);
user.password = password.value;
await navigateTo("/security");
}
}
</script>

<template>
<!-- Cloudflare Turnstile implementation. -->
<NuxtTurnstile v-model="token" />

<!-- Blurry effect in background. -->
<FontBubbles />

<!-- Banner prevention. -->
<Banner :content="$t('access.warn')" :can-close="false" />

<!-- Centered card containing inputs to connect. -->
<div absolute w-96vw h-98vh flex-col container space-y-6>
<div
class="bg-zinc-50 dark:bg-dark border border-gray-900 w-80 h-18 lg:w-96 container"
>
<p>{{ $t("logged_as", { vanity: user.vanity }) }}</p>
</div>

<div
class="bg-zinc-50 dark:bg-dark border border-gray-900 w-80 h-80 lg:w-96 shadow-lg"
>
<div mt-6 lg:mt-10 mb-8 lg:mb-10 divide-x space-x-2 container>
<NuxtImg
alt="Gravitalia"
src="/favicon.webp"
width="35"
height="35"
draggable="false"
/>

<div class="h-1.5rem bg-zinc-400 w-0.1rem"></div>

<h3 font-semibold>{{ $t("access.title") }}</h3>
</div>

<div flex-col container>
<!-- Errors. -->
<LabelError
v-if="isError.invalidToken.value"
mb-26
text="error.security_token"
/>
<LabelError
v-if="isError.rateLimited.value"
mb-26
text="error.rate_limit"
/>
<LabelError
v-if="isError.internalServerError.value"
mb-26
text="something_went_wrong"
/>
<LabelError
v-if="isError.missingPassword.value"
mb-26
text="error.missing_password"
/>
<LabelError
v-if="isError.invalidPassword.value"
mb-26
text="error.invalid_password"
/>

<!-- Password input. -->
<input
v-model="password"
:class="
isError.invalidPassword.value || isError.missingPassword.value
? 'border-red-500 dark:border-red-500'
: ''
"
input
type="password"
:placeholder="$t('password')"
/>

<!-- No more access. -->
<div mt-2 w-64 lg:w-72>
<NuxtLink
:to="'mailto:' + useRuntimeConfig().email"
text-sm
text-link
>
{{ $t("lost_account") }}
</NuxtLink>
</div>
</div>

<!-- Confirmation button. -->
<div flex container>
<div w-16.5rem lg:w-18.5rem mt-11>
<button
font-sans
font-medium
btn-base
w-full
type="button"
:disabled="isButtonDisable"
@click="signin()"
>
{{ $t("confirm") }}
</button>
</div>
</div>
</div>
</div>
</template>
Loading

0 comments on commit 209c75a

Please sign in to comment.