diff --git a/assets/common.css b/assets/common.css index 4423179..7922b2d 100644 --- a/assets/common.css +++ b/assets/common.css @@ -138,9 +138,6 @@ html.light-mode .light-block { display: block; } -.flex { - display: flex; -} .align-baseline { align-items: baseline; } @@ -150,11 +147,26 @@ html.light-mode .light-block { .align-stretch { align-items: stretch; } +.justify-center { + justify-content: center; +} +.justify-between { + justify-content: space-between; +} +.justify-evenly { + justify-content: space-evenly; +} +.justify-around { + justify-content: space-around; +} @media screen and (max-width: 750px) { .mobile-bigger { flex: 3 1 0 !important; } + .mobile-smaller { + flex: 1 3 0 !important; + } .mobile-hidden { display: none !important; } @@ -163,10 +175,82 @@ html.light-mode .light-block { } } @media screen and (min-width: 750px) { + .desktop-bigger { + flex: 3 1 0 !important; + } + .desktop-smaller { + flex: 1 3 0 !important; + } .desktop-hidden { display: none !important; } .desktop-block { display: inherit !important; } +} + + +.input-form.input-form-wide { + width: 600px; +} + +.input-form { + width: 400px; + display: flex; + flex-direction: column; + padding: 0 2em 2em 2em; + border: 1px solid var(--background-modifier-border); +} + +.input-group { + display: flex; + flex-direction: column; + padding: .5em; +} + +.input-form h1 { + font-size: x-large; +} + +.input-form button { + margin-top: 2em; +} + +.input-group .input-label { + padding: 4px 1em; +} + +.input-group .input-error { + padding: .5em 1em 4px; + color: var(--text-error); + user-select: text; +} + +.input-group .input-input.input-has-error { + border-color: var(--text-error); +} + +.password-validation-group { + display: flex; + flex-direction: column; + padding: .5em 2em; +} + +.password-validation-title { + font-style: italic; + font-size: small; + padding-bottom: 4px; +} + +.password-validation-item { + font-size: small; + padding-left: .5em; +} + +.password-validation-item pre { + user-select: text; +} + +.password-validation-item.validation-error { + color: var(--text-error); } \ No newline at end of file diff --git a/assets/global.css b/assets/global.css index 729b509..38ac617 100644 --- a/assets/global.css +++ b/assets/global.css @@ -2266,13 +2266,6 @@ button.mod-destructive { color: var(--text-on-accent); } -.input-label { - display: inline-block; - width: 150px; - text-align: right; - margin-right: var(--size-4-2); -} - .input-button { padding: 6px 14px; margin-left: 14px; diff --git a/bun.lockb b/bun.lockb index eb681e3..996f702 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/Input.vue b/components/Input.vue new file mode 100644 index 0000000..b0eeeea --- /dev/null +++ b/components/Input.vue @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/composables/useAuth.ts b/composables/useAuth.ts new file mode 100644 index 0000000..d2efa9d --- /dev/null +++ b/composables/useAuth.ts @@ -0,0 +1,54 @@ +export enum AuthStatus +{ + disconnected, loading, connected +}; +export interface Auth +{ + id: Ref; + data: Ref>; + token: Ref; + session_id: Ref; + status: Ref; + + lastRefresh: Ref; + + register: (username: string, email: string, password: string, data?: Record) => AuthStatus; + login: (usernameOrEmail: string, password: string) => AuthStatus; + logout: () => AuthStatus; + + refresh: () => AuthStatus; +} + +const id = useState("auth:id", () => 0); +const data = useState("auth:data", () => {}); +const token = useState("auth:token", () => ''); +const session_id = useState("auth:session_id", () => 0); +const status = useState("auth:status", () => 0); + +const lastRefresh = useState("auth:date", () => new Date()); + +function register(username: string, email: string, password: string, data?: Record): AuthStatus +{ + + return AuthStatus.disconnected; +} +function login(usernameOrEmail: string, password: string): AuthStatus +{ + return AuthStatus.disconnected; +} +function logout(): AuthStatus +{ + return AuthStatus.disconnected; +} + +function refresh(): AuthStatus +{ + return AuthStatus.disconnected; +} + +export default function useAuth(): Auth { + return { + id, data, token, session_id, status, lastRefresh, + register, login, logout, refresh + }; +} \ No newline at end of file diff --git a/composables/useDatabase.ts b/composables/useDatabase.ts new file mode 100644 index 0000000..8ab2990 --- /dev/null +++ b/composables/useDatabase.ts @@ -0,0 +1,21 @@ +import { Database } from "bun:sqlite"; + +let instance: Database | undefined; + +export default function useDatabase(): Database { + if(instance === undefined) + instance = getDatabase(); + + return instance; +} + +function getDatabase(): Database +{ + const { dbFile } = useRuntimeConfig(); + + const db = new Database(dbFile); + + db.exec("PRAGMA journal_mode = WAL;"); + + return db; +} \ No newline at end of file diff --git a/db.sqlite b/db.sqlite new file mode 100644 index 0000000..3d36073 Binary files /dev/null and b/db.sqlite differ diff --git a/middleware/auth.ts b/middleware/auth.ts new file mode 100644 index 0000000..fde17fc --- /dev/null +++ b/middleware/auth.ts @@ -0,0 +1,5 @@ +export default defineNuxtRouteMiddleware((to) => { + const meta = to.meta.auth; + + //to. +}) \ No newline at end of file diff --git a/nuxt.config.ts b/nuxt.config.ts index 39cb234..364e4ab 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -2,45 +2,17 @@ import CanvasModule from './transformer/canvas/module' export default defineNuxtConfig({ - modules: [CanvasModule, "@nuxt/content", "@nuxtjs/color-mode", '@sidebase/nuxt-auth'], - + modules: [CanvasModule, "@nuxt/content", "@nuxtjs/color-mode"], + css: ['~/assets/common.css', '~/assets/global.css'], + runtimeConfig: { + dbFile: '' + }, components: [ { path: '~/components', pathPrefix: false, }, ], - - router: { - options: { - scrollBehaviorType: 'smooth' - } - }, - - auth: { - baseURL: '/api/auth', - provider: { - type: 'local', - //type: 'refresh', - endpoints: { - signIn: { path: '/login', method: 'post' }, - signOut: { path: '/logout', method: 'post' }, - signUp: { path: '/register', method: 'post' }, - getSession: { path: '/session', method: 'get' }, - //refresh: { path: '/refresh', method: 'post' } - }, - session: { - dataType: { - id: 'string', - username: 'string', - email: 'string', - } - } - } - }, - - css: ['~/assets/common.css', '~/assets/global.css'], - content: { ignores: [ '98.Privé' @@ -78,12 +50,5 @@ export default defineNuxtConfig({ } } }, - - vite: { - vue: { - customElement: ['Line', 'Circle', 'Path'] - } - }, - compatibilityDate: '2024-07-25' }) \ No newline at end of file diff --git a/package.json b/package.json index fcffe21..696ba3e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "devDependencies": { "@nuxt/content": "^2.13.2", "@nuxtjs/color-mode": "^3.4.2", - "@sidebase/nuxt-auth": "^0.8.1", + "@types/bun": "^1.1.6", "nuxt": "^3.12.4", "vue": "^3.4.34", "vue-router": "^4.4.0" @@ -14,6 +14,7 @@ "dependencies": { "hast-util-to-html": "^9.0.1", "remark-breaks": "^4.0.0", - "remark-ofm": "link:remark-ofm" + "remark-ofm": "link:remark-ofm", + "zod": "^3.23.8" } } \ No newline at end of file diff --git a/pages/user/login.vue b/pages/user/login.vue index 8293750..42ea93b 100644 --- a/pages/user/login.vue +++ b/pages/user/login.vue @@ -1,6 +1,5 @@ + + \ No newline at end of file diff --git a/pages/user/signup.vue b/pages/user/signup.vue deleted file mode 100644 index 18c3632..0000000 --- a/pages/user/signup.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - \ No newline at end of file diff --git a/schemas/registration.ts b/schemas/registration.ts new file mode 100644 index 0000000..d4184f1 --- /dev/null +++ b/schemas/registration.ts @@ -0,0 +1,43 @@ +import { z } from "zod"; + +function securePassword(password: string, ctx: z.RefinementCtx): void { + const lowercase = password.toLowerCase(); + const uppercase = password.toUpperCase(); + + if(lowercase === password) + { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Votre mot de passe doit contenir au moins une majuscule", + }); + } + if(uppercase === password) + { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Votre mot de passe doit contenir au moins une minuscule", + }); + } + if(!/[0-9]/.test(password)) + { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Votre mot de passe doit contenir au moins un chiffre", + }); + } + if(!" !\"#$%&'()*+,-./:;<=>?@[]^_`{|}~".split("").some(e => password.includes(e))) + { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Votre mot de passe doit contenir au moins un symbole", + }); + } +} + +export const schema = z.object({ + username: z.string({ required_error: "Nom d'utilisateur obligatoire" }).min(3, "Votre nom d'utilisateur doit contenir au moins 3 caractères").max(32, "Votre nom d'utilisateur doit contenir au plus 32 caractères"), + email: z.string({ required_error: "Email obligatoire" }).email("Adresse mail invalide"), + password: z.string({ required_error: "Mot de passe obligatoire" }).min(8, "Votre mot de passe doit contenir au moins 8 caractères").max(128, "Votre mot de passe doit contenir au moins 8 caractères").superRefine(securePassword), +}); + +export type Registration = z.infer; \ No newline at end of file diff --git a/server/api/auth/login.post.ts b/server/api/auth/login.post.ts new file mode 100644 index 0000000..0e2f809 --- /dev/null +++ b/server/api/auth/login.post.ts @@ -0,0 +1,2 @@ +export default defineEventHandler(async (e) => { +}); \ No newline at end of file diff --git a/server/api/auth/logout.post.ts b/server/api/auth/logout.post.ts new file mode 100644 index 0000000..0e2f809 --- /dev/null +++ b/server/api/auth/logout.post.ts @@ -0,0 +1,2 @@ +export default defineEventHandler(async (e) => { +}); \ No newline at end of file diff --git a/server/api/auth/register.post.ts b/server/api/auth/register.post.ts new file mode 100644 index 0000000..4df29af --- /dev/null +++ b/server/api/auth/register.post.ts @@ -0,0 +1,35 @@ +import useDatabase from '~/composables/useDatabase'; +import { schema } from '~/schemas/registration'; + +export default defineEventHandler(async (e) => { + const body = await readValidatedBody(e, schema.safeParse); + + if(!body.success) + return body.error; + + const db = useDatabase(); + + const usernameQuery = db.query(`SELECT COUNT(*) as count FROM users WHERE username = ?1`); + const checkUsername = usernameQuery.get(body.data.username); + + const emailQuery = db.query(`SELECT COUNT(*) as count FROM users WHERE email = ?1`); + const checkEmail = emailQuery.get(body.data.email); + + const errors = []; + if(checkUsername.count !== 0) + errors.push({ path: ['username'], message: "Ce nom d'utilisateur est déjà utilisé" }); + if(checkEmail.count !== 0) + errors.push({ path: ['email'], message: "Cette adresse mail est déjà utilisée" }); + + if(errors.length > 0) + throw createError({ status: 406, message: "duplicates", data: errors }); + else + { + const hash = await Bun.password.hash(body.data.password); + const registration = db.query(`INSERT INTO users(username, email, hash) VALUES(?1, ?2, ?3)`); + const result = registration.get(body.data.username, body.data.email, hash); + + setResponseStatus(e, 201, "Created"); + return { success: true }; + } +}); \ No newline at end of file diff --git a/server/api/auth/session.get.ts b/server/api/auth/session.get.ts new file mode 100644 index 0000000..0e2f809 --- /dev/null +++ b/server/api/auth/session.get.ts @@ -0,0 +1,2 @@ +export default defineEventHandler(async (e) => { +}); \ No newline at end of file diff --git a/server/api/login.ts b/server/api/login.ts deleted file mode 100644 index ea120c7..0000000 --- a/server/api/login.ts +++ /dev/null @@ -1 +0,0 @@ -export default defineEventHandler(() => 'Hello World!'); \ No newline at end of file diff --git a/server/api/logout.ts b/server/api/logout.ts deleted file mode 100644 index ea120c7..0000000 --- a/server/api/logout.ts +++ /dev/null @@ -1 +0,0 @@ -export default defineEventHandler(() => 'Hello World!'); \ No newline at end of file diff --git a/server/api/register.ts b/server/api/register.ts deleted file mode 100644 index 7ba15ab..0000000 --- a/server/api/register.ts +++ /dev/null @@ -1 +0,0 @@ -export default defineEventHandler((...args) => console.log(...args)); \ No newline at end of file diff --git a/server/api/session.ts b/server/api/session.ts deleted file mode 100644 index ea120c7..0000000 --- a/server/api/session.ts +++ /dev/null @@ -1 +0,0 @@ -export default defineEventHandler(() => 'Hello World!'); \ No newline at end of file