From ec0afa968657a3dda2e194eebed93b0197dd3fa9 Mon Sep 17 00:00:00 2001 From: Peaceultime Date: Tue, 17 Dec 2024 17:51:12 +0100 Subject: [PATCH] Password reset and new email validation ID stored in DB for more security --- db.sqlite | Bin 569344 -> 569344 bytes db.sqlite-shm | Bin 32768 -> 32768 bytes db.sqlite-wal | Bin 57712 -> 140112 bytes db/schema.ts | 5 + drizzle/0005_panoramic_slayback.sql | 4 + drizzle/meta/0005_snapshot.json | 359 ++++++++++++++++++ drizzle/meta/_journal.json | 7 + nuxt.config.ts | 2 +- .../user/{ => (automatic)}/mailvalidated.vue | 0 pages/user/(automatic)/reset-password.vue | 47 +++ pages/user/(automatic)/resetting-password.vue | 87 +++++ pages/user/changing-password.vue | 88 +++++ pages/user/login.vue | 1 + pages/user/profile.vue | 4 +- server/api/auth/register.post.ts | 17 +- server/api/auth/request-reset.post.ts | 55 +++ server/api/auth/reset.post.ts | 99 +++++ server/api/users/[id]/change-password.post.ts | 75 ++++ server/api/users/[id]/reset-password.post.ts | 78 ++++ .../{revalidate.get.ts => revalidate.post.ts} | 24 +- server/components/mail/registration.vue | 9 +- server/components/mail/reset-password.vue | 29 ++ server/routes/user/mailvalidation.get.ts | 26 +- server/tasks/mail.ts | 24 +- server/tasks/validation.ts | 39 ++ todo.md | 3 +- 26 files changed, 1042 insertions(+), 40 deletions(-) create mode 100644 drizzle/0005_panoramic_slayback.sql create mode 100644 drizzle/meta/0005_snapshot.json rename pages/user/{ => (automatic)}/mailvalidated.vue (100%) create mode 100644 pages/user/(automatic)/reset-password.vue create mode 100644 pages/user/(automatic)/resetting-password.vue create mode 100644 pages/user/changing-password.vue create mode 100644 server/api/auth/request-reset.post.ts create mode 100644 server/api/auth/reset.post.ts create mode 100644 server/api/users/[id]/change-password.post.ts create mode 100644 server/api/users/[id]/reset-password.post.ts rename server/api/users/[id]/{revalidate.get.ts => revalidate.post.ts} (61%) create mode 100644 server/components/mail/reset-password.vue create mode 100644 server/tasks/validation.ts diff --git a/db.sqlite b/db.sqlite index 36ccc3d73f6776e7c16868a92237384ec0fccfba..ba7a31ef4d7b609abbf45b3ac6f57e69811e5b6b 100644 GIT binary patch delta 730 zcmZp8pw#d{X@az%FarZaHxRP|u_O?SP1G^w5@yh=i{#~2We}K{slaN$y@`)+v!Xx` z^JXa)Zgv*#&kVhjHMu*P>_sQ{vdd_v<|bz5#Fr)JWTqsRWaj6^8={LO^7=Dy01ao@ z{F^(Bk%{5q^QqDoHjv=lJA&yQyt_lh0(g_M0 zT$~A+DG3TCsTC!Y@3V+a7GsxVe7)IN z85((6lgyG03@wcBQ4d)Aj#AuQP(2b!cy17G}%Dc!pI^~*TBfa z#K6M9Jk`?7EIo0l-ll{*{3^sK-c<01p9QFadsD+o{%}JNx1_-0#Nez5uTEOER#QSh^m665)}OX6cW%CBq(SkK%_Sx?A+C*CNe0NjJ&B+#)5(%)l}wF(o~)$8uA` z9ex#J6mKf{!_NZLz`3bmC4abYs&lG-kf~W!v7u*nut#=zQJO_%x<{m2Nknc~YGjE= zqP}xQd693VS)fXprLVSkN=RU7o~wtef0(~lXjGA3P+pj6SYlCmRbpv|S(vL^c(!qn zNA{)(EA44z_M!_1Sb*Wfu$?V|;~PI4LsPoXvHxug7~2*wwJl(7Tfnkx0c!$CMbdUw V1=ipEj0$Z6Y;6MU%LF(U00822w4DF| diff --git a/db.sqlite-shm b/db.sqlite-shm index e5163194d43904cd87aef3e736f7d90c9a8a5408..6a642b251cd9271c0533cdd2e1a8c08de13a68da 100644 GIT binary patch delta 310 zcmZo@U}|V!s+V}A%K!qBK+MR%AfN=KyMcJcE0t1k_|B(Pxcw)UED<_c6JF!=slO4!l17h}x6UE`8svuD*AWj5gpbOz_m>5|5 z#)BUn8D%yeJj=x@#URTdzww|li!dVtD+4VZaq0m$6{NB}B4v0iZE9iE8~IFJy_W)a3$j++%2z4&<<8JHPZ8Q2*(8MqmEHw!Yp YXXRyLU}0cm;9%fl;9=kc3NdQ{0IOFyjQ{`u diff --git a/db.sqlite-wal b/db.sqlite-wal index de767733c1612fe73bdaf2315ae82a03a634cadc..09ce120985eda923e14a433c435d572f9f708248 100644 GIT binary patch literal 140112 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 diff --git a/db/schema.ts b/db/schema.ts index 88e8078..17d3c34 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -48,6 +48,11 @@ export const explorerContentTable = sqliteTable("explorer_content", { timestamp: int({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()), }); +export const emailValidationTable = sqliteTable("email_validation", { + id: text().primaryKey(), + timestamp: int({ mode: 'timestamp' }).notNull(), +}) + export const usersRelation = relations(usersTable, ({ one, many }) => ({ data: one(usersDataTable, { fields: [usersTable.id], references: [usersDataTable.id], }), session: many(userSessionsTable), diff --git a/drizzle/0005_panoramic_slayback.sql b/drizzle/0005_panoramic_slayback.sql new file mode 100644 index 0000000..506e447 --- /dev/null +++ b/drizzle/0005_panoramic_slayback.sql @@ -0,0 +1,4 @@ +CREATE TABLE `email_validation` ( + `id` text PRIMARY KEY NOT NULL, + `timestamp` integer NOT NULL +); diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..52f81f5 --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,359 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "a2731c1f-4150-4423-946e-670d794f8961", + "prevId": "b6acf5d6-d8df-4308-8d4d-55c25741cc4f", + "tables": { + "email_validation": { + "name": "email_validation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "explorer_content": { + "name": "explorer_content", + "columns": { + "path": { + "name": "path", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "blob", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "navigable": { + "name": "navigable", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "private": { + "name": "private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visit": { + "name": "visit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "explorer_content_owner_users_id_fk": { + "name": "explorer_content_owner_users_id_fk", + "tableFrom": "explorer_content", + "tableTo": "users", + "columnsFrom": [ + "owner" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_permissions": { + "name": "user_permissions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_permissions_id_users_id_fk": { + "name": "user_permissions_id_users_id_fk", + "tableFrom": "user_permissions", + "tableTo": "users", + "columnsFrom": [ + "id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_permissions_id_permission_pk": { + "columns": [ + "id", + "permission" + ], + "name": "user_permissions_id_permission_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_sessions": { + "name": "user_sessions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_sessions_user_id_users_id_fk": { + "name": "user_sessions_user_id_users_id_fk", + "tableFrom": "user_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_sessions_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "user_sessions_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users_data": { + "name": "users_data", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "signin": { + "name": "signin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "lastTimestamp": { + "name": "lastTimestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logCount": { + "name": "logCount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "users_data_id_users_id_fk": { + "name": "users_data_id_users_id_fk", + "tableFrom": "users_data", + "tableTo": "users", + "columnsFrom": [ + "id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "users_username_unique": { + "name": "users_username_unique", + "columns": [ + "username" + ], + "isUnique": true + }, + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "users_hash_unique": { + "name": "users_hash_unique", + "columns": [ + "hash" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 52c388e..609ed12 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1732722840534, "tag": "0004_ancient_thunderball", "breakpoints": true + }, + { + "idx": 5, + "version": "6", + "when": 1734426608563, + "tag": "0005_panoramic_slayback", + "breakpoints": true } ] } \ No newline at end of file diff --git a/nuxt.config.ts b/nuxt.config.ts index df2d740..b27f1d9 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -153,7 +153,7 @@ export default defineNuxtConfig({ xssValidator: false, }, sitemap: { - exclude: ['/admin/**', '/explore/edit', '/user/mailvalidated'], + exclude: ['/admin/**', '/explore/edit', '/user/mailvalidated', '/user/changing-password', '/user/reset-password'], sources: ['/api/__sitemap__/urls'] }, experimental: { diff --git a/pages/user/mailvalidated.vue b/pages/user/(automatic)/mailvalidated.vue similarity index 100% rename from pages/user/mailvalidated.vue rename to pages/user/(automatic)/mailvalidated.vue diff --git a/pages/user/(automatic)/reset-password.vue b/pages/user/(automatic)/reset-password.vue new file mode 100644 index 0000000..562467f --- /dev/null +++ b/pages/user/(automatic)/reset-password.vue @@ -0,0 +1,47 @@ + + + \ No newline at end of file diff --git a/pages/user/(automatic)/resetting-password.vue b/pages/user/(automatic)/resetting-password.vue new file mode 100644 index 0000000..e8ae7fd --- /dev/null +++ b/pages/user/(automatic)/resetting-password.vue @@ -0,0 +1,87 @@ + + + \ No newline at end of file diff --git a/pages/user/changing-password.vue b/pages/user/changing-password.vue new file mode 100644 index 0000000..5a7ff68 --- /dev/null +++ b/pages/user/changing-password.vue @@ -0,0 +1,88 @@ + + + \ No newline at end of file diff --git a/pages/user/login.vue b/pages/user/login.vue index 22ce527..b44c8c2 100644 --- a/pages/user/login.vue +++ b/pages/user/login.vue @@ -11,6 +11,7 @@ + Mot de passe oublié ? Pas de compte ? diff --git a/pages/user/profile.vue b/pages/user/profile.vue index 4fe637e..c6bacaa 100644 --- a/pages/user/profile.vue +++ b/pages/user/profile.vue @@ -12,7 +12,7 @@ async function revalidateUser() { loading.value = true; await $fetch(`/api/users/${user.value?.id}/revalidate`, { - method: 'get' + method: 'post' }); loading.value = false; toaster.add({ closeable: false, duration: 10000, timer: true, content: 'Un mail vous a été envoyé.', type: 'info' }); @@ -54,7 +54,7 @@ async function deleteUser()
- +
@@ -17,10 +17,11 @@ import { computed } from 'vue'; import Bun from 'bun'; -const { id, username, timestamp } = defineProps<{ +const { id, userId, username, timestamp } = defineProps<{ id: number + userId: number username: string timestamp: number }>(); -const hash = computed(() => Bun.hash(id.toString(), timestamp)); +const hash = computed(() => Bun.hash('1' + userId.toString(), timestamp)); \ No newline at end of file diff --git a/server/components/mail/reset-password.vue b/server/components/mail/reset-password.vue new file mode 100644 index 0000000..d0fcf0d --- /dev/null +++ b/server/components/mail/reset-password.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/server/routes/user/mailvalidation.get.ts b/server/routes/user/mailvalidation.get.ts index da6c030..ed31af8 100644 --- a/server/routes/user/mailvalidation.get.ts +++ b/server/routes/user/mailvalidation.get.ts @@ -1,10 +1,11 @@ -import { eq } from "drizzle-orm"; +import { eq, getTableColumns, lte } from "drizzle-orm"; import { z } from "zod"; import useDatabase from "~/composables/useDatabase"; -import { usersTable } from "~/db/schema"; +import { emailValidationTable, usersTable } from "~/db/schema"; const schema = z.object({ h: z.coerce.string(), + i: z.coerce.string(), u: z.coerce.number(), t: z.coerce.number(), }); @@ -15,22 +16,33 @@ export default defineEventHandler(async (e) => { if(!query.success) throw query.error; - if(Bun.hash(query.data.u.toString(), query.data.t).toString() !== query.data.h) + if(Bun.hash('1' + query.data.u.toString(), query.data.t).toString() !== query.data.h) { return createError({ statusCode: 400, message: 'Lien incorrect', - }) + }); } if(Date.now() > query.data.t + (60 * 60 * 1000)) { return createError({ statusCode: 400, message: 'Le lien a expiré', - }) + }); } const db = useDatabase(); + const validate = db.select(getTableColumns(emailValidationTable)).from(emailValidationTable).where(eq(emailValidationTable.id, query.data.i)).get(); + + if(!validate || validate.timestamp <= new Date()) + { + return createError({ + statusCode: 400, + message: 'Le lien a expiré', + }); + } + + db.delete(emailValidationTable).where(lte(emailValidationTable.timestamp, new Date())).run(); const result = db.select({ state: usersTable.state }).from(usersTable).where(eq(usersTable.id, query.data.u)).get(); if(result === undefined) @@ -38,14 +50,14 @@ export default defineEventHandler(async (e) => { return createError({ statusCode: 400, message: 'Aucune donnée utilisateur trouvée', - }) + }); } if(result?.state === 1) { return createError({ statusCode: 400, message: 'Votre compte a déjà été validé', - }) + }); } db.update(usersTable).set({ state: 1 }).where(eq(usersTable.id, query.data.u)).run(); diff --git a/server/tasks/mail.ts b/server/tasks/mail.ts index bd22a49..c203f59 100644 --- a/server/tasks/mail.ts +++ b/server/tasks/mail.ts @@ -3,28 +3,24 @@ import { createSSRApp, h } from 'vue'; import { renderToString } from 'vue/server-renderer'; import base from '../components/mail/base.vue'; -import registration from '../components/mail/registration.vue'; -//import revalidation from '../components/mail/revalidation.vue'; +import Registration from '../components/mail/registration.vue'; +import ResetPassword from '../components/mail/reset-password.vue'; const config = useRuntimeConfig(); const [domain, selector, dkim] = config.mail.dkim.split(":"); export const templates: Record = { - "registration": { component: registration, subject: 'Bienvenue sur d[any] 😎' }, -// "revalidate-mail": { component: revalidation, subject: 'd[any]: Valider votre email' }, + "registration": { component: Registration, subject: 'Bienvenue sur d[any] 😎' }, + "reset-password": { component: ResetPassword, subject: 'Réinitialisation de votre mot de passe' }, }; -import 'nitropack/types'; import type Mail from 'nodemailer/lib/mailer'; -declare module 'nitropack/types' +interface MailPayload { - interface TaskPayload - { - type: 'mail' - to: string[] - template: string - data: Record - } + type: 'mail' + to: string[] + template: string + data: Record } const transport = nodemailer.createTransport({ @@ -57,7 +53,7 @@ export default defineTask({ throw new Error(`Données inconnues`); } - const payload = e.payload; + const payload = e.payload as MailPayload; const template = templates[payload.template]; if(!template) diff --git a/server/tasks/validation.ts b/server/tasks/validation.ts new file mode 100644 index 0000000..2877499 --- /dev/null +++ b/server/tasks/validation.ts @@ -0,0 +1,39 @@ +import { lt } from "drizzle-orm"; +import { emailValidationTable } from "~/db/schema"; +import useDatabase from '~/composables/useDatabase'; + +interface ValidationPayload +{ + type: 'validation' + id: string + timestamp: number +} + +export default defineTask({ + meta: { + name: 'validation', + description: 'Add email ID to DB', + }, + async run(e) { + try { + if(e.payload.type !== 'validation') + { + throw new Error(`Données inconnues`); + } + + const payload = e.payload as ValidationPayload; + const db = useDatabase(); + + db.delete(emailValidationTable).where(lt(emailValidationTable.timestamp, new Date())).run(); + db.insert(emailValidationTable).values({ id: payload.id, timestamp: new Date(payload.timestamp) }).run(); + + return { result: true }; + } + catch(e) + { + console.error(e); + + return { result: false, error: e }; + } + }, +}) \ No newline at end of file diff --git a/todo.md b/todo.md index 00871d8..1a28a2a 100644 --- a/todo.md +++ b/todo.md @@ -1,4 +1,5 @@ -- [ ] Rename auto des liens au changement de path +- [x] Mot de passe oublié +- [x] Rename auto des liens au changement de path - [ ] Autocomplete des liens dans l'editeur - [ ] Editeur de graphe - [ ] Filtrage de lien avec le header id