Notes/src/services/etapi_tokens.ts

125 lines
2.9 KiB
TypeScript
Raw Normal View History

import becca from "../becca/becca.js";
import utils from "./utils.js";
import BEtapiToken from "../becca/entities/betapi_token.js";
import crypto from "crypto";
2022-01-10 17:09:20 +01:00
function getTokens() {
return becca.getEtapiTokens();
}
2024-02-17 19:55:40 +02:00
function getTokenHash(token: crypto.BinaryLike) {
2022-01-10 17:09:20 +01:00
return crypto.createHash('sha256').update(token).digest('base64');
}
2024-02-17 19:55:40 +02:00
function createToken(tokenName: string) {
2022-01-12 19:32:23 +01:00
const token = utils.randomSecureToken(32);
2022-01-10 17:09:20 +01:00
const tokenHash = getTokenHash(token);
const etapiToken = new BEtapiToken({
2022-01-10 17:09:20 +01:00
name: tokenName,
tokenHash
}).save();
2022-02-07 22:50:28 +01:00
2022-01-10 17:09:20 +01:00
return {
authToken: `${etapiToken.etapiTokenId}_${token}`
};
}
2024-04-03 19:22:49 +03:00
function parseAuthToken(auth: string | undefined) {
2022-01-10 17:09:20 +01:00
if (!auth) {
return null;
}
2022-02-07 22:50:28 +01:00
2022-10-08 20:59:11 +02:00
if (auth.startsWith("Basic ")) {
// allow also basic auth format for systems which allow this type of authentication
2022-10-09 21:34:01 +02:00
// expect ETAPI token in the password field, require "etapi" username
2022-10-08 20:59:11 +02:00
// https://github.com/zadam/trilium/issues/3181
const basicAuthStr = utils.fromBase64(auth.substring(6)).toString("utf-8");
2022-10-08 20:59:11 +02:00
const basicAuthChunks = basicAuthStr.split(":");
if (basicAuthChunks.length !== 2) {
2022-10-08 20:59:11 +02:00
return null;
}
if (basicAuthChunks[0] !== "etapi") {
return null;
}
auth = basicAuthChunks[1];
2022-10-08 20:59:11 +02:00
}
2022-01-10 17:09:20 +01:00
const chunks = auth.split("_");
2022-02-07 22:50:28 +01:00
2022-01-10 17:09:20 +01:00
if (chunks.length === 1) {
return { token: auth }; // legacy format without etapiTokenId
}
else if (chunks.length === 2) {
2022-02-07 22:50:28 +01:00
return {
2022-01-10 17:09:20 +01:00
etapiTokenId: chunks[0],
token: chunks[1]
}
}
else {
return null; // wrong format
}
}
2024-04-03 19:22:49 +03:00
function isValidAuthHeader(auth: string | undefined) {
2022-01-10 17:09:20 +01:00
const parsed = parseAuthToken(auth);
2022-02-07 22:50:28 +01:00
2022-01-10 17:09:20 +01:00
if (!parsed) {
return false;
}
2022-02-07 22:50:28 +01:00
const authTokenHash = getTokenHash(parsed.token);
2022-01-10 17:09:20 +01:00
if (parsed.etapiTokenId) {
const etapiToken = becca.getEtapiToken(parsed.etapiTokenId);
2022-02-07 22:50:28 +01:00
2022-01-10 17:09:20 +01:00
if (!etapiToken) {
return false;
}
2022-02-07 22:50:28 +01:00
2022-01-10 17:09:20 +01:00
return etapiToken.tokenHash === authTokenHash;
}
else {
for (const etapiToken of becca.getEtapiTokens()) {
if (etapiToken.tokenHash === authTokenHash) {
return true;
}
}
2022-02-07 22:50:28 +01:00
2022-01-10 17:09:20 +01:00
return false;
}
}
2024-02-17 19:55:40 +02:00
function renameToken(etapiTokenId: string, newName: string) {
2022-01-10 17:09:20 +01:00
const etapiToken = becca.getEtapiToken(etapiTokenId);
2022-02-07 22:50:28 +01:00
2022-01-10 17:09:20 +01:00
if (!etapiToken) {
2023-05-04 22:16:18 +02:00
throw new Error(`Token '${etapiTokenId}' does not exist`);
2022-01-10 17:09:20 +01:00
}
2022-02-07 22:50:28 +01:00
2022-01-10 17:09:20 +01:00
etapiToken.name = newName;
etapiToken.save();
}
2024-02-17 19:55:40 +02:00
function deleteToken(etapiTokenId: string) {
2022-01-10 17:09:20 +01:00
const etapiToken = becca.getEtapiToken(etapiTokenId);
2022-02-07 22:50:28 +01:00
2022-01-10 17:09:20 +01:00
if (!etapiToken) {
return; // ok, already deleted
}
2022-02-07 22:50:28 +01:00
etapiToken.markAsDeletedSimple();
2022-01-10 17:09:20 +01:00
}
export default {
2022-01-10 17:09:20 +01:00
getTokens,
createToken,
renameToken,
deleteToken,
parseAuthToken,
isValidAuthHeader
2022-02-07 22:50:28 +01:00
};