mirror of
https://github.com/zadam/trilium.git
synced 2025-11-07 13:56:11 +01:00
server-ts: Port etapi_tokens service
This commit is contained in:
124
src/services/etapi_tokens.ts
Normal file
124
src/services/etapi_tokens.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import becca = require('../becca/becca');
|
||||
import utils = require('./utils');
|
||||
import BEtapiToken = require('../becca/entities/betapi_token');
|
||||
import crypto = require("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) {
|
||||
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) {
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user