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 }}
|
{{ menuItem.label }}
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="navbar-end">
|
||||||
|
<a class="navbar-item" role="button" @click="onLogoutClick">
|
||||||
|
Se déconnecter
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from "~/stores/authStore"
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
label: "Liste des films",
|
label: "Liste des films",
|
||||||
|
@ -51,6 +70,13 @@ const menuItems = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const isBurgerOpen = ref(false)
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="sass"></style>
|
<style scoped lang="sass"></style>
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<AdminHeader />
|
<AdminHeader v-if="authStore.isLogged" />
|
||||||
<div class="container"><slot /></div>
|
<div class="container"><slot /></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from "~/stores/authStore"
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped lang="sass"></style>
|
<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.
|
# Additionally, we include login URLs for the browsable API.
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include(router.urls)),
|
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
|
getconf~=1.11.1
|
||||||
tmdbv3api~=1.7.6
|
tmdbv3api~=1.7.6
|
||||||
factory-boy==3.2.1
|
factory-boy==3.2.1
|
||||||
|
dj-rest-auth==2.2.5
|
||||||
|
|
|
@ -39,13 +39,14 @@ INSTALLED_APPS = [
|
||||||
"myapi.apps.MyapiConfig",
|
"myapi.apps.MyapiConfig",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"corsheaders",
|
"corsheaders",
|
||||||
|
"dj_rest_auth",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
|
||||||
"corsheaders.middleware.CorsMiddleware",
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
@ -128,3 +129,4 @@ REST_FRAMEWORK = {
|
||||||
}
|
}
|
||||||
|
|
||||||
TMDB_API_KEY = config.getstr("tmdb.api_key")
|
TMDB_API_KEY = config.getstr("tmdb.api_key")
|
||||||
|
REST_AUTH_TOKEN_MODEL = None
|
||||||
|
|
Loading…
Reference in a new issue