feat(auth): coordinate canonical projection publication

This commit is contained in:
User
2026-08-21 23:43:37 +02:00
parent 1e2c4e65c5
commit 903c0b4de5
10 changed files with 1272 additions and 8 deletions
+49 -1
View File
@@ -2,11 +2,14 @@ package authconfig
import (
"bytes"
"context"
"errors"
"io"
"os"
"path/filepath"
"time"
"github.com/aritmolab/thothii/tools/tht/internal/authprojection"
"github.com/aritmolab/thothii/tools/tht/internal/safeio"
"github.com/gofrs/flock"
"gopkg.in/yaml.v3"
@@ -53,6 +56,34 @@ func load(directory string) (Config, Registry, error) {
return config, registry, nil
}
// loadSnapshotBytes reads only canonical, validated authentication files and returns a detached,
// bounded projection snapshot. Callers that coordinate a mutation hold .auth.lock around it.
func loadSnapshotBytes(directory string) (authprojection.Snapshot, error) {
if err := requirePrivateDirectory(directory); err != nil {
return authprojection.Snapshot{}, err
}
authContents, err := readPrivateFile(filepath.Join(directory, authFileName))
if err != nil {
return authprojection.Snapshot{}, err
}
var config Config
if err := decodeStrictYAML(authContents, &config); err != nil || !validConfig(config) {
return authprojection.Snapshot{}, errInvalidAuthenticationConfig
}
var usersContents []byte
if config.Mode == "local" {
usersContents, err = readPrivateFile(filepath.Join(directory, config.Local.UsersFile))
if err != nil {
return authprojection.Snapshot{}, err
}
var registry Registry
if err := decodeStrictYAML(usersContents, &registry); err != nil || validateRegistry(registry) != nil {
return authprojection.Snapshot{}, errInvalidAuthenticationConfig
}
}
return authprojection.NewSnapshot(config.Mode, append([]byte(nil), authContents...), append([]byte(nil), usersContents...))
}
// MutateUsers serializes the entire read-check-write transaction under the configuration lock.
// The resulting registry is revalidated and atomically replaced only after all invariants hold.
func MutateUsers(directory string, mutate func(*Registry) error) error {
@@ -137,6 +168,13 @@ func readPrivateFile(path string) ([]byte, error) {
}
func acquireLock(directory string) (*flock.Flock, error) {
return acquireLockContext(context.Background(), directory)
}
func acquireLockContext(ctx context.Context, directory string) (*flock.Flock, error) {
if ctx == nil || ctx.Err() != nil {
return nil, errInvalidAuthenticationConfig
}
path := filepath.Join(directory, lockFileName)
if _, err := os.Lstat(path); errors.Is(err, os.ErrNotExist) {
if err := safeio.WriteCanonicalNewFile(path, nil, 0o600); err != nil {
@@ -153,9 +191,19 @@ func acquireLock(directory string) (*flock.Flock, error) {
return nil, err
}
lock := flock.New(path, flock.SetPermissions(0o600))
if err := lock.Lock(); err != nil {
locked, err := lock.TryLock()
if err != nil {
return nil, errInvalidAuthenticationConfig
}
if !locked {
if hook := coordinatorHooks().onCanonicalLockContention; hook != nil {
hook()
}
locked, err = lock.TryLockContext(ctx, 10*time.Millisecond)
if err != nil || !locked {
return nil, errInvalidAuthenticationConfig
}
}
if _, err := readPrivateFile(path); err != nil {
_ = lock.Unlock()
return nil, err