diff --git a/backend/strapi/src/api/note/controllers/note.js b/backend/strapi/src/api/note/controllers/note.js index 9cf8879..84e7a8d 100644 --- a/backend/strapi/src/api/note/controllers/note.js +++ b/backend/strapi/src/api/note/controllers/note.js @@ -50,6 +50,9 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({ lastViewed: Date.now() } }) + entry = await strapi.entityService.findOne(noteUid, noteId, { + populate: ['owners'], + }); return JSON.stringify(entry); } else { ctx.response.status = 403; @@ -63,22 +66,26 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({ async update(ctx) { const noteId = getNoteIdFromUrl(ctx.request.url) const userId = ctx.state.user.id; - const requestBody = ctx.request.body; + const requestBody = JSON.parse(ctx.request.body); + console.log(JSON.stringify(requestBody, null, 2)) const entry = await strapi.entityService.findOne(noteUid, noteId, { populate: ['owners'], }); const authorized = entry.owners.some(owner => owner.id === userId) - let allPreviousOwnersKept = false; + let allPreviousOwnersKept = true; if (requestBody.data.hasOwnProperty("owners")) { allPreviousOwnersKept = entry.owners.every(owner => requestBody.data.owners.includes(owner)); } + console.log({ + "auth": authorized, + "allprev": allPreviousOwnersKept, + }) if (!authorized) { ctx.response.status = 403; } else if (!allPreviousOwnersKept) { ctx.response.status = 400; - } else { - return super.update(ctx); } + return await strapi.entityService.update(noteUid, noteId, requestBody); }, /** * Creates a new note, automatically sets owners to the user making the request and lastViewed diff --git a/backend/strapi/src/extensions/users-permissions/content-types/user/schema.json b/backend/strapi/src/extensions/users-permissions/content-types/user/schema.json new file mode 100644 index 0000000..7413352 --- /dev/null +++ b/backend/strapi/src/extensions/users-permissions/content-types/user/schema.json @@ -0,0 +1,73 @@ +{ + "kind": "collectionType", + "collectionName": "up_users", + "info": { + "name": "user", + "description": "", + "singularName": "user", + "pluralName": "users", + "displayName": "User" + }, + "options": { + "draftAndPublish": false, + "timestamps": true + }, + "attributes": { + "username": { + "type": "string", + "minLength": 3, + "unique": true, + "configurable": false, + "required": true + }, + "email": { + "type": "email", + "minLength": 6, + "configurable": false, + "required": true + }, + "provider": { + "type": "string", + "configurable": false + }, + "password": { + "type": "password", + "minLength": 6, + "configurable": false, + "private": true + }, + "resetPasswordToken": { + "type": "string", + "configurable": false, + "private": true + }, + "confirmationToken": { + "type": "string", + "configurable": false, + "private": true + }, + "confirmed": { + "type": "boolean", + "default": false, + "configurable": false + }, + "blocked": { + "type": "boolean", + "default": false, + "configurable": false + }, + "role": { + "type": "relation", + "relation": "manyToOne", + "target": "plugin::users-permissions.role", + "inversedBy": "users", + "configurable": false + }, + "notes": { + "type": "relation", + "relation": "manyToMany", + "target": "api::note.note", + "inversedBy": "owners" + } + } +} diff --git a/frontend/svelte/src/models/PomeloUtils.ts b/frontend/svelte/src/models/PomeloUtils.ts index 5d290ae..cfec11b 100644 --- a/frontend/svelte/src/models/PomeloUtils.ts +++ b/frontend/svelte/src/models/PomeloUtils.ts @@ -1,4 +1,5 @@ -import {parseCookies} from "nookies"; +import type {Authentication} from "./authentication"; +import {createErrorToast} from "./customToasts"; /** * Capitalises first letter of string. @@ -22,13 +23,14 @@ export async function bearerFetch(endpoint: string, jwt: string, baseUrl: string }); } - -const getJwtCookie = () => { - // @ts-ignore - return parseCookies("/").jwt; -}; - -/** - * JWT Cookie - */ -export const jwt: string = getJwtCookie(); \ No newline at end of file +export function handleErrorsFromResponseWithToast(response: Authentication) { + if (response.error != null) { + if (response.error.details.errors) { + for (const error of response.error.details.errors) { + createErrorToast(error.message); + } + } else { + createErrorToast(response.error.message); + } + } +} \ No newline at end of file diff --git a/frontend/svelte/src/routes/login/models/authentication.ts b/frontend/svelte/src/models/authentication.ts similarity index 70% rename from frontend/svelte/src/routes/login/models/authentication.ts rename to frontend/svelte/src/models/authentication.ts index dccb434..c5adc10 100644 --- a/frontend/svelte/src/routes/login/models/authentication.ts +++ b/frontend/svelte/src/models/authentication.ts @@ -1,4 +1,4 @@ -import type {User} from "../../../models/user"; +import type {User} from "./user"; /** * User Login Auth. diff --git a/frontend/svelte/src/models/NoteRepository.ts b/frontend/svelte/src/models/repos/note/NoteRepository.ts similarity index 89% rename from frontend/svelte/src/models/NoteRepository.ts rename to frontend/svelte/src/models/repos/note/NoteRepository.ts index c83d5a6..84b6705 100644 --- a/frontend/svelte/src/models/NoteRepository.ts +++ b/frontend/svelte/src/models/repos/note/NoteRepository.ts @@ -1,4 +1,4 @@ -import type {Note} from "./types"; +import type {Note} from "../../types"; export interface NoteRepository { getNotes(): Promise; diff --git a/frontend/svelte/src/models/StrapiNoteRepository.ts b/frontend/svelte/src/models/repos/note/StrapiNoteRepository.ts similarity index 82% rename from frontend/svelte/src/models/StrapiNoteRepository.ts rename to frontend/svelte/src/models/repos/note/StrapiNoteRepository.ts index ab4a68f..47370e9 100644 --- a/frontend/svelte/src/models/StrapiNoteRepository.ts +++ b/frontend/svelte/src/models/repos/note/StrapiNoteRepository.ts @@ -1,6 +1,8 @@ -import type {Note} from "./types"; +import type {Note} from "../../types"; import {parseCookies} from "nookies"; import type {NoteRepository} from "./NoteRepository"; +import {currentNoteId} from "../../../stores"; + type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' @@ -13,13 +15,19 @@ export class StrapiNoteRepository implements NoteRepository { return this.instance; } - private constructor() {} + private constructor() { + currentNoteId.subscribe((value) => (this._currentNoteId = value)); + } - private _currentNoteId: number | undefined; + private _currentNoteId: unknown; private static apiNoteEndpoint: string = "http://localhost:1337/api/notes" public set currentNoteId(value: number | undefined) { - this._currentNoteId = value; + currentNoteId.set(value || -1); + } + + public get currentNoteId(): number { + return this._currentNoteId; } public async getNotes(): Promise{ @@ -36,7 +44,7 @@ export class StrapiNoteRepository implements NoteRepository { if (this._currentNoteId === null || this._currentNoteId === undefined) { return; } - return await this.getNote(this._currentNoteId); + return await this.getNote(this.currentNoteId); } public async updateNote(id: number, note: Partial): Promise { @@ -70,8 +78,8 @@ export class StrapiNoteRepository implements NoteRepository { return "bearer TOKEN" } - private static getAuthorizationHeader() { - const jwt = parseCookies().jwt; + static getAuthorizationHeader() { + const jwt = parseCookies('/').jwt; return `bearer ${jwt}` } } \ No newline at end of file diff --git a/frontend/svelte/src/models/repos/user/StrapiUserRepo.ts b/frontend/svelte/src/models/repos/user/StrapiUserRepo.ts new file mode 100644 index 0000000..58f2ea2 --- /dev/null +++ b/frontend/svelte/src/models/repos/user/StrapiUserRepo.ts @@ -0,0 +1,93 @@ +import type {UserRepository} from "./UserRepository"; +import type {Authentication} from "../../authentication"; +import type {HttpMethod} from "@sveltejs/kit/types/private"; +import {StrapiNoteRepository} from "../note/StrapiNoteRepository"; +import {error} from "@sveltejs/kit"; +import {User} from "../../user"; + +export class StrapiUserRepo implements UserRepository { + private static instance: StrapiUserRepo; + + public static getInstance(verification: boolean = true): StrapiUserRepo { + if (this.instance === undefined || this.instance === null) { + this.instance = new StrapiUserRepo(); + this.instance.verify().then(() => { + if (verification && !this.instance.verified) { + window.location.href = "/login"; + } + }); + } + return this.instance; + } + + private verified: boolean = false; + + private constructor() { + } + + private static api: string = "http://localhost:1337/api" + + private static apiUserEndpoint: string = StrapiUserRepo.api + "/auth/local" + + /** + * Verifies the current users jwt. + * @private + */ + private async verify() { + this.verified = false; + let result = await this.getMe(); + if (!result.error) { + this.verified = true; + } + } + + async getMe(): Promise { + const response = await StrapiUserRepo.fetchStrapi("/me", "GET", null, true, "/users") + return await response.json(); + } + + async registerUser(email: string, username: string, password: string): Promise { + const payload = { + email: email, + password: password, + username: username + }; + const response = await StrapiUserRepo.fetchStrapi("/register", "POST", payload, false); + return await response.json(); + } + + async loginUser(identifier: string, password: string): Promise { + const payload = { + identifier: identifier, + password: password + }; + const response = await StrapiUserRepo.fetchStrapi("/", "POST", payload, false); + return response.json(); + } + + private static async fetchStrapi(path: string, method: HttpMethod, body: any | null = null, authorization: boolean = true, customPath: any = null): Promise { + let requestInit: RequestInit = { + method: method, + }; + if (authorization && body) { + requestInit["headers"] = { + authorization: StrapiNoteRepository.getAuthorizationHeader() ?? '', + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + } else if (authorization) { + requestInit["headers"] = { + authorization: StrapiNoteRepository.getAuthorizationHeader() ?? '', + } + } else if (body) { + requestInit["headers"] = { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + } + if (body) { + requestInit["body"] = JSON.stringify(body) + } + return await fetch((customPath) ? (this.api + customPath + path) : StrapiUserRepo.apiUserEndpoint + path, requestInit); + } +} \ No newline at end of file diff --git a/frontend/svelte/src/models/repos/user/UserRepository.ts b/frontend/svelte/src/models/repos/user/UserRepository.ts new file mode 100644 index 0000000..3128bfb --- /dev/null +++ b/frontend/svelte/src/models/repos/user/UserRepository.ts @@ -0,0 +1,19 @@ +import type {Authentication} from "../../authentication"; + +export interface UserRepository { + /** + * Registers a new user. + * @param email + * @param username + * @param password + */ + registerUser(email: string, username: string, password: string): Promise; + + /** + * Gets the current user. + * @param jwt + */ + getMe(jwt: string): Promise; + + loginUser(identifier: string, password: string): Promise; +} \ No newline at end of file diff --git a/frontend/svelte/src/routes/+page.svelte b/frontend/svelte/src/routes/+page.svelte index 5d6c81e..e744ac0 100644 --- a/frontend/svelte/src/routes/+page.svelte +++ b/frontend/svelte/src/routes/+page.svelte @@ -1,7 +1,8 @@ - {"Pomelonote | Edit " + currNote.title} + Editor -
- {currNote.content} +
+

{title}

+
+ +
+ + +
+ + diff --git a/frontend/svelte/src/routes/login/+page.svelte b/frontend/svelte/src/routes/login/+page.svelte index af9b27e..537507d 100644 --- a/frontend/svelte/src/routes/login/+page.svelte +++ b/frontend/svelte/src/routes/login/+page.svelte @@ -1,9 +1,9 @@