diff --git a/app/composables/login/useLoginToken.ts b/app/composables/login/useLoginToken.ts index 661788a..140d00b 100644 --- a/app/composables/login/useLoginToken.ts +++ b/app/composables/login/useLoginToken.ts @@ -1,3 +1,6 @@ export function useLoginToken() { - return useLocalStorage("token", undefined); + return useCookie("token", { + maxAge: 60 * 60, + sameSite: true, + }); } diff --git a/app/stores/useTagStore.ts b/app/stores/useTagStore.ts index c7121af..6736894 100644 --- a/app/stores/useTagStore.ts +++ b/app/stores/useTagStore.ts @@ -7,7 +7,8 @@ export const useTagStore = defineStore("tags", { }), actions: { async fetch() { - const data = await $fetch("/api/tags", { + const fetch = useRequestFetch(); + const data = await fetch("/api/tags", { ...useFetchOptions(), }).catch(async (err) => { //todo - show in toast @@ -30,7 +31,8 @@ export const useTagStore = defineStore("tags", { async deleteTag(uuid: UUID) { this.data = this.data.filter((tag) => tag.uuid !== uuid); - await $fetch("/api/tags/" + uuid, { + const fetch = useRequestFetch(); + await fetch("/api/tags/" + uuid, { method: "DELETE", ...useFetchOptions(), }).catch(async (err) => { @@ -48,7 +50,8 @@ export const useTagStore = defineStore("tags", { this.data.unshift(tag); - await $fetch("/api/tags", { + const fetch = useRequestFetch(); + await fetch("/api/tags", { method: "POST", body: tag, ...useFetchOptions(), diff --git a/app/stores/useTodoStore.ts b/app/stores/useTodoStore.ts index 83e193e..8cbb623 100644 --- a/app/stores/useTodoStore.ts +++ b/app/stores/useTodoStore.ts @@ -8,7 +8,8 @@ export const useTodoStore = defineStore("todos", { }), actions: { async fetch() { - const data = await $fetch("/api/todos", { ...useFetchOptions() }).catch( + const fetch = useRequestFetch(); + const data = await fetch("/api/todos", { ...useFetchOptions() }).catch( async (err) => { //todo - show in toast console.warn(err); @@ -30,7 +31,8 @@ export const useTodoStore = defineStore("todos", { async removeTodo(uuid: UUID) { this.data = this.data.filter((todo) => todo.uuid !== uuid); - await $fetch("/api/todos/" + uuid, { + const fetch = useRequestFetch(); + await fetch("/api/todos/" + uuid, { method: "DELETE", ...useFetchOptions(), }).catch(async (err) => { @@ -50,7 +52,8 @@ export const useTodoStore = defineStore("todos", { this.data.push(todo); - await $fetch("/api/todos", { + const fetch = useRequestFetch(); + await fetch("/api/todos", { method: "POST", body: todo, ...useFetchOptions(), @@ -70,7 +73,8 @@ export const useTodoStore = defineStore("todos", { const index = this.data.findIndex((value) => value.uuid === uuid); this.data[index] = todo; - await $fetch("/api/todos", { + const fetch = useRequestFetch(); + await fetch("/api/todos", { method: "POST", body: todo, ...useFetchOptions(), @@ -88,7 +92,8 @@ export const useTodoStore = defineStore("todos", { const draged = this.data.splice(dragIndex, 1)[0]!; this.data.splice(dropIndex, 0, draged); - await $fetch("/api/todos/move", { + const fetch = useRequestFetch(); + await fetch("/api/todos/move", { method: "POST", body: { toMove, diff --git a/server/api/auth/validate_token/.get.ts b/server/api/auth/validate_token/.get.ts index fcffdc2..bc32f33 100644 --- a/server/api/auth/validate_token/.get.ts +++ b/server/api/auth/validate_token/.get.ts @@ -1,30 +1,14 @@ -import { getRequestHeaders } from "h3"; -import jwt from "jsonwebtoken"; import type { JwtValidation } from "#shared/types"; +import { H3Error } from "h3"; export default defineEventHandler(async (event): Promise => { - const headers = getRequestHeaders(event); - const authHeader = headers.authorization; - const token = authHeader?.startsWith("Bearer ") - ? authHeader.split(" ")[1] - : null; + const jwt = Auth.get(event); - if (!token) { - throw createError({ statusCode: 401, statusMessage: "Missing Token" }); + if (jwt instanceof H3Error) { + throw jwt; } - try { - const runtimeConfig = useRuntimeConfig(); - jwt.verify(token, runtimeConfig.jwtSecret); - - return { - success: true, - }; - } catch (err) { - console.warn(err); - - return { - success: false, - }; - } + return { + success: !!jwt, + }; }); diff --git a/server/api/tags/.get.ts b/server/api/tags/.get.ts index ca13143..28921cc 100644 --- a/server/api/tags/.get.ts +++ b/server/api/tags/.get.ts @@ -1,9 +1,6 @@ import type { Tag } from "~~/shared/types"; -import { H3Error } from "h3"; -export default defineEventHandler(async (): Promise => { - const tags = await Tags.getAll(); - - if (tags instanceof H3Error) throw tags; - return tags; +export default defineEventHandler(async (event): Promise => { + const token = Auth.getOrThrow(event); + return await Tags.getAll(token.sub); }); diff --git a/server/api/tags/.post.ts b/server/api/tags/.post.ts index 4cb7173..e63cc2a 100644 --- a/server/api/tags/.post.ts +++ b/server/api/tags/.post.ts @@ -6,9 +6,11 @@ export default defineEventHandler(async (event): Promise => { return TagSchema.parse(data) as Tag; }); - const tag = await Tags.updateOrAdd(newTag); + const token = Auth.getOrThrow(event); + + const tag = await Tags.updateOrAdd(token.sub, newTag); if (tag instanceof H3Error) throw tag; - TagEventStream.sendUpdate(); + TagEventStream.sendUpdate(token.sub); return tag; }); diff --git a/server/api/tags/[uuid].delete.ts b/server/api/tags/[uuid].delete.ts index fab39b4..d6e2836 100644 --- a/server/api/tags/[uuid].delete.ts +++ b/server/api/tags/[uuid].delete.ts @@ -11,9 +11,11 @@ export default defineEventHandler(async (event): Promise => { message: `no uuid set - uuid:'${uuid}'`, }); - const tag = await Tags.delete(uuid); + const token = Auth.getOrThrow(event); + + const tag = await Tags.delete(token.sub, uuid); if (tag instanceof H3Error) throw tag; - TagEventStream.sendUpdate(); + TagEventStream.sendUpdate(token.sub); return tag; }); diff --git a/server/api/tags/sse.ts b/server/api/tags/sse.ts index 7e46606..8c43d74 100644 --- a/server/api/tags/sse.ts +++ b/server/api/tags/sse.ts @@ -1,7 +1,8 @@ export default defineEventHandler(async (event) => { - const eventStream = createEventStream(event); + const token = Auth.getOrThrow(event); - TagEventStream.addStream(eventStream); + const eventStream = createEventStream(event); + TagEventStream.addStream(token.sub, eventStream); return eventStream.send(); }); diff --git a/server/api/todos/.get.ts b/server/api/todos/.get.ts index 850d10d..5cedcb0 100644 --- a/server/api/todos/.get.ts +++ b/server/api/todos/.get.ts @@ -1,9 +1,6 @@ import type { Todo } from "~~/shared/types"; -import { H3Error } from "h3"; -export default defineEventHandler(async (): Promise => { - const todos = await Todos.getAll(); - - if (todos instanceof H3Error) throw todos; - return todos; +export default defineEventHandler(async (event): Promise => { + const token = Auth.getOrThrow(event); + return await Todos.getAll(token.sub); }); diff --git a/server/api/todos/.post.ts b/server/api/todos/.post.ts index c4c1a2d..ea7b8d8 100644 --- a/server/api/todos/.post.ts +++ b/server/api/todos/.post.ts @@ -6,9 +6,11 @@ export default defineEventHandler(async (event): Promise => { return TodoSchema.parse(data) as Todo; }); - const todo = await Todos.updateOrAdd(newTodo); + const token = Auth.getOrThrow(event); + + const todo = await Todos.updateOrAdd(token.sub, newTodo); if (todo instanceof H3Error) throw todo; - TodoEventStream.sendUpdate(); + TodoEventStream.sendUpdate(token.sub); return todo; }); diff --git a/server/api/todos/[uuid].delete.ts b/server/api/todos/[uuid].delete.ts index 61a8260..3591b97 100644 --- a/server/api/todos/[uuid].delete.ts +++ b/server/api/todos/[uuid].delete.ts @@ -11,9 +11,11 @@ export default defineEventHandler(async (event): Promise => { message: `no uuid set - uuid:'${uuid}'`, }); - const todo = await Todos.delete(uuid); + const token = Auth.getOrThrow(event); + + const todo = await Todos.delete(token.sub, uuid); if (todo instanceof H3Error) throw todo; - TodoEventStream.sendUpdate(); + TodoEventStream.sendUpdate(token.sub); return todo; }); diff --git a/server/api/todos/move.post.ts b/server/api/todos/move.post.ts index cde7837..210afbe 100644 --- a/server/api/todos/move.post.ts +++ b/server/api/todos/move.post.ts @@ -17,8 +17,10 @@ export default defineEventHandler(async (event) => { }); }); - const todo = await Todos.move(body.toMove, body.to); + const token = Auth.getOrThrow(event); + + const todo = await Todos.move(token.sub, body.toMove, body.to); if (todo instanceof H3Error) throw todo; - TodoEventStream.sendUpdate(); + TodoEventStream.sendUpdate(token.sub); }); diff --git a/server/api/todos/sse.ts b/server/api/todos/sse.ts index 54ae61d..a589ac6 100644 --- a/server/api/todos/sse.ts +++ b/server/api/todos/sse.ts @@ -1,7 +1,8 @@ export default defineEventHandler(async (event) => { - const eventStream = createEventStream(event); + const token = Auth.getOrThrow(event); - TodoEventStream.addStream(eventStream); + const eventStream = createEventStream(event); + TodoEventStream.addStream(token.sub, eventStream); return eventStream.send(); }); diff --git a/server/plugins/database.ts b/server/plugins/database.ts deleted file mode 100644 index 1b5c1de..0000000 --- a/server/plugins/database.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TAGS_KEY } from "../utils/db/tags"; -import { TODOS_KEY } from "../utils/db/todos"; -import { getTestTags, getTestTodos } from "../utils/testData"; - -export default defineNitroPlugin(async () => { - const storage = useStorage(); - - storage.set(TODOS_KEY, getTestTodos(10)); - storage.set(TAGS_KEY, getTestTags()); -}); diff --git a/server/utils/auth.ts b/server/utils/auth.ts new file mode 100644 index 0000000..ffc9c83 --- /dev/null +++ b/server/utils/auth.ts @@ -0,0 +1,44 @@ +import { H3Error, type H3Event } from "h3"; +import jwt from "jsonwebtoken"; +import type { JwtPayload } from "~~/shared/types"; + +export const Auth = { + get(event: H3Event): undefined | JwtPayload | H3Error { + const token = getCookie(event, "token"); + + if (!token) { + return createError({ statusCode: 400, statusMessage: "No JwtToken set" }); + } + + try { + const runtimeConfig = useRuntimeConfig(); + + return jwt.verify(token, runtimeConfig.jwtSecret) as JwtPayload; + } catch { + return undefined; + } + }, + + getOrError(event: H3Event): JwtPayload | H3Error { + const token = Auth.get(event); + + if (!token) { + return createError({ + statusCode: 401, + statusMessage: "Invalid JwtToken", + }); + } + + return token; + }, + + getOrThrow(event: H3Event): JwtPayload { + const token = Auth.getOrError(event); + + if (token instanceof H3Error) { + throw token; + } + + return token; + }, +}; diff --git a/server/utils/db/tags.ts b/server/utils/db/tags.ts index b4904a7..0d48328 100644 --- a/server/utils/db/tags.ts +++ b/server/utils/db/tags.ts @@ -1,28 +1,22 @@ -import { H3Error } from "h3"; +import type { H3Error } from "h3"; import type { Tag, UUID } from "~~/shared/types"; -export const TAGS_KEY = "tags"; +function getKey(userId: string): string { + return `${userId}:tags`; +} export const Tags = { KEY: "tags", - async getAll(): Promise { + async getAll(userId: string): Promise { const storage = useStorage(); - const tags = await storage.get(TAGS_KEY); - - if (!tags) - return createError({ - status: 500, - statusMessage: "Internal Server Error", - message: `no tags found`, - }); - - return tags; + return ( + (await storage.get(getKey(userId))) ?? + (import.meta.dev ? getTestTags() : []) + ); }, - async updateOrAdd(tag: Tag): Promise { + async updateOrAdd(userId: string, tag: Tag): Promise { const storage = useStorage(); - const tags = await Tags.getAll(); - - if (tags instanceof H3Error) return tags; + const tags = await Tags.getAll(userId); const index = tags.findIndex((value) => value.uuid === tag.uuid); @@ -32,15 +26,13 @@ export const Tags = { tags[index] = tag; } - await storage.set(TAGS_KEY, tags); + await storage.set(getKey(userId), tags); return tag; }, - async delete(uuid: UUID): Promise { + async delete(userId: string, uuid: UUID): Promise { const storage = useStorage(); - const tags = await Tags.getAll(); - - if (tags instanceof H3Error) return tags; + const tags = await Tags.getAll(userId); const index = tags.findIndex((value) => value.uuid === uuid); if (index == -1) @@ -51,7 +43,7 @@ export const Tags = { }); const todo = tags.splice(index, 1)[0]!; - await storage.set(TAGS_KEY, tags); + await storage.set(getKey(userId), tags); return todo; }, diff --git a/server/utils/db/todos.ts b/server/utils/db/todos.ts index 11d7e39..7e312ef 100644 --- a/server/utils/db/todos.ts +++ b/server/utils/db/todos.ts @@ -1,28 +1,26 @@ -import { H3Error } from "h3"; +import type { H3Error } from "h3"; import type { Todo, UUID } from "~~/shared/types"; -export const TODOS_KEY = "todos"; +function getKey(userId: string): string { + return `${userId}:todos`; +} export const Todos = { - async getAll(): Promise { + async getAll(userId: string): Promise { const storage = useStorage(); - const todos = await storage.get(TODOS_KEY); - - if (!todos) - return createError({ - status: 500, - statusMessage: "Internal Server Error", - message: `no todos found`, - }); - - return todos; + return ( + (await storage.get(getKey(userId))) ?? + (import.meta.dev ? getTestTodos(10) : []) + ); }, - async move(toMove: UUID, to: UUID): Promise { + async move( + userId: string, + toMove: UUID, + to: UUID, + ): Promise { const storage = useStorage(); - const todos = await Todos.getAll(); - - if (todos instanceof H3Error) return todos; + const todos = await Todos.getAll(userId); const targetIndex = todos.findIndex((todo) => todo.uuid == to); const movedIndex = todos.findIndex((todo) => todo.uuid == toMove); @@ -37,14 +35,12 @@ export const Todos = { const moved = todos.splice(movedIndex, 1)[0]!; todos.splice(targetIndex, 0, moved); - await storage.set(TODOS_KEY, todos); + await storage.set(getKey(userId), todos); }, - async updateOrAdd(todo: Todo): Promise { + async updateOrAdd(userId: string, todo: Todo): Promise { const storage = useStorage(); - const todos = await Todos.getAll(); - - if (todos instanceof H3Error) return todos; + const todos = await Todos.getAll(userId); const index = todos.findIndex((value) => value.uuid === todo.uuid); @@ -54,16 +50,14 @@ export const Todos = { todos[index] = todo; } - await storage.set(TODOS_KEY, todos); + await storage.set(getKey(userId), todos); return todo; }, - async delete(uuid: UUID): Promise { + async delete(userId: string, uuid: UUID): Promise { const storage = useStorage(); - const todos = await Todos.getAll(); - - if (todos instanceof H3Error) return todos; + const todos = await Todos.getAll(userId); const index = todos.findIndex((value) => value.uuid === uuid); if (index == -1) @@ -74,7 +68,7 @@ export const Todos = { }); const todo = todos.splice(index, 1)[0]!; - await storage.set(TODOS_KEY, todos); + await storage.set(getKey(userId), todos); return todo; }, diff --git a/server/utils/sse.ts b/server/utils/sse.ts index 49961ef..d97f1c4 100644 --- a/server/utils/sse.ts +++ b/server/utils/sse.ts @@ -1,39 +1,54 @@ import type { EventStream } from "h3"; export const TodoEventStream = { - addStream(eventStream: EventStream) { - addEventStream(todosEventStreams, eventStream); + addStream(userId: string, eventStream: EventStream) { + addEventStream(userId, todosEventStreams, eventStream); }, - async sendUpdate() { - await publish(todosEventStreams, await Todos.getAll()); + async sendUpdate(userId: string) { + await publish(userId, todosEventStreams, await Todos.getAll(userId)); }, }; export const TagEventStream = { - addStream(eventStream: EventStream) { - addEventStream(tagsEventStreams, eventStream); + addStream(userId: string, eventStream: EventStream) { + addEventStream(userId, tagsEventStreams, eventStream); }, - async sendUpdate() { - await publish(tagsEventStreams, await Tags.getAll()); + async sendUpdate(userId: string) { + await publish(userId, tagsEventStreams, await Tags.getAll(userId)); }, }; -const todosEventStreams: EventStream[] = []; -const tagsEventStreams: EventStream[] = []; +const todosEventStreams: Map = new Map(); +const tagsEventStreams: Map = new Map(); -function addEventStream(eventStreams: EventStream[], eventStream: EventStream) { - eventStreams.push(eventStream); +function addEventStream( + key: string, + eventStreams: Map, + eventStream: EventStream, +) { + if (!eventStreams.has(key)) { + eventStreams.set(key, []); + } + const eventStreamList = eventStreams.get(key)!; + + eventStreamList.push(eventStream); eventStream.onClosed(async () => { - eventStreams.splice(eventStreams.indexOf(eventStream), 1); + eventStreamList.splice(eventStreamList.indexOf(eventStream), 1); await eventStream.close(); }); } -async function publish(eventStreams: EventStream[], data: object) { +async function publish( + key: string, + eventStreams: Map, + data: object, +) { const json = JSON.stringify(data); - eventStreams.forEach((eventStream) => { + const eventStreamList = eventStreams.get(key) ?? []; + + eventStreamList.forEach((eventStream) => { eventStream.push(json); }); }