From f105b22054989031a032dd02d656e976fd232afa Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 9 Dec 2025 16:42:41 -0600 Subject: [PATCH] [APP-1674] Add bday fallback for app passwords (#9513) * Add bday fallback for app passwords * Update birthdate copy, add isAppPassword handling on NoAccessScreen * Update copy * Update usage * Bump API package * HmmMMMmmm Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * s/a/your Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Say hello --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> (cherry picked from commit b47f5f023e5213cedf0654fd9bf5546634de8599) --- package.json | 2 +- .../components/NoAccessScreen.tsx | 36 +++++++++-- src/ageAssurance/data.tsx | 21 ++++++- src/ageAssurance/util.ts | 9 +++ src/lib/jwt.ts | 6 ++ yarn.lock | 61 +++++++++++++++++++ 6 files changed, 129 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 20c721499b..18d415641a 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "icons:optimize": "svgo -f ./assets/icons" }, "dependencies": { - "@atproto/api": "^0.18.4", + "@atproto/api": "^0.18.6", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", "@bsky.app/alf": "^0.1.5", diff --git a/src/ageAssurance/components/NoAccessScreen.tsx b/src/ageAssurance/components/NoAccessScreen.tsx index 3b4f7d7088..8607552703 100644 --- a/src/ageAssurance/components/NoAccessScreen.tsx +++ b/src/ageAssurance/components/NoAccessScreen.tsx @@ -9,11 +9,12 @@ import { useCreateSupportLink, } from '#/lib/hooks/useCreateSupportLink' import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' +import {isAppPassword} from '#/lib/jwt' import {logger} from '#/logger' import {isWeb} from '#/platform/detection' import {isNative} from '#/platform/detection' import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' -import {useSessionApi} from '#/state/session' +import {useSession, useSessionApi} from '#/state/session' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog' @@ -29,7 +30,7 @@ import {ShieldCheck_Stroke2_Corner0_Rounded as ShieldIcon} from '#/components/ic import {createStaticClick, SimpleInlineLinkText} from '#/components/Link' import {Outlet as PortalOutlet} from '#/components/Portal' import * as Toast from '#/components/Toast' -import {Text} from '#/components/Typography' +import {Span, Text} from '#/components/Typography' import {BottomSheetOutlet} from '#/../modules/bottom-sheet' import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssuranceDataContext} from '#/ageAssurance/data' @@ -54,6 +55,9 @@ export function NoAccessScreen() { const {logoutCurrentAccount} = useSessionApi() const createSupportLink = useCreateSupportLink() + const {currentAccount} = useSession() + const isUsingAppPassword = isAppPassword(currentAccount?.accessJwt || '') + const aa = useAgeAssurance() const isBlocked = aa.state.status === aa.Status.Blocked const isAARegion = !!region @@ -139,6 +143,9 @@ export function NoAccessScreen() { {isAARegion ? ( <> + + Hey there! + You are accessing Bluesky from a region that legally @@ -167,10 +174,20 @@ export function NoAccessScreen() { ) : ( + + Hi there! + - It looks like you haven't added your birthdate. You must - provide an accurate date of birth to use Bluesky. + In order to provide an age-appropriate experience, we need to + know your birthdate. This is a one-time thing, and your data + will be kept private. + + + + + Set your birthdate below and we'll get you back to posting and + exploring in no time! + + {isUsingAppPassword && ( + + + Hmm, it looks like you're logged in with an{' '} + App Password. To set your + birthdate, you'll need to log in with your main account + password, or ask whomever controls this account to do so. + + + )} )} diff --git a/src/ageAssurance/data.tsx b/src/ageAssurance/data.tsx index ab6cb00105..e06e291052 100644 --- a/src/ageAssurance/data.tsx +++ b/src/ageAssurance/data.tsx @@ -22,7 +22,10 @@ import { import {useAgent, useSession} from '#/state/session' import * as debug from '#/ageAssurance/debug' import {logger} from '#/ageAssurance/logger' -import {isLegacyBirthdateBug} from '#/ageAssurance/util' +import { + getBirthdateStringFromAge, + isLegacyBirthdateBug, +} from '#/ageAssurance/util' import {IS_DEV} from '#/env' import {device} from '#/storage' @@ -324,6 +327,22 @@ export async function getOtherRequiredData({ const data: OtherRequiredData = { birthdate: prefs.birthDate ? prefs.birthDate.toISOString() : undefined, } + + /** + * If we can't read a birthdate, it may be due to the user accessing the + * account via an app password. In that case, fall-back to declared age + * flags. + */ + if (!data.birthdate) { + if (prefs.declaredAge?.isOverAge18) { + data.birthdate = getBirthdateStringFromAge(18) + } else if (prefs.declaredAge?.isOverAge16) { + data.birthdate = getBirthdateStringFromAge(16) + } else if (prefs.declaredAge?.isOverAge13) { + data.birthdate = getBirthdateStringFromAge(13) + } + } + const did = getDidFromAgentSession(agent) if (data && did && birthdateCache.has(did)) { /* diff --git a/src/ageAssurance/util.ts b/src/ageAssurance/util.ts index 1043283305..51213d67c4 100644 --- a/src/ageAssurance/util.ts +++ b/src/ageAssurance/util.ts @@ -86,3 +86,12 @@ export function isUserUnderMinimumAge(birthDate: string) { export function isUserUnderAdultAge(birthDate: string) { return getAge(new Date(birthDate)) < 18 } + +export function getBirthdateStringFromAge(age: number) { + const today = new Date() + return new Date( + today.getFullYear() - age, + today.getMonth(), + today.getDate() - 1, // set to day before to ensure age is reached + ).toISOString() +} diff --git a/src/lib/jwt.ts b/src/lib/jwt.ts index 32a9878e89..8ba2f091ba 100644 --- a/src/lib/jwt.ts +++ b/src/lib/jwt.ts @@ -21,3 +21,9 @@ export function isJwtExpired(token: string) { return true // invalid token or parse error } } + +export function isAppPassword(token: string) { + const payload = jwtDecode(token) + // @ts-ignore + return payload.scope === 'com.atproto.appPass' +} diff --git a/yarn.lock b/yarn.lock index 747859356e..da61df108b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -96,6 +96,20 @@ tlds "^1.234.0" zod "^3.23.8" +"@atproto/api@^0.18.6": + version "0.18.6" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.6.tgz#04c26b97bda01cbe276dea523de6e4a184894c18" + integrity sha512-dkzy2OHSAGgzG9GExvOiwRY73EzVD2AiD3nksng+V6erG0kwLfbmVYjoP9mq9Y16BCXr/7q9lekfogthqU614Q== + dependencies: + "@atproto/common-web" "^0.4.7" + "@atproto/lexicon" "^0.6.0" + "@atproto/syntax" "^0.4.2" + "@atproto/xrpc" "^0.7.7" + await-lock "^2.2.2" + multiformats "^9.9.0" + tlds "^1.234.0" + zod "^3.23.8" + "@atproto/aws@^0.2.31": version "0.2.31" resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.31.tgz#e46d7db34ee57c4f9817269f1e73a7eddba2b9b8" @@ -189,6 +203,15 @@ "@atproto/lex-json" "0.0.2" zod "^3.23.8" +"@atproto/common-web@^0.4.7": + version "0.4.7" + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.7.tgz#6318f1daef105139449ff088ded6bfc97f3b6d59" + integrity sha512-vjw2+81KPo2/SAbbARGn64Ln+6JTI0FTI4xk8if0ebBfDxFRmHb2oSN1y77hzNq/ybGHqA2mecfhS03pxC5+lg== + dependencies: + "@atproto/lex-data" "0.0.3" + "@atproto/lex-json" "0.0.3" + zod "^3.23.8" + "@atproto/common@0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210" @@ -337,6 +360,17 @@ uint8arrays "3.0.0" unicode-segmenter "^0.14.0" +"@atproto/lex-data@0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.3.tgz#1ce1bd19e17af41b2991a2a02ad439a136dec4e9" + integrity sha512-ivo1IpY/EX+RIpxPgCf4cPhQo5bfu4nrpa1vJCt8hCm9SfoonJkDFGa0n4SMw4JnXZoUcGcrJ46L+D8bH6GI2g== + dependencies: + "@atproto/syntax" "0.4.2" + multiformats "^9.9.0" + tslib "^2.8.1" + uint8arrays "3.0.0" + unicode-segmenter "^0.14.0" + "@atproto/lex-document@0.0.4": version "0.0.4" resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.4.tgz#2ada83e30bcef84cc0ff010c5dd8eb2514160731" @@ -354,6 +388,14 @@ "@atproto/lex-data" "0.0.2" tslib "^2.8.1" +"@atproto/lex-json@0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.3.tgz#c74aa0ede948b79ccc7fc8d9d1a3501a81c1cd8d" + integrity sha512-ZVcY7XlRfdPYvQQ2WroKUepee0+NCovrSXgXURM3Xv+n5jflJCoczguROeRr8sN0xvT0ZbzMrDNHCUYKNnxcjw== + dependencies: + "@atproto/lex-data" "0.0.3" + tslib "^2.8.1" + "@atproto/lex-resolver@0.0.4": version "0.0.4" resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.4.tgz#769b10e95860a055bce6d7dbf876f2d7612b1c7a" @@ -389,6 +431,17 @@ multiformats "^9.9.0" zod "^3.23.8" +"@atproto/lexicon@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.6.0.tgz#444e340ba651f34725787d11916436f507770f09" + integrity sha512-5veb8aD+J5M0qszLJ+73KSFsFrJBgAY/nM1TSAJvGY7fNc9ZAT+PSUlmIyrdye9YznAZ07yktalls/TwNV7cHQ== + dependencies: + "@atproto/common-web" "^0.4.7" + "@atproto/syntax" "^0.4.2" + iso-datestring-validator "^2.2.2" + multiformats "^9.9.0" + zod "^3.23.8" + "@atproto/oauth-provider-api@0.3.4": version "0.3.4" resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-api/-/oauth-provider-api-0.3.4.tgz#bce9b1a5a6bd759b0de8f6b80c4aced9b64f0c79" @@ -621,6 +674,14 @@ "@atproto/lexicon" "^0.5.2" zod "^3.23.8" +"@atproto/xrpc@^0.7.7": + version "0.7.7" + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.7.7.tgz#c0e3106c854cb9bc7d3129de2f31b8256eb0ed11" + integrity sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA== + dependencies: + "@atproto/lexicon" "^0.6.0" + zod "^3.23.8" + "@aws-crypto/crc32@5.2.0": version "5.2.0" resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz#cfcc22570949c98c6689cfcbd2d693d36cdae2e1"