Tests connexion

This commit is contained in:
Peaceultime 2024-08-01 17:55:50 +02:00
parent edf23bdbaa
commit fb58a24170
15 changed files with 222 additions and 135 deletions

View File

@ -34,12 +34,12 @@ onMounted(() => {
</svg> </svg>
</div> </div>
<div class="gapx-3 flex align-stretch"> <div class="gapx-3 flex align-stretch">
<NuxtLink @click="hideLeftPanel" class="site-nav-bar-text" aria-label="Accueil" :href="'/'"> <NuxtLink @click="hideLeftPanel" class="site-nav-bar-text" aria-label="Accueil" to="/">
<ThemeIcon icon="logo" :width=40 :height=40 /> <ThemeIcon icon="logo" :width=40 :height=40 />
</NuxtLink> </NuxtLink>
<NuxtLink class="site-nav-bar-text mobile-hidden" aria-label="Systeme" :href="'/explorer'" <NuxtLink class="site-nav-bar-text mobile-hidden" aria-label="Systeme" to="/explorer"
:class="{'mod-active': $route.path.startsWith('/explorer')}">Systeme</NuxtLink> :class="{'mod-active': $route.path.startsWith('/explorer')}">Systeme</NuxtLink>
<NuxtLink class="site-nav-bar-text mobile-hidden" aria-label="Outils" :href="'/tools'" <NuxtLink class="site-nav-bar-text mobile-hidden" aria-label="Outils" to="/tools"
:class="{'mod-active': $route.path.startsWith('/tools')}">Outils</NuxtLink> :class="{'mod-active': $route.path.startsWith('/tools')}">Outils</NuxtLink>
</div> </div>
</div> </div>
@ -48,7 +48,7 @@ onMounted(() => {
</div> </div>
<div class="ps-1 gapx-1 flex align-center"> <div class="ps-1 gapx-1 flex align-center">
<ThemeSwitch class="mobile-hidden" /> <ThemeSwitch class="mobile-hidden" />
<NuxtLink class="site-login" href="/user/profile"> <NuxtLink class="site-login" to="/user/profile">
<ThemeIcon icon="user" :width=32 :height=32 /> <ThemeIcon icon="user" :width=32 :height=32 />
</NuxtLink> </NuxtLink>
</div> </div>

View File

@ -271,3 +271,10 @@ html.light-mode .light-block {
box-sizing: content-box; box-sizing: content-box;
animation: rotate 1s linear infinite; animation: rotate 1s linear infinite;
} }
.input-form a {
padding: .5em;
text-align: center;
font-style: italic;
font-weight: var(--font-extrabold);
}

View File

@ -160,7 +160,7 @@ function hidePreview(e: Event) {
</div> </div>
</Teleport> </Teleport>
</template> </template>
<a v-bind="$attrs" :href="href" :target="target" v-else> <NuxtLink v-bind="$attrs" :to="href" v-else>
<slot></slot> <slot></slot>
</a> </NuxtLink>
</template> </template>

View File

@ -2,6 +2,7 @@
interface Props interface Props
{ {
toc: Toc; toc: Toc;
comments?: Comment[];
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
</script> </script>
@ -9,14 +10,11 @@ const props = defineProps<Props>();
<template> <template>
<div class="site-body-right-column"> <div class="site-body-right-column">
<div class="site-body-right-column-inner"> <div class="site-body-right-column-inner">
<div v-if="comments !== undefined">
{{ comments.length }} commentaire{{ comments.length === 1 ? '' : 's' }}
</div>
<div v-if="!!toc" class="outline-view-outer node-insert-event"> <div v-if="!!toc" class="outline-view-outer node-insert-event">
<div class="list-item published-section-header"> <div class="list-item published-section-header">
<span class="published-section-header-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-list">
<line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line>
<line x1="3" y1="18" x2="3.01" y2="18"></line>
</svg>
</span>
<span>Sur cette page</span> <span>Sur cette page</span>
</div> </div>
<div class="outline-view"> <div class="outline-view">

View File

@ -1,96 +0,0 @@
export enum AuthStatus
{
disconnected, loading, connected
};
export interface Auth
{
id: Ref<number>;
data: Ref<Record<string, any>>;
sessionId: Ref<string>;
status: Ref<AuthStatus>;
lastRefresh: Ref<Date>;
register: (username: string, email: string, password: string, data?: Record<string, any>) => Promise<any>;
login: (usernameOrEmail: string, password: string) => Promise<void>;
logout: () => Promise<void>;
refresh: () => Promise<void>;
}
async function register(username: string, email: string, password: string, additionalData?: Record<string, any>): Promise<any>
{
const id = useState<number>("auth:id");
const data = useState<any>("auth:data");
const sessionId = useState<string>("auth:sessionId");
const status = useState<AuthStatus>("auth:status");
const lastRefresh = useState<Date>("auth:date");
status.value = AuthStatus.loading;
try
{
const result = await $fetch("/api/auth/register", {
method: 'POST',
body: { username, email, password, additionalData },
ignoreResponseError: true,
});
if(result.success)
{
id.value = result.id!;
data.value = { ...additionalData, username: username, email: email };
sessionId.value = result.sessionId!;
status.value = AuthStatus.connected;
lastRefresh.value = new Date();
return;
}
else if(result.error)
{
status.value = AuthStatus.disconnected;
return result.error;
}
else
{
status.value = AuthStatus.disconnected;
return;
}
}
catch(e) {
console.log(JSON.stringify(e));
status.value = AuthStatus.disconnected;
}
}
async function login(usernameOrEmail: string, password: string): Promise<void>
{
const status = useState<AuthStatus>("auth:status");
status.value = AuthStatus.disconnected;
}
async function logout(): Promise<void>
{
const status = useState<AuthStatus>("auth:status");
status.value = AuthStatus.disconnected;
}
async function refresh(): Promise<void>
{
const status = useState<AuthStatus>("auth:status");
status.value = AuthStatus.disconnected;
}
export default function useAuth(): Auth {
const id = useState<number>("auth:id", () => 0);
const data = useState<any>("auth:data", () => { });
const sessionId = useState<string>("auth:sessionId", () => '');
const status = useState<AuthStatus>("auth:status", () => AuthStatus.disconnected);
const lastRefresh = useState<Date>("auth:date", () => new Date());
return {
id, data, sessionId, status, lastRefresh,
register, login, logout, refresh
};
}

BIN
db.sqlite

Binary file not shown.

View File

@ -1,7 +0,0 @@
export default defineNuxtRouteMiddleware((to) => {
const meta = to.meta.auth;
//useSession(to.)
return to;
})

View File

@ -7,18 +7,22 @@ function toggleLeftPanel(_: Event) {
<script setup lang="ts"> <script setup lang="ts">
const { hash, path } = useRoute(); const { hash, path } = useRoute();
const route = path.substring(path.indexOf('/explorer') + '/explorer'.length);
const { data: page } = await useAsyncData('home', queryContent(path.substring(path.indexOf('/explorer') + '/explorer'.length)).findOne); const { data: page } = await useAsyncData('home', queryContent(route).findOne);
const { data: comments } = await useFetch(`/api/comments`, {
query: {
route: route
}
});
if(page.value) if(page.value)
useContentHead(page.value!);
if(hash)
{ {
console.log(document.getElementById(hash.substring(1))); useContentHead(page.value!);
document.getElementById(hash.substring(1))?.scrollTo({ behavior: 'smooth', top: 100 });
} }
const content = ref();
onMounted(() => { onMounted(() => {
document.querySelectorAll('.callout.is-collapsible .callout-title').forEach(e => { document.querySelectorAll('.callout.is-collapsible .callout-title').forEach(e => {
e.addEventListener('click', (_) => { e.addEventListener('click', (_) => {
@ -53,7 +57,7 @@ onMounted(() => {
</div> </div>
</div> </div>
</div> </div>
<RightComponent v-if="page.body?.toc" :toc="page?.body?.toc" /> <RightComponent v-if="page.body?.toc" :toc="page?.body?.toc" :comments="comments" />
</template> </template>
<CanvasRenderer v-else-if="!!page && page._type == 'canvas'" :key="page._id" :canvas="page" /> <CanvasRenderer v-else-if="!!page && page._type == 'canvas'" :key="page._id" :canvas="page" />
<div v-else-if="!!page"> <div v-else-if="!!page">

View File

@ -1,14 +1,70 @@
<script setup lang="ts"> <script setup lang="ts">
import { schema, type Login } from '~/schemas/login';
definePageMeta({ definePageMeta({
auth: { auth: {
unauthenticatedOnly: true, disconnectedOnly: true,
navigateAuthenticatedTo: '/user/profile' connectedRedirect: '/user/profile'
} }
}); });
const state = reactive<Login>({
username: '',
password: ''
});
const { status, login } = useAuth();
const usernameError = ref("");
const passwordError = ref("");
async function submit()
{
const data = schema.safeParse(state);
if(data.success && state.password !== "")
{
let errors = await login(data.data.username, data.data.password);
if(status.value === AuthStatus.connected)
{
await navigateTo('/user/profile', { replace: true });
}
else
{
errors = errors?.issues ?? errors;
usernameError.value = errors?.find((e: any) => e.path.includes("username"))?.message ?? "";
passwordError.value = errors?.find((e: any) => e.path.includes("password"))?.message ?? "";
}
}
else
{
usernameError.value = data.error?.issues.find(e => e.path.includes("username"))?.message ?? "";
passwordError.value = data.error?.issues.find(e => e.path.includes("password"))?.message ?? "";
}
}
</script> </script>
<template> <template>
<Head> <Head>
<Title>Se connecter</Title> <Title>Se connecter</Title>
</Head> </Head>
<div class="site-body-center-column">
<div class="render-container flex align-center justify-center">
<form v-if="status === AuthStatus.disconnected" @submit.prevent="submit" class="input-form input-form-wide">
<h1>Connexion</h1>
<Input type="text" autocomplete="username" v-model="state.username"
placeholder="" title="Nom d'utilisateur ou adresse mail" :error="usernameError" />
<Input type="password" autocomplete="current-password" v-model="state.password"
placeholder="" title="Mot de passe"
:error="passwordError" />
<button>Se connecter</button>
<NuxtLink to="/user/register">Pas de compte ?</NuxtLink>
</form>
<div v-else-if="status === AuthStatus.loading" class="input-form"><div class="loading"></div></div>
<div v-else class="not-found-container">
<div class="not-found-title">👀 Vous n'avez rien à faire ici. 👀</div>
</div>
</div>
</div>
</template> </template>

View File

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
auth: { auth: {
unauthenticatedOnly: false, disconnectedOnly: false,
navigateUnauthenticatedTo: '/user/login' disconnectedRedirect: '/user/login'
} }
}); });

View File

@ -3,8 +3,8 @@ import { schema, type Registration } from '~/schemas/registration';
definePageMeta({ definePageMeta({
auth: { auth: {
unauthenticatedOnly: true, disconnectedOnly: true,
navigateAuthenticatedTo: '/user/profile' connectedRedirect: '/user/profile'
} }
}); });
@ -86,7 +86,8 @@ async function submit()
<Input type="password" v-model="confirmPassword" placeholder="Confirmer le mot de passe" <Input type="password" v-model="confirmPassword" placeholder="Confirmer le mot de passe"
title="Confirmer le mot de passe" title="Confirmer le mot de passe"
:error="confirmPassword === '' || confirmPassword === state.password ? '' : 'Les mots de passe saisies ne sont pas identique'" /> :error="confirmPassword === '' || confirmPassword === state.password ? '' : 'Les mots de passe saisies ne sont pas identique'" />
<button>Valider</button> <button>S'inscrire</button>
<NuxtLink to="/user/login">Se connecter</NuxtLink>
</form> </form>
<div v-else-if="status === AuthStatus.loading" class="input-form"><div class="loading"></div></div> <div v-else-if="status === AuthStatus.loading" class="input-form"><div class="loading"></div></div>
<div v-else class="not-found-container"> <div v-else class="not-found-container">

8
schemas/login.ts Normal file
View File

@ -0,0 +1,8 @@
import { z } from "zod";
export const schema = z.object({
username: z.string({ required_error: "Nom d'utilisateur obligatoire" }),
password: z.string({ required_error: "Mot de passe obligatoire" }),
});
export type Login = z.infer<typeof schema>;

View File

@ -1,3 +1,96 @@
export default defineEventHandler(async (e) => { import useDatabase from '~/composables/useDatabase';
import { schema } from '~/schemas/login';
export default defineEventHandler(async (e) => {
const { sessionPassword } = useRuntimeConfig();
const session = await useSession(e, {
password: sessionPassword,
}); });
try
{
const db = useDatabase();
console.log(session.id);
if(session.id && session.data.id)
{
const checkSession = db.query("SELECT user_id FROM user_sessions WHERE id = ?1");
const sessionId = checkSession.get(session.id) as any;
console.log(sessionId);
if(sessionId && sessionId.user_id === session.data.id)
{
return { success: true, id: session.data.id, sessionId: session.id, data: session.data };
}
else
{
session.clear();
setResponseStatus(e, 406);
return { success: false, error: { path: ['global'], message: 'Vous êtes déjà connecté' } };
}
}
const body = await readValidatedBody(e, schema.safeParse);
if (!body.success)
{
session.clear();
setResponseStatus(e, 406);
return { success: false, error: body.error };
}
const hash = await Bun.password.hash(body.data.password);
const checkID = db.query(`SELECT id FROM users WHERE (username = ?1 or email = ?1)`);
const id = checkID.get(body.data.username) as any;
if(!id || !id.id)
{
session.clear();
setResponseStatus(e, 401);
return { success: false, error: { path: ['username'], message: 'Identifiant inconnu' } };
}
const checkHash = db.query(`SELECT COUNT(*) as count FROM users WHERE id = ?1 and hash = ?2`);
const validation = checkHash.get(id.id, hash) as any;
if(validation && validation.count && validation.count !== 1)
{
session.clear();
setResponseStatus(e, 401);
return { success: false, error: { path: ['password'], message: 'Mot de passe incorrect' } };
}
const loggingIn = db.query(`INSERT INTO user_sessions(id, user_id, ip, agent, lastRefresh) VALUES(?1, ?2, ?3, ?4, ?5)`);
loggingIn.get(session.id, id.id, getRequestIP(e), getRequestHeader(e, 'User-Agent'), Date.now());
await session.update(getData(db, id.id));
setResponseStatus(e, 201);
return { success: true, id: id.id, sessionId: session.id, data: session.data };
}
catch(e)
{
session.clear();
console.error(e);
return { success: false, error: e };
}
});
function getData(db: Database, id: string): any
{
const userQuery = db.query(`SELECT * FROM users WHERE id = ?1`);
const user = userQuery.get(id);
const userDataQuery = db.query(`SELECT * FROM users_data WHERE user_id = ?1`);
const userData = userDataQuery.get(id);
return { ...user, ...userData };
}

View File

@ -1,2 +0,0 @@
export default defineEventHandler(async (e) => {
});

View File

@ -0,0 +1,25 @@
import useDatabase from '~/composables/useDatabase';
export interface Comment
{
user: number;
file: string;
position: number;
length: number;
sequence: number;
text: string;
}
export default defineEventHandler(async (e) => {
const query = getQuery(e);
if(query && query.route !== undefined)
{
const db = useDatabase();
const comments = db.query("SELECT * FROM comments WHERE file = ?1").all(query.route as string) as Comment[];
return comments;
}
setResponseStatus(e, 404);
});