Changeset 0.30.2 (#201)

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
2026-03-18 15:10:11 +00:00
committed by xcaliber
parent 8fee53e440
commit 7b0b6eb061
22 changed files with 1217 additions and 47 deletions

View File

@@ -365,18 +365,28 @@ func DestroyVaultDB(ctx context.Context, stores store.Stores, userID string) (pr
// wrapped with a different password), the vault and personal providers are
// destroyed so initVault fires cleanly on next login.
//
// Used by BootstrapAdmin and SeedUsers where the password is known at
// startup but the UEK cache is not available.
// When uekCache is non-nil and the vault unlocks successfully, the UEK is
// cached so that BYOK operations work immediately without requiring a fresh
// login. This is important for bootstrapped/seeded users whose browser
// sessions survive server restarts.
//
// Used by BootstrapAdmin and SeedUsers where the password is known at startup.
// v0.29.0: accepts stores instead of using database.DB directly.
func ProbeAndRepairVault(ctx context.Context, stores store.Stores, userID, password string) {
// v0.30.2: accepts optional uekCache to pre-warm vault on restart.
func ProbeAndRepairVault(ctx context.Context, stores store.Stores, userID, password string, uekCache ...*crypto.UEKCache) {
vaultSet, encryptedUEK, salt, nonce, err := stores.Users.GetVaultKeys(ctx, userID)
if err != nil || !vaultSet {
return // no vault to probe
}
pdk := crypto.DeriveKeyFromPassword(password, salt)
if _, err := crypto.UnwrapUEK(encryptedUEK, nonce, pdk); err == nil {
return // vault seal matches current password — all good
uek, err := crypto.UnwrapUEK(encryptedUEK, nonce, pdk)
if err == nil {
// Vault seal matches current password — cache UEK if cache provided
if len(uekCache) > 0 && uekCache[0] != nil {
uekCache[0].Store(userID, uek)
}
return
}
// Stale seal: password has actually changed since the vault was sealed
@@ -465,7 +475,9 @@ func (h *AuthHandler) unlockVault(ctx context.Context, user *models.User, passwo
}
// BootstrapAdmin creates/updates the admin user from env vars (K8s secret).
func BootstrapAdmin(cfg *config.Config, s store.Stores) {
// When uekCache is provided, the admin's vault is pre-warmed so BYOK
// operations work immediately without requiring a fresh login.
func BootstrapAdmin(cfg *config.Config, s store.Stores, uekCache ...*crypto.UEKCache) {
if cfg.AdminUsername == "" || cfg.AdminPassword == "" {
return
}
@@ -492,7 +504,11 @@ func BootstrapAdmin(cfg *config.Config, s store.Stores) {
handle := auth.UniqueHandle(ctx, s.Users, models.HandleFromName(cfg.AdminUsername))
s.Users.Update(ctx, existing.ID, map[string]interface{}{"handle": handle})
}
ProbeAndRepairVault(ctx, s, existing.ID, cfg.AdminPassword)
var cache *crypto.UEKCache
if len(uekCache) > 0 {
cache = uekCache[0]
}
ProbeAndRepairVault(ctx, s, existing.ID, cfg.AdminPassword, cache)
log.Printf(" ✅ Admin user '%s' updated", cfg.AdminUsername)
return
}
@@ -524,7 +540,7 @@ func BootstrapAdmin(cfg *config.Config, s store.Stores) {
// Format: "user:pass:role,user2:pass2:role2" where role is "admin" or "user".
// Upsert: existing users get their password and role refreshed on every restart.
// Gated to non-production environments.
func SeedUsers(cfg *config.Config, s store.Stores) {
func SeedUsers(cfg *config.Config, s store.Stores, uekCache ...*crypto.UEKCache) {
if cfg.SeedUsers == "" {
log.Printf(" SEED_USERS not set, skipping")
return
@@ -582,7 +598,11 @@ func SeedUsers(cfg *config.Config, s store.Stores) {
handle := auth.UniqueHandle(ctx, s.Users, models.HandleFromName(username))
s.Users.Update(ctx, existing.ID, map[string]interface{}{"handle": handle})
}
ProbeAndRepairVault(ctx, s, existing.ID, password)
var cache *crypto.UEKCache
if len(uekCache) > 0 {
cache = uekCache[0]
}
ProbeAndRepairVault(ctx, s, existing.ID, password, cache)
log.Printf(" 🌱 Seed user '%s' updated (role=%s)", username, role)
continue
}