feat: Add password protection
All checks were successful
/ pre-commit (push) Successful in 1m20s

Fixes #37.

BREAKING CHANGE: API routes are prefixed by /api/note.

Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
Julien Riou 2025-09-27 08:35:26 +02:00
commit 9e0254c0b5
Signed by: jriou
GPG key ID: 9A099EDA51316854
16 changed files with 713 additions and 135 deletions

View file

@ -0,0 +1,8 @@
{{define "error"}}
<div class="container mb-4 text-center">
<div class="alert alert-danger" role="alert">
<p>Could not show note</p>
<p><strong>{{.Err}}</strong></p>
</div>
</div>
{{end}}

View file

@ -13,6 +13,14 @@
</div>
<div class="container text-center justify-content-center w-75 mb-4">
<div class="row align-items-center">
{{if .EnablePasswordProtection}}
<div class="col-1">
<label class="col-form-label col-form-label-sm" for="password">Password</label>
</div>
<div class="col">
<input type="password" class="form-control" id="password" name="password">
</div>
{{end}}
{{if .AllowClientEncryptionKey}}
<div class="col-1">
<label class="col-form-label col-form-label-sm" for="encryption-key">Encryption key</label>
@ -25,9 +33,9 @@
{{end}}
{{if .AllowNoEncryption}}
<div class="col-1">
<input type="checkbox" class="form-check-input" for="no-encryption-key" id="no-encryption-key"
value="no-encryption-key" name="no-encryption-key">
<label class="col-form-label col-form-label-sm" for="no-encryption-key">No encryption</label>
<input type="checkbox" class="form-check-input" for="no-encryption" id="no-encryption"
value="no-encryption" name="no-encryption">
<label class="col-form-label col-form-label-sm" for="no-encryption">No encryption</label>
</div>
{{end}}
<div class="col-1">

View file

@ -1,69 +1,51 @@
{{define "note"}}
<!DOCTYPE html>
<html lang="en" data-bs-theme="light">
{{block "head" .}}{{end}}
<body>
{{block "header" .}}{{end}}
{{if ne .Err nil}}
<div class="container mb-4 text-center">
<div class="alert alert-danger" role="alert">
<p>Could not show note</p>
<p><strong>{{.Err}}</strong></p>
{{if ne .Err nil}}
{{block "error" .}}{{end}}
{{else}}
<div class="container mb-4">
<div class="d-flex flex-wrap py-2">
<span class="fs-4 d-flex mb-3 mb-md-0 me-md-auto text-decoration-none">Note {{.Note.ID}}</span>
<ul class="nav nav-pills align-items-center">
<li class="nav-item px-2">
<a href="" id="rawURL">raw</a>
<script>document.getElementById("rawURL").href = window.location.href.replace(".html", "/raw");</script>
</li>
<li class="nav-item px-2">
{{.Note.Language}}
</li>
{{if eq .Note.DeleteAfterRead false}}
<li class="nav-item px-2">
expires in {{HumanDuration (TimeDiff .Note.ExpiresAt)}}
</li>
{{end}}
</ul>
</div>
<div class="row">
<div id="editor" name="editor" class="form-control" style="height: 300px; resize: vertical; overflow: auto;">
</div>
</div>
{{else}}
<div class="container mb-4">
<div class="d-flex flex-wrap py-2">
<span class="fs-4 d-flex mb-3 mb-md-0 me-md-auto text-decoration-none">Note {{.Note.ID}}</span>
<ul class="nav nav-pills align-items-center">
<li class="nav-item px-2">
<a href="" id="rawURL">raw</a>
<script>document.getElementById("rawURL").href = window.location.href.replace(".html", "");</script>
</li>
<li class="nav-item px-2">
{{.Note.Language}}
</li>
{{if eq .Note.DeleteAfterRead false}}
<li class="nav-item px-2">
expires in {{HumanDuration (TimeDiff .Note.ExpiresAt)}}
</li>
{{end}}
</ul>
</div>
<div class="row">
<div id="editor" name="editor" class="form-control"
style="height: 300px; resize: vertical; overflow: auto;"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs/loader.min.js"></script>
<script>
require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs' } });
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs/loader.min.js"></script>
<script>
require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs' } });
require(['vs/editor/editor.main'], function () {
var editor = monaco.editor.create(document.getElementById('editor'), {
theme: document.documentElement.getAttribute('data-bs-theme') == 'light' ? "vs" : "vs-dark",
language: "{{.Note.Language}}",
readOnly: true,
value: "{{string .Note.Content}}"
});
// Dark mode
document.getElementById("lightSwitch").addEventListener("click", () => {
if (document.documentElement.getAttribute('data-bs-theme') == 'light') {
monaco.editor.setTheme("vs")
} else if (document.documentElement.getAttribute('data-bs-theme') == 'dark') {
monaco.editor.setTheme("vs-dark")
}
});
require(['vs/editor/editor.main'], function () {
var editor = monaco.editor.create(document.getElementById('editor'), {
theme: document.documentElement.getAttribute('data-bs-theme') == 'light' ? "vs" : "vs-dark",
language: "{{.Note.Language}}",
readOnly: true,
value: "{{string .Note.Content}}"
});
</script>
</div>
{{end}}
{{block "footer" .}}{{end}}
</body>
</html>
// Dark mode
document.getElementById("lightSwitch").addEventListener("click", () => {
if (document.documentElement.getAttribute('data-bs-theme') == 'light') {
monaco.editor.setTheme("vs")
} else if (document.documentElement.getAttribute('data-bs-theme') == 'dark') {
monaco.editor.setTheme("vs-dark")
}
});
});
</script>
</div>
{{end}}
{{end}}

View file

@ -0,0 +1,21 @@
{{define "protectedNote"}}
<!DOCTYPE html>
<html lang="en" data-bs-theme="light">
{{block "head" .}}{{end}}
<body>
{{block "header" .}}{{end}}
{{if ne .Err nil}}
{{block "error" .}}{{end}}
{{else}}
{{block "note" .}}{{end}}
{{end}}
{{block "footer" .}}{{end}}
</body>
</html>
{{end}}

View file

@ -0,0 +1,42 @@
{{define "unprotectedNote"}}
<!DOCTYPE html>
<html lang="en" data-bs-theme="light">
{{block "head" .}}{{end}}
<body>
{{block "header" .}}{{end}}
{{if ne .Err nil}}
{{block "error" .}}{{end}}
{{else}}
<div class="container mb-4">
{{if gt (len .Note.PasswordHash) 0}}
<form id="form" action="#" method="post" enctype="multipart/form-data">
<div class="container mb-4 w-25">
<div class="row text-center justify-content-center">
<label class="col-form-label" for="password">Password</label>
</div>
</div>
<div class="container mb-4 w-25">
<div class="row text-center justify-content-center">
<input type="password" class="form-control" id="password" name="password">
</div>
</div>
<div class="container mb-4 w-25">
<div class="row text-center justify-content-center">
<button type="submit" id="submit" class="btn btn-success">Submit</button>
</div>
</div>
</form>
</div>
{{else}}
{{block "note" .}}{{end}}
{{end}}
{{end}}
{{block "footer" .}}{{end}}
</body>
</html>
{{end}}