feat: migrate instance_oidc into auth_providers (0005)
This commit is contained in:
@@ -101,6 +101,13 @@ func runSchemaSetup() {
|
||||
log.Fatalf("failed to ensure auth indexes: %v", err)
|
||||
}
|
||||
|
||||
// 0005 runs AFTER EnsureAuthIndexes: the unique (instance_id, provider_id)
|
||||
// index must exist before anything inserts providers, or a concurrent
|
||||
// re-run could double-insert before the index is there to refuse it.
|
||||
if err := services.MigrateAuthProviders(); err != nil {
|
||||
log.Fatalf("auth provider migration failed: %v", err)
|
||||
}
|
||||
|
||||
if err := services.EnsureSecretIndexes(); err != nil {
|
||||
log.Printf("warning: failed to ensure secret indexes: %v", err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/db"
|
||||
"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/models"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
// legacyInstanceOIDC is the pre-0005 shape: one document per instance, in a
|
||||
// collection this migration is the last thing to read.
|
||||
type legacyInstanceOIDC struct {
|
||||
InstanceID string `bson:"instance_id"`
|
||||
Issuer string `bson:"issuer"`
|
||||
ClientID string `bson:"client_id"`
|
||||
ClientSecretEnc string `bson:"client_secret_enc"`
|
||||
Enabled bool `bson:"enabled"`
|
||||
UpdatedAt time.Time `bson:"updated_at"`
|
||||
}
|
||||
|
||||
// MigrateAuthProviders copies each instance_oidc document into auth_providers.
|
||||
//
|
||||
// The ciphertext is copied verbatim rather than decrypted and re-encrypted: a
|
||||
// migration that needs KEY_ENCRYPTION_KEY fails on an instance that has none
|
||||
// and strands the SSO configuration it was supposed to preserve.
|
||||
//
|
||||
// instance_oidc is left in place and no longer read. Nothing deletes it — a
|
||||
// migration that drops the only copy of a client secret has no undo.
|
||||
func MigrateAuthProviders() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
const marker = "0005_auth_providers"
|
||||
if n, _ := db.Col("migrations").CountDocuments(ctx, bson.M{"_id": marker}); n > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cur, err := db.Col("instance_oidc").Find(ctx, bson.M{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var legacy []legacyInstanceOIDC
|
||||
if err := cur.All(ctx, &legacy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrated := 0
|
||||
for _, l := range legacy {
|
||||
if l.InstanceID == "" {
|
||||
continue
|
||||
}
|
||||
// Idempotent by skipping an instance that already has a provider, so a
|
||||
// re-run after a partial failure completes rather than duplicating.
|
||||
n, err := db.Col(authProviderCol).CountDocuments(ctx, bson.M{"instance_id": l.InstanceID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
continue
|
||||
}
|
||||
providerID, err := randomProviderID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
p := models.AuthProvider{
|
||||
InstanceID: l.InstanceID,
|
||||
ProviderID: providerID,
|
||||
Name: "Single sign-on",
|
||||
Kind: models.KindOIDC,
|
||||
Preset: "",
|
||||
Issuer: l.Issuer,
|
||||
ClientID: l.ClientID,
|
||||
ClientSecretEnc: l.ClientSecretEnc,
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
Enabled: l.Enabled,
|
||||
// This provider's redirect URI has changed and nobody has been told
|
||||
// yet. The settings card raises it until an administrator dismisses.
|
||||
CallbackNotice: true,
|
||||
Order: 0,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
if _, err := db.Col(authProviderCol).InsertOne(ctx, p); err != nil {
|
||||
return err
|
||||
}
|
||||
migrated++
|
||||
}
|
||||
if migrated > 0 {
|
||||
log.Printf("0005: migrated %d OIDC configuration(s) to auth_providers", migrated)
|
||||
}
|
||||
|
||||
_, err = db.Col("migrations").InsertOne(ctx, bson.M{"_id": marker, "applied_at": time.Now()})
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user