mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-09 10:57:08 +02:00
Load the signer lock-free once cached, read the signing key before generating one, cap the liveness cache at its limit, share one grantable-scope predicate, and let CreateFirstAdmin open its own locked transaction with an optional in-transaction follow-up.
54 lines
1.4 KiB
Go
54 lines
1.4 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/log"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/model/id"
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
var ErrSetupComplete = errors.New("setup already complete")
|
|
|
|
// CreateFirstAdmin counts and inserts in one locked transaction, so racing setups cannot both win.
|
|
// then, if not nil, runs in that same transaction with the new user.
|
|
func CreateFirstAdmin(ctx context.Context, ds model.DataStore, username, password string, then func(tx model.DataStore, u *model.User) error) (*model.User, error) {
|
|
var created *model.User
|
|
err := ds.WithTxImmediate(func(tx model.DataStore) error {
|
|
count, err := tx.User().CountAll(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("counting users: %w", err)
|
|
}
|
|
if count > 0 {
|
|
return ErrSetupComplete
|
|
}
|
|
log.Warn(ctx, "Creating initial user", "user", username)
|
|
u := model.User{
|
|
ID: id.NewRandom(),
|
|
UserName: username,
|
|
Name: cases.Title(language.Und).String(username),
|
|
NewPassword: password,
|
|
IsAdmin: true,
|
|
LastLoginAt: new(time.Now()),
|
|
}
|
|
if err := tx.User().Put(ctx, &u); err != nil {
|
|
return fmt.Errorf("creating initial user: %w", err)
|
|
}
|
|
if created, err = tx.User().Get(ctx, u.ID); err != nil {
|
|
return err
|
|
}
|
|
if then != nil {
|
|
return then(tx, created)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return created, nil
|
|
}
|