Optional client encryption (#1640)

Implements #1268
This commit is contained in:
Zef Hemel
2025-10-22 15:35:37 +02:00
committed by GitHub
parent cd6f4e9c58
commit 272f293ddb
21 changed files with 738 additions and 119 deletions
+20 -8
View File
@@ -12,6 +12,7 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)
// path to auth page in the client bundle
@@ -42,6 +43,10 @@ func addAuthEndpoints(r chi.Router, config *ServerConfig) {
// Auth page
r.Get("/.auth", func(w http.ResponseWriter, r *http.Request) {
spaceConfig := spaceConfigFromContext(r.Context())
if spaceConfig.Auth == nil {
http.Error(w, "Authentication not enabled", http.StatusForbidden)
return
}
if err := spaceConfig.InitAuth(); err != nil {
http.Error(w, "Failed to initialize authentication", http.StatusInternalServerError)
return
@@ -55,12 +60,10 @@ func addAuthEndpoints(r chi.Router, config *ServerConfig) {
tpl := template.Must(template.New("auth").Parse(string(data)))
templateData := struct {
HostPrefix string
SpaceName string
}{
HostPrefix: config.HostURLPrefix,
SpaceName: spaceConfig.SpaceName,
templateData := map[string]string{
"HostPrefix": config.HostURLPrefix,
"SpaceName": spaceConfig.SpaceName,
"EncryptionSalt": spaceConfig.JwtIssuer.Salt,
}
w.Header().Set("Content-type", "text/html")
@@ -138,18 +141,27 @@ func addAuthEndpoints(r chi.Router, config *ServerConfig) {
redirectPath = from
}
http.Redirect(w, r, applyURLPrefix(redirectPath, config.HostURLPrefix), http.StatusFound)
render.JSON(w, r, map[string]any{
"status": "ok",
"redirect": redirectPath,
})
} else {
log.Println("Authentication failed, redirecting to auth page.")
spaceConfig.LockoutTimer.AddCount()
http.Redirect(w, r, applyURLPrefix("/.auth?error=1", config.HostURLPrefix), http.StatusFound)
render.JSON(w, r, map[string]any{
"status": "error",
"error": "Invalid username and/or password",
})
}
})
}
func (spaceConfig *SpaceConfig) InitAuth() error {
if spaceConfig.JwtIssuer == nil {
spaceConfig.authMutex.Lock()
defer spaceConfig.authMutex.Unlock()
var err error
// Need to do some initialization
spaceConfig.JwtIssuer, err = CreateAuthenticator(path.Join(spaceConfig.SpaceFolderPath, ".silverbullet.auth.json"), spaceConfig.Auth)
+7
View File
@@ -20,6 +20,7 @@ type Authenticator struct {
path string
SecretKey string `json:"secret_key"`
AuthHash string `json:"auth_hash"`
Salt string `json:"salt"` // base64 encoded 16 byte randomized salt used for encryption
}
func CreateAuthenticator(path string, authOptions *AuthOptions) (*Authenticator, error) {
@@ -79,6 +80,12 @@ func (j *Authenticator) init(authConfig *AuthOptions) error {
j.AuthHash = newAuthHash
if j.Salt == "" {
b := make([]byte, 16)
rand.Read(b)
j.Salt = base64.StdEncoding.EncodeToString(b)
}
return j.save()
}
+5
View File
@@ -26,6 +26,9 @@ type BootConfig struct {
// Whether or not the client should push logs to the server
LogPush bool `json:"logPush"`
// Encryption
EnableClientEncryption bool `json:"enableClientEncryption"`
}
func Router(config *ServerConfig) chi.Router {
@@ -68,6 +71,8 @@ func Router(config *ServerConfig) chi.Router {
IndexPage: spaceConfig.IndexPage,
ReadOnly: spaceConfig.ReadOnlyMode,
LogPush: spaceConfig.LogPush,
// Client encryption is offered as an option when auth is enabled only
EnableClientEncryption: spaceConfig.Auth != nil,
}
w.Header().Set("Cache-Control", "no-cache")
+2
View File
@@ -3,6 +3,7 @@ package server
import (
"errors"
"net/http"
"sync"
)
type ServerConfig struct {
@@ -43,6 +44,7 @@ type SpaceConfig struct {
// Auth temporary objects
JwtIssuer *Authenticator
LockoutTimer *LockoutTimer
authMutex sync.Mutex
}
type ConfigResolver func(r *http.Request) (*SpaceConfig, error)