mirror of
https://github.com/Floriansylvain/RenewCMS.git
synced 2026-08-19 19:53:21 +02:00
Added cookies managment for Auth purpose
This commit is contained in:
@@ -6,7 +6,11 @@
|
|||||||
--semibold-weight: 600;
|
--semibold-weight: 600;
|
||||||
--bold-weight: 700;
|
--bold-weight: 700;
|
||||||
|
|
||||||
--radius: 10px;
|
--radius: 15px;
|
||||||
|
|
||||||
|
--font-body: 16px;
|
||||||
|
--font-small: 14px;
|
||||||
|
--font-verysmall: 12px;
|
||||||
|
|
||||||
--primary: #2CD358;
|
--primary: #2CD358;
|
||||||
--primary-dark: #00BA2A;
|
--primary-dark: #00BA2A;
|
||||||
@@ -15,6 +19,7 @@
|
|||||||
--secondary-dark: #BA0094;
|
--secondary-dark: #BA0094;
|
||||||
--secondary-light: #E88DCA;
|
--secondary-light: #E88DCA;
|
||||||
--neutral-dark: #121212;
|
--neutral-dark: #121212;
|
||||||
|
--neutral-medium: #707070;
|
||||||
--neutral-light: #aaaaaa;
|
--neutral-light: #aaaaaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,8 +29,11 @@ body {
|
|||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-family: 'Frank Ruhl Libre', serif;
|
font-family: 'Frank Ruhl Libre', serif;
|
||||||
|
font-size: 40px;
|
||||||
}
|
}
|
||||||
|
h2 { font-size: 32px; }
|
||||||
|
h3 { font-size: 24px; }
|
||||||
|
h4 { font-size: 18px; }
|
||||||
h2, h3, h4, h5, h6 {
|
h2, h3, h4, h5, h6 {
|
||||||
font-family: 'Nunito', sans-serif;
|
font-family: 'Nunito', sans-serif;
|
||||||
}
|
}
|
||||||
@@ -37,33 +45,53 @@ h2, h3, h4, h5, h6 {
|
|||||||
color: var(--neutral-dark);
|
color: var(--neutral-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-input {
|
.label-input,
|
||||||
|
.label-input-error {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-input label {
|
.label-input label,
|
||||||
font-weight: var(--medium-weight);
|
.label-input-error label {
|
||||||
|
font-size: var(--font-body);
|
||||||
|
font-weight: var(--semibold-weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-input input {
|
.label-input p,
|
||||||
height: 32px;
|
.label-input-error p {
|
||||||
padding: 0 8px;
|
color: var(--neutral-medium);
|
||||||
|
font-size: var(--font-body);
|
||||||
|
font-weight: var(--medium-weight);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-input input,
|
||||||
|
.label-input-error input {
|
||||||
|
padding: 3px 16px;
|
||||||
|
font-size: var(--font-body);
|
||||||
|
|
||||||
border: solid 1.25px var(--neutral-light);
|
border: solid 1.25px var(--neutral-light);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.label-input-error input {
|
||||||
|
border: solid 1.75px red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-input-error p {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
.button-primary {
|
.button-primary {
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
height: 32px;
|
padding: 3px 16px;
|
||||||
|
font-size: var(--font-body);
|
||||||
|
font-weight: var(--medium-weight);
|
||||||
|
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
|
|
||||||
font-weight: var(--medium-weight);
|
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory, type NavigationGuard } from 'vue-router'
|
||||||
import Home from '@/views/Home.vue'
|
import Home from '@/views/Home.vue'
|
||||||
import Debug from '@/views/Debug.vue'
|
import Debug from '@/views/Debug.vue'
|
||||||
import Login from '@/views/Login.vue'
|
import Login from '@/views/Login.vue'
|
||||||
|
import { useAuthStore } from '@/stores/AuthStore'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@@ -9,7 +10,7 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: Login
|
component: Login,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/debug',
|
path: '/debug',
|
||||||
@@ -24,4 +25,19 @@ const router = createRouter({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.beforeEach(async (to, from) => {
|
||||||
|
const isTokenValid = useAuthStore().isValid()
|
||||||
|
if (to.name === 'login') {
|
||||||
|
if (isTokenValid) return {
|
||||||
|
name: 'home'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!isTokenValid) {
|
||||||
|
return {
|
||||||
|
name: 'login',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { getCookie } from "@/utils/cookies";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { useErrorsStore } from "./ErrorsStore";
|
||||||
|
|
||||||
export interface jwtFormat {
|
export interface jwtFormat {
|
||||||
code: number,
|
code: number,
|
||||||
@@ -11,5 +13,35 @@ export const useAuthStore = defineStore("AuthStore", () => {
|
|||||||
const expire = ref("")
|
const expire = ref("")
|
||||||
const token = ref("")
|
const token = ref("")
|
||||||
|
|
||||||
return { expire, token }
|
function isSet(): boolean {
|
||||||
|
return token.value !== undefined && token.value !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function isExpired(): boolean {
|
||||||
|
const tokenDate = new Date(expire.value)
|
||||||
|
const currentDate = new Date()
|
||||||
|
|
||||||
|
if (currentDate.getTime() > tokenDate.getTime()) {
|
||||||
|
useErrorsStore().sessionExpired = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValid(): boolean {
|
||||||
|
return isSet() && !isExpired()
|
||||||
|
}
|
||||||
|
|
||||||
|
function initStore(): void {
|
||||||
|
const JWTtoken = getCookie('JWTtoken')
|
||||||
|
const JWTexpire = getCookie('JWTexpire')
|
||||||
|
if (JWTtoken !== "" && JWTexpire !== "") {
|
||||||
|
token.value = JWTtoken
|
||||||
|
expire.value = JWTexpire
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initStore()
|
||||||
|
|
||||||
|
return { expire, token, isValid }
|
||||||
})
|
})
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref, type Ref } from "vue";
|
||||||
|
|
||||||
|
interface storeErrors {
|
||||||
|
sessionExpired: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useErrorsStore = defineStore("ErrorsStore", () => {
|
||||||
|
const errors: Ref<storeErrors> = ref({
|
||||||
|
sessionExpired: false
|
||||||
|
})
|
||||||
|
|
||||||
|
return errors
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export interface cookie {
|
||||||
|
key: string,
|
||||||
|
value: string,
|
||||||
|
expire: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCookie(cookieData: cookie): void {
|
||||||
|
document.cookie = `${cookieData.key}=${cookieData.value}; SameSite=Strict; Expires=${cookieData.expire};Secure`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCookie(cookieName: string): string {
|
||||||
|
return document.cookie.match('(^|;)\\s*' + cookieName + '\\s*=\\s*([^;]+)')?.pop() || ''
|
||||||
|
}
|
||||||
@@ -1,22 +1,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAuthStore, type jwtFormat } from '@/stores/AuthStore';
|
import { useAuthStore, type jwtFormat } from '@/stores/AuthStore';
|
||||||
|
import { useErrorsStore } from '@/stores/ErrorsStore';
|
||||||
|
import { setCookie } from '@/utils/cookies';
|
||||||
import { ref, type Ref } from 'vue';
|
import { ref, type Ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const isTokenOK: Ref<boolean|undefined> = ref(undefined)
|
const isTokenOK: Ref<boolean|undefined> = ref(undefined)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
function updateJWTcookies(JWTdata: jwtFormat): void {
|
||||||
|
const cookieExpire = new Date(JWTdata.expire)
|
||||||
|
cookieExpire.setDate(cookieExpire.getDate() + 1)
|
||||||
|
|
||||||
|
setCookie({
|
||||||
|
key: 'JWTtoken',
|
||||||
|
value: JWTdata.token,
|
||||||
|
expire: cookieExpire.toString()
|
||||||
|
})
|
||||||
|
setCookie({
|
||||||
|
key: 'JWTexpire',
|
||||||
|
value: JWTdata.expire,
|
||||||
|
expire: cookieExpire.toString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableErrors(): void {
|
||||||
|
useErrorsStore().sessionExpired = false
|
||||||
|
}
|
||||||
|
|
||||||
function jwtHandler(apiResponse: jwtFormat): void {
|
function jwtHandler(apiResponse: jwtFormat): void {
|
||||||
if (apiResponse.code !== 200) {
|
if (apiResponse.code !== 200) {
|
||||||
console.error(apiResponse)
|
console.error(apiResponse)
|
||||||
|
isTokenOK.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
useAuthStore().token = apiResponse.token
|
updateJWTcookies(apiResponse)
|
||||||
useAuthStore().expire = apiResponse.expire
|
disableErrors()
|
||||||
|
authStore.token = apiResponse.token
|
||||||
|
authStore.expire = apiResponse.expire
|
||||||
isTokenOK.value = true
|
isTokenOK.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO rempalcer tous les :8080 par la var d'env du port de l'API (sinon c un peu dommage)
|
|
||||||
function login(email: string, password: string): void {
|
function login(email: string, password: string): void {
|
||||||
fetch(`http://${__APP_ENV__.APP_HOST_ADDRESS}:${__APP_ENV__.APP_API_PORT}/login/`, {
|
fetch(`http://${__APP_ENV__.APP_HOST_ADDRESS}:${__APP_ENV__.APP_API_PORT}/login/`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -46,13 +72,15 @@ const password = ref('')
|
|||||||
|
|
||||||
<form @submit.prevent="login(email, password)">
|
<form @submit.prevent="login(email, password)">
|
||||||
<div class="inputs-group">
|
<div class="inputs-group">
|
||||||
<div class="label-input">
|
<div :class="`label-input${isTokenOK === false ? '-error' : ''}`">
|
||||||
<label for="email">Adresse mail</label>
|
<label for="email">Adresse mail</label>
|
||||||
<input id="email" name="email" placeholder="E-mail" type="text" v-model="email">
|
<input id="email" name="email" placeholder="E-mail" type="text" v-model="email">
|
||||||
</div>
|
</div>
|
||||||
<div class="label-input">
|
<div :class="`label-input${isTokenOK === false ? '-error' : ''}`">
|
||||||
<label for="password">Mot de passe</label>
|
<label for="password">Mot de passe</label>
|
||||||
<input id="password" name="password" placeholder="Mot de passe" type="password" v-model="password">
|
<input id="password" name="password" placeholder="Mot de passe" type="password" v-model="password">
|
||||||
|
<p v-if="isTokenOK === false">❌ E-mail et/ou mot de passe incorrect(s).</p>
|
||||||
|
<p v-else-if="useErrorsStore().sessionExpired">⌛ Votre session a expiré.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="button-primary" type="submit">Se connecter</button>
|
<button class="button-primary" type="submit">Se connecter</button>
|
||||||
@@ -74,8 +102,8 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
width: 100%;
|
width: fit-content;
|
||||||
max-width: 350px;
|
max-width: 100%;
|
||||||
|
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user