Fixed the device sign

This commit is contained in:
2025-08-22 18:24:24 -04:00
parent b9fad16fa2
commit 5067913c21
8 changed files with 738 additions and 905 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,86 +1,29 @@
package api
import (
"log"
"mime"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
func init() {
// Ensure common types are known (some distros are sparse by default)
_ = mime.AddExtensionType(".js", "application/javascript; charset=utf-8")
_ = mime.AddExtensionType(".css", "text/css; charset=utf-8")
_ = mime.AddExtensionType(".html", "text/html; charset=utf-8")
_ = mime.AddExtensionType(".map", "application/json; charset=utf-8")
}
func (s *Server) MountStatic(dir string, baseURL string) {
if dir == "" {
return
}
if baseURL == "" {
baseURL = "/"
}
s.mux.Handle(baseURL, s.staticHandler(dir, baseURL))
if !strings.HasSuffix(baseURL, "/") {
s.mux.Handle(baseURL+"/", s.staticHandler(dir, baseURL))
}
}
func (s *Server) ListenFrontendHTTP(addr, dir, baseURL string) error {
if dir == "" || addr == "" {
return nil
}
log.Printf("frontend listening on %s (dir=%s base=%s)", addr, dir, baseURL)
mx := http.NewServeMux()
mx.Handle(baseURL, s.staticHandler(dir, baseURL))
if !strings.HasSuffix(baseURL, "/") {
mx.Handle(baseURL+"/", s.staticHandler(dir, baseURL))
}
server := &http.Server{
Addr: addr,
Handler: mx,
ReadHeaderTimeout: 5 * time.Second,
}
return server.ListenAndServe()
}
func (s *Server) staticHandler(dir, baseURL string) http.Handler {
if baseURL == "" {
baseURL = "/"
}
// secureHeaders adds strict, privacy-preserving headers to static responses.
func (s *Server) secureHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s.secureHeaders(w)
up := strings.TrimPrefix(r.URL.Path, baseURL)
if up == "" || strings.HasSuffix(r.URL.Path, "/") {
up = "index.html"
}
full := filepath.Join(dir, filepath.FromSlash(up))
if !strings.HasPrefix(filepath.Clean(full), filepath.Clean(dir)) {
http.NotFound(w, r)
return
}
// Serve file if it exists, else SPA-fallback to index.html
if st, err := os.Stat(full); err == nil && !st.IsDir() {
// Set Content-Type explicitly based on extension
if ctype := mime.TypeByExtension(filepath.Ext(full)); ctype != "" {
w.Header().Set("Content-Type", ctype)
}
http.ServeFile(w, r, full)
return
}
fallback := filepath.Join(dir, "index.html")
if _, err := os.Stat(fallback); err == nil {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
http.ServeFile(w, r, fallback)
return
}
http.NotFound(w, r)
w.Header().Set("Referrer-Policy", "no-referrer")
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
w.Header().Set("Cross-Origin-Resource-Policy", "same-site")
w.Header().Set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), interest-cohort=(), browsing-topics=()")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Strict-Transport-Security", "max-age=15552000; includeSubDomains; preload")
next.ServeHTTP(w, r)
})
}
// MountStatic mounts a static file server under a prefix onto the provided mux.
// Usage (from main): s.MountStatic(mux, "/", http.Dir(staticDir))
func (s *Server) MountStatic(mux *http.ServeMux, prefix string, fs http.FileSystem) {
if prefix == "" {
prefix = "/"
}
h := http.StripPrefix(prefix, http.FileServer(fs))
mux.Handle(prefix, s.secureHeaders(h))
}

View File

@@ -1,88 +1,63 @@
package index
import (
"sort"
"errors"
"sync"
"time"
)
// Entry is the minimal metadata we expose to clients.
type Entry struct {
Hash string `json:"hash"`
Bytes int64 `json:"bytes"`
StoredAt string `json:"stored_at"`
Private bool `json:"private"`
CreatorTZ string `json:"creator_tz,omitempty"`
}
type rec struct {
Hash string
Bytes int64
StoredAt time.Time
Private bool
CreatorTZ string
StoredAt string `json:"stored_at"` // RFC3339Nano
Private bool `json:"private"` // true if client marked encrypted
CreatorTZ string `json:"creator_tz,omitempty"` // optional IANA TZ from client
}
// Index is an in-memory map from hash -> Entry, safe for concurrent use.
type Index struct {
mu sync.RWMutex
hash map[string]rec
mu sync.RWMutex
m map[string]Entry
}
func New() *Index { return &Index{hash: make(map[string]rec)} }
func New() *Index {
return &Index{m: make(map[string]Entry)}
}
func (ix *Index) Put(e Entry) error {
if e.Hash == "" {
return errors.New("empty hash")
}
ix.mu.Lock()
defer ix.mu.Unlock()
t := parseWhen(e.StoredAt)
if t.IsZero() {
t = time.Now().UTC()
}
ix.hash[e.Hash] = rec{
Hash: e.Hash,
Bytes: e.Bytes,
StoredAt: t,
Private: e.Private,
CreatorTZ: e.CreatorTZ,
}
ix.m[e.Hash] = e
ix.mu.Unlock()
return nil
}
func (ix *Index) Delete(hash string) error {
if hash == "" {
return errors.New("empty hash")
}
ix.mu.Lock()
defer ix.mu.Unlock()
delete(ix.hash, hash)
delete(ix.m, hash)
ix.mu.Unlock()
return nil
}
func (ix *Index) List() ([]Entry, error) {
func (ix *Index) Get(hash string) (Entry, bool) {
ix.mu.RLock()
defer ix.mu.RUnlock()
tmp := make([]rec, 0, len(ix.hash))
for _, r := range ix.hash {
tmp = append(tmp, r)
}
sort.Slice(tmp, func(i, j int) bool { return tmp[i].StoredAt.After(tmp[j].StoredAt) })
out := make([]Entry, len(tmp))
for i, r := range tmp {
out[i] = Entry{
Hash: r.Hash,
Bytes: r.Bytes,
StoredAt: r.StoredAt.UTC().Format(time.RFC3339Nano),
Private: r.Private,
CreatorTZ: r.CreatorTZ,
}
}
return out, nil
e, ok := ix.m[hash]
ix.mu.RUnlock()
return e, ok
}
func parseWhen(s string) time.Time {
if s == "" {
return time.Time{}
// All returns an unsorted copy of all entries.
func (ix *Index) All() []Entry {
ix.mu.RLock()
out := make([]Entry, 0, len(ix.m))
for _, v := range ix.m {
out = append(out, v)
}
if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
return t
}
if t, err := time.Parse(time.RFC3339, s); err == nil {
return t
}
return time.Time{}
ix.mu.RUnlock()
return out
}