Improved input service. New Manager web app. Directory and small readme for output service.

This commit is contained in:
Kalzu Rekku
2026-01-06 14:27:26 +02:00
parent ec9fec5ce3
commit f7056082f6
11 changed files with 1695 additions and 293 deletions

215
manager/store.go Normal file
View File

@@ -0,0 +1,215 @@
package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/base32"
"encoding/hex"
"encoding/json"
"os"
"path/filepath"
"sync"
)
type User struct {
ID string `json:"id"`
TOTPSecret string `json:"totp_secret"`
}
type UserStore struct {
mu sync.RWMutex
filePath string
crypto *Crypto
cache map[string]*encryptedUserEntry
}
type encryptedUserEntry struct {
UserIDHash string `json:"hash"`
Data []byte `json:"data"`
}
type encryptedStore struct {
Users []encryptedUserEntry `json:"users"`
}
func NewUserStore(dataDir string, crypto *Crypto) *UserStore {
// Create data directory if it doesn't exist
if err := os.MkdirAll(dataDir, 0700); err != nil {
logger.Error("Failed to create data directory: %v", err)
}
filePath := filepath.Join(dataDir, "users.enc")
logger.Info("Initialized user store at: %s", filePath)
store := &UserStore{
filePath: filePath,
crypto: crypto,
cache: make(map[string]*encryptedUserEntry),
}
store.loadCache()
return store
}
func (s *UserStore) hashUserID(userID string) string {
hash := sha256.Sum256([]byte(userID))
return hex.EncodeToString(hash[:])
}
func (s *UserStore) Reload() error {
s.mu.Lock()
defer s.mu.Unlock()
// Clear existing cache
s.cache = make(map[string]*encryptedUserEntry)
// Reload from disk
return s.loadCacheInternal()
}
func (s *UserStore) loadCache() error {
s.mu.Lock()
defer s.mu.Unlock()
return s.loadCacheInternal()
}
func (s *UserStore) loadCacheInternal() error {
// Read encrypted store file
encryptedData, err := os.ReadFile(s.filePath)
if err != nil {
if os.IsNotExist(err) {
logger.Info("No existing user store found, starting fresh")
return nil
}
logger.Error("Failed to read store file: %v", err)
return err
}
// Decrypt with server key
decryptedData, err := s.crypto.DecryptWithServerKey(encryptedData)
if err != nil {
logger.Error("Failed to decrypt store: %v", err)
return err
}
var store encryptedStore
if err := json.Unmarshal(decryptedData, &store); err != nil {
logger.Error("Failed to unmarshal store: %v", err)
return err
}
// Load into cache
for i := range store.Users {
s.cache[store.Users[i].UserIDHash] = &store.Users[i]
}
logger.Info("Loaded %d encrypted user entries into cache", len(s.cache))
return nil
}
func (s *UserStore) save() error {
// Build store structure from cache
store := encryptedStore{
Users: make([]encryptedUserEntry, 0, len(s.cache)),
}
for _, entry := range s.cache {
store.Users = append(store.Users, *entry)
}
// Marshal to JSON
storeData, err := json.Marshal(store)
if err != nil {
return err
}
// Encrypt with server key
encryptedData, err := s.crypto.EncryptWithServerKey(storeData)
if err != nil {
logger.Error("Failed to encrypt store: %v", err)
return err
}
// Write to file
logger.Info("Saving user store with %d entries", len(s.cache))
if err := os.WriteFile(s.filePath, encryptedData, 0600); err != nil {
logger.Error("Failed to write store file: %v", err)
return err
}
return nil
}
func (s *UserStore) GetUser(userID string) (*User, error) {
s.mu.RLock()
defer s.mu.RUnlock()
userHash := s.hashUserID(userID)
entry, exists := s.cache[userHash]
if !exists {
logger.Warn("User not found in cache")
return nil, nil
}
// Decrypt with user key (derived from user ID)
userData, err := s.crypto.DecryptWithUserKey(entry.Data, userID)
if err != nil {
logger.Error("Failed to decrypt user data: %v", err)
return nil, err
}
var user User
if err := json.Unmarshal(userData, &user); err != nil {
logger.Error("Failed to unmarshal user data: %v", err)
return nil, err
}
logger.Info("Successfully loaded user: %s", user.ID)
return &user, nil
}
func (s *UserStore) AddUser(userID, totpSecret string) error {
s.mu.Lock()
defer s.mu.Unlock()
user := &User{
ID: userID,
TOTPSecret: totpSecret,
}
// Marshal user data
userData, err := json.Marshal(user)
if err != nil {
return err
}
// Encrypt with user key (derived from user ID)
userEncrypted, err := s.crypto.EncryptWithUserKey(userData, userID)
if err != nil {
logger.Error("Failed to encrypt with user key: %v", err)
return err
}
// Add to cache
userHash := s.hashUserID(userID)
s.cache[userHash] = &encryptedUserEntry{
UserIDHash: userHash,
Data: userEncrypted,
}
// Save entire store (encrypted with server key)
if err := s.save(); err != nil {
return err
}
logger.Info("Successfully saved user: %s", userID)
return nil
}
func generateSecret() (string, error) {
b := make([]byte, 20)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base32.StdEncoding.EncodeToString(b), nil
}