feat [both]: authentication
This commit is contained in:
parent
42fd29ce95
commit
04fedf7985
10 changed files with 178 additions and 4 deletions
|
@ -33,12 +33,31 @@
|
|||
{{ menuItem.label }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<a class="navbar-item" role="button" @click="onLogoutClick">
|
||||
Se déconnecter
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div v-if="errorMessage" class="modal is-active">
|
||||
<div class="modal-background" @click="errorMessage = ''" />
|
||||
<div class="modal-card">
|
||||
<section class="modal-card-body">
|
||||
La déconnexion a échoué avec le message d'erreur suivant : "{{
|
||||
errorMessage
|
||||
}}".
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from "~/stores/authStore"
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Liste des films",
|
||||
|
@ -51,6 +70,13 @@ const menuItems = [
|
|||
]
|
||||
|
||||
const isBurgerOpen = ref(false)
|
||||
const errorMessage = ref("")
|
||||
|
||||
async function onLogoutClick() {
|
||||
const { error } = await authStore.logout()
|
||||
errorMessage.value = error.value?.message || ""
|
||||
if (!error.value) await navigateTo("/", { replace: true })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="sass"></style>
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
<template>
|
||||
<AdminHeader />
|
||||
<AdminHeader v-if="authStore.isLogged" />
|
||||
<div class="container"><slot /></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from "~/stores/authStore"
|
||||
|
||||
const authStore = useAuthStore()
|
||||
</script>
|
||||
|
||||
<style scoped lang="sass"></style>
|
||||
|
|
12
front/middleware/toLogin.global.ts
Normal file
12
front/middleware/toLogin.global.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { useAuthStore } from "~/stores/authStore"
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
if (
|
||||
!process.server &&
|
||||
to.meta.layout === "admin" &&
|
||||
to.name !== "admin-login" &&
|
||||
!(await useAuthStore().isLogged)
|
||||
) {
|
||||
return navigateTo("/admin/login")
|
||||
}
|
||||
})
|
84
front/pages/admin/login.vue
Normal file
84
front/pages/admin/login.vue
Normal file
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div class="mt-5 hero is-fullheight">
|
||||
<div class="hero-body is-justify-content-center is-align-items-center">
|
||||
<div class="columns is-flex is-flex-direction-column box">
|
||||
<div class="column is-half-desktop is-offset-one-quarter-desktop">
|
||||
<div v-if="errorMessage" class="notification is-danger">
|
||||
La connexion a échoué pour le motif suivant : "{{ errorMessage }}".
|
||||
</div>
|
||||
<form action="#" @submit.prevent="onSubmit">
|
||||
<fieldset>
|
||||
<label for="username">Identifiant</label>
|
||||
<input
|
||||
id="username"
|
||||
v-model="login.username"
|
||||
class="input is-primary"
|
||||
type="text"
|
||||
placeholder="Identifiant"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="password">Mot de passe</label>
|
||||
<input
|
||||
id="password"
|
||||
v-model="login.password"
|
||||
class="input is-primary"
|
||||
type="password"
|
||||
placeholder="Mot de passe"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label class="button is-primary is-fullwidth" for="submit">
|
||||
Connexion
|
||||
</label>
|
||||
<input id="submit" type="submit" />
|
||||
</fieldset>
|
||||
|
||||
<div class="has-text-centered">
|
||||
<p class="is-size-7">
|
||||
Pas de compte ? Oublié votre mot de passe ?
|
||||
<a href="mailto:cineclub-contact@ens.psl.eu">
|
||||
Contactez le ciné-club.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from "~/stores/authStore"
|
||||
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
})
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const login = ref({ username: "", password: "" })
|
||||
const errorMessage = ref("")
|
||||
|
||||
async function onSubmit() {
|
||||
const { error } = await authStore.login(
|
||||
login.value.username,
|
||||
login.value.password
|
||||
)
|
||||
errorMessage.value = error.value?.message || ""
|
||||
if (!error.value) await navigateTo("/admin/", { replace: true })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="sass">
|
||||
fieldset
|
||||
margin-bottom: 1.5rem
|
||||
|
||||
input
|
||||
margin-top: 0.5rem
|
||||
|
||||
input[type="submit"]
|
||||
display: none
|
||||
</style>
|
12
front/plugins/init-data.client.ts
Normal file
12
front/plugins/init-data.client.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { useAuthStore } from "~/stores/authStore";
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.hook("app:mounted", async () => {
|
||||
// the data should already be fetched from SSR
|
||||
// but if it's missing, we try again from the client
|
||||
const authStore = useAuthStore()
|
||||
if (authStore.logStatus === undefined) {
|
||||
await authStore.updateLogStatus()
|
||||
}
|
||||
})
|
||||
})
|
5
front/plugins/init-data.server.ts
Normal file
5
front/plugins/init-data.server.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { useAuthStore } from "~/stores/authStore";
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.hook("vue:setup", () => useAuthStore().updateLogStatus())
|
||||
})
|
28
front/stores/authStore.ts
Normal file
28
front/stores/authStore.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { defineStore } from "pinia"
|
||||
|
||||
export const useAuthStore = defineStore("auth", {
|
||||
state: () =>
|
||||
({
|
||||
logStatus: undefined,
|
||||
} as { logStatus: boolean | undefined }),
|
||||
actions: {
|
||||
async login(username: string, password: string) {
|
||||
const res = await apiPost("auth/login/", { username, password })
|
||||
if (!res.error.value) this.logStatus = true
|
||||
return res
|
||||
},
|
||||
async logout() {
|
||||
const res = await apiPost("auth/logout/")
|
||||
if (!res.error.value) this.logStatus = false
|
||||
return res
|
||||
},
|
||||
async updateLogStatus() {
|
||||
const res = await apiGet("auth/user/")
|
||||
this.logStatus = !res.error.value
|
||||
return res
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
isLogged: (state) => state.logStatus,
|
||||
},
|
||||
})
|
|
@ -12,5 +12,5 @@ router.register(r"tmdb", TmdbViewSet, "tmdb")
|
|||
# Additionally, we include login URLs for the browsable API.
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
|
||||
path('auth/', include('dj_rest_auth.urls')),
|
||||
]
|
||||
|
|
|
@ -5,3 +5,4 @@ djangorestframework-camel-case==1.3.0
|
|||
getconf~=1.11.1
|
||||
tmdbv3api~=1.7.6
|
||||
factory-boy==3.2.1
|
||||
dj-rest-auth==2.2.5
|
||||
|
|
|
@ -39,13 +39,14 @@ INSTALLED_APPS = [
|
|||
"myapi.apps.MyapiConfig",
|
||||
"rest_framework",
|
||||
"corsheaders",
|
||||
"dj_rest_auth",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
|
@ -128,3 +129,4 @@ REST_FRAMEWORK = {
|
|||
}
|
||||
|
||||
TMDB_API_KEY = config.getstr("tmdb.api_key")
|
||||
REST_AUTH_TOKEN_MODEL = None
|
||||
|
|
Loading…
Reference in a new issue