auth_identity_legacy_migration_integration_test.go 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959
  1. //go:build integration
  2. package repository
  3. import (
  4. "context"
  5. "database/sql"
  6. "os"
  7. "path/filepath"
  8. "strconv"
  9. "testing"
  10. "github.com/stretchr/testify/require"
  11. )
  12. func TestAuthIdentityLegacyExternalBackfillMigration(t *testing.T) {
  13. tx := testTx(t)
  14. ctx := context.Background()
  15. migrationPath := filepath.Join("..", "..", "migrations", "115_auth_identity_legacy_external_backfill.sql")
  16. migrationSQL, err := os.ReadFile(migrationPath)
  17. require.NoError(t, err)
  18. prepareLegacyExternalIdentitiesTable(t, tx, ctx)
  19. truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
  20. var linuxDoUserID int64
  21. require.NoError(t, tx.QueryRowContext(ctx, `
  22. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  23. VALUES ('legacy-linuxdo@example.com', 'hash', 'user', 'active', 0, 1)
  24. RETURNING id`).Scan(&linuxDoUserID))
  25. var wechatUnionUserID int64
  26. require.NoError(t, tx.QueryRowContext(ctx, `
  27. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  28. VALUES ('legacy-wechat-union@example.com', 'hash', 'user', 'active', 0, 1)
  29. RETURNING id`).Scan(&wechatUnionUserID))
  30. var wechatOpenIDOnlyUserID int64
  31. require.NoError(t, tx.QueryRowContext(ctx, `
  32. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  33. VALUES ('legacy-wechat-openid@example.com', 'hash', 'user', 'active', 0, 1)
  34. RETURNING id`).Scan(&wechatOpenIDOnlyUserID))
  35. var syntheticAuthIdentityID int64
  36. require.NoError(t, tx.QueryRowContext(ctx, `
  37. INSERT INTO auth_identities (user_id, provider_type, provider_key, provider_subject, metadata)
  38. VALUES ($1, 'wechat', 'wechat-main', 'openid-synthetic', '{"backfill_source":"synthetic_email"}'::jsonb)
  39. RETURNING id`, wechatOpenIDOnlyUserID).Scan(&syntheticAuthIdentityID))
  40. var linuxDoLegacyID int64
  41. require.NoError(t, tx.QueryRowContext(ctx, `
  42. INSERT INTO user_external_identities (
  43. user_id,
  44. provider,
  45. provider_user_id,
  46. provider_union_id,
  47. provider_username,
  48. display_name,
  49. metadata
  50. ) VALUES ($1, 'linuxdo', 'linuxdo-user-1', NULL, 'linux-user', 'Linux User', '{"source":"legacy"}')
  51. RETURNING id
  52. `, linuxDoUserID).Scan(&linuxDoLegacyID))
  53. var wechatUnionLegacyID int64
  54. require.NoError(t, tx.QueryRowContext(ctx, `
  55. INSERT INTO user_external_identities (
  56. user_id,
  57. provider,
  58. provider_user_id,
  59. provider_union_id,
  60. provider_username,
  61. display_name,
  62. metadata
  63. ) VALUES ($1, 'wechat', 'openid-union-1', 'union-1', 'wechat-union-user', 'WeChat Union User', '{"channel":"oa","appid":"wx-app-1"}')
  64. RETURNING id
  65. `, wechatUnionUserID).Scan(&wechatUnionLegacyID))
  66. var wechatOpenIDLegacyID int64
  67. require.NoError(t, tx.QueryRowContext(ctx, `
  68. INSERT INTO user_external_identities (
  69. user_id,
  70. provider,
  71. provider_user_id,
  72. provider_union_id,
  73. provider_username,
  74. display_name,
  75. metadata
  76. ) VALUES ($1, 'wechat', 'openid-only-1', NULL, 'wechat-openid-user', 'WeChat OpenID User', '{"channel":"oa","appid":"wx-app-2"}')
  77. RETURNING id
  78. `, wechatOpenIDOnlyUserID).Scan(&wechatOpenIDLegacyID))
  79. _, err = tx.ExecContext(ctx, string(migrationSQL))
  80. require.NoError(t, err)
  81. var linuxDoCount int
  82. require.NoError(t, tx.QueryRowContext(ctx, `
  83. SELECT COUNT(*)
  84. FROM auth_identities
  85. WHERE user_id = $1
  86. AND provider_type = 'linuxdo'
  87. AND provider_key = 'linuxdo'
  88. AND provider_subject = 'linuxdo-user-1'
  89. `, linuxDoUserID).Scan(&linuxDoCount))
  90. require.Equal(t, 1, linuxDoCount)
  91. var wechatSubject string
  92. require.NoError(t, tx.QueryRowContext(ctx, `
  93. SELECT provider_subject
  94. FROM auth_identities
  95. WHERE user_id = $1
  96. AND provider_type = 'wechat'
  97. AND provider_key = 'wechat-main'
  98. AND provider_subject = 'union-1'
  99. `, wechatUnionUserID).Scan(&wechatSubject))
  100. require.Equal(t, "union-1", wechatSubject)
  101. var wechatChannelCount int
  102. require.NoError(t, tx.QueryRowContext(ctx, `
  103. SELECT COUNT(*)
  104. FROM auth_identity_channels channel
  105. JOIN auth_identities ai ON ai.id = channel.identity_id
  106. WHERE ai.user_id = $1
  107. AND channel.provider_type = 'wechat'
  108. AND channel.provider_key = 'wechat-main'
  109. AND channel.channel = 'oa'
  110. AND channel.channel_app_id = 'wx-app-1'
  111. AND channel.channel_subject = 'openid-union-1'
  112. `, wechatUnionUserID).Scan(&wechatChannelCount))
  113. require.Equal(t, 1, wechatChannelCount)
  114. var legacyOpenIDOnlyReportCount int
  115. require.NoError(t, tx.QueryRowContext(ctx, `
  116. SELECT COUNT(*)
  117. FROM auth_identity_migration_reports
  118. WHERE report_type = 'wechat_openid_only_requires_remediation'
  119. AND report_key = $1
  120. `, "legacy_external_identity:"+strconv.FormatInt(wechatOpenIDLegacyID, 10)).Scan(&legacyOpenIDOnlyReportCount))
  121. require.Equal(t, 1, legacyOpenIDOnlyReportCount)
  122. var syntheticReviewCount int
  123. require.NoError(t, tx.QueryRowContext(ctx, `
  124. SELECT COUNT(*)
  125. FROM auth_identity_migration_reports
  126. WHERE report_type = 'wechat_openid_only_requires_remediation'
  127. AND report_key = $1
  128. `, "synthetic_auth_identity:"+strconv.FormatInt(syntheticAuthIdentityID, 10)).Scan(&syntheticReviewCount))
  129. require.Equal(t, 1, syntheticReviewCount)
  130. var unionLegacyReportCount int
  131. require.NoError(t, tx.QueryRowContext(ctx, `
  132. SELECT COUNT(*)
  133. FROM auth_identity_migration_reports
  134. WHERE report_type = 'wechat_openid_only_requires_remediation'
  135. AND report_key = $1
  136. `, "legacy_external_identity:"+strconv.FormatInt(wechatUnionLegacyID, 10)).Scan(&unionLegacyReportCount))
  137. require.Zero(t, unionLegacyReportCount)
  138. require.NotZero(t, linuxDoLegacyID)
  139. }
  140. func TestAuthIdentityLegacyExternalBackfillMigration_IsSafeWhenLegacyTableMissing(t *testing.T) {
  141. tx := testTx(t)
  142. ctx := context.Background()
  143. migrationPath := filepath.Join("..", "..", "migrations", "115_auth_identity_legacy_external_backfill.sql")
  144. migrationSQL, err := os.ReadFile(migrationPath)
  145. require.NoError(t, err)
  146. var beforeCount int
  147. require.NoError(t, tx.QueryRowContext(ctx, `
  148. SELECT COUNT(*)
  149. FROM auth_identity_migration_reports
  150. `).Scan(&beforeCount))
  151. _, err = tx.ExecContext(ctx, string(migrationSQL))
  152. require.NoError(t, err)
  153. var afterCount int
  154. require.NoError(t, tx.QueryRowContext(ctx, `
  155. SELECT COUNT(*)
  156. FROM auth_identity_migration_reports
  157. `).Scan(&afterCount))
  158. require.Equal(t, beforeCount, afterCount)
  159. }
  160. func TestAuthIdentityLegacyExternalMigrations_ChainHandlesMalformedAndNonObjectMetadata(t *testing.T) {
  161. tx := testTx(t)
  162. ctx := context.Background()
  163. migration115Path := filepath.Join("..", "..", "migrations", "115_auth_identity_legacy_external_backfill.sql")
  164. migration115SQL, err := os.ReadFile(migration115Path)
  165. require.NoError(t, err)
  166. migration116Path := filepath.Join("..", "..", "migrations", "116_auth_identity_legacy_external_safety_reports.sql")
  167. migration116SQL, err := os.ReadFile(migration116Path)
  168. require.NoError(t, err)
  169. prepareLegacyExternalIdentitiesTable(t, tx, ctx)
  170. truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
  171. var linuxDoMalformedUserID int64
  172. require.NoError(t, tx.QueryRowContext(ctx, `
  173. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  174. VALUES ('legacy-linuxdo-malformed@example.com', 'hash', 'user', 'active', 0, 1)
  175. RETURNING id`).Scan(&linuxDoMalformedUserID))
  176. var linuxDoArrayUserID int64
  177. require.NoError(t, tx.QueryRowContext(ctx, `
  178. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  179. VALUES ('legacy-linuxdo-array@example.com', 'hash', 'user', 'active', 0, 1)
  180. RETURNING id`).Scan(&linuxDoArrayUserID))
  181. var wechatUnionArrayUserID int64
  182. require.NoError(t, tx.QueryRowContext(ctx, `
  183. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  184. VALUES ('legacy-wechat-array@example.com', 'hash', 'user', 'active', 0, 1)
  185. RETURNING id`).Scan(&wechatUnionArrayUserID))
  186. var wechatOpenIDArrayUserID int64
  187. require.NoError(t, tx.QueryRowContext(ctx, `
  188. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  189. VALUES ('legacy-wechat-openid-array@example.com', 'hash', 'user', 'active', 0, 1)
  190. RETURNING id`).Scan(&wechatOpenIDArrayUserID))
  191. var linuxDoMalformedLegacyID int64
  192. require.NoError(t, tx.QueryRowContext(ctx, `
  193. INSERT INTO user_external_identities (
  194. user_id,
  195. provider,
  196. provider_user_id,
  197. provider_union_id,
  198. provider_username,
  199. display_name,
  200. metadata
  201. ) VALUES ($1, 'linuxdo', 'linuxdo-malformed', NULL, 'legacy-linuxdo-malformed', 'Legacy LinuxDo Malformed', '{invalid')
  202. RETURNING id
  203. `, linuxDoMalformedUserID).Scan(&linuxDoMalformedLegacyID))
  204. var linuxDoArrayLegacyID int64
  205. require.NoError(t, tx.QueryRowContext(ctx, `
  206. INSERT INTO user_external_identities (
  207. user_id,
  208. provider,
  209. provider_user_id,
  210. provider_union_id,
  211. provider_username,
  212. display_name,
  213. metadata
  214. ) VALUES ($1, 'linuxdo', 'linuxdo-array', NULL, 'legacy-linuxdo-array', 'Legacy LinuxDo Array', '["legacy-linuxdo-array"]')
  215. RETURNING id
  216. `, linuxDoArrayUserID).Scan(&linuxDoArrayLegacyID))
  217. var wechatUnionArrayLegacyID int64
  218. require.NoError(t, tx.QueryRowContext(ctx, `
  219. INSERT INTO user_external_identities (
  220. user_id,
  221. provider,
  222. provider_user_id,
  223. provider_union_id,
  224. provider_username,
  225. display_name,
  226. metadata
  227. ) VALUES ($1, 'wechat', 'openid-array', 'union-array', 'legacy-wechat-array', 'Legacy WeChat Array', '["legacy-wechat-array"]')
  228. RETURNING id
  229. `, wechatUnionArrayUserID).Scan(&wechatUnionArrayLegacyID))
  230. var wechatOpenIDArrayLegacyID int64
  231. require.NoError(t, tx.QueryRowContext(ctx, `
  232. INSERT INTO user_external_identities (
  233. user_id,
  234. provider,
  235. provider_user_id,
  236. provider_union_id,
  237. provider_username,
  238. display_name,
  239. metadata
  240. ) VALUES ($1, 'wechat', 'openid-array-only', NULL, 'legacy-wechat-array-only', 'Legacy WeChat Array Only', '["legacy-wechat-openid-array"]')
  241. RETURNING id
  242. `, wechatOpenIDArrayUserID).Scan(&wechatOpenIDArrayLegacyID))
  243. _, err = tx.ExecContext(ctx, string(migration115SQL))
  244. require.NoError(t, err)
  245. _, err = tx.ExecContext(ctx, string(migration116SQL))
  246. require.NoError(t, err)
  247. var linuxDoMalformedMetadataType string
  248. require.NoError(t, tx.QueryRowContext(ctx, `
  249. SELECT jsonb_typeof(metadata)
  250. FROM auth_identities
  251. WHERE user_id = $1
  252. AND provider_type = 'linuxdo'
  253. AND provider_key = 'linuxdo'
  254. AND provider_subject = 'linuxdo-malformed'
  255. `, linuxDoMalformedUserID).Scan(&linuxDoMalformedMetadataType))
  256. require.Equal(t, "object", linuxDoMalformedMetadataType)
  257. var linuxDoArrayMetadataType string
  258. require.NoError(t, tx.QueryRowContext(ctx, `
  259. SELECT jsonb_typeof(metadata)
  260. FROM auth_identities
  261. WHERE user_id = $1
  262. AND provider_type = 'linuxdo'
  263. AND provider_key = 'linuxdo'
  264. AND provider_subject = 'linuxdo-array'
  265. `, linuxDoArrayUserID).Scan(&linuxDoArrayMetadataType))
  266. require.Equal(t, "object", linuxDoArrayMetadataType)
  267. var wechatUnionArrayMetadataType string
  268. require.NoError(t, tx.QueryRowContext(ctx, `
  269. SELECT jsonb_typeof(metadata)
  270. FROM auth_identities
  271. WHERE user_id = $1
  272. AND provider_type = 'wechat'
  273. AND provider_key = 'wechat-main'
  274. AND provider_subject = 'union-array'
  275. `, wechatUnionArrayUserID).Scan(&wechatUnionArrayMetadataType))
  276. require.Equal(t, "object", wechatUnionArrayMetadataType)
  277. var invalidJSONReportDetailsType string
  278. require.NoError(t, tx.QueryRowContext(ctx, `
  279. SELECT jsonb_typeof(details)
  280. FROM auth_identity_migration_reports
  281. WHERE report_type = 'legacy_external_identity_invalid_metadata_json'
  282. AND report_key = $1
  283. `, "legacy_external_identity:"+strconv.FormatInt(linuxDoMalformedLegacyID, 10)).Scan(&invalidJSONReportDetailsType))
  284. require.Equal(t, "object", invalidJSONReportDetailsType)
  285. var openIDOnlyReportDetailsType string
  286. require.NoError(t, tx.QueryRowContext(ctx, `
  287. SELECT jsonb_typeof(details)
  288. FROM auth_identity_migration_reports
  289. WHERE report_type = 'wechat_openid_only_requires_remediation'
  290. AND report_key = $1
  291. `, "legacy_external_identity:"+strconv.FormatInt(wechatOpenIDArrayLegacyID, 10)).Scan(&openIDOnlyReportDetailsType))
  292. require.Equal(t, "object", openIDOnlyReportDetailsType)
  293. var preservedArrayMetadataCount int
  294. require.NoError(t, tx.QueryRowContext(ctx, `
  295. SELECT COUNT(*)
  296. FROM auth_identities
  297. WHERE id IN (
  298. SELECT id
  299. FROM auth_identities
  300. WHERE (user_id = $1 AND provider_subject = 'linuxdo-array')
  301. OR (user_id = $2 AND provider_subject = 'union-array')
  302. )
  303. AND metadata ? '_legacy_metadata_raw_json'
  304. `, linuxDoArrayUserID, wechatUnionArrayUserID).Scan(&preservedArrayMetadataCount))
  305. require.Equal(t, 2, preservedArrayMetadataCount)
  306. require.NotZero(t, linuxDoArrayLegacyID)
  307. require.NotZero(t, wechatUnionArrayLegacyID)
  308. }
  309. func TestAuthIdentityLegacyExternalSafetyMigration_ReportsConflictsAndDowngradesInvalidJSON(t *testing.T) {
  310. tx := testTx(t)
  311. ctx := context.Background()
  312. migrationPath := filepath.Join("..", "..", "migrations", "116_auth_identity_legacy_external_safety_reports.sql")
  313. migrationSQL, err := os.ReadFile(migrationPath)
  314. require.NoError(t, err)
  315. prepareLegacyExternalIdentitiesTable(t, tx, ctx)
  316. truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
  317. userIDs := make([]int64, 0, 8)
  318. for _, email := range []string{
  319. "linuxdo-conflict-legacy@example.com",
  320. "linuxdo-conflict-owner@example.com",
  321. "wechat-conflict-legacy@example.com",
  322. "wechat-conflict-owner@example.com",
  323. "wechat-channel-legacy@example.com",
  324. "wechat-channel-owner@example.com",
  325. "linuxdo-invalid-json@example.com",
  326. "wechat-openid-invalid-json@example.com",
  327. } {
  328. var userID int64
  329. require.NoError(t, tx.QueryRowContext(ctx, `
  330. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  331. VALUES ($1, 'hash', 'user', 'active', 0, 1)
  332. RETURNING id`, email).Scan(&userID))
  333. userIDs = append(userIDs, userID)
  334. }
  335. linuxdoConflictLegacyUserID := userIDs[0]
  336. linuxdoConflictOwnerUserID := userIDs[1]
  337. wechatConflictLegacyUserID := userIDs[2]
  338. wechatConflictOwnerUserID := userIDs[3]
  339. wechatChannelLegacyUserID := userIDs[4]
  340. wechatChannelOwnerUserID := userIDs[5]
  341. linuxdoInvalidJSONUserID := userIDs[6]
  342. wechatInvalidOpenIDUserID := userIDs[7]
  343. require.NoError(t, tx.QueryRowContext(ctx, `
  344. INSERT INTO auth_identities (user_id, provider_type, provider_key, provider_subject, metadata)
  345. VALUES ($1, 'linuxdo', 'linuxdo', 'linuxdo-conflict', '{}'::jsonb)
  346. RETURNING id`, linuxdoConflictOwnerUserID).Scan(new(int64)))
  347. require.NoError(t, tx.QueryRowContext(ctx, `
  348. INSERT INTO auth_identities (user_id, provider_type, provider_key, provider_subject, metadata)
  349. VALUES ($1, 'wechat', 'wechat-main', 'union-conflict', '{}'::jsonb)
  350. RETURNING id`, wechatConflictOwnerUserID).Scan(new(int64)))
  351. var wechatChannelOwnerIdentityID int64
  352. require.NoError(t, tx.QueryRowContext(ctx, `
  353. INSERT INTO auth_identities (user_id, provider_type, provider_key, provider_subject, metadata)
  354. VALUES ($1, 'wechat', 'wechat-main', 'union-channel-owner', '{}'::jsonb)
  355. RETURNING id`, wechatChannelOwnerUserID).Scan(&wechatChannelOwnerIdentityID))
  356. require.NoError(t, tx.QueryRowContext(ctx, `
  357. INSERT INTO auth_identity_channels (
  358. identity_id,
  359. provider_type,
  360. provider_key,
  361. channel,
  362. channel_app_id,
  363. channel_subject,
  364. metadata
  365. )
  366. VALUES ($1, 'wechat', 'wechat-main', 'oa', 'wx-app-conflict', 'openid-channel-conflict', '{}'::jsonb)
  367. RETURNING id`, wechatChannelOwnerIdentityID).Scan(new(int64)))
  368. var linuxdoConflictLegacyID int64
  369. require.NoError(t, tx.QueryRowContext(ctx, `
  370. INSERT INTO user_external_identities (
  371. user_id,
  372. provider,
  373. provider_user_id,
  374. provider_union_id,
  375. provider_username,
  376. display_name,
  377. metadata
  378. ) VALUES ($1, 'linuxdo', 'linuxdo-conflict', NULL, 'legacy-linuxdo', 'Legacy LinuxDo Conflict', '{"source":"legacy"}')
  379. RETURNING id
  380. `, linuxdoConflictLegacyUserID).Scan(&linuxdoConflictLegacyID))
  381. var wechatConflictLegacyID int64
  382. require.NoError(t, tx.QueryRowContext(ctx, `
  383. INSERT INTO user_external_identities (
  384. user_id,
  385. provider,
  386. provider_user_id,
  387. provider_union_id,
  388. provider_username,
  389. display_name,
  390. metadata
  391. ) VALUES ($1, 'wechat', 'openid-union-conflict', 'union-conflict', 'legacy-wechat', 'Legacy WeChat Conflict', '{"channel":"oa","appid":"wx-app-conflict-canon"}')
  392. RETURNING id
  393. `, wechatConflictLegacyUserID).Scan(&wechatConflictLegacyID))
  394. var wechatChannelConflictLegacyID int64
  395. require.NoError(t, tx.QueryRowContext(ctx, `
  396. INSERT INTO user_external_identities (
  397. user_id,
  398. provider,
  399. provider_user_id,
  400. provider_union_id,
  401. provider_username,
  402. display_name,
  403. metadata
  404. ) VALUES ($1, 'wechat', 'openid-channel-conflict', 'union-channel-legacy', 'legacy-wechat-channel', 'Legacy WeChat Channel Conflict', '{"channel":"oa","appid":"wx-app-conflict"}')
  405. RETURNING id
  406. `, wechatChannelLegacyUserID).Scan(&wechatChannelConflictLegacyID))
  407. var linuxdoInvalidJSONLegacyID int64
  408. require.NoError(t, tx.QueryRowContext(ctx, `
  409. INSERT INTO user_external_identities (
  410. user_id,
  411. provider,
  412. provider_user_id,
  413. provider_union_id,
  414. provider_username,
  415. display_name,
  416. metadata
  417. ) VALUES ($1, 'linuxdo', 'linuxdo-invalid-json', NULL, 'legacy-linuxdo-invalid', 'Legacy LinuxDo Invalid JSON', '{invalid')
  418. RETURNING id
  419. `, linuxdoInvalidJSONUserID).Scan(&linuxdoInvalidJSONLegacyID))
  420. var wechatInvalidOpenIDLegacyID int64
  421. require.NoError(t, tx.QueryRowContext(ctx, `
  422. INSERT INTO user_external_identities (
  423. user_id,
  424. provider,
  425. provider_user_id,
  426. provider_union_id,
  427. provider_username,
  428. display_name,
  429. metadata
  430. ) VALUES ($1, 'wechat', 'openid-invalid-json-only', NULL, 'legacy-wechat-invalid', 'Legacy WeChat Invalid JSON', '{still-invalid')
  431. RETURNING id
  432. `, wechatInvalidOpenIDUserID).Scan(&wechatInvalidOpenIDLegacyID))
  433. _, err = tx.ExecContext(ctx, string(migrationSQL))
  434. require.NoError(t, err)
  435. var linuxdoConflictReportCount int
  436. require.NoError(t, tx.QueryRowContext(ctx, `
  437. SELECT COUNT(*)
  438. FROM auth_identity_migration_reports
  439. WHERE report_type = 'legacy_external_identity_conflict'
  440. AND report_key = $1
  441. `, "legacy_external_identity:"+strconv.FormatInt(linuxdoConflictLegacyID, 10)).Scan(&linuxdoConflictReportCount))
  442. require.Equal(t, 1, linuxdoConflictReportCount)
  443. var wechatConflictReportCount int
  444. require.NoError(t, tx.QueryRowContext(ctx, `
  445. SELECT COUNT(*)
  446. FROM auth_identity_migration_reports
  447. WHERE report_type = 'legacy_external_identity_conflict'
  448. AND report_key = $1
  449. `, "legacy_external_identity:"+strconv.FormatInt(wechatConflictLegacyID, 10)).Scan(&wechatConflictReportCount))
  450. require.Equal(t, 1, wechatConflictReportCount)
  451. var channelConflictReportCount int
  452. require.NoError(t, tx.QueryRowContext(ctx, `
  453. SELECT COUNT(*)
  454. FROM auth_identity_migration_reports
  455. WHERE report_type = 'legacy_external_channel_conflict'
  456. AND report_key = $1
  457. `, "legacy_external_identity:"+strconv.FormatInt(wechatChannelConflictLegacyID, 10)).Scan(&channelConflictReportCount))
  458. require.Equal(t, 1, channelConflictReportCount)
  459. var invalidJSONReportCount int
  460. require.NoError(t, tx.QueryRowContext(ctx, `
  461. SELECT COUNT(*)
  462. FROM auth_identity_migration_reports
  463. WHERE report_type = 'legacy_external_identity_invalid_metadata_json'
  464. AND report_key IN ($1, $2)
  465. `, "legacy_external_identity:"+strconv.FormatInt(linuxdoInvalidJSONLegacyID, 10), "legacy_external_identity:"+strconv.FormatInt(wechatInvalidOpenIDLegacyID, 10)).Scan(&invalidJSONReportCount))
  466. require.Equal(t, 2, invalidJSONReportCount)
  467. var linuxdoInvalidIdentityCount int
  468. require.NoError(t, tx.QueryRowContext(ctx, `
  469. SELECT COUNT(*)
  470. FROM auth_identities
  471. WHERE user_id = $1
  472. AND provider_type = 'linuxdo'
  473. AND provider_key = 'linuxdo'
  474. AND provider_subject = 'linuxdo-invalid-json'
  475. `, linuxdoInvalidJSONUserID).Scan(&linuxdoInvalidIdentityCount))
  476. require.Equal(t, 1, linuxdoInvalidIdentityCount)
  477. var wechatOpenIDOnlyReportCount int
  478. require.NoError(t, tx.QueryRowContext(ctx, `
  479. SELECT COUNT(*)
  480. FROM auth_identity_migration_reports
  481. WHERE report_type = 'wechat_openid_only_requires_remediation'
  482. AND report_key = $1
  483. `, "legacy_external_identity:"+strconv.FormatInt(wechatInvalidOpenIDLegacyID, 10)).Scan(&wechatOpenIDOnlyReportCount))
  484. require.Equal(t, 1, wechatOpenIDOnlyReportCount)
  485. }
  486. func TestAuthIdentityLegacyExternalSafetyMigration_IsSafeWhenLegacyTableMissing(t *testing.T) {
  487. tx := testTx(t)
  488. ctx := context.Background()
  489. migrationPath := filepath.Join("..", "..", "migrations", "116_auth_identity_legacy_external_safety_reports.sql")
  490. migrationSQL, err := os.ReadFile(migrationPath)
  491. require.NoError(t, err)
  492. var beforeCount int
  493. require.NoError(t, tx.QueryRowContext(ctx, `
  494. SELECT COUNT(*)
  495. FROM auth_identity_migration_reports
  496. `).Scan(&beforeCount))
  497. _, err = tx.ExecContext(ctx, string(migrationSQL))
  498. require.NoError(t, err)
  499. var afterCount int
  500. require.NoError(t, tx.QueryRowContext(ctx, `
  501. SELECT COUNT(*)
  502. FROM auth_identity_migration_reports
  503. `).Scan(&afterCount))
  504. require.Equal(t, beforeCount, afterCount)
  505. }
  506. func TestAuthIdentityLegacyExternalBackfillMigration_SkipsAmbiguousCanonicalSubjects(t *testing.T) {
  507. tx := testTx(t)
  508. ctx := context.Background()
  509. migrationPath := filepath.Join("..", "..", "migrations", "115_auth_identity_legacy_external_backfill.sql")
  510. migrationSQL, err := os.ReadFile(migrationPath)
  511. require.NoError(t, err)
  512. prepareLegacyExternalIdentitiesTable(t, tx, ctx)
  513. truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
  514. var linuxDoFirstUserID int64
  515. require.NoError(t, tx.QueryRowContext(ctx, `
  516. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  517. VALUES ('legacy-linuxdo-ambiguous-a@example.com', 'hash', 'user', 'active', 0, 1)
  518. RETURNING id`).Scan(&linuxDoFirstUserID))
  519. var linuxDoSecondUserID int64
  520. require.NoError(t, tx.QueryRowContext(ctx, `
  521. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  522. VALUES ('legacy-linuxdo-ambiguous-b@example.com', 'hash', 'user', 'active', 0, 1)
  523. RETURNING id`).Scan(&linuxDoSecondUserID))
  524. var wechatFirstUserID int64
  525. require.NoError(t, tx.QueryRowContext(ctx, `
  526. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  527. VALUES ('legacy-wechat-ambiguous-a@example.com', 'hash', 'user', 'active', 0, 1)
  528. RETURNING id`).Scan(&wechatFirstUserID))
  529. var wechatSecondUserID int64
  530. require.NoError(t, tx.QueryRowContext(ctx, `
  531. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  532. VALUES ('legacy-wechat-ambiguous-b@example.com', 'hash', 'user', 'active', 0, 1)
  533. RETURNING id`).Scan(&wechatSecondUserID))
  534. require.NoError(t, tx.QueryRowContext(ctx, `
  535. INSERT INTO user_external_identities (
  536. user_id,
  537. provider,
  538. provider_user_id,
  539. provider_union_id,
  540. provider_username,
  541. display_name,
  542. metadata
  543. ) VALUES ($1, 'linuxdo', 'linuxdo-ambiguous-subject', NULL, 'legacy-linuxdo-ambiguous-a', 'Legacy LinuxDo Ambiguous A', '{"source":"legacy"}')
  544. RETURNING id
  545. `, linuxDoFirstUserID).Scan(new(int64)))
  546. require.NoError(t, tx.QueryRowContext(ctx, `
  547. INSERT INTO user_external_identities (
  548. user_id,
  549. provider,
  550. provider_user_id,
  551. provider_union_id,
  552. provider_username,
  553. display_name,
  554. metadata
  555. ) VALUES ($1, 'linuxdo', 'linuxdo-ambiguous-subject', NULL, 'legacy-linuxdo-ambiguous-b', 'Legacy LinuxDo Ambiguous B', '{"source":"legacy"}')
  556. RETURNING id
  557. `, linuxDoSecondUserID).Scan(new(int64)))
  558. require.NoError(t, tx.QueryRowContext(ctx, `
  559. INSERT INTO user_external_identities (
  560. user_id,
  561. provider,
  562. provider_user_id,
  563. provider_union_id,
  564. provider_username,
  565. display_name,
  566. metadata
  567. ) VALUES ($1, 'wechat', 'openid-ambiguous-a', 'union-ambiguous-subject', 'legacy-wechat-ambiguous-a', 'Legacy WeChat Ambiguous A', '{"channel":"oa","appid":"wx-ambiguous-a"}')
  568. RETURNING id
  569. `, wechatFirstUserID).Scan(new(int64)))
  570. require.NoError(t, tx.QueryRowContext(ctx, `
  571. INSERT INTO user_external_identities (
  572. user_id,
  573. provider,
  574. provider_user_id,
  575. provider_union_id,
  576. provider_username,
  577. display_name,
  578. metadata
  579. ) VALUES ($1, 'wechat', 'openid-ambiguous-b', 'union-ambiguous-subject', 'legacy-wechat-ambiguous-b', 'Legacy WeChat Ambiguous B', '{"channel":"oa","appid":"wx-ambiguous-b"}')
  580. RETURNING id
  581. `, wechatSecondUserID).Scan(new(int64)))
  582. _, err = tx.ExecContext(ctx, string(migrationSQL))
  583. require.NoError(t, err)
  584. var linuxDoIdentityCount int
  585. require.NoError(t, tx.QueryRowContext(ctx, `
  586. SELECT COUNT(*)
  587. FROM auth_identities
  588. WHERE provider_type = 'linuxdo'
  589. AND provider_key = 'linuxdo'
  590. AND provider_subject = 'linuxdo-ambiguous-subject'
  591. `).Scan(&linuxDoIdentityCount))
  592. require.Zero(t, linuxDoIdentityCount)
  593. var wechatIdentityCount int
  594. require.NoError(t, tx.QueryRowContext(ctx, `
  595. SELECT COUNT(*)
  596. FROM auth_identities
  597. WHERE provider_type = 'wechat'
  598. AND provider_key = 'wechat-main'
  599. AND provider_subject = 'union-ambiguous-subject'
  600. `).Scan(&wechatIdentityCount))
  601. require.Zero(t, wechatIdentityCount)
  602. var wechatChannelCount int
  603. require.NoError(t, tx.QueryRowContext(ctx, `
  604. SELECT COUNT(*)
  605. FROM auth_identity_channels
  606. WHERE provider_type = 'wechat'
  607. AND provider_key = 'wechat-main'
  608. AND channel = 'oa'
  609. AND channel_app_id IN ('wx-ambiguous-a', 'wx-ambiguous-b')
  610. `).Scan(&wechatChannelCount))
  611. require.Zero(t, wechatChannelCount)
  612. }
  613. func TestAuthIdentityLegacyExternalMigrations_ReportAmbiguousCanonicalSubjectsWithoutWinnerAttribution(t *testing.T) {
  614. tx := testTx(t)
  615. ctx := context.Background()
  616. migration115Path := filepath.Join("..", "..", "migrations", "115_auth_identity_legacy_external_backfill.sql")
  617. migration115SQL, err := os.ReadFile(migration115Path)
  618. require.NoError(t, err)
  619. migration116Path := filepath.Join("..", "..", "migrations", "116_auth_identity_legacy_external_safety_reports.sql")
  620. migration116SQL, err := os.ReadFile(migration116Path)
  621. require.NoError(t, err)
  622. prepareLegacyExternalIdentitiesTable(t, tx, ctx)
  623. truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
  624. var linuxDoFirstUserID int64
  625. require.NoError(t, tx.QueryRowContext(ctx, `
  626. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  627. VALUES ('legacy-linuxdo-conflict-a@example.com', 'hash', 'user', 'active', 0, 1)
  628. RETURNING id`).Scan(&linuxDoFirstUserID))
  629. var linuxDoSecondUserID int64
  630. require.NoError(t, tx.QueryRowContext(ctx, `
  631. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  632. VALUES ('legacy-linuxdo-conflict-b@example.com', 'hash', 'user', 'active', 0, 1)
  633. RETURNING id`).Scan(&linuxDoSecondUserID))
  634. var wechatFirstUserID int64
  635. require.NoError(t, tx.QueryRowContext(ctx, `
  636. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  637. VALUES ('legacy-wechat-conflict-a@example.com', 'hash', 'user', 'active', 0, 1)
  638. RETURNING id`).Scan(&wechatFirstUserID))
  639. var wechatSecondUserID int64
  640. require.NoError(t, tx.QueryRowContext(ctx, `
  641. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  642. VALUES ('legacy-wechat-conflict-b@example.com', 'hash', 'user', 'active', 0, 1)
  643. RETURNING id`).Scan(&wechatSecondUserID))
  644. var linuxDoFirstLegacyID int64
  645. require.NoError(t, tx.QueryRowContext(ctx, `
  646. INSERT INTO user_external_identities (
  647. user_id,
  648. provider,
  649. provider_user_id,
  650. provider_union_id,
  651. provider_username,
  652. display_name,
  653. metadata
  654. ) VALUES ($1, 'linuxdo', 'linuxdo-conflict-subject', NULL, 'legacy-linuxdo-conflict-a', 'Legacy LinuxDo Conflict A', '{"source":"legacy"}')
  655. RETURNING id
  656. `, linuxDoFirstUserID).Scan(&linuxDoFirstLegacyID))
  657. var linuxDoSecondLegacyID int64
  658. require.NoError(t, tx.QueryRowContext(ctx, `
  659. INSERT INTO user_external_identities (
  660. user_id,
  661. provider,
  662. provider_user_id,
  663. provider_union_id,
  664. provider_username,
  665. display_name,
  666. metadata
  667. ) VALUES ($1, 'linuxdo', 'linuxdo-conflict-subject', NULL, 'legacy-linuxdo-conflict-b', 'Legacy LinuxDo Conflict B', '{"source":"legacy"}')
  668. RETURNING id
  669. `, linuxDoSecondUserID).Scan(&linuxDoSecondLegacyID))
  670. var wechatFirstLegacyID int64
  671. require.NoError(t, tx.QueryRowContext(ctx, `
  672. INSERT INTO user_external_identities (
  673. user_id,
  674. provider,
  675. provider_user_id,
  676. provider_union_id,
  677. provider_username,
  678. display_name,
  679. metadata
  680. ) VALUES ($1, 'wechat', 'openid-conflict-a', 'union-conflict-subject', 'legacy-wechat-conflict-a', 'Legacy WeChat Conflict A', '{"channel":"oa","appid":"wx-conflict-a"}')
  681. RETURNING id
  682. `, wechatFirstUserID).Scan(&wechatFirstLegacyID))
  683. var wechatSecondLegacyID int64
  684. require.NoError(t, tx.QueryRowContext(ctx, `
  685. INSERT INTO user_external_identities (
  686. user_id,
  687. provider,
  688. provider_user_id,
  689. provider_union_id,
  690. provider_username,
  691. display_name,
  692. metadata
  693. ) VALUES ($1, 'wechat', 'openid-conflict-b', 'union-conflict-subject', 'legacy-wechat-conflict-b', 'Legacy WeChat Conflict B', '{"channel":"oa","appid":"wx-conflict-b"}')
  694. RETURNING id
  695. `, wechatSecondUserID).Scan(&wechatSecondLegacyID))
  696. _, err = tx.ExecContext(ctx, string(migration115SQL))
  697. require.NoError(t, err)
  698. _, err = tx.ExecContext(ctx, string(migration116SQL))
  699. require.NoError(t, err)
  700. var identityCount int
  701. require.NoError(t, tx.QueryRowContext(ctx, `
  702. SELECT COUNT(*)
  703. FROM auth_identities
  704. WHERE (provider_type = 'linuxdo' AND provider_key = 'linuxdo' AND provider_subject = 'linuxdo-conflict-subject')
  705. OR (provider_type = 'wechat' AND provider_key = 'wechat-main' AND provider_subject = 'union-conflict-subject')
  706. `).Scan(&identityCount))
  707. require.Zero(t, identityCount)
  708. var conflictReportCount int
  709. require.NoError(t, tx.QueryRowContext(ctx, `
  710. SELECT COUNT(*)
  711. FROM auth_identity_migration_reports
  712. WHERE report_type = 'legacy_external_identity_conflict'
  713. AND report_key IN ($1, $2, $3, $4)
  714. `, "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))
  715. require.Equal(t, 4, conflictReportCount)
  716. var winnerAttributedReportCount int
  717. require.NoError(t, tx.QueryRowContext(ctx, `
  718. SELECT COUNT(*)
  719. FROM auth_identity_migration_reports
  720. WHERE report_type = 'legacy_external_identity_conflict'
  721. AND report_key IN ($1, $2, $3, $4)
  722. AND details ->> 'existing_identity_id' IS NOT NULL
  723. `, "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))
  724. require.Zero(t, winnerAttributedReportCount)
  725. }
  726. func TestAuthIdentityMigrationReportTypeWideningPreflightKeeps109And116SafeBefore121(t *testing.T) {
  727. tx := testTx(t)
  728. ctx := context.Background()
  729. migration108aPath := filepath.Join("..", "..", "migrations", "108a_widen_auth_identity_migration_report_type.sql")
  730. migration108aSQL, err := os.ReadFile(migration108aPath)
  731. require.NoError(t, err)
  732. migration109Path := filepath.Join("..", "..", "migrations", "109_auth_identity_compat_backfill.sql")
  733. migration109SQL, err := os.ReadFile(migration109Path)
  734. require.NoError(t, err)
  735. migration116Path := filepath.Join("..", "..", "migrations", "116_auth_identity_legacy_external_safety_reports.sql")
  736. migration116SQL, err := os.ReadFile(migration116Path)
  737. require.NoError(t, err)
  738. prepareLegacyExternalIdentitiesTable(t, tx, ctx)
  739. truncateAuthIdentityLegacyFixtureTables(t, tx, ctx)
  740. _, err = tx.ExecContext(ctx, `
  741. ALTER TABLE auth_identity_migration_reports
  742. ALTER COLUMN report_type TYPE VARCHAR(40);
  743. `)
  744. require.NoError(t, err)
  745. var oidcSyntheticUserID int64
  746. require.NoError(t, tx.QueryRowContext(ctx, `
  747. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  748. VALUES ('oidc-before-121@oidc-connect.invalid', 'hash', 'user', 'active', 0, 1)
  749. RETURNING id`).Scan(&oidcSyntheticUserID))
  750. var linuxdoLegacyUserID int64
  751. require.NoError(t, tx.QueryRowContext(ctx, `
  752. INSERT INTO users (email, password_hash, role, status, balance, concurrency)
  753. VALUES ('legacy-linuxdo-before-121@example.com', 'hash', 'user', 'active', 0, 1)
  754. RETURNING id`).Scan(&linuxdoLegacyUserID))
  755. var invalidMetadataLegacyID int64
  756. require.NoError(t, tx.QueryRowContext(ctx, `
  757. INSERT INTO user_external_identities (
  758. user_id,
  759. provider,
  760. provider_user_id,
  761. provider_union_id,
  762. provider_username,
  763. display_name,
  764. metadata
  765. ) VALUES ($1, 'linuxdo', 'linuxdo-before-121', NULL, 'legacy-linuxdo-before-121', 'Legacy LinuxDo Before 121', '{invalid')
  766. RETURNING id
  767. `, linuxdoLegacyUserID).Scan(&invalidMetadataLegacyID))
  768. _, err = tx.ExecContext(ctx, string(migration108aSQL))
  769. require.NoError(t, err)
  770. _, err = tx.ExecContext(ctx, string(migration109SQL))
  771. require.NoError(t, err)
  772. _, err = tx.ExecContext(ctx, string(migration116SQL))
  773. require.NoError(t, err)
  774. var reportTypeWidth int
  775. require.NoError(t, tx.QueryRowContext(ctx, `
  776. SELECT character_maximum_length
  777. FROM information_schema.columns
  778. WHERE table_schema = 'public'
  779. AND table_name = 'auth_identity_migration_reports'
  780. AND column_name = 'report_type'
  781. `).Scan(&reportTypeWidth))
  782. require.Equal(t, 80, reportTypeWidth)
  783. var oidcSyntheticRecoveryReportCount int
  784. require.NoError(t, tx.QueryRowContext(ctx, `
  785. SELECT COUNT(*)
  786. FROM auth_identity_migration_reports
  787. WHERE report_type = 'oidc_synthetic_email_requires_manual_recovery'
  788. AND report_key = $1
  789. `, strconv.FormatInt(oidcSyntheticUserID, 10)).Scan(&oidcSyntheticRecoveryReportCount))
  790. require.Equal(t, 1, oidcSyntheticRecoveryReportCount)
  791. var invalidMetadataReportCount int
  792. require.NoError(t, tx.QueryRowContext(ctx, `
  793. SELECT COUNT(*)
  794. FROM auth_identity_migration_reports
  795. WHERE report_type = 'legacy_external_identity_invalid_metadata_json'
  796. AND report_key = $1
  797. `, "legacy_external_identity:"+strconv.FormatInt(invalidMetadataLegacyID, 10)).Scan(&invalidMetadataReportCount))
  798. require.Equal(t, 1, invalidMetadataReportCount)
  799. }
  800. func prepareLegacyExternalIdentitiesTable(t *testing.T, tx *sql.Tx, ctx context.Context) {
  801. t.Helper()
  802. _, err := tx.ExecContext(ctx, `
  803. CREATE TABLE IF NOT EXISTS user_external_identities (
  804. id BIGSERIAL PRIMARY KEY,
  805. user_id BIGINT NOT NULL,
  806. provider TEXT NOT NULL,
  807. provider_user_id TEXT NOT NULL,
  808. provider_union_id TEXT NULL,
  809. provider_username TEXT NOT NULL DEFAULT '',
  810. display_name TEXT NOT NULL DEFAULT '',
  811. profile_url TEXT NOT NULL DEFAULT '',
  812. avatar_url TEXT NOT NULL DEFAULT '',
  813. metadata TEXT NOT NULL DEFAULT '{}',
  814. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  815. updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
  816. );
  817. `)
  818. require.NoError(t, err)
  819. }
  820. func truncateAuthIdentityLegacyFixtureTables(t *testing.T, tx *sql.Tx, ctx context.Context) {
  821. t.Helper()
  822. _, err := tx.ExecContext(ctx, `
  823. TRUNCATE TABLE
  824. auth_identity_channels,
  825. identity_adoption_decisions,
  826. pending_auth_sessions,
  827. auth_identities,
  828. auth_identity_migration_reports,
  829. user_provider_default_grants,
  830. user_avatars,
  831. user_external_identities,
  832. users
  833. RESTART IDENTITY CASCADE;
  834. `)
  835. require.NoError(t, err)
  836. }