diff --git a/backend/internal/bootstrap/actors_bootstrap.go b/backend/internal/bootstrap/actors_bootstrap.go index 01114fbe..fb02b175 100644 --- a/backend/internal/bootstrap/actors_bootstrap.go +++ b/backend/internal/bootstrap/actors_bootstrap.go @@ -115,27 +115,32 @@ func (o *NewActorsOpts) getPSK() ([]byte, error) { // NewActorStateStore creates a minimal actor host that can read and write actor state directly, without joining the cluster or binding a network port. // It's meant for short-lived contexts such as CLI commands that need to persist actor state (for example, one-time access tokens) without running the full actor host. // The returned host must NOT be Run(): only direct state operations (Get/Set/Delete on state) are supported, and they require the actor state tables to already exist, which is the case whenever the server has run at least once against this database. -func NewActorStateStore(db *gorm.DB, pg *pgxpool.Pool) (*local.Host, error) { - opts := &NewActorsOpts{DB: db, Postgres: pg} - if pg == nil { - sqlDB, err := db.DB() +func NewActorStateStore(o NewActorsOpts) (*local.Host, error) { + if o.Postgres == nil { + sqlDB, err := o.DB.DB() if err != nil { return nil, fmt.Errorf("failed to get *sql.DB connection from Gorm: %w", err) } - opts.SQLite = sqlDB + o.SQLite = sqlDB } - providerOpt, err := opts.getProviderOption() + providerOpt, err := o.getProviderOption() if err != nil { return nil, err } + psk, err := o.getPSK() + if err != nil { + return nil, fmt.Errorf("failed to derive PSK: %w", err) + } + return local.NewHost( // The address is required by the host but never bound, since the host is not Run local.WithAddress("127.0.0.1:1"), local.WithLogger(slog.Default().With("scope", "actor-state-store")), // The health-check deadline only needs to exceed the provider's query timeout to pass validation local.WithHostHealthCheckDeadline(90*time.Second), + local.WithRuntimePSKs(psk), providerOpt, ) } diff --git a/backend/internal/cmds/one_time_access_token.go b/backend/internal/cmds/one_time_access_token.go index dd64024d..590272e6 100644 --- a/backend/internal/cmds/one_time_access_token.go +++ b/backend/internal/cmds/one_time_access_token.go @@ -11,6 +11,7 @@ import ( "github.com/pocket-id/pocket-id/backend/internal/bootstrap" "github.com/pocket-id/pocket-id/backend/internal/common" + "github.com/pocket-id/pocket-id/backend/internal/instanceid" "github.com/pocket-id/pocket-id/backend/internal/model" "github.com/pocket-id/pocket-id/backend/internal/onetimeaccess" ) @@ -47,9 +48,19 @@ var oneTimeAccessTokenCmd = &cobra.Command{ return errors.New("invalid user loaded: ID is empty") } + instanceID, err := instanceid.Load(cmd.Context(), db) + if err != nil { + return err + } + // One-time access tokens are stored in the actor state store // The CLI doesn't run the full actor host, so it uses a minimal state store to persist the token directly - actorStore, err := bootstrap.NewActorStateStore(db, pg) + actorStore, err := bootstrap.NewActorStateStore(bootstrap.NewActorsOpts{ + DB: db, + Postgres: pg, + EnvConfig: &common.EnvConfig, + InstanceID: instanceID, + }) if err != nil { return fmt.Errorf("failed to initialize the actor state store: %w", err) }