You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Sign in with Apple: при удалении аккаунта отзывать токены Apple (App Store 5.1.1(v)) — до публичной подачи #407
PR #406 (#277) добавляет вход через Apple. Правило App Store 5.1.1(v) (с 30.06.2022) требует: если приложение даёт Sign in with Apple, удаление аккаунта должно отзывать токены Apple через REST API Apple (/auth/revoke). Сейчас «Удалить аккаунт» (/api/v1/account/delete, #309) стирает наши данные и удаляет пользователя в Clerk, но токены Apple не отзывает — это строка реестра из #406. TestFlight это правило не проверяет, проверка будет при подаче в стор.
Что выяснить и сделать
Делает ли это Clerk сам при users.deleteUser для пользователя с внешним аккаунтом Apple. Ответ брать из документации Clerk и поведения установленного @clerk/backend, не по памяти.
Если нет — отзыв на нашей стороне в маршруте удаления: Clerk отдаёт токен OAuth Apple (users.getUserOauthAccessToken или аналог), запрос в appleid.apple.com/auth/revoke с client secret (JWT, подписанный ключом Sign in with Apple — ключ создаёт владелец в Apple Developer). Отзыв best-effort, без блокировки удаления, лог по id.
Ключ Sign in with Apple (.p8) и Key ID — секреты, только в env Vercel. Это действие владельца.
Критерии
Ответ на п.1 с доказательством. При необходимости — реализация с тестом (мок Apple) и кейсом лог-аудита. Обновлённая строка реестра.
GDPR: применимо — отзыв доступа стороннего обработчика при стирании (ст. 17).
Коротко: Clerk токены Apple не отзывает и отзывать ему нечем — у него нет ни refresh-, ни access-токена Apple для пользователя, вошедшего нативно. Отзыв делаем своим путём, документированным Apple: свежий authorizationCode с устройства при «Delete account» → обмен на сервере → /auth/revoke.
Доказательства
Что нативный поток отдаёт Clerk. Установленный @clerk/expo 4.7.1, mobile/node_modules/@clerk/expo/dist/hooks/useSignInWithApple.ios.js:
authorizationCode из credential не читается вовсе. Типы @clerk/shared (dist/types/signUpFuture.d.ts): «'oauth_token_apple': The user will be authenticated using a native Sign in with Apple identity token».
Что принимает /auth/revoke. Apple, Revoke tokens: token — «The user refresh token or access token intended to be revoked»; token_type_hint — refresh_token или access_token; и прямо: «In order to revoke authorization for a user, you must obtain a valid refresh token or access token. If you don't have either token for the user, you can generate tokens when validating an authorization code». Identity token в этот список не входит.
Откуда берутся refresh/access-токены. Apple, Generate and validate tokens: только обмен authorization_code на POST /auth/token (client_secret обязателен); код «single-use only and valid for five minutes». Clerk кода не получал → обменивать ему нечего → токенов у него нет.
@clerk/backend 3.22.0 (установлен): getUserOauthAccessToken(userId, provider) = GET /users/{id}/oauth_access_tokens/oauth_apple; ресурс OauthAccessToken несёт token (access), scopes, expiresAt, idToken — поля refresh-токена нет вовсе. Для нативного пользователя там пусто (п.3); даже у веб-OAuth access-токен Apple живёт час (expires_in: 3600) — к моменту удаления он мёртв, надёжной ручкой для отзыва он не является. deleteUser = голый DELETE /users/{id}.
Сервер (lib/apple-revoke.ts, новый): после успешного eraseUser и ДО users.deleteUser — client secret = JWT ES256 (kid = APPLE_SIGN_IN_KEY_ID, iss = APPLE_TEAM_ID, sub = client_id = bundle id tv.matio.app, aud = https://appleid.apple.com, жизнь 5 мин), подписанный APPLE_SIGN_IN_PRIVATE_KEY через уже установленный jsonwebtoken (им же подписываются JWT Mux) → POST /auth/token (grant_type=authorization_code, без redirect_uri — у нативного кода его нет) → POST /auth/revoke с refresh-токеном. Простой fetch, по 5 с, без ретраев, никогда не бросает. Типизированный статус: revoked / skipped_no_code / skipped_unconfigured / failed (+ шаг client_secret/token/revoke, HTTP-статус, код ошибки Apple из её закрытого перечня или класс исключения). Пишется в строку account delete: done по id пользователя; failed — ещё предупреждение в Sentry по id. Никогда не логируются: код, токены, client secret, тело ответа Apple.
Почему после стирания. Отзываем только когда удаление действительно идёт: сбой eraseUser (500) оставляет и аккаунт, и грант Apple; каждый повтор приносит свежий код (одноразовый, 5 мин).
Ручной путь (pnpm erase-user --apply) отозвать не может — кода нет; ранбук §4 скажет, что человек снимает Matio сам в Настройки → Apple ID → Вход с Apple.
Env (действие владельца): APPLE_TEAM_ID, APPLE_SIGN_IN_KEY_ID, APPLE_SIGN_IN_PRIVATE_KEY (.p8 как есть или base64 от него). Без них — skipped_unconfigured, удаление работает как сегодня.
Тесты: мок Apple — успех, ошибка Apple, таймаут, не настроено; в каждом случае стирание и удаление в Clerk завершаются. Кейс лог-аудита. Тесты приложения — в проекте vitest mobile.
Код влит (PR #427, порядок по варианту (а) владельца, 11.10). Задача открыта с needs:owner: ключ Sign in with Apple в Vercel (APPLE_TEAM_ID / APPLE_SIGN_IN_KEY_ID / APPLE_SIGN_IN_PRIVATE_KEY) и проверки на устройстве из строки реестра #407 — гейт перед публичной подачей и перед включением apple в APP_SOCIAL_SIGNIN.
Activity
fix: замечания ревью #406 — вход через Apple и Google (#277)
feat: вход через Apple и Google в приложении iOS (#277) (#406)
efymd9 commentedon Oct 9, 2026
Ответ на п.1 спеки и выбранный дизайн (до кода)
Коротко: Clerk токены Apple не отзывает и отзывать ему нечем — у него нет ни refresh-, ни access-токена Apple для пользователя, вошедшего нативно. Отзыв делаем своим путём, документированным Apple: свежий
authorizationCodeс устройства при «Delete account» → обмен на сервере →/auth/revoke.Доказательства
@clerk/expo4.7.1,mobile/node_modules/@clerk/expo/dist/hooks/useSignInWithApple.ios.js:authorizationCodeиз credential не читается вовсе. Типы@clerk/shared(dist/types/signUpFuture.d.ts): «'oauth_token_apple': The user will be authenticated using a native Sign in with Apple identity token»./auth/revoke. Apple, Revoke tokens:token— «The user refresh token or access token intended to be revoked»;token_type_hint—refresh_tokenилиaccess_token; и прямо: «In order to revoke authorization for a user, you must obtain a valid refresh token or access token. If you don't have either token for the user, you can generate tokens when validating an authorization code». Identity token в этот список не входит.authorization_codeнаPOST /auth/token(client_secret обязателен); код «single-use only and valid for five minutes». Clerk кода не получал → обменивать ему нечего → токенов у него нет.@clerk/backend3.22.0 (установлен):getUserOauthAccessToken(userId, provider)=GET /users/{id}/oauth_access_tokens/oauth_apple; ресурсOauthAccessTokenнесётtoken(access),scopes,expiresAt,idToken— поля refresh-токена нет вовсе. Для нативного пользователя там пусто (п.3); даже у веб-OAuth access-токен Apple живёт час (expires_in: 3600) — к моменту удаления он мёртв, надёжной ручкой для отзыва он не является.deleteUser= голыйDELETE /users/{id}.useSignInWithApple,getUserOauthAccessToken) про отзыв токенов Apple при удалении пользователя не говорит ничего.Итог: вариант «Clerk делает сам» (a) и «Clerk отдаёт токен» (b) отпадают. Делаем (c).
Дизайн (c)
apple(user.externalAccounts,provider === "apple") —AppleAuthentication.signInAsync({ requestedScopes: [] })(expo-apple-authentication, зависимость с Приложение: вход через Google (и Sign in with Apple — обязателен по правилу App Store 4.8) #277 — новой нет) →authorizationCode→POST /api/v1/account/deleteс телом{ appleAuthorizationCode }. Отмена листа Apple, ошибка, Android, аккаунт без Apple → запрос без кода: удаление идёт всё равно (отзыв best-effort, никогда не блокирует). Поведение Мобильное: удаление аккаунта в приложении — App Store 5.1.1(v) [после #299] #309 / Стирание аккаунта: остаточные дыры самообслуживания (#309) — гонка heal, поздний user.created, несостоявшаяся отмена Stripe, таймаут клиента #336 / Пред-релизная проверка 0.18.0: 5 низких находок (ложный «удалён» в приложении, pnpm-баннер с адресом, дрейф CLAUDE.md/services.md) #398 не меняется.lib/apple-revoke.ts, новый): после успешногоeraseUserи ДОusers.deleteUser— client secret = JWT ES256 (kid=APPLE_SIGN_IN_KEY_ID,iss=APPLE_TEAM_ID,sub= client_id = bundle idtv.matio.app,aud=https://appleid.apple.com, жизнь 5 мин), подписанныйAPPLE_SIGN_IN_PRIVATE_KEYчерез уже установленныйjsonwebtoken(им же подписываются JWT Mux) →POST /auth/token(grant_type=authorization_code, безredirect_uri— у нативного кода его нет) →POST /auth/revokeс refresh-токеном. Простойfetch, по 5 с, без ретраев, никогда не бросает. Типизированный статус:revoked/skipped_no_code/skipped_unconfigured/failed(+ шагclient_secret/token/revoke, HTTP-статус, код ошибки Apple из её закрытого перечня или класс исключения). Пишется в строкуaccount delete: doneпо id пользователя;failed— ещё предупреждение в Sentry по id. Никогда не логируются: код, токены, client secret, тело ответа Apple.eraseUser(500) оставляет и аккаунт, и грант Apple; каждый повтор приносит свежий код (одноразовый, 5 мин).pnpm erase-user --apply) отозвать не может — кода нет; ранбук §4 скажет, что человек снимает Matio сам в Настройки → Apple ID → Вход с Apple.APPLE_TEAM_ID,APPLE_SIGN_IN_KEY_ID,APPLE_SIGN_IN_PRIVATE_KEY(.p8 как есть или base64 от него). Без них —skipped_unconfigured, удаление работает как сегодня.mobile.fix: отзыв Apple при удалении — параллельно с Clerk, дедлайн листа, п…
fix: лист Apple при удалении — между двумя подтверждениями, отмена на…
fix: удаление аккаунта с Apple — код чужого Apple ID отбрасывается, A…
fix: реестр #407 — копия для Android и миграция Apple-id после перено…
fix: «Delete account» отзывает грант Sign in with Apple — свежий код …
efymd9 commentedon Oct 11, 2026
Код влит (PR #427, порядок по варианту (а) владельца, 11.10). Задача открыта с needs:owner: ключ Sign in with Apple в Vercel (APPLE_TEAM_ID / APPLE_SIGN_IN_KEY_ID / APPLE_SIGN_IN_PRIVATE_KEY) и проверки на устройстве из строки реестра #407 — гейт перед публичной подачей и перед включением apple в APP_SOCIAL_SIGNIN.