fix: harden auth identity legacy migrations
This commit is contained in:
@@ -205,6 +205,199 @@ FROM auth_identity_migration_reports
|
|||||||
require.Equal(t, beforeCount, 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)
|
||||||
|
|
||||||
|
_, 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)
|
||||||
|
|
||||||
|
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) {
|
func TestAuthIdentityLegacyExternalSafetyMigration_ReportsConflictsAndDowngradesInvalidJSON(t *testing.T) {
|
||||||
tx := testTx(t)
|
tx := testTx(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ var (
|
|||||||
"AUTH_IDENTITY_CHANNEL_OWNERSHIP_CONFLICT",
|
"AUTH_IDENTITY_CHANNEL_OWNERSHIP_CONFLICT",
|
||||||
"auth identity channel already belongs to another user",
|
"auth identity channel already belongs to another user",
|
||||||
)
|
)
|
||||||
|
ErrAuthIdentityChannelProviderMismatch = infraerrors.BadRequest(
|
||||||
|
"AUTH_IDENTITY_CHANNEL_PROVIDER_MISMATCH",
|
||||||
|
"auth identity channel provider must match canonical identity",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProviderGrantReason string
|
type ProviderGrantReason string
|
||||||
@@ -133,6 +137,10 @@ func (r *userRepository) WithUserProfileIdentityTx(ctx context.Context, fn func(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *userRepository) CreateAuthIdentity(ctx context.Context, input CreateAuthIdentityInput) (*CreateAuthIdentityResult, error) {
|
func (r *userRepository) CreateAuthIdentity(ctx context.Context, input CreateAuthIdentityInput) (*CreateAuthIdentityResult, error) {
|
||||||
|
if err := validateAuthIdentityChannelProviderMatch(input.Canonical, input.Channel); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
client := clientFromContext(ctx, r.client)
|
client := clientFromContext(ctx, r.client)
|
||||||
|
|
||||||
create := client.AuthIdentity.Create().
|
create := client.AuthIdentity.Create().
|
||||||
@@ -240,6 +248,10 @@ func (r *userRepository) ListUserAuthIdentities(ctx context.Context, userID int6
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *userRepository) BindAuthIdentityToUser(ctx context.Context, input BindAuthIdentityInput) (*CreateAuthIdentityResult, error) {
|
func (r *userRepository) BindAuthIdentityToUser(ctx context.Context, input BindAuthIdentityInput) (*CreateAuthIdentityResult, error) {
|
||||||
|
if err := validateAuthIdentityChannelProviderMatch(input.Canonical, input.Channel); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var result *CreateAuthIdentityResult
|
var result *CreateAuthIdentityResult
|
||||||
err := r.WithUserProfileIdentityTx(ctx, func(txCtx context.Context) error {
|
err := r.WithUserProfileIdentityTx(ctx, func(txCtx context.Context) error {
|
||||||
client := clientFromContext(txCtx, r.client)
|
client := clientFromContext(txCtx, r.client)
|
||||||
@@ -531,6 +543,23 @@ func copyMetadata(in map[string]any) map[string]any {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateAuthIdentityChannelProviderMatch(canonical AuthIdentityKey, channel *AuthIdentityChannelKey) error {
|
||||||
|
if channel == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
canonicalProviderType := strings.TrimSpace(canonical.ProviderType)
|
||||||
|
canonicalProviderKey := strings.TrimSpace(canonical.ProviderKey)
|
||||||
|
channelProviderType := strings.TrimSpace(channel.ProviderType)
|
||||||
|
channelProviderKey := strings.TrimSpace(channel.ProviderKey)
|
||||||
|
|
||||||
|
if canonicalProviderType != channelProviderType || canonicalProviderKey != channelProviderKey {
|
||||||
|
return ErrAuthIdentityChannelProviderMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func txAwareSQLExecutor(ctx context.Context, fallback sqlExecutor, client *dbent.Client) sqlQueryExecutor {
|
func txAwareSQLExecutor(ctx context.Context, fallback sqlExecutor, client *dbent.Client) sqlQueryExecutor {
|
||||||
if tx := dbent.TxFromContext(ctx); tx != nil {
|
if tx := dbent.TxFromContext(ctx); tx != nil {
|
||||||
if exec := sqlExecutorFromEntClient(tx.Client()); exec != nil {
|
if exec := sqlExecutorFromEntClient(tx.Client()); exec != nil {
|
||||||
|
|||||||
@@ -187,6 +187,48 @@ func (s *UserProfileIdentityRepoSuite) TestBindAuthIdentityToUser_IsIdempotentAn
|
|||||||
s.Require().ErrorIs(err, ErrAuthIdentityChannelOwnershipConflict)
|
s.Require().ErrorIs(err, ErrAuthIdentityChannelOwnershipConflict)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserProfileIdentityRepoSuite) TestCreateAuthIdentity_RejectsChannelProviderMismatch() {
|
||||||
|
user := s.mustCreateUser("provider-mismatch-create")
|
||||||
|
|
||||||
|
_, err := s.repo.CreateAuthIdentity(s.ctx, CreateAuthIdentityInput{
|
||||||
|
UserID: user.ID,
|
||||||
|
Canonical: AuthIdentityKey{
|
||||||
|
ProviderType: "wechat",
|
||||||
|
ProviderKey: "wechat-main",
|
||||||
|
ProviderSubject: "union-create-mismatch",
|
||||||
|
},
|
||||||
|
Channel: &AuthIdentityChannelKey{
|
||||||
|
ProviderType: "linuxdo",
|
||||||
|
ProviderKey: "linuxdo-main",
|
||||||
|
Channel: "oauth",
|
||||||
|
ChannelAppID: "app-mismatch",
|
||||||
|
ChannelSubject: "openid-create-mismatch",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
s.Require().ErrorIs(err, ErrAuthIdentityChannelProviderMismatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserProfileIdentityRepoSuite) TestBindAuthIdentityToUser_RejectsChannelProviderMismatch() {
|
||||||
|
user := s.mustCreateUser("provider-mismatch-bind")
|
||||||
|
|
||||||
|
_, err := s.repo.BindAuthIdentityToUser(s.ctx, BindAuthIdentityInput{
|
||||||
|
UserID: user.ID,
|
||||||
|
Canonical: AuthIdentityKey{
|
||||||
|
ProviderType: "wechat",
|
||||||
|
ProviderKey: "wechat-main",
|
||||||
|
ProviderSubject: "union-bind-mismatch",
|
||||||
|
},
|
||||||
|
Channel: &AuthIdentityChannelKey{
|
||||||
|
ProviderType: "wechat",
|
||||||
|
ProviderKey: "wechat-legacy",
|
||||||
|
Channel: "oa",
|
||||||
|
ChannelAppID: "wx-app-bind-mismatch",
|
||||||
|
ChannelSubject: "openid-bind-mismatch",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
s.Require().ErrorIs(err, ErrAuthIdentityChannelProviderMismatch)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserProfileIdentityRepoSuite) TestWithUserProfileIdentityTx_RollsBackIdentityAndGrantOnError() {
|
func (s *UserProfileIdentityRepoSuite) TestWithUserProfileIdentityTx_RollsBackIdentityAndGrantOnError() {
|
||||||
user := s.mustCreateUser("tx-rollback")
|
user := s.mustCreateUser("tx-rollback")
|
||||||
expectedErr := errors.New("rollback")
|
expectedErr := errors.New("rollback")
|
||||||
|
|||||||
@@ -1,3 +1,29 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION public.__migration_115_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;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF to_regclass('public.user_external_identities') IS NULL THEN
|
IF to_regclass('public.user_external_identities') IS NULL THEN
|
||||||
@@ -33,7 +59,7 @@ FROM (
|
|||||||
BTRIM(uei.provider_user_id) AS provider_user_id,
|
BTRIM(uei.provider_user_id) AS provider_user_id,
|
||||||
BTRIM(uei.provider_username) AS provider_username,
|
BTRIM(uei.provider_username) AS provider_username,
|
||||||
BTRIM(uei.display_name) AS display_name,
|
BTRIM(uei.display_name) AS display_name,
|
||||||
COALESCE(NULLIF(BTRIM(COALESCE(uei.metadata, '')), '')::jsonb, '{}'::jsonb) AS metadata_json,
|
public.__migration_115_safe_legacy_metadata_jsonb(uei.metadata) AS metadata_json,
|
||||||
uei.created_at,
|
uei.created_at,
|
||||||
uei.updated_at
|
uei.updated_at
|
||||||
FROM user_external_identities AS uei
|
FROM user_external_identities AS uei
|
||||||
@@ -78,7 +104,7 @@ FROM (
|
|||||||
BTRIM(uei.provider_union_id) AS provider_union_id,
|
BTRIM(uei.provider_union_id) AS provider_union_id,
|
||||||
BTRIM(uei.provider_username) AS provider_username,
|
BTRIM(uei.provider_username) AS provider_username,
|
||||||
BTRIM(uei.display_name) AS display_name,
|
BTRIM(uei.display_name) AS display_name,
|
||||||
COALESCE(NULLIF(BTRIM(COALESCE(uei.metadata, '')), '')::jsonb, '{}'::jsonb) AS metadata_json,
|
public.__migration_115_safe_legacy_metadata_jsonb(uei.metadata) AS metadata_json,
|
||||||
uei.created_at,
|
uei.created_at,
|
||||||
uei.updated_at
|
uei.updated_at
|
||||||
FROM user_external_identities AS uei
|
FROM user_external_identities AS uei
|
||||||
@@ -123,7 +149,7 @@ FROM (
|
|||||||
FROM user_external_identities AS uei
|
FROM user_external_identities AS uei
|
||||||
JOIN users AS u ON u.id = uei.user_id
|
JOIN users AS u ON u.id = uei.user_id
|
||||||
CROSS JOIN LATERAL (
|
CROSS JOIN LATERAL (
|
||||||
SELECT COALESCE(NULLIF(BTRIM(COALESCE(uei.metadata, '')), '')::jsonb, '{}'::jsonb) AS metadata_json
|
SELECT public.__migration_115_safe_legacy_metadata_jsonb(uei.metadata) AS metadata_json
|
||||||
) AS meta
|
) AS meta
|
||||||
WHERE u.deleted_at IS NULL
|
WHERE u.deleted_at IS NULL
|
||||||
AND LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat'
|
AND LOWER(BTRIM(COALESCE(uei.provider, ''))) = 'wechat'
|
||||||
@@ -157,7 +183,7 @@ FROM (
|
|||||||
uei.id,
|
uei.id,
|
||||||
uei.user_id,
|
uei.user_id,
|
||||||
BTRIM(uei.provider_user_id) AS provider_user_id,
|
BTRIM(uei.provider_user_id) AS provider_user_id,
|
||||||
COALESCE(NULLIF(BTRIM(COALESCE(uei.metadata, '')), '')::jsonb, '{}'::jsonb) AS metadata_json
|
public.__migration_115_safe_legacy_metadata_jsonb(uei.metadata) AS metadata_json
|
||||||
FROM user_external_identities AS uei
|
FROM user_external_identities AS uei
|
||||||
JOIN users AS u ON u.id = uei.user_id
|
JOIN users AS u ON u.id = uei.user_id
|
||||||
WHERE u.deleted_at IS NULL
|
WHERE u.deleted_at IS NULL
|
||||||
@@ -185,3 +211,5 @@ WHERE ai.provider_type = 'wechat'
|
|||||||
AND COALESCE(ai.metadata ->> 'backfill_source', '') = 'synthetic_email'
|
AND COALESCE(ai.metadata ->> 'backfill_source', '') = 'synthetic_email'
|
||||||
AND BTRIM(COALESCE(ai.metadata ->> 'unionid', '')) = ''
|
AND BTRIM(COALESCE(ai.metadata ->> 'unionid', '')) = ''
|
||||||
ON CONFLICT (report_type, report_key) DO NOTHING;
|
ON CONFLICT (report_type, report_key) DO NOTHING;
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS public.__migration_115_safe_legacy_metadata_jsonb(TEXT);
|
||||||
|
|||||||
@@ -332,5 +332,38 @@ ON CONFLICT (report_type, report_key) DO NOTHING;
|
|||||||
$sql$;
|
$sql$;
|
||||||
END $$;
|
END $$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'auth_identities_metadata_is_object_check'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE auth_identities
|
||||||
|
ADD CONSTRAINT auth_identities_metadata_is_object_check
|
||||||
|
CHECK (jsonb_typeof(metadata) = 'object');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'auth_identity_channels_metadata_is_object_check'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE auth_identity_channels
|
||||||
|
ADD CONSTRAINT auth_identity_channels_metadata_is_object_check
|
||||||
|
CHECK (jsonb_typeof(metadata) = 'object');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'auth_identity_migration_reports_details_is_object_check'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE auth_identity_migration_reports
|
||||||
|
ADD CONSTRAINT auth_identity_migration_reports_details_is_object_check
|
||||||
|
CHECK (jsonb_typeof(details) = 'object');
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
DROP FUNCTION IF EXISTS public.__migration_116_is_valid_legacy_metadata_jsonb(TEXT);
|
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);
|
DROP FUNCTION IF EXISTS public.__migration_116_safe_legacy_metadata_jsonb(TEXT);
|
||||||
|
|||||||
Reference in New Issue
Block a user