Add legacy identity safety remediation migration
This commit is contained in:
@@ -200,6 +200,252 @@ FROM auth_identity_migration_reports
|
|||||||
var afterCount int
|
var afterCount int
|
||||||
require.NoError(t, tx.QueryRowContext(ctx, `
|
require.NoError(t, tx.QueryRowContext(ctx, `
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
|
FROM auth_identity_migration_reports
|
||||||
|
`).Scan(&afterCount))
|
||||||
|
require.Equal(t, beforeCount, afterCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
_, 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
|
||||||
|
);
|
||||||
|
|
||||||
|
TRUNCATE TABLE
|
||||||
|
auth_identity_channels,
|
||||||
|
auth_identities,
|
||||||
|
auth_identity_migration_reports,
|
||||||
|
user_external_identities,
|
||||||
|
users
|
||||||
|
RESTART IDENTITY;
|
||||||
|
`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
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
|
FROM auth_identity_migration_reports
|
||||||
`).Scan(&afterCount))
|
`).Scan(&afterCount))
|
||||||
require.Equal(t, beforeCount, afterCount)
|
require.Equal(t, beforeCount, afterCount)
|
||||||
|
|||||||
@@ -0,0 +1,336 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION public.__migration_116_safe_legacy_metadata_jsonb(input_text TEXT)
|
||||||
|
RETURNS JSONB
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
parsed JSONB;
|
||||||
|
BEGIN
|
||||||
|
IF input_text IS NULL OR BTRIM(input_text) = '' THEN
|
||||||
|
RETURN '{}'::jsonb;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
parsed := input_text::jsonb;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
RETURN '{}'::jsonb;
|
||||||
|
END;
|
||||||
|
|
||||||
|
IF jsonb_typeof(parsed) = 'object' THEN
|
||||||
|
RETURN parsed;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN jsonb_build_object('_legacy_metadata_raw_json', parsed);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.__migration_116_is_valid_legacy_metadata_jsonb(input_text TEXT)
|
||||||
|
RETURNS BOOLEAN
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
parsed JSONB;
|
||||||
|
BEGIN
|
||||||
|
IF input_text IS NULL OR BTRIM(input_text) = '' THEN
|
||||||
|
RETURN TRUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
parsed := input_text::jsonb;
|
||||||
|
RETURN TRUE;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
RETURN FALSE;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF to_regclass('public.user_external_identities') IS NULL THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
EXECUTE $sql$
|
||||||
|
INSERT INTO auth_identity_migration_reports (report_type, report_key, details)
|
||||||
|
SELECT
|
||||||
|
'legacy_external_identity_invalid_metadata_json',
|
||||||
|
'legacy_external_identity:' || uei.id::text,
|
||||||
|
jsonb_build_object(
|
||||||
|
'legacy_identity_id', uei.id,
|
||||||
|
'user_id', uei.user_id,
|
||||||
|
'provider', LOWER(BTRIM(COALESCE(uei.provider, ''))),
|
||||||
|
'provider_user_id', BTRIM(COALESCE(uei.provider_user_id, '')),
|
||||||
|
'provider_union_id', BTRIM(COALESCE(uei.provider_union_id, '')),
|
||||||
|
'reason', 'legacy metadata is not valid JSON; migration downgraded metadata to empty object',
|
||||||
|
'raw_metadata', LEFT(BTRIM(COALESCE(uei.metadata, '')), 1000),
|
||||||
|
'migration', '116_auth_identity_legacy_external_safety_reports'
|
||||||
|
)
|
||||||
|
FROM user_external_identities AS uei
|
||||||
|
JOIN users AS u ON u.id = uei.user_id
|
||||||
|
WHERE u.deleted_at IS NULL
|
||||||
|
AND BTRIM(COALESCE(uei.metadata, '')) <> ''
|
||||||
|
AND NOT public.__migration_116_is_valid_legacy_metadata_jsonb(uei.metadata)
|
||||||
|
ON CONFLICT (report_type, report_key) DO NOTHING;
|
||||||
|
$sql$;
|
||||||
|
|
||||||
|
EXECUTE $sql$
|
||||||
|
INSERT INTO auth_identity_migration_reports (report_type, report_key, details)
|
||||||
|
SELECT
|
||||||
|
'legacy_external_identity_conflict',
|
||||||
|
'legacy_external_identity:' || legacy.id::text,
|
||||||
|
legacy.metadata_json || jsonb_build_object(
|
||||||
|
'legacy_identity_id', legacy.id,
|
||||||
|
'legacy_user_id', legacy.user_id,
|
||||||
|
'existing_identity_id', ai.id,
|
||||||
|
'existing_user_id', ai.user_id,
|
||||||
|
'provider_type', legacy.provider_type,
|
||||||
|
'provider_key', legacy.provider_key,
|
||||||
|
'provider_subject', legacy.provider_subject,
|
||||||
|
'reason', 'legacy canonical identity subject already belongs to another user',
|
||||||
|
'migration', '116_auth_identity_legacy_external_safety_reports'
|
||||||
|
)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
uei.id,
|
||||||
|
uei.user_id,
|
||||||
|
LOWER(BTRIM(COALESCE(uei.provider, ''))) AS provider_type,
|
||||||
|
CASE
|
||||||
|
WHEN LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat' THEN 'wechat-main'
|
||||||
|
ELSE 'linuxdo'
|
||||||
|
END AS provider_key,
|
||||||
|
CASE
|
||||||
|
WHEN LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat' THEN BTRIM(COALESCE(uei.provider_union_id, ''))
|
||||||
|
ELSE BTRIM(COALESCE(uei.provider_user_id, ''))
|
||||||
|
END AS provider_subject,
|
||||||
|
BTRIM(COALESCE(uei.provider_user_id, '')) AS provider_user_id,
|
||||||
|
BTRIM(COALESCE(uei.provider_union_id, '')) AS provider_union_id,
|
||||||
|
BTRIM(COALESCE(uei.provider_username, '')) AS provider_username,
|
||||||
|
BTRIM(COALESCE(uei.display_name, '')) AS display_name,
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) AS metadata_json
|
||||||
|
FROM user_external_identities AS uei
|
||||||
|
JOIN users AS u ON u.id = uei.user_id
|
||||||
|
WHERE u.deleted_at IS NULL
|
||||||
|
AND LOWER(BTRIM(COALESCE(uei.provider, ''))) IN ('linuxdo', 'wechat')
|
||||||
|
AND (
|
||||||
|
(LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'linuxdo' AND BTRIM(COALESCE(uei.provider_user_id, '')) <> '')
|
||||||
|
OR
|
||||||
|
(LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat' AND BTRIM(COALESCE(uei.provider_union_id, '')) <> '')
|
||||||
|
)
|
||||||
|
) AS legacy
|
||||||
|
JOIN auth_identities AS ai
|
||||||
|
ON ai.provider_type = legacy.provider_type
|
||||||
|
AND ai.provider_key = legacy.provider_key
|
||||||
|
AND ai.provider_subject = legacy.provider_subject
|
||||||
|
WHERE ai.user_id <> legacy.user_id
|
||||||
|
ON CONFLICT (report_type, report_key) DO NOTHING;
|
||||||
|
$sql$;
|
||||||
|
|
||||||
|
EXECUTE $sql$
|
||||||
|
INSERT INTO auth_identities (
|
||||||
|
user_id,
|
||||||
|
provider_type,
|
||||||
|
provider_key,
|
||||||
|
provider_subject,
|
||||||
|
verified_at,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
legacy.user_id,
|
||||||
|
legacy.provider_type,
|
||||||
|
legacy.provider_key,
|
||||||
|
legacy.provider_subject,
|
||||||
|
legacy.verified_at,
|
||||||
|
legacy.metadata_json || jsonb_build_object(
|
||||||
|
'legacy_identity_id', legacy.id,
|
||||||
|
'provider_user_id', legacy.provider_user_id,
|
||||||
|
'provider_union_id', NULLIF(legacy.provider_union_id, ''),
|
||||||
|
'provider_username', legacy.provider_username,
|
||||||
|
'display_name', legacy.display_name,
|
||||||
|
'migration', '116_auth_identity_legacy_external_safety_reports'
|
||||||
|
)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
uei.id,
|
||||||
|
uei.user_id,
|
||||||
|
LOWER(BTRIM(COALESCE(uei.provider, ''))) AS provider_type,
|
||||||
|
CASE
|
||||||
|
WHEN LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat' THEN 'wechat-main'
|
||||||
|
ELSE 'linuxdo'
|
||||||
|
END AS provider_key,
|
||||||
|
CASE
|
||||||
|
WHEN LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat' THEN BTRIM(COALESCE(uei.provider_union_id, ''))
|
||||||
|
ELSE BTRIM(COALESCE(uei.provider_user_id, ''))
|
||||||
|
END AS provider_subject,
|
||||||
|
BTRIM(COALESCE(uei.provider_user_id, '')) AS provider_user_id,
|
||||||
|
BTRIM(COALESCE(uei.provider_union_id, '')) AS provider_union_id,
|
||||||
|
BTRIM(COALESCE(uei.provider_username, '')) AS provider_username,
|
||||||
|
BTRIM(COALESCE(uei.display_name, '')) AS display_name,
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) AS metadata_json,
|
||||||
|
COALESCE(uei.updated_at, uei.created_at, NOW()) AS verified_at
|
||||||
|
FROM user_external_identities AS uei
|
||||||
|
JOIN users AS u ON u.id = uei.user_id
|
||||||
|
WHERE u.deleted_at IS NULL
|
||||||
|
AND LOWER(BTRIM(COALESCE(uei.provider, ''))) IN ('linuxdo', 'wechat')
|
||||||
|
AND (
|
||||||
|
(LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'linuxdo' AND BTRIM(COALESCE(uei.provider_user_id, '')) <> '')
|
||||||
|
OR
|
||||||
|
(LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat' AND BTRIM(COALESCE(uei.provider_union_id, '')) <> '')
|
||||||
|
)
|
||||||
|
) AS legacy
|
||||||
|
LEFT JOIN auth_identities AS ai
|
||||||
|
ON ai.provider_type = legacy.provider_type
|
||||||
|
AND ai.provider_key = legacy.provider_key
|
||||||
|
AND ai.provider_subject = legacy.provider_subject
|
||||||
|
WHERE ai.id IS NULL
|
||||||
|
ON CONFLICT (provider_type, provider_key, provider_subject) DO NOTHING;
|
||||||
|
$sql$;
|
||||||
|
|
||||||
|
EXECUTE $sql$
|
||||||
|
INSERT INTO auth_identity_migration_reports (report_type, report_key, details)
|
||||||
|
SELECT
|
||||||
|
'legacy_external_channel_conflict',
|
||||||
|
'legacy_external_identity:' || legacy.id::text,
|
||||||
|
legacy.metadata_json || jsonb_build_object(
|
||||||
|
'legacy_identity_id', legacy.id,
|
||||||
|
'legacy_user_id', legacy.user_id,
|
||||||
|
'existing_channel_id', channel.id,
|
||||||
|
'existing_identity_id', existing_ai.id,
|
||||||
|
'existing_user_id', existing_ai.user_id,
|
||||||
|
'provider_type', 'wechat',
|
||||||
|
'provider_key', 'wechat-main',
|
||||||
|
'provider_subject', legacy.provider_union_id,
|
||||||
|
'channel', legacy.channel,
|
||||||
|
'channel_app_id', legacy.channel_app_id,
|
||||||
|
'channel_subject', legacy.provider_user_id,
|
||||||
|
'reason', 'legacy channel subject already belongs to another user',
|
||||||
|
'migration', '116_auth_identity_legacy_external_safety_reports'
|
||||||
|
)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
uei.id,
|
||||||
|
uei.user_id,
|
||||||
|
BTRIM(COALESCE(uei.provider_user_id, '')) AS provider_user_id,
|
||||||
|
BTRIM(COALESCE(uei.provider_union_id, '')) AS provider_union_id,
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) AS metadata_json,
|
||||||
|
BTRIM(COALESCE(public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) ->> 'channel', '')) AS channel,
|
||||||
|
BTRIM(COALESCE(
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) ->> 'channel_app_id',
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) ->> 'appid',
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) ->> 'app_id',
|
||||||
|
''
|
||||||
|
)) AS channel_app_id
|
||||||
|
FROM user_external_identities AS uei
|
||||||
|
JOIN users AS u ON u.id = uei.user_id
|
||||||
|
WHERE u.deleted_at IS NULL
|
||||||
|
AND LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat'
|
||||||
|
AND BTRIM(COALESCE(uei.provider_union_id, '')) <> ''
|
||||||
|
AND BTRIM(COALESCE(uei.provider_user_id, '')) <> ''
|
||||||
|
) AS legacy
|
||||||
|
JOIN auth_identities AS legacy_ai
|
||||||
|
ON legacy_ai.user_id = legacy.user_id
|
||||||
|
AND legacy_ai.provider_type = 'wechat'
|
||||||
|
AND legacy_ai.provider_key = 'wechat-main'
|
||||||
|
AND legacy_ai.provider_subject = legacy.provider_union_id
|
||||||
|
JOIN auth_identity_channels AS channel
|
||||||
|
ON channel.provider_type = 'wechat'
|
||||||
|
AND channel.provider_key = 'wechat-main'
|
||||||
|
AND channel.channel = legacy.channel
|
||||||
|
AND channel.channel_app_id = legacy.channel_app_id
|
||||||
|
AND channel.channel_subject = legacy.provider_user_id
|
||||||
|
JOIN auth_identities AS existing_ai
|
||||||
|
ON existing_ai.id = channel.identity_id
|
||||||
|
WHERE legacy.channel <> ''
|
||||||
|
AND legacy.channel_app_id <> ''
|
||||||
|
AND existing_ai.user_id <> legacy.user_id
|
||||||
|
ON CONFLICT (report_type, report_key) DO NOTHING;
|
||||||
|
$sql$;
|
||||||
|
|
||||||
|
EXECUTE $sql$
|
||||||
|
INSERT INTO auth_identity_channels (
|
||||||
|
identity_id,
|
||||||
|
provider_type,
|
||||||
|
provider_key,
|
||||||
|
channel,
|
||||||
|
channel_app_id,
|
||||||
|
channel_subject,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
legacy_ai.id,
|
||||||
|
'wechat',
|
||||||
|
'wechat-main',
|
||||||
|
legacy.channel,
|
||||||
|
legacy.channel_app_id,
|
||||||
|
legacy.provider_user_id,
|
||||||
|
legacy.metadata_json || jsonb_build_object(
|
||||||
|
'openid', legacy.provider_user_id,
|
||||||
|
'unionid', legacy.provider_union_id,
|
||||||
|
'migration', '116_auth_identity_legacy_external_safety_reports'
|
||||||
|
)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
uei.user_id,
|
||||||
|
BTRIM(COALESCE(uei.provider_user_id, '')) AS provider_user_id,
|
||||||
|
BTRIM(COALESCE(uei.provider_union_id, '')) AS provider_union_id,
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) AS metadata_json,
|
||||||
|
BTRIM(COALESCE(public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) ->> 'channel', '')) AS channel,
|
||||||
|
BTRIM(COALESCE(
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) ->> 'channel_app_id',
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) ->> 'appid',
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) ->> 'app_id',
|
||||||
|
''
|
||||||
|
)) AS channel_app_id
|
||||||
|
FROM user_external_identities AS uei
|
||||||
|
JOIN users AS u ON u.id = uei.user_id
|
||||||
|
WHERE u.deleted_at IS NULL
|
||||||
|
AND LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat'
|
||||||
|
AND BTRIM(COALESCE(uei.provider_union_id, '')) <> ''
|
||||||
|
AND BTRIM(COALESCE(uei.provider_user_id, '')) <> ''
|
||||||
|
) AS legacy
|
||||||
|
JOIN auth_identities AS legacy_ai
|
||||||
|
ON legacy_ai.user_id = legacy.user_id
|
||||||
|
AND legacy_ai.provider_type = 'wechat'
|
||||||
|
AND legacy_ai.provider_key = 'wechat-main'
|
||||||
|
AND legacy_ai.provider_subject = legacy.provider_union_id
|
||||||
|
LEFT JOIN auth_identity_channels AS channel
|
||||||
|
ON channel.provider_type = 'wechat'
|
||||||
|
AND channel.provider_key = 'wechat-main'
|
||||||
|
AND channel.channel = legacy.channel
|
||||||
|
AND channel.channel_app_id = legacy.channel_app_id
|
||||||
|
AND channel.channel_subject = legacy.provider_user_id
|
||||||
|
WHERE legacy.channel <> ''
|
||||||
|
AND legacy.channel_app_id <> ''
|
||||||
|
AND channel.id IS NULL
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
$sql$;
|
||||||
|
|
||||||
|
EXECUTE $sql$
|
||||||
|
INSERT INTO auth_identity_migration_reports (report_type, report_key, details)
|
||||||
|
SELECT
|
||||||
|
'wechat_openid_only_requires_remediation',
|
||||||
|
'legacy_external_identity:' || legacy.id::text,
|
||||||
|
legacy.metadata_json || jsonb_build_object(
|
||||||
|
'legacy_identity_id', legacy.id,
|
||||||
|
'user_id', legacy.user_id,
|
||||||
|
'openid', legacy.provider_user_id,
|
||||||
|
'reason', 'legacy user_external_identities row only has openid and cannot be canonicalized offline',
|
||||||
|
'migration', '116_auth_identity_legacy_external_safety_reports'
|
||||||
|
)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
uei.id,
|
||||||
|
uei.user_id,
|
||||||
|
BTRIM(COALESCE(uei.provider_user_id, '')) AS provider_user_id,
|
||||||
|
public.__migration_116_safe_legacy_metadata_jsonb(uei.metadata) AS metadata_json
|
||||||
|
FROM user_external_identities AS uei
|
||||||
|
JOIN users AS u ON u.id = uei.user_id
|
||||||
|
WHERE u.deleted_at IS NULL
|
||||||
|
AND LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat'
|
||||||
|
AND BTRIM(COALESCE(uei.provider_user_id, '')) <> ''
|
||||||
|
AND BTRIM(COALESCE(uei.provider_union_id, '')) = ''
|
||||||
|
) AS legacy
|
||||||
|
ON CONFLICT (report_type, report_key) DO NOTHING;
|
||||||
|
$sql$;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS public.__migration_116_is_valid_legacy_metadata_jsonb(TEXT);
|
||||||
|
DROP FUNCTION IF EXISTS public.__migration_116_safe_legacy_metadata_jsonb(TEXT);
|
||||||
Reference in New Issue
Block a user