server: 平台抽象层+bilibili、服务端加密凭据库、角色中间件;web: 精简页面/路由、macOS 客户端(Swift)与多份方案文档;移除误入库的编译产物

This commit is contained in:
Qiufeng
2026-08-21 10:53:32 +08:00
parent 0daa9782c9
commit 3585c39bab
103 changed files with 5842 additions and 3197 deletions
+193
View File
@@ -0,0 +1,193 @@
package credentials
// Package credentials stores platform secrets outside the SQL account model.
// Files are encrypted with AES-256-GCM and keyed by workspace/account IDs so a
// leaked account row or browser event never contains cookies.
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"sync"
)
var errInvalidID = errors.New("workspace and account IDs must be positive")
// Store is a process-safe encrypted credential store.
type Store struct {
root string
key []byte
mu sync.Mutex
}
// Open creates or loads a 32-byte master key in root. The key file is never
// returned by an API and is permission-restricted to the current user.
func Open(root string) (*Store, error) {
if root == "" {
return nil, errors.New("credential directory is empty")
}
if err := os.MkdirAll(root, 0o700); err != nil {
return nil, err
}
keyPath := filepath.Join(root, "master.key")
key, err := os.ReadFile(keyPath)
if errors.Is(err, os.ErrNotExist) {
key = make([]byte, 32)
if _, err = io.ReadFull(rand.Reader, key); err != nil {
return nil, err
}
if err = writePrivate(keyPath, key); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
if len(key) != 32 {
return nil, errors.New("credential master key must be 32 bytes")
}
return &Store{root: root, key: append([]byte(nil), key...)}, nil
}
func (s *Store) path(workspaceID, accountID uint64) (string, error) {
if workspaceID == 0 || accountID == 0 {
return "", errInvalidID
}
dir := filepath.Join(s.root, strconv.FormatUint(workspaceID, 10))
return filepath.Join(dir, strconv.FormatUint(accountID, 10)+".enc"), nil
}
// Save encrypts value and atomically replaces the account file.
func (s *Store) Save(workspaceID, accountID uint64, value []byte) error {
path, err := s.path(workspaceID, accountID)
if err != nil {
return err
}
block, err := aes.NewCipher(s.key)
if err != nil {
return err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return err
}
ciphertext := gcm.Seal(nil, nonce, value, nil)
payload := struct {
Nonce string `json:"nonce"`
Data string `json:"data"`
}{base64.RawStdEncoding.EncodeToString(nonce), base64.RawStdEncoding.EncodeToString(ciphertext)}
raw, err := json.Marshal(payload)
if err != nil {
return err
}
s.mu.Lock()
defer s.mu.Unlock()
if err = os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
return err
}
tmp, err := os.CreateTemp(filepath.Dir(path), ".credential-*")
if err != nil {
return err
}
tmpName := tmp.Name()
defer os.Remove(tmpName)
if err = tmp.Chmod(0o600); err == nil {
_, err = tmp.Write(raw)
}
if closeErr := tmp.Close(); err == nil {
err = closeErr
}
if err != nil {
return err
}
return os.Rename(tmpName, path)
}
// Load decrypts credentials for one workspace/account pair.
func (s *Store) Load(workspaceID, accountID uint64) ([]byte, error) {
path, err := s.path(workspaceID, accountID)
if err != nil {
return nil, err
}
s.mu.Lock()
raw, err := os.ReadFile(path)
s.mu.Unlock()
if err != nil {
return nil, err
}
var payload struct {
Nonce string `json:"nonce"`
Data string `json:"data"`
}
if err = json.Unmarshal(raw, &payload); err != nil {
return nil, fmt.Errorf("credential envelope: %w", err)
}
nonce, err := base64.RawStdEncoding.DecodeString(payload.Nonce)
if err != nil {
return nil, fmt.Errorf("credential nonce: %w", err)
}
ciphertext, err := base64.RawStdEncoding.DecodeString(payload.Data)
if err != nil {
return nil, fmt.Errorf("credential data: %w", err)
}
block, err := aes.NewCipher(s.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
if len(nonce) != gcm.NonceSize() {
return nil, errors.New("credential nonce has invalid size")
}
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, errors.New("credential authentication failed")
}
return plain, nil
}
// Delete removes credentials. Missing files are treated as success.
func (s *Store) Delete(workspaceID, accountID uint64) error {
path, err := s.path(workspaceID, accountID)
if err != nil {
return err
}
s.mu.Lock()
defer s.mu.Unlock()
if err = os.Remove(path); errors.Is(err, os.ErrNotExist) {
return nil
}
return err
}
func writePrivate(path string, value []byte) error {
tmp, err := os.CreateTemp(filepath.Dir(path), ".master-*")
if err != nil {
return err
}
tmpName := tmp.Name()
defer os.Remove(tmpName)
if err = tmp.Chmod(0o600); err == nil {
_, err = tmp.Write(value)
}
if closeErr := tmp.Close(); err == nil {
err = closeErr
}
if err != nil {
return err
}
return os.Rename(tmpName, path)
}
+49
View File
@@ -0,0 +1,49 @@
package credentials
import (
"os"
"path/filepath"
"testing"
)
func TestStoreRoundTripAndIsolation(t *testing.T) {
root := t.TempDir()
s, err := Open(root)
if err != nil {
t.Fatal(err)
}
secret := []byte(`{"cookies":"SESSDATA=secret"}`)
if err = s.Save(7, 11, secret); err != nil {
t.Fatal(err)
}
got, err := s.Load(7, 11)
if err != nil {
t.Fatal(err)
}
if string(got) != string(secret) {
t.Fatalf("round trip mismatch: %q", got)
}
if _, err = s.Load(8, 11); !os.IsNotExist(err) {
t.Fatalf("workspace isolation failed: %v", err)
}
raw, err := os.ReadFile(filepath.Join(root, "7", "11.enc"))
if err != nil {
t.Fatal(err)
}
if string(raw) == string(secret) {
t.Fatal("credential file is plaintext")
}
if err = s.Delete(7, 11); err != nil {
t.Fatal(err)
}
}
func TestStoreRejectsInvalidIDs(t *testing.T) {
s, err := Open(t.TempDir())
if err != nil {
t.Fatal(err)
}
if err = s.Save(0, 1, []byte("x")); err == nil {
t.Fatal("expected invalid ID error")
}
}