| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959 |
- //go:build integration
- package repository
- import (
- "context"
- "database/sql"
- "os"
- "path/filepath"
- "strconv"
- "testing"
- "github.com/stretchr/testify/require"
- )
- func TestAuthIdentityLegacyExternalBackfillMigration(t *testing.T) {
- tx := testTx(t)
- ctx := context.Background()
- migrationPath := filepath.Join("..", "..", "migrations", "115_auth_identity_legacy_external_backfill.sql")
- migrationSQL, err := os.ReadFile(migrationPath)
- require.NoError(t, err)
- prepareLegacyExternalIdentitiesTable(t, tx, ctx)
- truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
- var linuxDoUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-linuxdo@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&linuxDoUserID))
- var wechatUnionUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-wechat-union@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&wechatUnionUserID))
- var wechatOpenIDOnlyUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-wechat-openid@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&wechatOpenIDOnlyUserID))
- var syntheticAuthIdentityID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO auth_identities (user_id, provider_type, provider_key, provider_subject, metadata)
- VALUES ($1, 'wechat', 'wechat-main', 'openid-synthetic', '{"backfill_source":"synthetic_email"}'::jsonb)
- RETURNING id`, wechatOpenIDOnlyUserID).Scan(&syntheticAuthIdentityID))
- var linuxDoLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'linuxdo', 'linuxdo-user-1', NULL, 'linux-user', 'Linux User', '{"source":"legacy"}')
- RETURNING id
- `, linuxDoUserID).Scan(&linuxDoLegacyID))
- var wechatUnionLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-union-1', 'union-1', 'wechat-union-user', 'WeChat Union User', '{"channel":"oa","appid":"wx-app-1"}')
- RETURNING id
- `, wechatUnionUserID).Scan(&wechatUnionLegacyID))
- var wechatOpenIDLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-only-1', NULL, 'wechat-openid-user', 'WeChat OpenID User', '{"channel":"oa","appid":"wx-app-2"}')
- RETURNING id
- `, wechatOpenIDOnlyUserID).Scan(&wechatOpenIDLegacyID))
- _, err = tx.ExecContext(ctx, string(migrationSQL))
- require.NoError(t, err)
- var linuxDoCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identities
- WHERE user_id = $1
- AND provider_type = 'linuxdo'
- AND provider_key = 'linuxdo'
- AND provider_subject = 'linuxdo-user-1'
- `, linuxDoUserID).Scan(&linuxDoCount))
- require.Equal(t, 1, linuxDoCount)
- var wechatSubject string
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT provider_subject
- FROM auth_identities
- WHERE user_id = $1
- AND provider_type = 'wechat'
- AND provider_key = 'wechat-main'
- AND provider_subject = 'union-1'
- `, wechatUnionUserID).Scan(&wechatSubject))
- require.Equal(t, "union-1", wechatSubject)
- var wechatChannelCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_channels channel
- JOIN auth_identities ai ON ai.id = channel.identity_id
- WHERE ai.user_id = $1
- AND channel.provider_type = 'wechat'
- AND channel.provider_key = 'wechat-main'
- AND channel.channel = 'oa'
- AND channel.channel_app_id = 'wx-app-1'
- AND channel.channel_subject = 'openid-union-1'
- `, wechatUnionUserID).Scan(&wechatChannelCount))
- require.Equal(t, 1, wechatChannelCount)
- var legacyOpenIDOnlyReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'wechat_openid_only_requires_remediation'
- AND report_key = $1
- `, "legacy_external_identity:"+strconv.FormatInt(wechatOpenIDLegacyID, 10)).Scan(&legacyOpenIDOnlyReportCount))
- require.Equal(t, 1, legacyOpenIDOnlyReportCount)
- var syntheticReviewCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'wechat_openid_only_requires_remediation'
- AND report_key = $1
- `, "synthetic_auth_identity:"+strconv.FormatInt(syntheticAuthIdentityID, 10)).Scan(&syntheticReviewCount))
- require.Equal(t, 1, syntheticReviewCount)
- var unionLegacyReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'wechat_openid_only_requires_remediation'
- AND report_key = $1
- `, "legacy_external_identity:"+strconv.FormatInt(wechatUnionLegacyID, 10)).Scan(&unionLegacyReportCount))
- require.Zero(t, unionLegacyReportCount)
- require.NotZero(t, linuxDoLegacyID)
- }
- func TestAuthIdentityLegacyExternalBackfillMigration_IsSafeWhenLegacyTableMissing(t *testing.T) {
- tx := testTx(t)
- ctx := context.Background()
- migrationPath := filepath.Join("..", "..", "migrations", "115_auth_identity_legacy_external_backfill.sql")
- migrationSQL, err := os.ReadFile(migrationPath)
- require.NoError(t, err)
- var beforeCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- `).Scan(&beforeCount))
- _, err = tx.ExecContext(ctx, string(migrationSQL))
- require.NoError(t, err)
- var afterCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- `).Scan(&afterCount))
- require.Equal(t, beforeCount, afterCount)
- }
- func TestAuthIdentityLegacyExternalMigrations_ChainHandlesMalformedAndNonObjectMetadata(t *testing.T) {
- tx := testTx(t)
- ctx := context.Background()
- migration115Path := filepath.Join("..", "..", "migrations", "115_auth_identity_legacy_external_backfill.sql")
- migration115SQL, err := os.ReadFile(migration115Path)
- require.NoError(t, err)
- migration116Path := filepath.Join("..", "..", "migrations", "116_auth_identity_legacy_external_safety_reports.sql")
- migration116SQL, err := os.ReadFile(migration116Path)
- require.NoError(t, err)
- prepareLegacyExternalIdentitiesTable(t, tx, ctx)
- truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
- var linuxDoMalformedUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-linuxdo-malformed@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&linuxDoMalformedUserID))
- var linuxDoArrayUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-linuxdo-array@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&linuxDoArrayUserID))
- var wechatUnionArrayUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-wechat-array@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&wechatUnionArrayUserID))
- var wechatOpenIDArrayUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-wechat-openid-array@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&wechatOpenIDArrayUserID))
- var linuxDoMalformedLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'linuxdo', 'linuxdo-malformed', NULL, 'legacy-linuxdo-malformed', 'Legacy LinuxDo Malformed', '{invalid')
- RETURNING id
- `, linuxDoMalformedUserID).Scan(&linuxDoMalformedLegacyID))
- var linuxDoArrayLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'linuxdo', 'linuxdo-array', NULL, 'legacy-linuxdo-array', 'Legacy LinuxDo Array', '["legacy-linuxdo-array"]')
- RETURNING id
- `, linuxDoArrayUserID).Scan(&linuxDoArrayLegacyID))
- var wechatUnionArrayLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-array', 'union-array', 'legacy-wechat-array', 'Legacy WeChat Array', '["legacy-wechat-array"]')
- RETURNING id
- `, wechatUnionArrayUserID).Scan(&wechatUnionArrayLegacyID))
- var wechatOpenIDArrayLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-array-only', NULL, 'legacy-wechat-array-only', 'Legacy WeChat Array Only', '["legacy-wechat-openid-array"]')
- RETURNING id
- `, wechatOpenIDArrayUserID).Scan(&wechatOpenIDArrayLegacyID))
- _, err = tx.ExecContext(ctx, string(migration115SQL))
- require.NoError(t, err)
- _, err = tx.ExecContext(ctx, string(migration116SQL))
- require.NoError(t, err)
- var linuxDoMalformedMetadataType string
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT jsonb_typeof(metadata)
- FROM auth_identities
- WHERE user_id = $1
- AND provider_type = 'linuxdo'
- AND provider_key = 'linuxdo'
- AND provider_subject = 'linuxdo-malformed'
- `, linuxDoMalformedUserID).Scan(&linuxDoMalformedMetadataType))
- require.Equal(t, "object", linuxDoMalformedMetadataType)
- var linuxDoArrayMetadataType string
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT jsonb_typeof(metadata)
- FROM auth_identities
- WHERE user_id = $1
- AND provider_type = 'linuxdo'
- AND provider_key = 'linuxdo'
- AND provider_subject = 'linuxdo-array'
- `, linuxDoArrayUserID).Scan(&linuxDoArrayMetadataType))
- require.Equal(t, "object", linuxDoArrayMetadataType)
- var wechatUnionArrayMetadataType string
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT jsonb_typeof(metadata)
- FROM auth_identities
- WHERE user_id = $1
- AND provider_type = 'wechat'
- AND provider_key = 'wechat-main'
- AND provider_subject = 'union-array'
- `, wechatUnionArrayUserID).Scan(&wechatUnionArrayMetadataType))
- require.Equal(t, "object", wechatUnionArrayMetadataType)
- var invalidJSONReportDetailsType string
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT jsonb_typeof(details)
- FROM auth_identity_migration_reports
- WHERE report_type = 'legacy_external_identity_invalid_metadata_json'
- AND report_key = $1
- `, "legacy_external_identity:"+strconv.FormatInt(linuxDoMalformedLegacyID, 10)).Scan(&invalidJSONReportDetailsType))
- require.Equal(t, "object", invalidJSONReportDetailsType)
- var openIDOnlyReportDetailsType string
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT jsonb_typeof(details)
- FROM auth_identity_migration_reports
- WHERE report_type = 'wechat_openid_only_requires_remediation'
- AND report_key = $1
- `, "legacy_external_identity:"+strconv.FormatInt(wechatOpenIDArrayLegacyID, 10)).Scan(&openIDOnlyReportDetailsType))
- require.Equal(t, "object", openIDOnlyReportDetailsType)
- var preservedArrayMetadataCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identities
- WHERE id IN (
- SELECT id
- FROM auth_identities
- WHERE (user_id = $1 AND provider_subject = 'linuxdo-array')
- OR (user_id = $2 AND provider_subject = 'union-array')
- )
- AND metadata ? '_legacy_metadata_raw_json'
- `, linuxDoArrayUserID, wechatUnionArrayUserID).Scan(&preservedArrayMetadataCount))
- require.Equal(t, 2, preservedArrayMetadataCount)
- require.NotZero(t, linuxDoArrayLegacyID)
- require.NotZero(t, wechatUnionArrayLegacyID)
- }
- func TestAuthIdentityLegacyExternalSafetyMigration_ReportsConflictsAndDowngradesInvalidJSON(t *testing.T) {
- tx := testTx(t)
- ctx := context.Background()
- migrationPath := filepath.Join("..", "..", "migrations", "116_auth_identity_legacy_external_safety_reports.sql")
- migrationSQL, err := os.ReadFile(migrationPath)
- require.NoError(t, err)
- prepareLegacyExternalIdentitiesTable(t, tx, ctx)
- truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
- userIDs := make([]int64, 0, 8)
- for _, email := range []string{
- "linuxdo-conflict-legacy@example.com",
- "linuxdo-conflict-owner@example.com",
- "wechat-conflict-legacy@example.com",
- "wechat-conflict-owner@example.com",
- "wechat-channel-legacy@example.com",
- "wechat-channel-owner@example.com",
- "linuxdo-invalid-json@example.com",
- "wechat-openid-invalid-json@example.com",
- } {
- var userID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ($1, 'hash', 'user', 'active', 0, 1)
- RETURNING id`, email).Scan(&userID))
- userIDs = append(userIDs, userID)
- }
- linuxdoConflictLegacyUserID := userIDs[0]
- linuxdoConflictOwnerUserID := userIDs[1]
- wechatConflictLegacyUserID := userIDs[2]
- wechatConflictOwnerUserID := userIDs[3]
- wechatChannelLegacyUserID := userIDs[4]
- wechatChannelOwnerUserID := userIDs[5]
- linuxdoInvalidJSONUserID := userIDs[6]
- wechatInvalidOpenIDUserID := userIDs[7]
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO auth_identities (user_id, provider_type, provider_key, provider_subject, metadata)
- VALUES ($1, 'linuxdo', 'linuxdo', 'linuxdo-conflict', '{}'::jsonb)
- RETURNING id`, linuxdoConflictOwnerUserID).Scan(new(int64)))
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO auth_identities (user_id, provider_type, provider_key, provider_subject, metadata)
- VALUES ($1, 'wechat', 'wechat-main', 'union-conflict', '{}'::jsonb)
- RETURNING id`, wechatConflictOwnerUserID).Scan(new(int64)))
- var wechatChannelOwnerIdentityID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO auth_identities (user_id, provider_type, provider_key, provider_subject, metadata)
- VALUES ($1, 'wechat', 'wechat-main', 'union-channel-owner', '{}'::jsonb)
- RETURNING id`, wechatChannelOwnerUserID).Scan(&wechatChannelOwnerIdentityID))
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO auth_identity_channels (
- identity_id,
- provider_type,
- provider_key,
- channel,
- channel_app_id,
- channel_subject,
- metadata
- )
- VALUES ($1, 'wechat', 'wechat-main', 'oa', 'wx-app-conflict', 'openid-channel-conflict', '{}'::jsonb)
- RETURNING id`, wechatChannelOwnerIdentityID).Scan(new(int64)))
- var linuxdoConflictLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'linuxdo', 'linuxdo-conflict', NULL, 'legacy-linuxdo', 'Legacy LinuxDo Conflict', '{"source":"legacy"}')
- RETURNING id
- `, linuxdoConflictLegacyUserID).Scan(&linuxdoConflictLegacyID))
- var wechatConflictLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-union-conflict', 'union-conflict', 'legacy-wechat', 'Legacy WeChat Conflict', '{"channel":"oa","appid":"wx-app-conflict-canon"}')
- RETURNING id
- `, wechatConflictLegacyUserID).Scan(&wechatConflictLegacyID))
- var wechatChannelConflictLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-channel-conflict', 'union-channel-legacy', 'legacy-wechat-channel', 'Legacy WeChat Channel Conflict', '{"channel":"oa","appid":"wx-app-conflict"}')
- RETURNING id
- `, wechatChannelLegacyUserID).Scan(&wechatChannelConflictLegacyID))
- var linuxdoInvalidJSONLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'linuxdo', 'linuxdo-invalid-json', NULL, 'legacy-linuxdo-invalid', 'Legacy LinuxDo Invalid JSON', '{invalid')
- RETURNING id
- `, linuxdoInvalidJSONUserID).Scan(&linuxdoInvalidJSONLegacyID))
- var wechatInvalidOpenIDLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-invalid-json-only', NULL, 'legacy-wechat-invalid', 'Legacy WeChat Invalid JSON', '{still-invalid')
- RETURNING id
- `, wechatInvalidOpenIDUserID).Scan(&wechatInvalidOpenIDLegacyID))
- _, err = tx.ExecContext(ctx, string(migrationSQL))
- require.NoError(t, err)
- var linuxdoConflictReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'legacy_external_identity_conflict'
- AND report_key = $1
- `, "legacy_external_identity:"+strconv.FormatInt(linuxdoConflictLegacyID, 10)).Scan(&linuxdoConflictReportCount))
- require.Equal(t, 1, linuxdoConflictReportCount)
- var wechatConflictReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'legacy_external_identity_conflict'
- AND report_key = $1
- `, "legacy_external_identity:"+strconv.FormatInt(wechatConflictLegacyID, 10)).Scan(&wechatConflictReportCount))
- require.Equal(t, 1, wechatConflictReportCount)
- var channelConflictReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'legacy_external_channel_conflict'
- AND report_key = $1
- `, "legacy_external_identity:"+strconv.FormatInt(wechatChannelConflictLegacyID, 10)).Scan(&channelConflictReportCount))
- require.Equal(t, 1, channelConflictReportCount)
- var invalidJSONReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'legacy_external_identity_invalid_metadata_json'
- AND report_key IN ($1, $2)
- `, "legacy_external_identity:"+strconv.FormatInt(linuxdoInvalidJSONLegacyID, 10), "legacy_external_identity:"+strconv.FormatInt(wechatInvalidOpenIDLegacyID, 10)).Scan(&invalidJSONReportCount))
- require.Equal(t, 2, invalidJSONReportCount)
- var linuxdoInvalidIdentityCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identities
- WHERE user_id = $1
- AND provider_type = 'linuxdo'
- AND provider_key = 'linuxdo'
- AND provider_subject = 'linuxdo-invalid-json'
- `, linuxdoInvalidJSONUserID).Scan(&linuxdoInvalidIdentityCount))
- require.Equal(t, 1, linuxdoInvalidIdentityCount)
- var wechatOpenIDOnlyReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'wechat_openid_only_requires_remediation'
- AND report_key = $1
- `, "legacy_external_identity:"+strconv.FormatInt(wechatInvalidOpenIDLegacyID, 10)).Scan(&wechatOpenIDOnlyReportCount))
- require.Equal(t, 1, wechatOpenIDOnlyReportCount)
- }
- func TestAuthIdentityLegacyExternalSafetyMigration_IsSafeWhenLegacyTableMissing(t *testing.T) {
- tx := testTx(t)
- ctx := context.Background()
- migrationPath := filepath.Join("..", "..", "migrations", "116_auth_identity_legacy_external_safety_reports.sql")
- migrationSQL, err := os.ReadFile(migrationPath)
- require.NoError(t, err)
- var beforeCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- `).Scan(&beforeCount))
- _, err = tx.ExecContext(ctx, string(migrationSQL))
- require.NoError(t, err)
- var afterCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- `).Scan(&afterCount))
- require.Equal(t, beforeCount, afterCount)
- }
- func TestAuthIdentityLegacyExternalBackfillMigration_SkipsAmbiguousCanonicalSubjects(t *testing.T) {
- tx := testTx(t)
- ctx := context.Background()
- migrationPath := filepath.Join("..", "..", "migrations", "115_auth_identity_legacy_external_backfill.sql")
- migrationSQL, err := os.ReadFile(migrationPath)
- require.NoError(t, err)
- prepareLegacyExternalIdentitiesTable(t, tx, ctx)
- truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
- var linuxDoFirstUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-linuxdo-ambiguous-a@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&linuxDoFirstUserID))
- var linuxDoSecondUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-linuxdo-ambiguous-b@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&linuxDoSecondUserID))
- var wechatFirstUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-wechat-ambiguous-a@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&wechatFirstUserID))
- var wechatSecondUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-wechat-ambiguous-b@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&wechatSecondUserID))
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'linuxdo', 'linuxdo-ambiguous-subject', NULL, 'legacy-linuxdo-ambiguous-a', 'Legacy LinuxDo Ambiguous A', '{"source":"legacy"}')
- RETURNING id
- `, linuxDoFirstUserID).Scan(new(int64)))
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'linuxdo', 'linuxdo-ambiguous-subject', NULL, 'legacy-linuxdo-ambiguous-b', 'Legacy LinuxDo Ambiguous B', '{"source":"legacy"}')
- RETURNING id
- `, linuxDoSecondUserID).Scan(new(int64)))
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-ambiguous-a', 'union-ambiguous-subject', 'legacy-wechat-ambiguous-a', 'Legacy WeChat Ambiguous A', '{"channel":"oa","appid":"wx-ambiguous-a"}')
- RETURNING id
- `, wechatFirstUserID).Scan(new(int64)))
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-ambiguous-b', 'union-ambiguous-subject', 'legacy-wechat-ambiguous-b', 'Legacy WeChat Ambiguous B', '{"channel":"oa","appid":"wx-ambiguous-b"}')
- RETURNING id
- `, wechatSecondUserID).Scan(new(int64)))
- _, err = tx.ExecContext(ctx, string(migrationSQL))
- require.NoError(t, err)
- var linuxDoIdentityCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identities
- WHERE provider_type = 'linuxdo'
- AND provider_key = 'linuxdo'
- AND provider_subject = 'linuxdo-ambiguous-subject'
- `).Scan(&linuxDoIdentityCount))
- require.Zero(t, linuxDoIdentityCount)
- var wechatIdentityCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identities
- WHERE provider_type = 'wechat'
- AND provider_key = 'wechat-main'
- AND provider_subject = 'union-ambiguous-subject'
- `).Scan(&wechatIdentityCount))
- require.Zero(t, wechatIdentityCount)
- var wechatChannelCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_channels
- WHERE provider_type = 'wechat'
- AND provider_key = 'wechat-main'
- AND channel = 'oa'
- AND channel_app_id IN ('wx-ambiguous-a', 'wx-ambiguous-b')
- `).Scan(&wechatChannelCount))
- require.Zero(t, wechatChannelCount)
- }
- func TestAuthIdentityLegacyExternalMigrations_ReportAmbiguousCanonicalSubjectsWithoutWinnerAttribution(t *testing.T) {
- tx := testTx(t)
- ctx := context.Background()
- migration115Path := filepath.Join("..", "..", "migrations", "115_auth_identity_legacy_external_backfill.sql")
- migration115SQL, err := os.ReadFile(migration115Path)
- require.NoError(t, err)
- migration116Path := filepath.Join("..", "..", "migrations", "116_auth_identity_legacy_external_safety_reports.sql")
- migration116SQL, err := os.ReadFile(migration116Path)
- require.NoError(t, err)
- prepareLegacyExternalIdentitiesTable(t, tx, ctx)
- truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
- var linuxDoFirstUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-linuxdo-conflict-a@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&linuxDoFirstUserID))
- var linuxDoSecondUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-linuxdo-conflict-b@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&linuxDoSecondUserID))
- var wechatFirstUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-wechat-conflict-a@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&wechatFirstUserID))
- var wechatSecondUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-wechat-conflict-b@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&wechatSecondUserID))
- var linuxDoFirstLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'linuxdo', 'linuxdo-conflict-subject', NULL, 'legacy-linuxdo-conflict-a', 'Legacy LinuxDo Conflict A', '{"source":"legacy"}')
- RETURNING id
- `, linuxDoFirstUserID).Scan(&linuxDoFirstLegacyID))
- var linuxDoSecondLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'linuxdo', 'linuxdo-conflict-subject', NULL, 'legacy-linuxdo-conflict-b', 'Legacy LinuxDo Conflict B', '{"source":"legacy"}')
- RETURNING id
- `, linuxDoSecondUserID).Scan(&linuxDoSecondLegacyID))
- var wechatFirstLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-conflict-a', 'union-conflict-subject', 'legacy-wechat-conflict-a', 'Legacy WeChat Conflict A', '{"channel":"oa","appid":"wx-conflict-a"}')
- RETURNING id
- `, wechatFirstUserID).Scan(&wechatFirstLegacyID))
- var wechatSecondLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'wechat', 'openid-conflict-b', 'union-conflict-subject', 'legacy-wechat-conflict-b', 'Legacy WeChat Conflict B', '{"channel":"oa","appid":"wx-conflict-b"}')
- RETURNING id
- `, wechatSecondUserID).Scan(&wechatSecondLegacyID))
- _, err = tx.ExecContext(ctx, string(migration115SQL))
- require.NoError(t, err)
- _, err = tx.ExecContext(ctx, string(migration116SQL))
- require.NoError(t, err)
- var identityCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identities
- WHERE (provider_type = 'linuxdo' AND provider_key = 'linuxdo' AND provider_subject = 'linuxdo-conflict-subject')
- OR (provider_type = 'wechat' AND provider_key = 'wechat-main' AND provider_subject = 'union-conflict-subject')
- `).Scan(&identityCount))
- require.Zero(t, identityCount)
- var conflictReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'legacy_external_identity_conflict'
- AND report_key IN ($1, $2, $3, $4)
- `, "legacy_external_identity:"+strconv.FormatInt(linuxDoFirstLegacyID, 10), "legacy_external_identity:"+strconv.FormatInt(linuxDoSecondLegacyID, 10), "legacy_external_identity:"+strconv.FormatInt(wechatFirstLegacyID, 10), "legacy_external_identity:"+strconv.FormatInt(wechatSecondLegacyID, 10)).Scan(&conflictReportCount))
- require.Equal(t, 4, conflictReportCount)
- var winnerAttributedReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'legacy_external_identity_conflict'
- AND report_key IN ($1, $2, $3, $4)
- AND details ->> 'existing_identity_id' IS NOT NULL
- `, "legacy_external_identity:"+strconv.FormatInt(linuxDoFirstLegacyID, 10), "legacy_external_identity:"+strconv.FormatInt(linuxDoSecondLegacyID, 10), "legacy_external_identity:"+strconv.FormatInt(wechatFirstLegacyID, 10), "legacy_external_identity:"+strconv.FormatInt(wechatSecondLegacyID, 10)).Scan(&winnerAttributedReportCount))
- require.Zero(t, winnerAttributedReportCount)
- }
- func TestAuthIdentityMigrationReportTypeWideningPreflightKeeps109And116SafeBefore121(t *testing.T) {
- tx := testTx(t)
- ctx := context.Background()
- migration108aPath := filepath.Join("..", "..", "migrations", "108a_widen_auth_identity_migration_report_type.sql")
- migration108aSQL, err := os.ReadFile(migration108aPath)
- require.NoError(t, err)
- migration109Path := filepath.Join("..", "..", "migrations", "109_auth_identity_compat_backfill.sql")
- migration109SQL, err := os.ReadFile(migration109Path)
- require.NoError(t, err)
- migration116Path := filepath.Join("..", "..", "migrations", "116_auth_identity_legacy_external_safety_reports.sql")
- migration116SQL, err := os.ReadFile(migration116Path)
- require.NoError(t, err)
- prepareLegacyExternalIdentitiesTable(t, tx, ctx)
- truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
- _, err = tx.ExecContext(ctx, `
- ALTER TABLE auth_identity_migration_reports
- ALTER COLUMN report_type TYPE VARCHAR(40);
- `)
- require.NoError(t, err)
- var oidcSyntheticUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('oidc-before-121@oidc-connect.invalid', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&oidcSyntheticUserID))
- var linuxdoLegacyUserID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO users (email, password_hash, role, status, balance, concurrency)
- VALUES ('legacy-linuxdo-before-121@example.com', 'hash', 'user', 'active', 0, 1)
- RETURNING id`).Scan(&linuxdoLegacyUserID))
- var invalidMetadataLegacyID int64
- require.NoError(t, tx.QueryRowContext(ctx, `
- INSERT INTO user_external_identities (
- user_id,
- provider,
- provider_user_id,
- provider_union_id,
- provider_username,
- display_name,
- metadata
- ) VALUES ($1, 'linuxdo', 'linuxdo-before-121', NULL, 'legacy-linuxdo-before-121', 'Legacy LinuxDo Before 121', '{invalid')
- RETURNING id
- `, linuxdoLegacyUserID).Scan(&invalidMetadataLegacyID))
- _, err = tx.ExecContext(ctx, string(migration108aSQL))
- require.NoError(t, err)
- _, err = tx.ExecContext(ctx, string(migration109SQL))
- require.NoError(t, err)
- _, err = tx.ExecContext(ctx, string(migration116SQL))
- require.NoError(t, err)
- var reportTypeWidth int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT character_maximum_length
- FROM information_schema.columns
- WHERE table_schema = 'public'
- AND table_name = 'auth_identity_migration_reports'
- AND column_name = 'report_type'
- `).Scan(&reportTypeWidth))
- require.Equal(t, 80, reportTypeWidth)
- var oidcSyntheticRecoveryReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'oidc_synthetic_email_requires_manual_recovery'
- AND report_key = $1
- `, strconv.FormatInt(oidcSyntheticUserID, 10)).Scan(&oidcSyntheticRecoveryReportCount))
- require.Equal(t, 1, oidcSyntheticRecoveryReportCount)
- var invalidMetadataReportCount int
- require.NoError(t, tx.QueryRowContext(ctx, `
- SELECT COUNT(*)
- FROM auth_identity_migration_reports
- WHERE report_type = 'legacy_external_identity_invalid_metadata_json'
- AND report_key = $1
- `, "legacy_external_identity:"+strconv.FormatInt(invalidMetadataLegacyID, 10)).Scan(&invalidMetadataReportCount))
- require.Equal(t, 1, invalidMetadataReportCount)
- }
- func prepareLegacyExternalIdentitiesTable(t *testing.T, tx *sql.Tx, ctx context.Context) {
- t.Helper()
- _, err := tx.ExecContext(ctx, `
- CREATE TABLE IF NOT EXISTS user_external_identities (
- id BIGSERIAL PRIMARY KEY,
- user_id BIGINT NOT NULL,
- provider TEXT NOT NULL,
- provider_user_id TEXT NOT NULL,
- provider_union_id TEXT NULL,
- provider_username TEXT NOT NULL DEFAULT '',
- display_name TEXT NOT NULL DEFAULT '',
- profile_url TEXT NOT NULL DEFAULT '',
- avatar_url TEXT NOT NULL DEFAULT '',
- metadata TEXT NOT NULL DEFAULT '{}',
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
- );
- `)
- require.NoError(t, err)
- }
- func truncateAuthIdentityLegacyFixtureTables(t *testing.T, tx *sql.Tx, ctx context.Context) {
- t.Helper()
- _, err := tx.ExecContext(ctx, `
- TRUNCATE TABLE
- auth_identity_channels,
- identity_adoption_decisions,
- pending_auth_sessions,
- auth_identities,
- auth_identity_migration_reports,
- user_provider_default_grants,
- user_avatars,
- user_external_identities,
- users
- RESTART IDENTITY CASCADE;
- `)
- require.NoError(t, err)
- }
|