mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-08-31 08:11:27 +02:00
FRANCIS_HOST decides where the Francis actor runtime lives. When it is empty or set to "embedded" (the default) nothing changes: Pocket ID starts the runtime inside its own process, backed by its own database. Any other value is the address, or a comma-separated list of addresses, of a standalone Francis runtime; Pocket ID then connects to it as a remote actor host and starts no embedded runtime. Connecting to a standalone runtime also needs FRANCIS_HOST_PSK, the host bootstrap pre-shared key the runtime is configured with, and optionally FRANCIS_CA, the PEM-encoded cluster CA to pin before the first connection. Without a pinned CA Francis trusts the certificate it is served on first use, and warns about it. The actor host is now held as the topology-agnostic francis host.Host interface, since the concrete type depends on the configuration. The commands that reach the actor data through Pocket ID's own database (export, import, and one-time-access-token) fail with an explicit error when a standalone runtime owns that data instead, rather than silently operating on the wrong store.
71 lines
2.3 KiB
Go
71 lines
2.3 KiB
Go
package scimsync
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/italypaleale/francis/actor"
|
|
francishost "github.com/italypaleale/francis/host"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/pocket-id/pocket-id/backend/internal/httpserver"
|
|
)
|
|
|
|
type Dependencies struct {
|
|
DB *gorm.DB
|
|
Actors francishost.Host
|
|
HTTPClient *http.Client
|
|
|
|
// ScheduleDisabled keeps automatic synchronizations from being armed
|
|
// It's set in the test environment, where SCIM syncs are driven explicitly by the end-to-end tests
|
|
ScheduleDisabled bool
|
|
}
|
|
|
|
type Module struct {
|
|
service *Service
|
|
handler *handler
|
|
actors *actor.Service
|
|
scheduleDisabled bool
|
|
}
|
|
|
|
func New(deps Dependencies) (*Module, error) {
|
|
service := newService(deps.DB, deps.HTTPClient)
|
|
|
|
// Register the singleton so recurring and debounced synchronizations run once for the entire cluster
|
|
err := deps.Actors.RegisterSingletonActor(ActorType, NewActor(service, deps.ScheduleDisabled))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error registering the %s actor: %w", ActorType, err)
|
|
}
|
|
|
|
return &Module{
|
|
service: service,
|
|
handler: newHandler(service),
|
|
actors: deps.Actors.Service(),
|
|
scheduleDisabled: deps.ScheduleDisabled,
|
|
}, nil
|
|
}
|
|
|
|
// RegisterRoutes mounts the SCIM service provider endpoints
|
|
func (m *Module) RegisterRoutes(apiGroup *gin.RouterGroup, auth gin.HandlerFunc) {
|
|
apiGroup.GET("/oidc/clients/:id/scim-service-provider", auth, httpserver.Handle(m.handler.getServiceProviderByClient))
|
|
apiGroup.POST("/scim/service-provider", auth, httpserver.Handle(m.handler.createServiceProvider))
|
|
apiGroup.POST("/scim/service-provider/:id/sync", auth, httpserver.Handle(m.handler.syncServiceProvider))
|
|
apiGroup.PUT("/scim/service-provider/:id", auth, httpserver.Handle(m.handler.updateServiceProvider))
|
|
apiGroup.DELETE("/scim/service-provider/:id", auth, httpserver.Handle(m.handler.deleteServiceProvider))
|
|
}
|
|
|
|
// ScheduleSync schedules a debounced cluster-wide synchronization after SCIM-relevant data changes
|
|
func (m *Module) ScheduleSync(ctx context.Context) {
|
|
if m.scheduleDisabled {
|
|
return
|
|
}
|
|
|
|
_, err := m.actors.Invoke(ctx, ActorType, actor.SingletonActorID, methodScheduleSync, nil)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Failed to schedule SCIM sync", slog.Any("error", err))
|
|
}
|
|
}
|