Add a passphrase-locked vault
A second notebook whose pages are encrypted with a key made from a passphrase plus the server key. It is unlocked into memory only (15 minutes idle, Lock, or sign-out), is not in search, lists or downloads of the ordinary notebook, and limits wrong guesses. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7ff424fa55
commit
c08a8b8718
12 files changed
+706
-28
No files matched your search
+28
-2
@@ -15,6 +15,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.veillax.com/Veillax/mako/internal/config"
|
||||
"git.veillax.com/Veillax/mako/internal/oidc"
|
||||
@@ -34,6 +35,11 @@ type Server struct {
|
||||
|
||||
mu sync.Mutex
|
||||
books map[string]*store.Store
|
||||
|
||||
vmu sync.Mutex
|
||||
vstate map[string]*vaultState // unlocked vaults, by the folder name of their owner
|
||||
vtoken map[string]string // vault cookie value to that folder name
|
||||
vfails map[string][]time.Time // recent wrong passphrases, by the same name
|
||||
}
|
||||
|
||||
// principal is who is making a request, with the notebook that is theirs alone.
|
||||
@@ -49,17 +55,23 @@ func New(h *config.Holder, version string) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &Server{cfg: h, version: version, books: map[string]*store.Store{},
|
||||
vstate: map[string]*vaultState{}, vtoken: map[string]string{}, vfails: map[string][]time.Time{},
|
||||
oidc: oidc.New(c.OIDC.Issuer, c.OIDC.ClientID, c.OIDC.ClientSecret, c.RedirectURL())}, nil
|
||||
}
|
||||
|
||||
// OIDC is the sign-in client, for the check command.
|
||||
func (s *Server) OIDC() *oidc.Client { return s.oidc }
|
||||
|
||||
// userKey names a person's folder, from a hash of who they are.
|
||||
func userKey(c config.Config, sub string) string {
|
||||
sum := sha256.Sum256([]byte(c.OIDC.Issuer + "|" + sub))
|
||||
return hex.EncodeToString(sum[:16])
|
||||
}
|
||||
|
||||
// notebook opens the notes that belong to one person. Their folder is named from a hash of who they
|
||||
// are, so nothing a person controls ever becomes part of a path.
|
||||
func (s *Server) notebook(c config.Config, sub, name, email string) (*store.Store, error) {
|
||||
sum := sha256.Sum256([]byte(c.OIDC.Issuer + "|" + sub))
|
||||
key := hex.EncodeToString(sum[:16])
|
||||
key := userKey(c, sub)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if st, ok := s.books[key]; ok {
|
||||
@@ -104,6 +116,20 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("GET /api/find", s.api(s.find))
|
||||
mux.HandleFunc("GET /api/export", s.api(s.export))
|
||||
|
||||
mux.HandleFunc("GET /api/vault", s.api(s.vaultInfo))
|
||||
mux.HandleFunc("POST /api/vault", s.api(s.vaultCreate))
|
||||
mux.HandleFunc("POST /api/vault/unlock", s.api(s.vaultUnlock))
|
||||
mux.HandleFunc("POST /api/vault/lock", s.api(s.vaultLock))
|
||||
mux.HandleFunc("GET /api/vault/pages", s.vaultAPI(s.pages))
|
||||
mux.HandleFunc("GET /api/vault/notes", s.vaultAPI(s.listNotes))
|
||||
mux.HandleFunc("POST /api/vault/notes", s.vaultAPI(s.createNote))
|
||||
mux.HandleFunc("GET /api/vault/notes/{id}", s.vaultAPI(s.getNote))
|
||||
mux.HandleFunc("PUT /api/vault/notes/{id}", s.vaultAPI(s.updateNote))
|
||||
mux.HandleFunc("DELETE /api/vault/notes/{id}", s.vaultAPI(s.deleteNote))
|
||||
mux.HandleFunc("POST /api/vault/notes/{id}/restore", s.vaultAPI(s.restoreNote))
|
||||
mux.HandleFunc("DELETE /api/vault/trash", s.vaultAPI(s.emptyTrash))
|
||||
mux.HandleFunc("GET /api/vault/find", s.vaultAPI(s.find))
|
||||
|
||||
static, _ := fs.Sub(staticFiles, "static")
|
||||
mux.Handle("GET /", http.FileServerFS(static))
|
||||
return s.secure(mux)
|
||||
|
||||
Reference in new issue
Block a user