Skip to content

Commit

Permalink
write delete endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
mcndt committed Nov 21, 2022
1 parent de5395b commit e6dc2d8
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 57 deletions.
2 changes: 1 addition & 1 deletion plugin
73 changes: 73 additions & 0 deletions server/src/controllers/note/note.delete.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { validateOrReject, ValidationError } from "class-validator";
import { NextFunction, Request, Response } from "express";
import { deleteNote, getNote } from "../../db/note.dao";
import checkId from "../../lib/checkUserId";
import EventLogger, { WriteEvent } from "../../logging/EventLogger";
import { getConnectingIp, getNoteSize } from "../../util";
import { NoteDeleteRequest } from "../../validation/Request";

export async function deleteNoteController(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
const event: WriteEvent = {
success: false,
host: getConnectingIp(req),
user_id: req.body.user_id,
user_plugin_version: req.body.plugin_version,
};

// Validate request body
const noteDeleteRequest = new NoteDeleteRequest();
Object.assign(noteDeleteRequest, req.body);
try {
await validateOrReject(noteDeleteRequest);
} catch (_err: any) {
const err = _err as ValidationError;
res.status(400).send(err.toString());
event.error = err.toString();
await EventLogger.deleteEvent(event);
return;
}

// Validate user ID, if present
if (noteDeleteRequest.user_id && !checkId(noteDeleteRequest.user_id)) {
console.log("invalid user id");
res.status(400).send("Invalid user id (checksum failed)");
event.error = "Invalid user id (checksum failed)";
EventLogger.writeEvent(event);
return;
}

// get note from db
const note = await getNote(req.params.id);
if (!note) {
res.status(404).send("Note not found");
event.error = "Note not found";
await EventLogger.deleteEvent(event);
return;
}

// Validate secret token
if (note.secret_token !== req.body.secret_token) {
res.status(401).send("Invalid token");
event.error = "Invalid secret token";
await EventLogger.deleteEvent(event);
return;
}

// Delete note
try {
await deleteNote(note.id);
res.status(200);
event.success = true;
event.note_id = note.id;
event.size_bytes = getNoteSize(note);
await EventLogger.deleteEvent(event);
} catch (err) {
event.error = (err as Error).toString();
await EventLogger.deleteEvent(event);
next(err);
}
}
Empty file.
57 changes: 3 additions & 54 deletions server/src/controllers/note/note.post.controller.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,16 @@
import { EncryptedNote } from "@prisma/client";
import { NextFunction, Request, Response } from "express";
import { crc16 as crc } from "crc";
import { createNote } from "../../db/note.dao";
import { addDays, getConnectingIp, getNoteSize } from "../../util";
import EventLogger, { WriteEvent } from "../../logging/EventLogger";
import {
validateOrReject,
IsBase64,
IsHexadecimal,
IsNotEmpty,
ValidateIf,
ValidationError,
Matches,
} from "class-validator";
import { validateOrReject, ValidationError } from "class-validator";
import { generateToken } from "../../crypto/GenerateToken";
import { NotePostRequest } from "../../validation/Request";
import checkId from "../../lib/checkUserId";

/**
* Request body for creating a note
*/
export class NotePostRequest {
@IsBase64()
@IsNotEmpty()
ciphertext: string | undefined;

@IsBase64()
@ValidateIf((o) => !o.iv)
hmac?: string | undefined;

@IsBase64()
@ValidateIf((o) => !o.hmac)
iv?: string | undefined;

@ValidateIf((o) => o.user_id != null)
@IsHexadecimal()
user_id: string | undefined;

@ValidateIf((o) => o.plugin_version != null)
@Matches("^[0-9]+\\.[0-9]+\\.[0-9]+$")
plugin_version: string | undefined;

@Matches("^v[0-9]+$")
crypto_version: string = "v1";
}

export async function postNoteController(
req: Request,
Expand Down Expand Up @@ -110,23 +79,3 @@ export async function postNoteController(
next(err);
});
}

/**
* @param id {string} a 16 character base16 string with 12 random characters and 4 CRC characters
* @returns {boolean} true if the id is valid, false otherwise
*/
function checkId(id: string): boolean {
// check length
if (id.length !== 16) {
return false;
}
// extract the random number and the checksum
const random = id.slice(0, 12);
const checksum = id.slice(12, 16);

// compute the CRC of the random number
const computedChecksum = crc(random).toString(16).padStart(4, "0");

// compare the computed checksum with the one in the id
return computedChecksum === checksum;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import supertest from "supertest";
import { vi, describe, it, beforeEach, afterEach, expect } from "vitest";
import * as noteDao from "../../db/note.dao";
import EventLogger from "../../logging/EventLogger";
import { NotePostRequest, postNoteController } from "./note.post.controller";
import { NotePostRequest } from "../../validation/Request";
import { postNoteController } from "./note.post.controller";

vi.mock("../../db/note.dao");
vi.mock("../../logging/EventLogger");
Expand Down
2 changes: 2 additions & 0 deletions server/src/controllers/note/note.router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import express from "express";
import rateLimit from "express-rate-limit";
import { deleteNoteController } from "./note.delete.controller";
import { getNoteController } from "./note.get.controller";
import { postNoteController } from "./note.post.controller";

Expand All @@ -25,3 +26,4 @@ const getRateLimit = rateLimit({
notesRoute.use(jsonParser);
notesRoute.post("", postRateLimit, postNoteController);
notesRoute.get("/:id", getRateLimit, getNoteController);
notesRoute.delete("/:id", getRateLimit, deleteNoteController);
6 changes: 6 additions & 0 deletions server/src/db/note.dao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export async function getExpiredNotes(): Promise<EncryptedNote[]> {
});
}

export async function deleteNote(noteId: string): Promise<EncryptedNote> {
return prisma.encryptedNote.delete({
where: { id: noteId },
});
}

export async function deleteNotes(noteIds: string[]): Promise<number> {
return prisma.encryptedNote
.deleteMany({
Expand Down
21 changes: 21 additions & 0 deletions server/src/lib/checkUserId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { crc16 as crc } from "crc";

/**
* @param id {string} a 16 character base16 string with 12 random characters and 4 CRC characters
* @returns {boolean} true if the id is valid, false otherwise
*/
export default function checkId(id: string): boolean {
// check length
if (id.length !== 16) {
return false;
}
// extract the random number and the checksum
const random = id.slice(0, 12);
const checksum = id.slice(12, 16);

// compute the CRC of the random number
const computedChecksum = crc(random).toString(16).padStart(4, "0");

// compare the computed checksum with the one in the id
return computedChecksum === checksum;
}
22 changes: 21 additions & 1 deletion server/src/logging/EventLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import logger from "./logger";
export enum EventType {
WRITE = "WRITE",
READ = "READ",
DELETE = "DELETE",
UPDATE = "UPDATE",
PURGE = "PURGE",
}

interface Event {
export interface Event {
success: boolean;
error?: string;
}
Expand All @@ -26,6 +28,10 @@ export interface WriteEvent extends ClientEvent {
expire_window_days?: number;
}

interface DeleteEvent extends ClientEvent {}

interface UpdateEvent extends ClientEvent {}

interface ReadEvent extends ClientEvent {}

interface PurgeEvent extends Event {
Expand Down Expand Up @@ -54,6 +60,20 @@ export default class EventLogger {
});
}

public static deleteEvent(event: DeleteEvent): Promise<event> {
this.printError(event);
return prisma.event.create({
data: { type: EventType.DELETE, ...event },
});
}

public static updateEvent(event: UpdateEvent): Promise<event> {
this.printError(event);
return prisma.event.create({
data: { type: EventType.UPDATE, ...event },
});
}

public static purgeEvent(event: PurgeEvent): Promise<event> {
this.printError(event);
return prisma.event.create({
Expand Down
40 changes: 40 additions & 0 deletions server/src/validation/Request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
IsBase64,
IsHexadecimal,
IsNotEmpty,
Matches,
ValidateIf,
} from "class-validator";

abstract class NoteRequestBody {
@ValidateIf((o) => o.user_id != null)
@IsHexadecimal()
user_id: string | undefined;

@ValidateIf((o) => o.plugin_version != null)
@Matches("^[0-9]+\\.[0-9]+\\.[0-9]+$")
plugin_version: string | undefined;
}

export class NotePostRequest extends NoteRequestBody {
@IsBase64()
@IsNotEmpty()
ciphertext: string | undefined;

@IsBase64()
@ValidateIf((o) => !o.iv)
hmac?: string | undefined;

@IsBase64()
@ValidateIf((o) => !o.hmac)
iv?: string | undefined;

@Matches("^v[0-9]+$")
crypto_version: string = "v1";
}

export class NoteDeleteRequest extends NoteRequestBody {
@IsBase64()
@IsNotEmpty()
secret_token: string | undefined;
}

0 comments on commit e6dc2d8

Please sign in to comment.