diff --git a/driver/registry.go b/driver/registry.go index dc31f7305633..9c6cc7e780f5 100644 --- a/driver/registry.go +++ b/driver/registry.go @@ -7,45 +7,38 @@ import ( "context" "io/fs" - "github.com/ory/kratos/selfservice/sessiontokenexchange" - "github.com/ory/x/contextx" - "github.com/ory/x/jsonnetsecure" - "github.com/ory/x/otelx" - prometheus "github.com/ory/x/prometheusx" - "github.com/gorilla/sessions" "github.com/pkg/errors" - "github.com/ory/nosurf" - - "github.com/ory/x/logrusx" - "github.com/ory/kratos/continuity" "github.com/ory/kratos/courier" + "github.com/ory/kratos/driver/config" "github.com/ory/kratos/hash" + "github.com/ory/kratos/identity" + "github.com/ory/kratos/persistence" "github.com/ory/kratos/schema" + "github.com/ory/kratos/selfservice/errorx" + "github.com/ory/kratos/selfservice/flow/login" + "github.com/ory/kratos/selfservice/flow/logout" "github.com/ory/kratos/selfservice/flow/recovery" + "github.com/ory/kratos/selfservice/flow/registration" "github.com/ory/kratos/selfservice/flow/settings" "github.com/ory/kratos/selfservice/flow/verification" + "github.com/ory/kratos/selfservice/sessiontokenexchange" "github.com/ory/kratos/selfservice/strategy/code" "github.com/ory/kratos/selfservice/strategy/link" - - "github.com/ory/x/healthx" - - "github.com/ory/kratos/persistence" - "github.com/ory/kratos/selfservice/flow/login" - "github.com/ory/kratos/selfservice/flow/logout" - "github.com/ory/kratos/selfservice/flow/registration" - - "github.com/ory/kratos/x" - - "github.com/ory/x/dbal" - - "github.com/ory/kratos/driver/config" - "github.com/ory/kratos/identity" - "github.com/ory/kratos/selfservice/errorx" password2 "github.com/ory/kratos/selfservice/strategy/password" "github.com/ory/kratos/session" + "github.com/ory/kratos/x" + "github.com/ory/nosurf" + "github.com/ory/x/contextx" + "github.com/ory/x/dbal" + "github.com/ory/x/healthx" + "github.com/ory/x/jsonnetsecure" + "github.com/ory/x/logrusx" + "github.com/ory/x/otelx" + "github.com/ory/x/popx" + prometheus "github.com/ory/x/prometheusx" ) type Registry interface { @@ -186,10 +179,12 @@ type options struct { replaceIdentitySchemaProvider func(Registry) schema.IdentitySchemaProvider inspect func(Registry) error extraMigrations []fs.FS - replacementStrategies []NewStrategy - extraHooks map[string]func(config.SelfServiceHook) any - disableMigrationLogging bool - jsonnetPool jsonnetsecure.Pool + extraGoMigrations popx.Migrations + + replacementStrategies []NewStrategy + extraHooks map[string]func(config.SelfServiceHook) any + disableMigrationLogging bool + jsonnetPool jsonnetsecure.Pool } type RegistryOption func(*options) @@ -251,6 +246,12 @@ func WithExtraMigrations(m ...fs.FS) RegistryOption { } } +func WithExtraGoMigrations(m ...popx.Migration) RegistryOption { + return func(o *options) { + o.extraGoMigrations = append(o.extraGoMigrations, m...) + } +} + func WithDisabledMigrationLogging() RegistryOption { return func(o *options) { o.disableMigrationLogging = true diff --git a/driver/registry_default.go b/driver/registry_default.go index 9f88c7375dd0..9a89cb052136 100644 --- a/driver/registry_default.go +++ b/driver/registry_default.go @@ -672,7 +672,10 @@ func (m *RegistryDefault) Init(ctx context.Context, ctxer contextx.Contextualize m.Logger().WithError(err).Warnf("Unable to open database, retrying.") return errors.WithStack(err) } - p, err := sql.NewPersister(ctx, m, c, sql.WithExtraMigrations(o.extraMigrations...), sql.WithDisabledLogging(o.disableMigrationLogging)) + p, err := sql.NewPersister(ctx, m, c, + sql.WithExtraMigrations(o.extraMigrations...), + sql.WithExtraGoMigrations(o.extraGoMigrations...), + sql.WithDisabledLogging(o.disableMigrationLogging)) if err != nil { m.Logger().WithError(err).Warnf("Unable to initialize persister, retrying.") return err diff --git a/identity/credentials.go b/identity/credentials.go index 9fc2d93851bb..315e5e9d6963 100644 --- a/identity/credentials.go +++ b/identity/credentials.go @@ -7,12 +7,17 @@ import ( "context" "database/sql" "reflect" + "sync" "time" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" + "github.com/pkg/errors" "github.com/wI2L/jsondiff" + "go.opentelemetry.io/otel/trace" "github.com/ory/kratos/ui/node" + "github.com/ory/x/otelx" "github.com/ory/x/sqlxx" ) @@ -191,11 +196,54 @@ func (c Credentials) GetID() uuid.UUID { return c.ID } +func (c Credentials) GetNID() uuid.UUID { + return c.NID +} + +var ( + typeTable map[uuid.UUID]CredentialsType + typeErr error + typeOnce sync.Once +) + +func (c *Credentials) AfterFind(con *pop.Connection) error { + typeOnce.Do(func() { + span := trace.SpanFromContext(con.Context()) + ctx, span := span.TracerProvider().Tracer("").Start(con.Context(), "identity.Credentials.AfterFind") + con = con.WithContext(ctx) + defer otelx.End(span, &typeErr) + + var table []CredentialsTypeTable + if typeErr = con.All(&table); typeErr != nil { + return + } + typeTable = make(map[uuid.UUID]CredentialsType, len(table)) + for _, t := range table { + typeTable[t.ID] = t.Name + } + }) + if typeErr != nil { + return typeErr + } + + var ok bool + c.Type, ok = typeTable[c.IdentityCredentialTypeID] + if !ok { + return errors.New("could not find credentials type") + } + + return nil +} + +var _ pop.AfterFindable = (*Credentials)(nil) + type ( // swagger:ignore CredentialIdentifier struct { ID uuid.UUID `db:"id"` Identifier string `db:"identifier"` + // Identity is a helper struct field for gobuffalo.pop. + IdentityID uuid.UUID `json:"-" db:"identity_id"` // IdentityCredentialsID is a helper struct field for gobuffalo.pop. IdentityCredentialsID uuid.UUID `json:"-" db:"identity_credential_id"` // IdentityCredentialsTypeID is a helper struct field for gobuffalo.pop. diff --git a/identity/test/pool.go b/identity/test/pool.go index 4d9f4c440910..09a4cd6c28e2 100644 --- a/identity/test/pool.go +++ b/identity/test/pool.go @@ -1347,8 +1347,8 @@ func TestPool(ctx context.Context, p persistence.Persister, m *identity.Manager, require.NoError(t, p.GetConnection(ctx).RawQuery("INSERT INTO identity_credentials (id, identity_id, nid, identity_credential_type_id, created_at, updated_at, config) VALUES (?, ?, ?, ?, ?, ?, '{}')", cid2, iid, nid2, m[0].ID, time.Now(), time.Now()).Exec()) ici1, ici2 := x.NewUUID(), x.NewUUID() - require.NoError(t, p.GetConnection(ctx).RawQuery("INSERT INTO identity_credential_identifiers (id, identity_credential_id, nid, identifier, created_at, updated_at, identity_credential_type_id) VALUES (?, ?, ?, ?, ?, ?, ?)", ici1, cid1, nid1, "nid1", time.Now(), time.Now(), m[0].ID).Exec()) - require.NoError(t, p.GetConnection(ctx).RawQuery("INSERT INTO identity_credential_identifiers (id, identity_credential_id, nid, identifier, created_at, updated_at, identity_credential_type_id) VALUES (?, ?, ?, ?, ?, ?, ?)", ici2, cid2, nid2, "nid2", time.Now(), time.Now(), m[0].ID).Exec()) + require.NoError(t, p.GetConnection(ctx).RawQuery("INSERT INTO identity_credential_identifiers (id, identity_id, identity_credential_id, nid, identifier, created_at, updated_at, identity_credential_type_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", ici1, iid, cid1, nid1, "nid1", time.Now(), time.Now(), m[0].ID).Exec()) + require.NoError(t, p.GetConnection(ctx).RawQuery("INSERT INTO identity_credential_identifiers (id, identity_id, identity_credential_id, nid, identifier, created_at, updated_at, identity_credential_type_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", ici2, iid, cid2, nid2, "nid2", time.Now(), time.Now(), m[0].ID).Exec()) _, err := p.GetIdentity(ctx, nid1, identity.ExpandNothing) require.ErrorIs(t, err, sqlcon.ErrNoRows) diff --git a/persistence/sql/identity/persister_identity.go b/persistence/sql/identity/persister_identity.go index 4523ce7f2146..9fba52d3bb41 100644 --- a/persistence/sql/identity/persister_identity.go +++ b/persistence/sql/identity/persister_identity.go @@ -377,6 +377,7 @@ func (p *IdentityPersister) createIdentityCredentials(ctx context.Context, conn identifiers = append(identifiers, &identity.CredentialIdentifier{ Identifier: identifier, + IdentityID: cred.IdentityID, IdentityCredentialsID: cred.ID, IdentityCredentialsTypeID: ct.ID, NID: p.NetworkID(ctx), @@ -787,7 +788,7 @@ func QueryForCredentials(con *pop.Connection, where ...Where) (map[uuid.UUID](ma ici := "identity_credential_identifiers" switch con.Dialect.Name() { case "cockroach": - ici += "@identity_credential_identifiers_nid_identity_credential_id_idx" + ici += "@primary" case "sqlite3": ici += " INDEXED BY identity_credential_identifiers_nid_identity_credential_id_idx" case "mysql": @@ -811,7 +812,9 @@ func QueryForCredentials(con *pop.Connection, where ...Where) (map[uuid.UUID](ma "(identity_credentials.identity_credential_type_id = ict.id)", ).LeftJoin( ici, - "identity_credential_identifiers.identity_credential_id = identity_credentials.id AND identity_credential_identifiers.nid = identity_credentials.nid", + `identity_credential_identifiers.identity_id = identity_credentials.identity_id + AND identity_credential_identifiers.identity_credential_id = identity_credentials.id + AND identity_credential_identifiers.nid = identity_credentials.nid`, ) for _, w := range where { q = q.Where("("+w.Condition+")", w.Args...) diff --git a/persistence/sql/migratest/migration_test.go b/persistence/sql/migratest/migration_test.go index bf727683248b..6c1ccb2467d5 100644 --- a/persistence/sql/migratest/migration_test.go +++ b/persistence/sql/migratest/migration_test.go @@ -8,30 +8,21 @@ import ( "encoding/json" "os" "path/filepath" + "slices" "sync" "testing" "time" - "github.com/ory/x/pagination/keysetpagination" - "github.com/ory/x/servicelocatorx" - - "github.com/ory/kratos/identity" - "github.com/bradleyjkemp/cupaloy/v2" - "github.com/stretchr/testify/assert" - - "github.com/ory/x/dbal" - - "github.com/ory/kratos/x/xsql" - - "github.com/ory/x/migratest" - "github.com/gobuffalo/pop/v6" "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ory/kratos/driver" "github.com/ory/kratos/driver/config" + "github.com/ory/kratos/identity" + "github.com/ory/kratos/persistence/sql/migrations/gomigrations" "github.com/ory/kratos/selfservice/flow/login" "github.com/ory/kratos/selfservice/flow/recovery" "github.com/ory/kratos/selfservice/flow/registration" @@ -41,9 +32,14 @@ import ( "github.com/ory/kratos/selfservice/strategy/link" "github.com/ory/kratos/session" "github.com/ory/kratos/x" + "github.com/ory/kratos/x/xsql" "github.com/ory/x/configx" + "github.com/ory/x/dbal" "github.com/ory/x/logrusx" + "github.com/ory/x/migratest" + "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/popx" + "github.com/ory/x/servicelocatorx" "github.com/ory/x/sqlcon" "github.com/ory/x/sqlcon/dockertest" ) @@ -134,6 +130,10 @@ func testDatabase(t *testing.T, db string, c *pop.Connection) { os.DirFS("../migrations/sql"), popx.NewMigrator(c, l, nil, 1*time.Minute), popx.WithTestdata(t, os.DirFS("./testdata")), + popx.WithGoMigrations(slices.Concat( + gomigrations.IdentityPrimaryKeysStep1, + gomigrations.IdentityPrimaryKeysStep2, + )), ) require.NoError(t, err) tm.DumpMigrations = true diff --git a/persistence/sql/migrations/gomigrations/identity_pk.go b/persistence/sql/migrations/gomigrations/identity_pk.go new file mode 100644 index 000000000000..449bf361020e --- /dev/null +++ b/persistence/sql/migrations/gomigrations/identity_pk.go @@ -0,0 +1,290 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package gomigrations + +import ( + "fmt" + "path/filepath" + "runtime" + + "github.com/gobuffalo/pop/v6" + "github.com/pkg/errors" + + "github.com/ory/x/popx" +) + +func path() string { + _, file, line, _ := runtime.Caller(1) + return fmt.Sprintf("%s:%d", filepath.Base(file), line) +} + +var IdentityPrimaryKeysStep1 = []popx.Migration{ + { + Version: "20240208000000000000", + Path: path(), + Name: "Change primary key for identity_verifiable_addresses", + Direction: "up", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE identity_verifiable_addresses ALTER PRIMARY KEY USING COLUMNS (identity_id,id)") + return errors.WithStack(err) + }, + }, + { + Version: "20240208000000000000", + Path: path(), + Name: "Revert primary key for identity_verifiable_addresses", + Direction: "down", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE identity_verifiable_addresses ALTER PRIMARY KEY USING COLUMNS (id)") + return errors.WithStack(err) + }, + }, + { + Version: "20240208000000000001", + Path: path(), + Name: "Change primary key for identity_recovery_addresses", + Direction: "up", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE identity_recovery_addresses ALTER PRIMARY KEY USING COLUMNS (identity_id,id)") + return errors.WithStack(err) + }, + }, + { + Version: "20240208000000000001", + Path: path(), + Name: "Revert primary key for identity_recovery_addresses", + Direction: "down", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE identity_recovery_addresses ALTER PRIMARY KEY USING COLUMNS (id)") + return errors.WithStack(err) + }, + }, + { + Version: "20240208000000000002", + Path: path(), + Name: "Change primary key for identity_credentials", + Direction: "up", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE identity_credentials ALTER PRIMARY KEY USING COLUMNS (identity_id,id)") + return errors.WithStack(err) + }, + }, + { + Version: "20240208000000000002", + Path: path(), + Name: "Revert primary key for identity_credentials", + Direction: "down", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE identity_credentials ALTER PRIMARY KEY USING COLUMNS (id)") + return errors.WithStack(err) + }, + }, + { + Version: "20240208000000000003", + Path: path(), + Name: "Add column identity_id to identity_credential_identifiers and session_devices", + Direction: "up", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE identity_credential_identifiers ADD COLUMN identity_id UUID NULL REFERENCES identities(id) ON DELETE CASCADE") + if err != nil { + return errors.WithStack(err) + } + _, err = c.Store.Exec("ALTER TABLE session_devices ADD COLUMN identity_id UUID NULL REFERENCES identities(id) ON DELETE CASCADE") + return errors.WithStack(err) + }, + }, + { + Version: "20240208000000000003", + Path: path(), + Name: "Drop column identity_id to identity_credential_identifiers and session_devices", + Direction: "down", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE identity_credential_identifiers DROP COLUMN identity_id") + if err != nil { + return errors.WithStack(err) + } + _, err = c.Store.Exec("ALTER TABLE session_devices DROP COLUMN identity_id") + return errors.WithStack(err) + }, + }, +} + +var IdentityPrimaryKeysStep2 = []popx.Migration{ + { + Version: "20240208000000000004", + Path: path(), + Name: "Backfill column identity_id in identity_credential_identifiers", + Direction: "up", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + for { + res, err := c.Store.Exec(` + UPDATE + identity_credential_identifiers ici + SET + identity_id = ic.identity_id + FROM + identity_credentials ic + WHERE + ici.identity_credential_id = ic.id + AND ici.nid = ic.nid + AND ici.identity_id IS NULL + LIMIT 100`) + if err != nil { + return errors.WithStack(err) + } + n, err := res.RowsAffected() + if err != nil { + return errors.WithStack(err) + } + if n == 0 { + break + } + fmt.Printf("Backfilled %d rows in identity_credential_identifiers\n", n) + } + return nil + }, + }, + { + Version: "20240208000000000004", + Path: path(), + Name: "Revert backfill column identity_id in identity_credential_identifiers (noop)", + Direction: "down", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + // nothing + return nil + }, + }, + { + Version: "20240208000000000005", + Path: path(), + Name: "Change primary key of identity_credential_identifiers", + Direction: "up", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE identity_credential_identifiers ALTER identity_id SET NOT NULL") + if err != nil { + return errors.WithStack(err) + } + _, err = c.Store.Exec("ALTER TABLE identity_credential_identifiers ALTER PRIMARY KEY USING COLUMNS (identity_id, identity_credential_id, id)") + return errors.WithStack(err) + }, + }, + { + Version: "20240208000000000005", + Path: path(), + Name: "Revert primary key of identity_credential_identifiers", + Direction: "down", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE identity_credential_identifiers ALTER PRIMARY KEY USING COLUMNS (id)") + if err != nil { + return errors.WithStack(err) + } + _, err = c.Store.Exec("ALTER TABLE identity_credential_identifiers ALTER identity_id DROP NOT NULL") + return errors.WithStack(err) + }, + }, + { + Version: "20240208000000000006", + Path: path(), + Name: "Backfill column identity_id in session_devices", + Direction: "up", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + for { + res, err := c.Store.Exec(` + UPDATE + session_devices sd + SET + identity_id = s.identity_id + FROM + sessions s + WHERE + sd.session_id = s.id + AND sd.nid = s.nid + AND sd.identity_id IS NULL + LIMIT 100`) + if err != nil { + return errors.WithStack(err) + } + n, err := res.RowsAffected() + if err != nil { + return errors.WithStack(err) + } + if n == 0 { + break + } + fmt.Printf("Backfilled %d rows in session_devices\n", n) + } + return nil + }, + }, + { + Version: "20240208000000000006", + Path: path(), + Name: "Revert backfill column identity_id in Backfill column identity_id in session_devices (noop)", + Direction: "down", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + // nothing + return nil + }, + }, + { + Version: "20240208000000000007", + Path: path(), + Name: "Change primary key of session_devices", + Direction: "up", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE session_devices ALTER identity_id SET NOT NULL") + if err != nil { + return errors.WithStack(err) + } + _, err = c.Store.Exec("ALTER TABLE session_devices ALTER PRIMARY KEY USING COLUMNS (session_id, identity_id, id)") + return errors.WithStack(err) + }, + }, + { + Version: "20240208000000000007", + Path: path(), + Name: "Revert primary key of session_devices", + Direction: "down", + Type: "go", + DBType: "cockroach", + RunnerNoTx: func(m popx.Migration, c *pop.Connection) error { + _, err := c.Store.Exec("ALTER TABLE session_devices ALTER PRIMARY KEY USING COLUMNS (id)") + if err != nil { + return errors.WithStack(err) + } + _, err = c.Store.Exec("ALTER TABLE session_devices ALTER identity_id DROP NOT NULL") + return errors.WithStack(err) + }, + }, +} diff --git a/persistence/sql/migrations/sql/20210817181232000000_unique_credentials.sqlite3.down.sql b/persistence/sql/migrations/sql/20210817181232000000_unique_credentials.sqlite3.down.sql index ebb42cf99e00..7d1f91589640 100644 --- a/persistence/sql/migrations/sql/20210817181232000000_unique_credentials.sqlite3.down.sql +++ b/persistence/sql/migrations/sql/20210817181232000000_unique_credentials.sqlite3.down.sql @@ -1 +1,2 @@ -CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_uq_idx" ON "identity_credential_identifiers" (nid, identifier); \ No newline at end of file +DELETE FROM "identity_credential_identifiers"; -- This migration is destructive. +CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_uq_idx" ON "identity_credential_identifiers" (nid, identifier); diff --git a/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.mysql.down.sql b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.mysql.down.sql new file mode 100644 index 000000000000..91863660ac61 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.mysql.down.sql @@ -0,0 +1,7 @@ +ALTER TABLE identity_verifiable_addresses + DROP FOREIGN KEY identity_verifiable_addresses_ibfk_1; + +ALTER TABLE identity_verifiable_addresses + DROP PRIMARY KEY, + ADD PRIMARY KEY (id), + ADD CONSTRAINT identity_verifiable_addresses_ibfk_1 FOREIGN KEY (identity_id) REFERENCES identities(id) ON DELETE CASCADE; diff --git a/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.mysql.up.sql b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.mysql.up.sql new file mode 100644 index 000000000000..4ad5ad9aa98d --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.mysql.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_verifiable_addresses + DROP PRIMARY KEY, + ADD PRIMARY KEY (identity_id, id); diff --git a/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.postgres.down.sql b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.postgres.down.sql new file mode 100644 index 000000000000..ed3fe5674e96 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.postgres.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_verifiable_addresses + DROP CONSTRAINT identity_verifiable_addresses_pkey, + ADD PRIMARY KEY (id); diff --git a/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.postgres.up.sql b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.postgres.up.sql new file mode 100644 index 000000000000..67cdda56393c --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.postgres.up.sql @@ -0,0 +1,17 @@ +CREATE UNIQUE INDEX identity_verifiable_addresses_id_uq_idx ON identity_verifiable_addresses (id); + +ALTER TABLE identity_verification_codes + DROP CONSTRAINT identity_verification_codes_identity_verifiable_addresses_id_fk; + +ALTER TABLE identity_verification_tokens + DROP CONSTRAINT identity_verification_tokens_identity_verifiable_address_i_fkey; + +ALTER TABLE identity_verifiable_addresses + DROP CONSTRAINT identity_verifiable_addresses_pkey, + ADD PRIMARY KEY (identity_id, id); + +ALTER TABLE identity_verification_codes + ADD CONSTRAINT identity_verification_codes_identity_verifiable_addresses_id_fk FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses(id) ON DELETE CASCADE; + +ALTER TABLE identity_verification_tokens + ADD CONSTRAINT identity_verification_tokens_identity_verifiable_address_i_fkey FOREIGN KEY (identity_verifiable_address_id) REFERENCES identity_verifiable_addresses(id) ON DELETE CASCADE; diff --git a/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.sqlite.down.sql b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.sqlite.down.sql new file mode 100644 index 000000000000..461a5e8fa392 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.sqlite.down.sql @@ -0,0 +1,27 @@ +CREATE TABLE IF NOT EXISTS "_identity_verifiable_addresses_tmp" ( +"id" TEXT PRIMARY KEY, +"status" TEXT NOT NULL, +"via" TEXT NOT NULL, +"verified" bool NOT NULL, +"value" TEXT NOT NULL, +"verified_at" DATETIME, +"identity_id" TEXT NOT NULL, +"created_at" DATETIME NOT NULL, +"updated_at" DATETIME NOT NULL, +"nid" TEXT NOT NULL, +FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON UPDATE RESTRICT ON DELETE CASCADE, +FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE +); + +INSERT INTO "_identity_verifiable_addresses_tmp" + ("id", "status", "via", "verified", "value", "verified_at", "identity_id", "created_at", "updated_at", "nid") +SELECT + "id", "status", "via", "verified", "value", "verified_at", "identity_id", "created_at", "updated_at", "nid" +FROM "identity_verifiable_addresses"; + +DROP TABLE "identity_verifiable_addresses"; +ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; + +CREATE UNIQUE INDEX IF NOT EXISTS "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (nid, via, value); +CREATE INDEX IF NOT EXISTS "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (nid, via, value); +CREATE INDEX IF NOT EXISTS identity_recovery_addresses_nid_id_idx ON identity_recovery_addresses (nid, id); diff --git a/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.sqlite.up.sql b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.sqlite.up.sql new file mode 100644 index 000000000000..dae62489b9e6 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000000_identity_verifiable_addresses_pk.sqlite.up.sql @@ -0,0 +1,30 @@ +CREATE TABLE IF NOT EXISTS "_identity_verifiable_addresses_tmp" ( +"id" TEXT NOT NULL, +"status" TEXT NOT NULL, +"via" TEXT NOT NULL, +"verified" bool NOT NULL, +"value" TEXT NOT NULL, +"verified_at" DATETIME, +"identity_id" TEXT NOT NULL, +"created_at" DATETIME NOT NULL, +"updated_at" DATETIME NOT NULL, +"nid" TEXT NOT NULL, +PRIMARY KEY ("identity_id","id"), +FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON UPDATE RESTRICT ON DELETE CASCADE, +FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON UPDATE RESTRICT ON DELETE CASCADE +); + +INSERT INTO "_identity_verifiable_addresses_tmp" + ("id", "status", "via", "verified", "value", "verified_at", "identity_id", "created_at", "updated_at", "nid") +SELECT + "id", "status", "via", "verified", "value", "verified_at", "identity_id", "created_at", "updated_at", "nid" +FROM "identity_verifiable_addresses"; + +DROP TABLE "identity_verifiable_addresses"; +ALTER TABLE "_identity_verifiable_addresses_tmp" RENAME TO "identity_verifiable_addresses"; + +CREATE UNIQUE INDEX "identity_verifiable_addresses_status_via_uq_idx" ON "identity_verifiable_addresses" (nid, via, value); +CREATE UNIQUE INDEX "identity_verifiable_addresses_id_uq_idx" ON "identity_verifiable_addresses" (id); +CREATE INDEX "identity_verifiable_addresses_status_via_idx" ON "identity_verifiable_addresses" (nid, via, value); +CREATE INDEX identity_verifiable_addresses_nid_id_idx ON identity_recovery_addresses (nid, id); +CREATE INDEX identity_verifiable_addresses_id_nid_idx ON identity_recovery_addresses (id, nid); diff --git a/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.mysql.down.sql b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.mysql.down.sql new file mode 100644 index 000000000000..132924783396 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.mysql.down.sql @@ -0,0 +1,24 @@ +ALTER TABLE identity_recovery_codes + DROP FOREIGN KEY identity_recovery_codes_identity_recovery_addresses_id_fk, + DROP FOREIGN KEY identity_recovery_codes_identity_id_fk; + +ALTER TABLE identity_recovery_tokens + DROP FOREIGN KEY identity_recovery_tokens_ibfk_1, + DROP FOREIGN KEY identity_recovery_tokens_identity_id_fk_idx; + +ALTER TABLE identity_recovery_addresses + DROP FOREIGN KEY identity_recovery_addresses_ibfk_1; + +ALTER TABLE identity_recovery_addresses + DROP PRIMARY KEY, + ADD PRIMARY KEY (id), + ADD CONSTRAINT identity_recovery_addresses_ibfk_1 FOREIGN KEY (identity_id) REFERENCES identities(id) ON DELETE CASCADE; + +ALTER TABLE identity_recovery_codes + ADD CONSTRAINT identity_recovery_codes_identity_recovery_addresses_id_fk FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON DELETE CASCADE, + ADD CONSTRAINT identity_recovery_tokens_identity_id_fk FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE CASCADE; -- this foreign key constraint was previously misnamed and this down-migration restores the incorrect name + + +ALTER TABLE identity_recovery_tokens + ADD CONSTRAINT identity_recovery_tokens_ibfk_1 FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses(id) ON DELETE CASCADE, + ADD CONSTRAINT identity_recovery_tokens_identity_id_fk_idx FOREIGN KEY (identity_id) REFERENCES identities(id) ON DELETE CASCADE; diff --git a/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.mysql.up.sql b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.mysql.up.sql new file mode 100644 index 000000000000..34015cca5a41 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.mysql.up.sql @@ -0,0 +1,19 @@ +ALTER TABLE identity_recovery_codes + DROP FOREIGN KEY identity_recovery_codes_identity_recovery_addresses_id_fk, + DROP FOREIGN KEY identity_recovery_tokens_identity_id_fk; -- this foreign key constraint was previously misnamed + +ALTER TABLE identity_recovery_tokens + DROP FOREIGN KEY identity_recovery_tokens_ibfk_1, + DROP FOREIGN KEY identity_recovery_tokens_identity_id_fk_idx; + +ALTER TABLE identity_recovery_addresses + DROP PRIMARY KEY, + ADD PRIMARY KEY (identity_id, id); + +ALTER TABLE identity_recovery_codes + ADD CONSTRAINT identity_recovery_codes_identity_recovery_addresses_id_fk FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses (id) ON DELETE CASCADE, + ADD CONSTRAINT identity_recovery_codes_identity_id_fk FOREIGN KEY (identity_id) REFERENCES identities (id) ON DELETE CASCADE; -- this foreign key constraint was previously misnamed, this is the correct name + +ALTER TABLE identity_recovery_tokens + ADD CONSTRAINT identity_recovery_tokens_ibfk_1 FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses(id) ON DELETE CASCADE, + ADD CONSTRAINT identity_recovery_tokens_identity_id_fk_idx FOREIGN KEY (identity_id) REFERENCES identities(id) ON DELETE CASCADE; diff --git a/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.postgres.down.sql b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.postgres.down.sql new file mode 100644 index 000000000000..4d0b140857a4 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.postgres.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_recovery_addresses + DROP CONSTRAINT identity_recovery_addresses_pkey, + ADD PRIMARY KEY (id); diff --git a/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.postgres.up.sql b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.postgres.up.sql new file mode 100644 index 000000000000..859f0f1bd2c2 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.postgres.up.sql @@ -0,0 +1,17 @@ +CREATE UNIQUE INDEX identity_recovery_addresses_id_uq_idx ON identity_recovery_addresses (id); + +ALTER TABLE identity_recovery_codes + DROP CONSTRAINT identity_recovery_codes_identity_recovery_addresses_id_fk; + +ALTER TABLE identity_recovery_tokens + DROP CONSTRAINT identity_recovery_tokens_identity_recovery_address_id_fkey; + +ALTER TABLE identity_recovery_addresses + DROP CONSTRAINT identity_recovery_addresses_pkey, + ADD PRIMARY KEY (identity_id, id); + +ALTER TABLE identity_recovery_codes + ADD CONSTRAINT identity_recovery_codes_identity_recovery_addresses_id_fk FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses(id) ON DELETE CASCADE; + +ALTER TABLE identity_recovery_tokens + ADD CONSTRAINT identity_recovery_tokens_identity_recovery_address_id_fkey FOREIGN KEY (identity_recovery_address_id) REFERENCES identity_recovery_addresses(id) ON DELETE CASCADE; diff --git a/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.sqlite.down.sql b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.sqlite.down.sql new file mode 100644 index 000000000000..923af86dca4b --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.sqlite.down.sql @@ -0,0 +1,25 @@ +CREATE TABLE "_identity_recovery_addresses_tmp" ( +"id" TEXT PRIMARY KEY, +"via" TEXT NOT NULL, +"value" TEXT NOT NULL, +"identity_id" char(36) NOT NULL, +"created_at" DATETIME NOT NULL, +"updated_at" DATETIME NOT NULL, +"nid" char(36), +FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE +); + +INSERT INTO "_identity_recovery_addresses_tmp" + ("id", "via", "value", "identity_id", "created_at", "updated_at", "nid") +SELECT + "id", "via", "value", "identity_id", "created_at", "updated_at", "nid" +FROM "identity_recovery_addresses"; + +DROP TABLE "identity_recovery_addresses"; +ALTER TABLE "_identity_recovery_addresses_tmp" RENAME TO "identity_recovery_addresses"; + +CREATE INDEX identity_recovery_addresses_nid_id_idx ON identity_recovery_addresses (nid, id); +CREATE INDEX identity_recovery_addresses_id_nid_idx ON identity_recovery_addresses (id, nid); +CREATE UNIQUE INDEX "identity_recovery_addresses_id_uq_idx" ON "identity_recovery_addresses" (id); +CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (nid, via, value); +CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (nid, via, value); diff --git a/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.sqlite.up.sql b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.sqlite.up.sql new file mode 100644 index 000000000000..154e25167585 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000001_identity_recovery_addresses_pk.sqlite.up.sql @@ -0,0 +1,26 @@ +CREATE TABLE "_identity_recovery_addresses_tmp" ( +"id" TEXT NOT NULL, +"via" TEXT NOT NULL, +"value" TEXT NOT NULL, +"identity_id" TEXT NOT NULL, +"created_at" DATETIME NOT NULL, +"updated_at" DATETIME NOT NULL, +"nid" TEXT NOT NULL, +PRIMARY KEY (identity_id,id), +FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE, +FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE +); + +INSERT INTO "_identity_recovery_addresses_tmp" + ("id", "via", "value", "identity_id", "created_at", "updated_at", "nid") +SELECT + "id", "via", "value", "identity_id", "created_at", "updated_at", "nid" +FROM "identity_recovery_addresses"; + +DROP TABLE "identity_recovery_addresses"; +ALTER TABLE "_identity_recovery_addresses_tmp" RENAME TO "identity_recovery_addresses"; + +CREATE INDEX identity_recovery_addresses_nid_id_idx ON identity_recovery_addresses (nid, id); +CREATE UNIQUE INDEX "identity_recovery_addresses_id_uq_idx" ON "identity_recovery_addresses" (id); +CREATE UNIQUE INDEX "identity_recovery_addresses_status_via_uq_idx" ON "identity_recovery_addresses" (nid, via, value); +CREATE INDEX "identity_recovery_addresses_status_via_idx" ON "identity_recovery_addresses" (nid, via, value); diff --git a/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.mysql.down.sql b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.mysql.down.sql new file mode 100644 index 000000000000..e6322379fe13 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.mysql.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_credentials + DROP PRIMARY KEY, + ADD PRIMARY KEY (id(36)); diff --git a/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.mysql.up.sql b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.mysql.up.sql new file mode 100644 index 000000000000..e41634aab4ee --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.mysql.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_credentials + DROP PRIMARY KEY, + ADD PRIMARY KEY (identity_id(36), id(36)); diff --git a/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.postgres.down.sql b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.postgres.down.sql new file mode 100644 index 000000000000..726a86b5bed1 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.postgres.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_credentials + DROP CONSTRAINT identity_credentials_pkey, + ADD PRIMARY KEY (id); diff --git a/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.postgres.up.sql b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.postgres.up.sql new file mode 100644 index 000000000000..02849e53a59c --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.postgres.up.sql @@ -0,0 +1,11 @@ +CREATE UNIQUE INDEX identity_credentials_id_uq_idx ON identity_credentials(id); + +ALTER TABLE identity_credential_identifiers + DROP CONSTRAINT identity_credential_identifiers_identity_credential_id_fkey; + +ALTER TABLE identity_credentials + DROP CONSTRAINT identity_credentials_pkey, + ADD PRIMARY KEY (identity_id, id); + +ALTER TABLE identity_credential_identifiers + ADD CONSTRAINT identity_credential_identifiers_identity_credential_id_fkey FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials(id) ON DELETE CASCADE; diff --git a/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.sqlite.down.sql b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.sqlite.down.sql new file mode 100644 index 000000000000..080f3725be55 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.sqlite.down.sql @@ -0,0 +1,24 @@ +CREATE TABLE "_identity_credentials_tmp" ( +"id" TEXT PRIMARY KEY, +"config" TEXT NOT NULL, +"identity_credential_type_id" char(36) NOT NULL, +"identity_id" char(36) NOT NULL, +"created_at" DATETIME NOT NULL, +"updated_at" DATETIME NOT NULL, +"nid" char(36), version INT NOT NULL DEFAULT '0', +FOREIGN KEY (identity_credential_type_id) REFERENCES identity_credential_types (id) ON UPDATE NO ACTION ON DELETE CASCADE, +FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE +); + +INSERT INTO "_identity_credentials_tmp" + ("id", "config", "identity_credential_type_id", "identity_id", "created_at", "updated_at", "nid", "version") +SELECT + "id", "config", "identity_credential_type_id", "identity_id", "created_at", "updated_at", "nid", "version" +FROM "identity_credentials"; + +DROP TABLE "identity_credentials"; +ALTER TABLE "_identity_credentials_tmp" RENAME TO "identity_credentials"; + +CREATE INDEX identity_credentials_nid_id_idx ON identity_credentials (nid, id); +CREATE INDEX identity_credentials_id_nid_idx ON identity_credentials (id, nid); +CREATE UNIQUE INDEX identity_credentials_id_uq_idx ON identity_credentials (id); diff --git a/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.sqlite.up.sql b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.sqlite.up.sql new file mode 100644 index 000000000000..d35be1e3d9ab --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000002_identity_credentials_pk.sqlite.up.sql @@ -0,0 +1,25 @@ +CREATE TABLE "_identity_credentials_tmp" ( +"id" TEXT NOT NULL, +"config" TEXT NOT NULL, +"identity_credential_type_id" TEXT NOT NULL, +"identity_id" TEXT NOT NULL, +"created_at" DATETIME NOT NULL, +"updated_at" DATETIME NOT NULL, +"nid" TEXT NOT NULL, +"version" INT NOT NULL DEFAULT '0', +PRIMARY KEY (identity_id,id), +FOREIGN KEY (identity_credential_type_id) REFERENCES identity_credential_types (id) ON UPDATE NO ACTION ON DELETE CASCADE, +FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE NO ACTION ON DELETE CASCADE +); + +INSERT INTO "_identity_credentials_tmp" + ("id", "config", "identity_credential_type_id", "identity_id", "created_at", "updated_at", "nid", "version") +SELECT + "id", "config", "identity_credential_type_id", "identity_id", "created_at", "updated_at", "nid", "version" +FROM "identity_credentials"; + +DROP TABLE "identity_credentials"; +ALTER TABLE "_identity_credentials_tmp" RENAME TO "identity_credentials"; + +CREATE INDEX identity_credentials_nid_id_idx ON identity_credentials (nid, id); +CREATE UNIQUE INDEX identity_credentials_id_uq_idx ON identity_credentials (id); diff --git a/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.mysql.down.sql b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.mysql.down.sql new file mode 100644 index 000000000000..568a99a1aadd --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.mysql.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE identity_credential_identifiers + DROP PRIMARY KEY, + ADD PRIMARY KEY(id); diff --git a/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.mysql.up.sql b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.mysql.up.sql new file mode 100644 index 000000000000..d2ece3d0c6b3 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.mysql.up.sql @@ -0,0 +1,17 @@ +ALTER TABLE identity_credential_identifiers ADD COLUMN identity_id char(36) NULL; + +UPDATE + identity_credential_identifiers ici +JOIN + identity_credentials ic + ON ici.identity_credential_id = ic.id + AND ici.nid = ic.nid +SET + ici.identity_id = ic.identity_id +WHERE + ici.identity_id IS NULL; + +ALTER TABLE identity_credential_identifiers + MODIFY identity_id char(36) NOT NULL, + DROP PRIMARY KEY, + ADD PRIMARY KEY(identity_id, identity_credential_id, id); diff --git a/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.postgres.down.sql b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.postgres.down.sql new file mode 100644 index 000000000000..84108c191ab1 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.postgres.down.sql @@ -0,0 +1,4 @@ +ALTER TABLE identity_credential_identifiers + DROP CONSTRAINT identity_credential_identifiers_pkey, + ADD PRIMARY KEY (id), + DROP COLUMN identity_id; diff --git a/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.postgres.up.sql b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.postgres.up.sql new file mode 100644 index 000000000000..32278a8cb66e --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.postgres.up.sql @@ -0,0 +1,19 @@ +ALTER TABLE identity_credential_identifiers ADD COLUMN identity_id UUID NULL; + +CREATE UNIQUE INDEX identity_credential_identifiers_id_uq_idx ON identity_credential_identifiers(id); + +UPDATE + identity_credential_identifiers ici +SET + identity_id = ic.identity_id +FROM + identity_credentials ic +WHERE + ici.identity_credential_id = ic.id + AND ici.nid = ic.nid + AND ici.identity_id IS NULL; + +ALTER TABLE identity_credential_identifiers + ALTER identity_id SET NOT NULL, + DROP CONSTRAINT identity_credential_identifiers_pkey, + ADD PRIMARY KEY(identity_id, identity_credential_id, id); diff --git a/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.sqlite.down.sql b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.sqlite.down.sql new file mode 100644 index 000000000000..9686890480b4 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.sqlite.down.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS "_identity_credential_identifiers_tmp" ( +"id" TEXT PRIMARY KEY, +"identifier" TEXT NOT NULL, +"identity_credential_id" char(36) NOT NULL, +"created_at" DATETIME NOT NULL, +"updated_at" DATETIME NOT NULL, +"nid" char(36), +"identity_credential_type_id" char(36) NOT NULL, +FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON UPDATE NO ACTION ON DELETE CASCADE +); + +INSERT INTO _identity_credential_identifiers_tmp (id, identifier, identity_credential_id, created_at, updated_at, nid, identity_credential_type_id) + SELECT id, identifier, identity_credential_id, created_at, updated_at, nid, identity_credential_type_id + FROM identity_credential_identifiers; + +DROP TABLE identity_credential_identifiers; +ALTER TABLE "_identity_credential_identifiers_tmp" RENAME TO "identity_credential_identifiers"; + +CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_type_uq_idx" ON "identity_credential_identifiers" (nid, identity_credential_type_id, identifier); +CREATE INDEX "identity_credential_identifiers_nid_identity_credential_id_idx" ON "identity_credential_identifiers" (identity_credential_id, nid); +CREATE INDEX identity_credential_identifiers_nid_id_idx ON identity_credential_identifiers (nid, id); +CREATE INDEX identity_credential_identifiers_id_nid_idx ON identity_credential_identifiers (id, nid); +CREATE INDEX identity_credential_identifiers_nid_i_ici_idx ON identity_credential_identifiers (nid, identifier, identity_credential_id); diff --git a/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.sqlite.up.sql b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.sqlite.up.sql new file mode 100644 index 000000000000..5414ebd14166 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000003_identity_credential_identifiers_identity_id.sqlite.up.sql @@ -0,0 +1,30 @@ +CREATE TABLE "_identity_credential_identifiers_tmp" ( +"id" TEXT NOT NULL, +"identifier" TEXT NOT NULL, +"identity_credential_id" TEXT NOT NULL, +"identity_id" TEXT NOT NULL, +"created_at" DATETIME NOT NULL, +"updated_at" DATETIME NOT NULL, +"nid" TEXT NOT NULL, +"identity_credential_type_id" TEXT NOT NULL, +PRIMARY KEY (identity_id, identity_credential_id, id), +FOREIGN KEY (identity_id) REFERENCES identities (id) ON UPDATE RESTRICT ON DELETE CASCADE, +FOREIGN KEY (identity_credential_id) REFERENCES identity_credentials (id) ON UPDATE RESTRICT ON DELETE CASCADE, +FOREIGN KEY (nid) REFERENCES networks (id) ON UPDATE RESTRICT ON DELETE CASCADE +); + + +INSERT INTO _identity_credential_identifiers_tmp (id, identifier, identity_credential_id, created_at, updated_at, nid, identity_credential_type_id, identity_id) + SELECT ici.id, ici.identifier, ici.identity_credential_id, ici.created_at, ici.updated_at, ici.nid, ici.identity_credential_type_id, ic.identity_id + FROM identity_credential_identifiers ici + INNER JOIN identity_credentials ic ON ici.identity_credential_id = ic.id AND ici.nid = ic.nid; + +DROP TABLE identity_credential_identifiers; +ALTER TABLE "_identity_credential_identifiers_tmp" RENAME TO "identity_credential_identifiers"; + +CREATE UNIQUE INDEX "identity_credential_identifiers_identifier_nid_type_uq_idx" ON "identity_credential_identifiers" (nid, identity_credential_type_id, identifier); +CREATE INDEX "identity_credential_identifiers_nid_identity_credential_id_idx" ON "identity_credential_identifiers" (identity_credential_id, nid); +CREATE UNIQUE INDEX "identity_credential_identifiers_id_uq_idx" ON "identity_credential_identifiers" (id); +CREATE INDEX identity_credential_identifiers_nid_id_idx ON identity_credential_identifiers (nid, id); +CREATE INDEX identity_credential_identifiers_id_nid_idx ON identity_credential_identifiers (id, nid); +CREATE INDEX identity_credential_identifiers_nid_i_ici_idx ON identity_credential_identifiers (nid, identifier, identity_credential_id); diff --git a/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.mysql.down.sql b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.mysql.down.sql new file mode 100644 index 000000000000..2e2f4ac81658 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.mysql.down.sql @@ -0,0 +1,7 @@ +ALTER TABLE session_devices + DROP FOREIGN KEY session_devices_ibfk_1, + DROP PRIMARY KEY, + ADD PRIMARY KEY(id), + DROP COLUMN identity_id; +ALTER TABLE session_devices + ADD CONSTRAINT session_devices_ibfk_1 FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE; diff --git a/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.mysql.up.sql b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.mysql.up.sql new file mode 100644 index 000000000000..5e40603830fc --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.mysql.up.sql @@ -0,0 +1,18 @@ +ALTER TABLE session_devices ADD COLUMN identity_id char(36) NULL; + +UPDATE + session_devices sd +JOIN + sessions s ON sd.session_id = s.id +SET + sd.identity_id = s.identity_id +WHERE + sd.identity_id IS NULL; + +ALTER TABLE session_devices + MODIFY identity_id char(36) NOT NULL, + DROP FOREIGN KEY session_devices_ibfk_1, + DROP PRIMARY KEY, + ADD PRIMARY KEY(session_id(36), identity_id(36), id(36)); +ALTER TABLE session_devices + ADD CONSTRAINT session_devices_ibfk_1 FOREIGN KEY (nid,identity_id,session_id) REFERENCES sessions(nid,identity_id,id) ON DELETE CASCADE; diff --git a/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.postgres.down.sql b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.postgres.down.sql new file mode 100644 index 000000000000..68601d6a52b8 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.postgres.down.sql @@ -0,0 +1,4 @@ +ALTER TABLE session_devices + DROP CONSTRAINT session_devices_pkey, + ADD PRIMARY KEY (id), + DROP COLUMN identity_id; diff --git a/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.postgres.up.sql b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.postgres.up.sql new file mode 100644 index 000000000000..26aa7eb513b3 --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.postgres.up.sql @@ -0,0 +1,19 @@ +ALTER TABLE session_devices ADD COLUMN identity_id uuid NULL; + +CREATE UNIQUE INDEX session_devices_id_uq_idx ON session_devices(id); + +UPDATE + session_devices sd +SET + identity_id = s.identity_id +FROM + sessions s +WHERE + sd.session_id = s.id + AND sd.nid = s.nid + AND sd.identity_id IS NULL; + +ALTER TABLE session_devices + ALTER COLUMN identity_id SET NOT NULL, + DROP CONSTRAINT session_devices_pkey, + ADD PRIMARY KEY (session_id, identity_id, id); diff --git a/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.sqlite.down.sql b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.sqlite.down.sql new file mode 100644 index 000000000000..0f84fa6f2baa --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.sqlite.down.sql @@ -0,0 +1,26 @@ +CREATE TABLE IF NOT EXISTS "_session_devices_tmp" +( + "id" UUID PRIMARY KEY NOT NULL, + "ip_address" VARCHAR(50) DEFAULT '', + "user_agent" VARCHAR(512) DEFAULT '', + "location" VARCHAR(512) DEFAULT '', + "nid" UUID NOT NULL, + "session_id" UUID NOT NULL, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL, + CONSTRAINT "session_metadata_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE cascade, + CONSTRAINT "session_metadata_nid_fk" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON DELETE cascade, + CONSTRAINT unique_session_device UNIQUE (nid, session_id, ip_address, user_agent) +); + +INSERT INTO "_session_devices_tmp" + ("id", "ip_address", "user_agent", "location", "nid", "session_id", "created_at", "updated_at") +SELECT + "id", "ip_address", "user_agent", "location", "nid", "session_id", "created_at", "updated_at" +FROM "session_devices"; + +DROP TABLE "session_devices"; +ALTER TABLE "_session_devices_tmp" RENAME TO "session_devices"; + +CREATE INDEX "session_devices_id_nid_idx" ON "session_devices" (id, nid); +CREATE INDEX "session_devices_session_id_nid_idx" ON "session_devices" (session_id, nid); diff --git a/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.sqlite.up.sql b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.sqlite.up.sql new file mode 100644 index 000000000000..7500baef0f7d --- /dev/null +++ b/persistence/sql/migrations/sql/20240208000000000004_session_devices_pk.sqlite.up.sql @@ -0,0 +1,29 @@ +CREATE TABLE IF NOT EXISTS "_session_devices_tmp" +( + "session_id" TEXT NOT NULL, + "identity_id" TEXT NOT NULL, + "id" TEXT NOT NULL, + "ip_address" VARCHAR(50) DEFAULT '', + "user_agent" VARCHAR(512) DEFAULT '', + "location" VARCHAR(512) DEFAULT '', + "nid" TEXT NOT NULL, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL, + PRIMARY KEY (session_id, identity_id, id), + CONSTRAINT "session_metadata_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "sessions" ("id") ON DELETE cascade, + CONSTRAINT "session_metadata_identity_id_fk" FOREIGN KEY ("identity_id") REFERENCES "identities" ("id") ON DELETE cascade, + CONSTRAINT "session_metadata_nid_fk" FOREIGN KEY ("nid") REFERENCES "networks" ("id") ON DELETE cascade, + CONSTRAINT unique_session_device UNIQUE (nid, session_id, ip_address, user_agent), + CONSTRAINT "unique_session_device_id" UNIQUE (id) +); + +INSERT INTO _session_devices_tmp + (id, identity_id, session_id, ip_address, user_agent, "location", nid, created_at, updated_at) +SELECT + sd.id, s.identity_id, sd.session_id, sd.ip_address, sd.user_agent, sd.location, sd.nid, sd.created_at, sd.updated_at +FROM session_devices sd + JOIN sessions s + ON s.id = sd.session_id AND s.nid = sd.nid; + +DROP TABLE "session_devices"; +ALTER TABLE "_session_devices_tmp" RENAME TO "session_devices"; diff --git a/persistence/sql/persister.go b/persistence/sql/persister.go index 85bcdf7466c8..18049c8a895d 100644 --- a/persistence/sql/persister.go +++ b/persistence/sql/persister.go @@ -7,11 +7,11 @@ import ( "context" "embed" "io/fs" + "slices" "time" "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" - "github.com/laher/mergefs" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -20,10 +20,12 @@ import ( "github.com/ory/kratos/persistence" "github.com/ory/kratos/persistence/sql/devices" idpersistence "github.com/ory/kratos/persistence/sql/identity" + "github.com/ory/kratos/persistence/sql/migrations/gomigrations" "github.com/ory/kratos/schema" "github.com/ory/kratos/session" "github.com/ory/kratos/x" "github.com/ory/x/contextx" + "github.com/ory/x/fsx" "github.com/ory/x/networkx" "github.com/ory/x/otelx" "github.com/ory/x/popx" @@ -57,8 +59,9 @@ type ( ) type persisterOptions struct { - extraMigrations []fs.FS - disableLogging bool + extraMigrations []fs.FS + extraGoMigrations popx.Migrations + disableLogging bool } type persisterOption func(o *persisterOptions) @@ -69,6 +72,12 @@ func WithExtraMigrations(fss ...fs.FS) persisterOption { } } +func WithExtraGoMigrations(ms ...popx.Migration) persisterOption { + return func(o *persisterOptions) { + o.extraGoMigrations = ms + } +} + func WithDisabledLogging(v bool) persisterOption { return func(o *persisterOptions) { o.disableLogging = v @@ -85,15 +94,13 @@ func NewPersister(ctx context.Context, r persisterDependencies, c *pop.Connectio logger.Logrus().SetLevel(logrus.WarnLevel) } m, err := popx.NewMigrationBox( - mergefs.Merge( - append( - []fs.FS{ - migrations, networkx.Migrations, - }, - o.extraMigrations..., - )..., - ), + fsx.Merge(append([]fs.FS{migrations, networkx.Migrations}, o.extraMigrations...)...), popx.NewMigrator(c, logger, r.Tracer(ctx), 0), + popx.WithGoMigrations(slices.Concat( + gomigrations.IdentityPrimaryKeysStep1, + gomigrations.IdentityPrimaryKeysStep2, + o.extraGoMigrations, + )), ) if err != nil { return nil, err diff --git a/persistence/sql/persister_session.go b/persistence/sql/persister_session.go index 6279af56d26a..85e3b7de43d2 100644 --- a/persistence/sql/persister_session.go +++ b/persistence/sql/persister_session.go @@ -247,7 +247,11 @@ func (p *Persister) UpsertSession(ctx context.Context, s *session.Session) (err ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UpsertSession") defer otelx.End(span, &err) + s.IdentityID = s.Identity.ID s.NID = p.NetworkID(ctx) + if s.NID != s.Identity.NID { + return errors.New("nid mismatch") + } var updated bool defer func() { @@ -289,6 +293,7 @@ func (p *Persister) UpsertSession(ctx context.Context, s *session.Session) (err for i := range s.Devices { device := &(s.Devices[i]) device.SessionID = s.ID + device.IdentityID = s.IdentityID device.NID = s.NID if device.Location != nil { diff --git a/session/session.go b/session/session.go index 63261dce807f..55fcb0f64f6b 100644 --- a/session/session.go +++ b/session/session.go @@ -49,6 +49,9 @@ type Device struct { // SessionID is a helper struct field for gobuffalo.pop. SessionID uuid.UUID `json:"-" faker:"-" db:"session_id"` + // IdentityID is a helper struct field for gobuffalo.pop. + IdentityID uuid.UUID `json:"-" faker:"-" db:"identity_id"` + // IPAddress of the client IPAddress *string `json:"ip_address" faker:"ptr_ipv4" db:"ip_address"` @@ -258,8 +261,9 @@ func NewInactiveSession() *Session { func (s *Session) SetSessionDeviceInformation(r *http.Request) { device := Device{ - SessionID: s.ID, - IPAddress: pointerx.Ptr(httpx.ClientIP(r)), + SessionID: s.ID, + IdentityID: s.IdentityID, + IPAddress: pointerx.Ptr(httpx.ClientIP(r)), } agent := r.Header["User-Agent"] diff --git a/session/session_test.go b/session/session_test.go index 75fc61ea300a..1c00dc227a70 100644 --- a/session/session_test.go +++ b/session/session_test.go @@ -109,7 +109,8 @@ func TestSession(t *testing.T) { assert.Equal(t, identity.NoAuthenticatorAssuranceLevel, s.AuthenticatorAssuranceLevel) assert.Equal(t, authAt, s.AuthenticatedAt) assert.Equal(t, 1, len(s.Devices)) - assert.Equal(t, s.ID.String(), s.Devices[0].SessionID.String()) + assert.Equal(t, s.ID, s.Devices[0].SessionID) + assert.Equal(t, s.IdentityID, s.Devices[0].IdentityID) assert.Equal(t, tc.expected, *s.Devices[0].IPAddress) assert.Equal(t, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", *s.Devices[0].UserAgent) assert.Equal(t, "", *s.Devices[0].Location) @@ -129,9 +130,10 @@ func TestSession(t *testing.T) { assert.Equal(t, identity.NoAuthenticatorAssuranceLevel, s.AuthenticatorAssuranceLevel) assert.Equal(t, authAt, s.AuthenticatedAt) assert.Equal(t, 1, len(s.Devices)) - assert.Equal(t, s.ID.String(), s.Devices[0].SessionID.String()) - assert.NotNil(t, s.Devices[0].UpdatedAt) - assert.NotNil(t, s.Devices[0].CreatedAt) + assert.Equal(t, s.ID, s.Devices[0].SessionID) + assert.Equal(t, s.IdentityID, s.Devices[0].IdentityID) + assert.NotZero(t, s.Devices[0].UpdatedAt) + assert.NotZero(t, s.Devices[0].CreatedAt) assert.Equal(t, "54.155.246.155", *s.Devices[0].IPAddress) assert.Equal(t, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", *s.Devices[0].UserAgent) assert.Equal(t, "", *s.Devices[0].Location) @@ -149,9 +151,10 @@ func TestSession(t *testing.T) { assert.Equal(t, identity.NoAuthenticatorAssuranceLevel, s.AuthenticatorAssuranceLevel) assert.Equal(t, authAt, s.AuthenticatedAt) assert.Equal(t, 1, len(s.Devices)) - assert.Equal(t, s.ID.String(), s.Devices[0].SessionID.String()) - assert.NotNil(t, s.Devices[0].UpdatedAt) - assert.NotNil(t, s.Devices[0].CreatedAt) + assert.Equal(t, s.ID, s.Devices[0].SessionID) + assert.Equal(t, s.IdentityID, s.Devices[0].IdentityID) + assert.NotZero(t, s.Devices[0].UpdatedAt) + assert.NotZero(t, s.Devices[0].CreatedAt) assert.Equal(t, "54.155.246.155", *s.Devices[0].IPAddress) assert.Equal(t, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", *s.Devices[0].UserAgent) assert.Equal(t, "", *s.Devices[0].Location) @@ -170,7 +173,8 @@ func TestSession(t *testing.T) { assert.Equal(t, identity.NoAuthenticatorAssuranceLevel, s.AuthenticatorAssuranceLevel) assert.Equal(t, authAt, s.AuthenticatedAt) assert.Equal(t, 1, len(s.Devices)) - assert.Equal(t, s.ID.String(), s.Devices[0].SessionID.String()) + assert.Equal(t, s.ID, s.Devices[0].SessionID) + assert.Equal(t, s.IdentityID, s.Devices[0].IdentityID) assert.Equal(t, "54.155.246.232", *s.Devices[0].IPAddress) assert.Equal(t, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", *s.Devices[0].UserAgent) assert.Equal(t, "Munich, Germany", *s.Devices[0].Location) diff --git a/session/test/persistence.go b/session/test/persistence.go index d124aa23eb4f..dc5d80f13390 100644 --- a/session/test/persistence.go +++ b/session/test/persistence.go @@ -90,6 +90,7 @@ func TestPersister(ctx context.Context, conf *config.Config, p interface { for i, d := range actual { assert.Equal(t, expected.Devices[i].SessionID, d.SessionID) + assert.Equal(t, expected.Devices[i].IdentityID, d.IdentityID) assert.Equal(t, expected.Devices[i].NID, d.NID) assert.Equal(t, *expected.Devices[i].IPAddress, *d.IPAddress) assert.Equal(t, expected.Devices[i].UserAgent, d.UserAgent) @@ -149,11 +150,15 @@ func TestPersister(ctx context.Context, conf *config.Config, p interface { var identity2Session session.Session require.NoError(t, faker.FakeData(&identity2)) require.NoError(t, faker.FakeData(&identity2Session)) + require.Zero(t, identity2.ID) + require.Zero(t, identity2Session.ID) // Create seed identities _, l := testhelpers.NewNetwork(t, ctx, p) require.NoError(t, l.CreateIdentity(ctx, &identity1)) require.NoError(t, l.CreateIdentity(ctx, &identity2)) + require.NotZero(t, identity1.ID) + require.NotZero(t, identity2.ID) seedSessionIDs := make([]uuid.UUID, 5) seedSessionsList := make([]session.Session, 5)