mirror of
https://github.com/zadam/trilium.git
synced 2025-06-05 01:18:44 +02:00
optional basic auth for shared notes, closes #2781
This commit is contained in:
parent
3ebfaec1bc
commit
46deceedc9
657
package-lock.json
generated
657
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -70,6 +70,7 @@
|
||||
"react-dom": "18.2.0",
|
||||
"request": "2.88.2",
|
||||
"rimraf": "3.0.2",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize-html": "2.7.1",
|
||||
"sax": "1.2.4",
|
||||
|
@ -218,6 +218,7 @@ const ATTR_HELP = {
|
||||
"shareRoot": "marks note which is served on /share root.",
|
||||
"shareRaw": "note will be served in its raw format, without HTML wrapper",
|
||||
"shareDisallowRobotIndexing": `will forbid robot indexing of this note via <code>X-Robots-Tag: noindex</code> header`,
|
||||
"shareCredentials": "require credentials to access this shared note. Value is expected to be in format 'username:password'. Don't forget to make this inheritable to apply to child-notes/images.",
|
||||
"displayRelations": "comma delimited names of relations which should be displayed. All other ones will be hidden.",
|
||||
"hideRelations": "comma delimited names of relations which should be hidden. All other ones will be displayed.",
|
||||
"titleTemplate": `default title of notes created as children of this note. The value is evaluated as JavaScript string
|
||||
|
@ -49,6 +49,7 @@ module.exports = [
|
||||
{ type: 'label', name: 'shareRoot' },
|
||||
{ type: 'label', name: 'shareRaw' },
|
||||
{ type: 'label', name: 'shareDisallowRobotIndexing' },
|
||||
{ type: 'label', name: 'shareCredentials' },
|
||||
{ type: 'label', name: 'displayRelations' },
|
||||
{ type: 'label', name: 'hideRelations' },
|
||||
{ type: 'label', name: 'titleTemplate', isDangerous: true },
|
||||
|
@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const safeCompare = require('safe-compare');
|
||||
|
||||
const shaca = require("./shaca/shaca");
|
||||
const shacaLoader = require("./shaca/shaca_loader");
|
||||
@ -29,13 +30,59 @@ function addNoIndexHeader(note, res) {
|
||||
}
|
||||
}
|
||||
|
||||
function reject(res) {
|
||||
res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm", charset="UTF-8"')
|
||||
.sendStatus(401);
|
||||
}
|
||||
|
||||
function checkNoteAccess(noteId, req, res) {
|
||||
const note = shaca.getNote(noteId);
|
||||
|
||||
if (!note) {
|
||||
res.setHeader("Content-Type", "text/plain")
|
||||
.status(404)
|
||||
.send(`Note '${noteId}' not found`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const credentials = note.getCredentials();
|
||||
|
||||
if (credentials.length === 0) {
|
||||
return note;
|
||||
}
|
||||
|
||||
const header = req.header("Authorization");
|
||||
|
||||
if (!header?.startsWith("Basic ")) {
|
||||
reject(res);
|
||||
return false;
|
||||
}
|
||||
|
||||
const base64Str = header.substring("Basic ".length);
|
||||
const buffer = Buffer.from(base64Str, 'base64');
|
||||
const authString = buffer.toString('utf-8');
|
||||
|
||||
for (const credentialLabel of credentials) {
|
||||
if (safeCompare(authString, credentialLabel.value)) {
|
||||
return note; // success;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function register(router) {
|
||||
function renderNote(note, res) {
|
||||
function renderNote(note, req, res) {
|
||||
if (!note) {
|
||||
res.status(404).render("share/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkNoteAccess(note.noteId, req, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
addNoIndexHeader(note, res);
|
||||
|
||||
if (note.hasLabel('shareRaw') || ['image', 'file'].includes(note.type)) {
|
||||
@ -63,7 +110,7 @@ function register(router) {
|
||||
router.get(['/share', '/share/'], (req, res, next) => {
|
||||
shacaLoader.ensureLoad();
|
||||
|
||||
renderNote(shaca.shareRootNote, res);
|
||||
renderNote(shaca.shareRootNote, req, res);
|
||||
});
|
||||
|
||||
router.get('/share/:shareId', (req, res, next) => {
|
||||
@ -73,19 +120,15 @@ function register(router) {
|
||||
|
||||
const note = shaca.aliasToNote[shareId] || shaca.notes[shareId];
|
||||
|
||||
renderNote(note, res);
|
||||
renderNote(note, req, res);
|
||||
});
|
||||
|
||||
router.get('/share/api/notes/:noteId', (req, res, next) => {
|
||||
shacaLoader.ensureLoad();
|
||||
let note;
|
||||
|
||||
const {noteId} = req.params;
|
||||
const note = shaca.getNote(noteId);
|
||||
|
||||
if (!note) {
|
||||
return res.setHeader("Content-Type", "text/plain")
|
||||
.status(404)
|
||||
.send(`Note '${noteId}' not found`);
|
||||
if (!(note = checkNoteAccess(req.params.noteId, req, res))) {
|
||||
return;
|
||||
}
|
||||
|
||||
addNoIndexHeader(note, res);
|
||||
@ -96,13 +139,10 @@ function register(router) {
|
||||
router.get('/share/api/notes/:noteId/download', (req, res, next) => {
|
||||
shacaLoader.ensureLoad();
|
||||
|
||||
const {noteId} = req.params;
|
||||
const note = shaca.getNote(noteId);
|
||||
let note;
|
||||
|
||||
if (!note) {
|
||||
return res.setHeader("Content-Type", "text/plain")
|
||||
.status(404)
|
||||
.send(`Note '${noteId}' not found`);
|
||||
if (!(note = checkNoteAccess(req.params.noteId, req, res))) {
|
||||
return;
|
||||
}
|
||||
|
||||
addNoIndexHeader(note, res);
|
||||
@ -123,14 +163,13 @@ function register(router) {
|
||||
router.get('/share/api/images/:noteId/:filename', (req, res, next) => {
|
||||
shacaLoader.ensureLoad();
|
||||
|
||||
const image = shaca.getNote(req.params.noteId);
|
||||
let image;
|
||||
|
||||
if (!image) {
|
||||
return res.setHeader('Content-Type', 'text/plain')
|
||||
.status(404)
|
||||
.send(`Note '${req.params.noteId}' not found`);
|
||||
if (!(image = checkNoteAccess(req.params.noteId, req, res))) {
|
||||
return;
|
||||
}
|
||||
else if (!["image", "canvas"].includes(image.type)) {
|
||||
|
||||
if (!["image", "canvas"].includes(image.type)) {
|
||||
return res.setHeader('Content-Type', 'text/plain')
|
||||
.status(400)
|
||||
.send("Requested note is not a shareable image");
|
||||
@ -165,13 +204,10 @@ function register(router) {
|
||||
router.get('/share/api/notes/:noteId/view', (req, res, next) => {
|
||||
shacaLoader.ensureLoad();
|
||||
|
||||
const {noteId} = req.params;
|
||||
const note = shaca.getNote(noteId);
|
||||
let note;
|
||||
|
||||
if (!note) {
|
||||
return res.setHeader('Content-Type', 'text/plain')
|
||||
.status(404)
|
||||
.send(`Note '${noteId}' not found`);
|
||||
if (!(note = checkNoteAccess(req.params.noteId, req, res))) {
|
||||
return;
|
||||
}
|
||||
|
||||
addNoIndexHeader(note, res);
|
||||
|
@ -6,6 +6,9 @@ const AbstractEntity = require('./abstract_entity');
|
||||
|
||||
const LABEL = 'label';
|
||||
const RELATION = 'relation';
|
||||
const CREDENTIALS = 'shareCredentials';
|
||||
|
||||
const isCredentials = attr => attr.type === 'label' && attr.name === CREDENTIALS;
|
||||
|
||||
class Note extends AbstractEntity {
|
||||
constructor([noteId, title, type, mime, utcDateModified]) {
|
||||
@ -115,19 +118,25 @@ class Note extends AbstractEntity {
|
||||
this.__getAttributes([]);
|
||||
|
||||
if (type && name) {
|
||||
return this.__attributeCache.filter(attr => attr.type === type && attr.name === name);
|
||||
return this.__attributeCache.filter(attr => attr.type === type && attr.name === name && !isCredentials(attr));
|
||||
}
|
||||
else if (type) {
|
||||
return this.__attributeCache.filter(attr => attr.type === type);
|
||||
return this.__attributeCache.filter(attr => attr.type === type && !isCredentials(attr));
|
||||
}
|
||||
else if (name) {
|
||||
return this.__attributeCache.filter(attr => attr.name === name);
|
||||
return this.__attributeCache.filter(attr => attr.name === name && !isCredentials(attr));
|
||||
}
|
||||
else {
|
||||
return this.__attributeCache.slice();
|
||||
return this.__attributeCache.filter(attr => !isCredentials(attr));
|
||||
}
|
||||
}
|
||||
|
||||
getCredentials() {
|
||||
this.__getAttributes([]);
|
||||
|
||||
return this.__attributeCache.filter(isCredentials);
|
||||
}
|
||||
|
||||
__getAttributes(path) {
|
||||
if (path.includes(this.noteId)) {
|
||||
return [];
|
||||
|
Loading…
x
Reference in New Issue
Block a user