chore(types): adapt to new express type definitions

This commit is contained in:
Elian Doran 2024-12-10 22:35:23 +02:00
parent 6f371915bf
commit 5190b28d3c
No known key found for this signature in database
13 changed files with 66 additions and 64 deletions

View File

@ -5,8 +5,8 @@ import becca from "../becca/becca.js";
import etapiTokenService from "../services/etapi_tokens.js"; import etapiTokenService from "../services/etapi_tokens.js";
import config from "../services/config.js"; import config from "../services/config.js";
import { NextFunction, Request, RequestHandler, Response, Router } from 'express'; import { NextFunction, Request, RequestHandler, Response, Router } from 'express';
import { AppRequest, AppRequestHandler } from '../routes/route-interface.js';
import { ValidatorMap } from './etapi-interface.js'; import { ValidatorMap } from './etapi-interface.js';
import { ApiRequestHandler } from "../routes/routes.js";
const GENERIC_CODE = "GENERIC"; const GENERIC_CODE = "GENERIC";
type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head"; type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head";
@ -48,7 +48,7 @@ function checkEtapiAuth(req: Request, res: Response, next: NextFunction) {
} }
} }
function processRequest(req: Request, res: Response, routeHandler: AppRequestHandler, next: NextFunction, method: string, path: string) { function processRequest(req: Request, res: Response, routeHandler: ApiRequestHandler, next: NextFunction, method: string, path: string) {
try { try {
cls.namespace.bindEmitter(req); cls.namespace.bindEmitter(req);
cls.namespace.bindEmitter(res); cls.namespace.bindEmitter(res);
@ -57,7 +57,7 @@ function processRequest(req: Request, res: Response, routeHandler: AppRequestHan
cls.set('componentId', "etapi"); cls.set('componentId', "etapi");
cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']); cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']);
const cb = () => routeHandler(req as AppRequest, res, next); const cb = () => routeHandler(req, res, next);
return sql.transactional(cb); return sql.transactional(cb);
}); });
@ -72,7 +72,7 @@ function processRequest(req: Request, res: Response, routeHandler: AppRequestHan
} }
} }
function route(router: Router, method: HttpMethod, path: string, routeHandler: AppRequestHandler) { function route(router: Router, method: HttpMethod, path: string, routeHandler: ApiRequestHandler) {
router[method](path, checkEtapiAuth, (req: Request, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path)); router[method](path, checkEtapiAuth, (req: Request, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path));
} }

View File

@ -9,8 +9,7 @@ import searchService from "../services/search/services/search.js";
import SearchContext from "../services/search/search_context.js"; import SearchContext from "../services/search/search_context.js";
import zipExportService from "../services/export/zip.js"; import zipExportService from "../services/export/zip.js";
import zipImportService from "../services/import/zip.js"; import zipImportService from "../services/import/zip.js";
import { Router } from 'express'; import { Request, Router } from 'express';
import { AppRequest } from '../routes/route-interface.js';
import { ParsedQs } from 'qs'; import { ParsedQs } from 'qs';
import { NoteParams } from '../services/note-interface.js'; import { NoteParams } from '../services/note-interface.js';
import { SearchParams } from '../services/search/services/types.js'; import { SearchParams } from '../services/search/services/types.js';
@ -192,7 +191,7 @@ function register(router: Router) {
}); });
} }
function parseSearchParams(req: AppRequest) { function parseSearchParams(req: Request) {
const rawSearchParams: SearchParams = { const rawSearchParams: SearchParams = {
fastSearch: parseBoolean(req.query, 'fastSearch'), fastSearch: parseBoolean(req.query, 'fastSearch'),
includeArchivedNotes: parseBoolean(req.query, 'includeArchivedNotes'), includeArchivedNotes: parseBoolean(req.query, 'includeArchivedNotes'),

21
src/express.d.ts vendored Normal file
View File

@ -0,0 +1,21 @@
import { Session } from "express-session";
export declare module "express-serve-static-core" {
interface Request {
session: Session & {
loggedIn: boolean;
},
headers: {
"x-local-date"?: string;
"x-labels"?: string;
"authorization"?: string;
"trilium-cred"?: string;
"x-csrf-token"?: string;
"trilium-component-id"?: string;
"trilium-local-now-datetime"?: string;
"trilium-hoisted-note-id"?: string;
}
}
}

View File

@ -14,9 +14,8 @@ import ValidationError from "../../errors/validation_error.js";
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import BNote from "../../becca/entities/bnote.js"; import BNote from "../../becca/entities/bnote.js";
import BAttachment from "../../becca/entities/battachment.js"; import BAttachment from "../../becca/entities/battachment.js";
import { AppRequest } from '../route-interface.js';
function updateFile(req: AppRequest) { function updateFile(req: Request) {
const note = becca.getNoteOrThrow(req.params.noteId); const note = becca.getNoteOrThrow(req.params.noteId);
const file = req.file; const file = req.file;
@ -43,7 +42,7 @@ function updateFile(req: AppRequest) {
}; };
} }
function updateAttachment(req: AppRequest) { function updateAttachment(req: Request) {
const attachment = becca.getAttachmentOrThrow(req.params.attachmentId); const attachment = becca.getAttachmentOrThrow(req.params.attachmentId);
const file = req.file; const file = req.file;
if (!file) { if (!file) {

View File

@ -6,7 +6,6 @@ import fs from "fs";
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import BNote from "../../becca/entities/bnote.js"; import BNote from "../../becca/entities/bnote.js";
import BRevision from "../../becca/entities/brevision.js"; import BRevision from "../../becca/entities/brevision.js";
import { AppRequest } from '../route-interface.js';
import { RESOURCE_DIR } from "../../services/resource_dir.js"; import { RESOURCE_DIR } from "../../services/resource_dir.js";
function returnImageFromNote(req: Request, res: Response) { function returnImageFromNote(req: Request, res: Response) {
@ -82,7 +81,7 @@ function returnAttachedImage(req: Request, res: Response) {
res.send(attachment.getContent()); res.send(attachment.getContent());
} }
function updateImage(req: AppRequest) { function updateImage(req: Request) {
const {noteId} = req.params; const {noteId} = req.params;
const {file} = req; const {file} = req;

View File

@ -13,9 +13,8 @@ import TaskContext from "../../services/task_context.js";
import ValidationError from "../../errors/validation_error.js"; import ValidationError from "../../errors/validation_error.js";
import { Request } from 'express'; import { Request } from 'express';
import BNote from "../../becca/entities/bnote.js"; import BNote from "../../becca/entities/bnote.js";
import { AppRequest } from '../route-interface.js';
async function importNotesToBranch(req: AppRequest) { async function importNotesToBranch(req: Request) {
const { parentNoteId } = req.params; const { parentNoteId } = req.params;
const { taskId, last } = req.body; const { taskId, last } = req.body;
@ -97,7 +96,7 @@ async function importNotesToBranch(req: AppRequest) {
return note.getPojo(); return note.getPojo();
} }
async function importAttachmentsToNote(req: AppRequest) { async function importAttachmentsToNote(req: Request) {
const { parentNoteId } = req.params; const { parentNoteId } = req.params;
const { taskId, last } = req.body; const { taskId, last } = req.body;

View File

@ -13,9 +13,8 @@ import sql from "../../services/sql.js";
import ws from "../../services/ws.js"; import ws from "../../services/ws.js";
import etapiTokenService from "../../services/etapi_tokens.js"; import etapiTokenService from "../../services/etapi_tokens.js";
import { Request } from 'express'; import { Request } from 'express';
import { AppRequest } from '../route-interface.js';
function loginSync(req: AppRequest) { function loginSync(req: Request) {
if (!sqlInit.schemaExists()) { if (!sqlInit.schemaExists()) {
return [500, { message: "DB schema does not exist, can't sync." }]; return [500, { message: "DB schema does not exist, can't sync." }];
} }

View File

@ -6,9 +6,8 @@ import noteService from "../../services/notes.js";
import sanitize_attribute_name from "../../services/sanitize_attribute_name.js"; import sanitize_attribute_name from "../../services/sanitize_attribute_name.js";
import specialNotesService from "../../services/special_notes.js"; import specialNotesService from "../../services/special_notes.js";
import { Request } from 'express'; import { Request } from 'express';
import { AppRequest } from '../route-interface.js';
function uploadImage(req: AppRequest) { function uploadImage(req: Request) {
const file = req.file; const file = req.file;
if (!file) { if (!file) {

View File

@ -9,7 +9,6 @@ import assetPath from "../services/asset_path.js";
import appPath from "../services/app_path.js"; import appPath from "../services/app_path.js";
import ValidationError from "../errors/validation_error.js"; import ValidationError from "../errors/validation_error.js";
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { AppRequest } from './route-interface.js';
function loginPage(req: Request, res: Response) { function loginPage(req: Request, res: Response) {
res.render('login', { res.render('login', {
@ -57,7 +56,7 @@ function setPassword(req: Request, res: Response) {
res.redirect('login'); res.redirect('login');
} }
function login(req: AppRequest, res: Response) { function login(req: Request, res: Response) {
const guessedPassword = req.body.password; const guessedPassword = req.body.password;
if (verifyPassword(guessedPassword)) { if (verifyPassword(guessedPassword)) {
@ -93,7 +92,7 @@ function verifyPassword(guessedPassword: string) {
return guess_hashed.equals(hashed_password); return guess_hashed.equals(hashed_password);
} }
function logout(req: AppRequest, res: Response) { function logout(req: Request, res: Response) {
req.session.regenerate(() => { req.session.regenerate(() => {
req.session.loggedIn = false; req.session.loggedIn = false;

View File

@ -1,21 +0,0 @@
import { NextFunction, Request, Response } from "express";
import { Session, SessionData } from "express-session";
export interface AppRequest extends Request {
headers: {
authorization?: string;
"trilium-cred"?: string;
"x-local-date"?: string;
"x-labels"?: string;
"trilium-local-now-datetime"?: string;
}
session: Session & Partial<SessionData> & {
loggedIn: boolean;
}
}
export type AppRequestHandler = (
req: AppRequest,
res: Response,
next: NextFunction
) => void;

View File

@ -70,7 +70,6 @@ import etapiNoteRoutes from "../etapi/notes.js";
import etapiSpecialNoteRoutes from "../etapi/special_notes.js"; import etapiSpecialNoteRoutes from "../etapi/special_notes.js";
import etapiSpecRoute from "../etapi/spec.js"; import etapiSpecRoute from "../etapi/spec.js";
import etapiBackupRoute from "../etapi/backup.js"; import etapiBackupRoute from "../etapi/backup.js";
import { AppRequest, AppRequestHandler } from './route-interface.js';
const csrfMiddleware = csurf({ const csrfMiddleware = csurf({
cookie: { cookie: {
@ -81,7 +80,8 @@ const csrfMiddleware = csurf({
const MAX_ALLOWED_FILE_SIZE_MB = 250; const MAX_ALLOWED_FILE_SIZE_MB = 250;
const GET = 'get', PST = 'post', PUT = 'put', PATCH = 'patch', DEL = 'delete'; const GET = 'get', PST = 'post', PUT = 'put', PATCH = 'patch', DEL = 'delete';
type ApiResultHandler = (req: express.Request, res: express.Response, result: unknown) => number; export type ApiResultHandler = (req: express.Request, res: express.Response, result: unknown) => number;
export type ApiRequestHandler = (req: express.Request, res: express.Response, next: express.NextFunction) => unknown;
// TODO: Deduplicate with etapi_utils.ts afterwards. // TODO: Deduplicate with etapi_utils.ts afterwards.
type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head"; type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head";
@ -438,11 +438,11 @@ function send(res: express.Response, statusCode: number, response: unknown) {
} }
} }
function apiRoute(method: HttpMethod, path: string, routeHandler: express.Handler) { function apiRoute(method: HttpMethod, path: string, routeHandler: ApiRequestHandler) {
route(method, path, [auth.checkApiAuth, csrfMiddleware], routeHandler, apiResultHandler); route(method, path, [auth.checkApiAuth, csrfMiddleware], routeHandler, apiResultHandler);
} }
function route(method: HttpMethod, path: string, middleware: (express.Handler | AppRequestHandler)[], routeHandler: AppRequestHandler, resultHandler: ApiResultHandler | null = null, transactional = true) { function route(method: HttpMethod, path: string, middleware: express.Handler[], routeHandler: ApiRequestHandler, resultHandler: ApiResultHandler | null = null, transactional = true) {
router[method](path, ...(middleware as express.Handler[]), (req: express.Request, res: express.Response, next: express.NextFunction) => { router[method](path, ...(middleware as express.Handler[]), (req: express.Request, res: express.Response, next: express.NextFunction) => {
const start = Date.now(); const start = Date.now();
@ -455,7 +455,7 @@ function route(method: HttpMethod, path: string, middleware: (express.Handler |
cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']); cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']);
cls.set('hoistedNoteId', req.headers['trilium-hoisted-note-id'] || 'root'); cls.set('hoistedNoteId', req.headers['trilium-hoisted-note-id'] || 'root');
const cb = () => routeHandler(req as AppRequest, res, next); const cb = () => routeHandler(req, res, next);
return transactional ? sql.transactional(cb) : cb(); return transactional ? sql.transactional(cb) : cb();
}); });

View File

@ -8,11 +8,10 @@ import passwordEncryptionService from "./encryption/password_encryption.js";
import config from "./config.js"; import config from "./config.js";
import passwordService from "./encryption/password.js"; import passwordService from "./encryption/password.js";
import type { NextFunction, Request, Response } from 'express'; import type { NextFunction, Request, Response } from 'express';
import { AppRequest } from '../routes/route-interface.js';
const noAuthentication = config.General && config.General.noAuthentication === true; const noAuthentication = config.General && config.General.noAuthentication === true;
function checkAuth(req: AppRequest, res: Response, next: NextFunction) { function checkAuth(req: Request, res: Response, next: NextFunction) {
if (!sqlInit.isDbInitialized()) { if (!sqlInit.isDbInitialized()) {
res.redirect("setup"); res.redirect("setup");
} }
@ -26,7 +25,7 @@ function checkAuth(req: AppRequest, res: Response, next: NextFunction) {
// for electron things which need network stuff // for electron things which need network stuff
// currently, we're doing that for file upload because handling form data seems to be difficult // currently, we're doing that for file upload because handling form data seems to be difficult
function checkApiAuthOrElectron(req: AppRequest, res: Response, next: NextFunction) { function checkApiAuthOrElectron(req: Request, res: Response, next: NextFunction) {
if (!req.session.loggedIn && !utils.isElectron() && !noAuthentication) { if (!req.session.loggedIn && !utils.isElectron() && !noAuthentication) {
reject(req, res, "Logged in session not found"); reject(req, res, "Logged in session not found");
} }
@ -35,7 +34,7 @@ function checkApiAuthOrElectron(req: AppRequest, res: Response, next: NextFuncti
} }
} }
function checkApiAuth(req: AppRequest, res: Response, next: NextFunction) { function checkApiAuth(req: Request, res: Response, next: NextFunction) {
if (!req.session.loggedIn && !noAuthentication) { if (!req.session.loggedIn && !noAuthentication) {
reject(req, res, "Logged in session not found"); reject(req, res, "Logged in session not found");
} }
@ -44,7 +43,7 @@ function checkApiAuth(req: AppRequest, res: Response, next: NextFunction) {
} }
} }
function checkAppInitialized(req: AppRequest, res: Response, next: NextFunction) { function checkAppInitialized(req: Request, res: Response, next: NextFunction) {
if (!sqlInit.isDbInitialized()) { if (!sqlInit.isDbInitialized()) {
res.redirect("setup"); res.redirect("setup");
} }
@ -53,7 +52,7 @@ function checkAppInitialized(req: AppRequest, res: Response, next: NextFunction)
} }
} }
function checkPasswordSet(req: AppRequest, res: Response, next: NextFunction) { function checkPasswordSet(req: Request, res: Response, next: NextFunction) {
if (!utils.isElectron() && !passwordService.isPasswordSet()) { if (!utils.isElectron() && !passwordService.isPasswordSet()) {
res.redirect("set-password"); res.redirect("set-password");
} else { } else {
@ -61,7 +60,7 @@ function checkPasswordSet(req: AppRequest, res: Response, next: NextFunction) {
} }
} }
function checkPasswordNotSet(req: AppRequest, res: Response, next: NextFunction) { function checkPasswordNotSet(req: Request, res: Response, next: NextFunction) {
if (!utils.isElectron() && passwordService.isPasswordSet()) { if (!utils.isElectron() && passwordService.isPasswordSet()) {
res.redirect("login"); res.redirect("login");
} else { } else {
@ -69,7 +68,7 @@ function checkPasswordNotSet(req: AppRequest, res: Response, next: NextFunction)
} }
} }
function checkAppNotInitialized(req: AppRequest, res: Response, next: NextFunction) { function checkAppNotInitialized(req: Request, res: Response, next: NextFunction) {
if (sqlInit.isDbInitialized()) { if (sqlInit.isDbInitialized()) {
reject(req, res, "App already initialized."); reject(req, res, "App already initialized.");
} }
@ -78,7 +77,7 @@ function checkAppNotInitialized(req: AppRequest, res: Response, next: NextFuncti
} }
} }
function checkEtapiToken(req: AppRequest, res: Response, next: NextFunction) { function checkEtapiToken(req: Request, res: Response, next: NextFunction) {
if (etapiTokenService.isValidAuthHeader(req.headers.authorization)) { if (etapiTokenService.isValidAuthHeader(req.headers.authorization)) {
next(); next();
} }
@ -87,7 +86,7 @@ function checkEtapiToken(req: AppRequest, res: Response, next: NextFunction) {
} }
} }
function reject(req: AppRequest, res: Response, message: string) { function reject(req: Request, res: Response, message: string) {
log.info(`${req.method} ${req.path} rejected with 401 ${message}`); log.info(`${req.method} ${req.path} rejected with 401 ${message}`);
res.setHeader("Content-Type", "text/plain") res.setHeader("Content-Type", "text/plain")
@ -95,7 +94,7 @@ function reject(req: AppRequest, res: Response, message: string) {
.send(message); .send(message);
} }
function checkCredentials(req: AppRequest, res: Response, next: NextFunction) { function checkCredentials(req: Request, res: Response, next: NextFunction) {
if (!sqlInit.isDbInitialized()) { if (!sqlInit.isDbInitialized()) {
res.setHeader("Content-Type", "text/plain") res.setHeader("Content-Type", "text/plain")
.status(400) .status(400)
@ -111,6 +110,13 @@ function checkCredentials(req: AppRequest, res: Response, next: NextFunction) {
} }
const header = req.headers['trilium-cred'] || ''; const header = req.headers['trilium-cred'] || '';
if (typeof header !== "string") {
res.setHeader("Content-Type", "text/plain")
.status(400)
.send('Invalid data type for trilium-cred.');
return;
}
const auth = Buffer.from(header, 'base64').toString(); const auth = Buffer.from(header, 'base64').toString();
const colonIndex = auth.indexOf(':'); const colonIndex = auth.indexOf(':');
const password = colonIndex === -1 ? "" : auth.substr(colonIndex + 1); const password = colonIndex === -1 ? "" : auth.substr(colonIndex + 1);

View File

@ -209,8 +209,9 @@ function register(router: Router) {
shacaLoader.ensureLoad(); shacaLoader.ensureLoad();
if (!shaca.shareRootNote) { if (!shaca.shareRootNote) {
return res.status(404) res.status(404)
.json({ message: "Share root note not found" }); .json({ message: "Share root note not found" });
return;
} }
renderNote(shaca.shareRootNote, req, res); renderNote(shaca.shareRootNote, req, res);
@ -282,7 +283,7 @@ function register(router: Router) {
} else if (image.type === "mindMap") { } else if (image.type === "mindMap") {
renderImageAttachment(image, res, 'mindmap-export.svg'); renderImageAttachment(image, res, 'mindmap-export.svg');
} else { } else {
return res.status(400) res.status(400)
.json({ message: "Requested note is not a shareable image" }); .json({ message: "Requested note is not a shareable image" });
} }
}); });
@ -302,7 +303,7 @@ function register(router: Router) {
addNoIndexHeader(attachment.note, res); addNoIndexHeader(attachment.note, res);
res.send(attachment.getContent()); res.send(attachment.getContent());
} else { } else {
return res.status(400) res.status(400)
.json({ message: "Requested attachment is not a shareable image" }); .json({ message: "Requested attachment is not a shareable image" });
} }
}); });
@ -354,7 +355,8 @@ function register(router: Router) {
let note; let note;
if (typeof ancestorNoteId !== "string") { if (typeof ancestorNoteId !== "string") {
return res.status(400).json({ message: "'ancestorNoteId' parameter is mandatory." }); res.status(400).json({ message: "'ancestorNoteId' parameter is mandatory." });
return;
} }
// This will automatically return if no ancestorNoteId is provided and there is no shareIndex // This will automatically return if no ancestorNoteId is provided and there is no shareIndex
@ -365,7 +367,8 @@ function register(router: Router) {
const { search } = req.query; const { search } = req.query;
if (typeof search !== "string" || !search?.trim()) { if (typeof search !== "string" || !search?.trim()) {
return res.status(400).json({ message: "'search' parameter is mandatory." }); res.status(400).json({ message: "'search' parameter is mandatory." });
return;
} }
const searchContext = new SearchContext({ ancestorNoteId: ancestorNoteId }); const searchContext = new SearchContext({ ancestorNoteId: ancestorNoteId });