import becca from "../becca/becca.js"; import utils from "./utils.js"; import BEtapiToken from "../becca/entities/betapi_token.js"; import crypto from "crypto"; function getTokens() { return becca.getEtapiTokens(); } function getTokenHash(token: crypto.BinaryLike) { return crypto.createHash('sha256').update(token).digest('base64'); } function createToken(tokenName: string) { const token = utils.randomSecureToken(32); const tokenHash = getTokenHash(token); const etapiToken = new BEtapiToken({ name: tokenName, tokenHash }).save(); return { authToken: `${etapiToken.etapiTokenId}_${token}` }; } function parseAuthToken(auth: string | undefined) { if (!auth) { return null; } if (auth.startsWith("Basic ")) { // allow also basic auth format for systems which allow this type of authentication // expect ETAPI token in the password field, require "etapi" username // https://github.com/zadam/trilium/issues/3181 const basicAuthStr = utils.fromBase64(auth.substring(6)).toString("utf-8"); const basicAuthChunks = basicAuthStr.split(":"); if (basicAuthChunks.length !== 2) { return null; } if (basicAuthChunks[0] !== "etapi") { return null; } auth = basicAuthChunks[1]; } const chunks = auth.split("_"); if (chunks.length === 1) { return { token: auth }; // legacy format without etapiTokenId } else if (chunks.length === 2) { return { etapiTokenId: chunks[0], token: chunks[1] } } else { return null; // wrong format } } function isValidAuthHeader(auth: string | undefined) { const parsed = parseAuthToken(auth); if (!parsed) { return false; } const authTokenHash = getTokenHash(parsed.token); if (parsed.etapiTokenId) { const etapiToken = becca.getEtapiToken(parsed.etapiTokenId); if (!etapiToken) { return false; } return etapiToken.tokenHash === authTokenHash; } else { for (const etapiToken of becca.getEtapiTokens()) { if (etapiToken.tokenHash === authTokenHash) { return true; } } return false; } } function renameToken(etapiTokenId: string, newName: string) { const etapiToken = becca.getEtapiToken(etapiTokenId); if (!etapiToken) { throw new Error(`Token '${etapiTokenId}' does not exist`); } etapiToken.name = newName; etapiToken.save(); } function deleteToken(etapiTokenId: string) { const etapiToken = becca.getEtapiToken(etapiTokenId); if (!etapiToken) { return; // ok, already deleted } etapiToken.markAsDeletedSimple(); } export = { getTokens, createToken, renameToken, deleteToken, parseAuthToken, isValidAuthHeader };