Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork client

[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>

authored by

Eric Bailey
surfdude29
and committed by
GitHub
b47f5f02 4d50555e

+129 -6
+1 -1
package.json
··· 72 72 "icons:optimize": "svgo -f ./assets/icons" 73 73 }, 74 74 "dependencies": { 75 - "@atproto/api": "^0.18.4", 75 + "@atproto/api": "^0.18.6", 76 76 "@bitdrift/react-native": "^0.6.8", 77 77 "@braintree/sanitize-url": "^6.0.2", 78 78 "@bsky.app/alf": "^0.1.5",
+32 -4
src/ageAssurance/components/NoAccessScreen.tsx
··· 9 9 useCreateSupportLink, 10 10 } from '#/lib/hooks/useCreateSupportLink' 11 11 import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 12 + import {isAppPassword} from '#/lib/jwt' 12 13 import {logger} from '#/logger' 13 14 import {isWeb} from '#/platform/detection' 14 15 import {isNative} from '#/platform/detection' 15 16 import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' 16 - import {useSessionApi} from '#/state/session' 17 + import {useSession, useSessionApi} from '#/state/session' 17 18 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 18 19 import {Admonition} from '#/components/Admonition' 19 20 import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog' ··· 29 30 import {createStaticClick, SimpleInlineLinkText} from '#/components/Link' 30 31 import {Outlet as PortalOutlet} from '#/components/Portal' 31 32 import * as Toast from '#/components/Toast' 32 - import {Text} from '#/components/Typography' 33 + import {Span, Text} from '#/components/Typography' 33 34 import {BottomSheetOutlet} from '#/../modules/bottom-sheet' 34 35 import {useAgeAssurance} from '#/ageAssurance' 35 36 import {useAgeAssuranceDataContext} from '#/ageAssurance/data' ··· 53 54 const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed() 54 55 const {logoutCurrentAccount} = useSessionApi() 55 56 const createSupportLink = useCreateSupportLink() 57 + 58 + const {currentAccount} = useSession() 59 + const isUsingAppPassword = isAppPassword(currentAccount?.accessJwt || '') 56 60 57 61 const aa = useAgeAssurance() 58 62 const isBlocked = aa.state.status === aa.Status.Blocked ··· 140 144 <> 141 145 <View style={[a.gap_lg]}> 142 146 <Text style={[textStyles]}> 147 + <Trans>Hey there!</Trans> 148 + </Text> 149 + <Text style={[textStyles]}> 143 150 <Trans> 144 151 You are accessing Bluesky from a region that legally 145 152 requires us to verify your age before allowing you to ··· 168 175 ) : ( 169 176 <View style={[a.gap_lg]}> 170 177 <Text style={[textStyles]}> 178 + <Trans>Hi there!</Trans> 179 + </Text> 180 + <Text style={[textStyles]}> 171 181 <Trans> 172 - It looks like you haven't added your birthdate. You must 173 - provide an accurate date of birth to use Bluesky. 182 + In order to provide an age-appropriate experience, we need to 183 + know your birthdate. This is a one-time thing, and your data 184 + will be kept private. 185 + </Trans> 186 + </Text> 187 + <Text style={[textStyles]}> 188 + <Trans> 189 + Set your birthdate below and we'll get you back to posting and 190 + exploring in no time! 174 191 </Trans> 175 192 </Text> 176 193 <Button ··· 182 199 <Trans>Add your birthdate</Trans> 183 200 </ButtonText> 184 201 </Button> 202 + 203 + {isUsingAppPassword && ( 204 + <Admonition type="info"> 205 + <Trans> 206 + Hmm, it looks like you're logged in with an{' '} 207 + <Span style={[a.italic]}>App Password</Span>. To set your 208 + birthdate, you'll need to log in with your main account 209 + password, or ask whomever controls this account to do so. 210 + </Trans> 211 + </Admonition> 212 + )} 185 213 </View> 186 214 )} 187 215
+20 -1
src/ageAssurance/data.tsx
··· 22 22 import {useAgent, useSession} from '#/state/session' 23 23 import * as debug from '#/ageAssurance/debug' 24 24 import {logger} from '#/ageAssurance/logger' 25 - import {isLegacyBirthdateBug} from '#/ageAssurance/util' 25 + import { 26 + getBirthdateStringFromAge, 27 + isLegacyBirthdateBug, 28 + } from '#/ageAssurance/util' 26 29 import {IS_DEV} from '#/env' 27 30 import {device} from '#/storage' 28 31 ··· 324 327 const data: OtherRequiredData = { 325 328 birthdate: prefs.birthDate ? prefs.birthDate.toISOString() : undefined, 326 329 } 330 + 331 + /** 332 + * If we can't read a birthdate, it may be due to the user accessing the 333 + * account via an app password. In that case, fall-back to declared age 334 + * flags. 335 + */ 336 + if (!data.birthdate) { 337 + if (prefs.declaredAge?.isOverAge18) { 338 + data.birthdate = getBirthdateStringFromAge(18) 339 + } else if (prefs.declaredAge?.isOverAge16) { 340 + data.birthdate = getBirthdateStringFromAge(16) 341 + } else if (prefs.declaredAge?.isOverAge13) { 342 + data.birthdate = getBirthdateStringFromAge(13) 343 + } 344 + } 345 + 327 346 const did = getDidFromAgentSession(agent) 328 347 if (data && did && birthdateCache.has(did)) { 329 348 /*
+9
src/ageAssurance/util.ts
··· 86 86 export function isUserUnderAdultAge(birthDate: string) { 87 87 return getAge(new Date(birthDate)) < 18 88 88 } 89 + 90 + export function getBirthdateStringFromAge(age: number) { 91 + const today = new Date() 92 + return new Date( 93 + today.getFullYear() - age, 94 + today.getMonth(), 95 + today.getDate() - 1, // set to day before to ensure age is reached 96 + ).toISOString() 97 + }
+6
src/lib/jwt.ts
··· 21 21 return true // invalid token or parse error 22 22 } 23 23 } 24 + 25 + export function isAppPassword(token: string) { 26 + const payload = jwtDecode(token) 27 + // @ts-ignore 28 + return payload.scope === 'com.atproto.appPass' 29 + }
+61
yarn.lock
··· 96 96 tlds "^1.234.0" 97 97 zod "^3.23.8" 98 98 99 + "@atproto/api@^0.18.6": 100 + version "0.18.6" 101 + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.6.tgz#04c26b97bda01cbe276dea523de6e4a184894c18" 102 + integrity sha512-dkzy2OHSAGgzG9GExvOiwRY73EzVD2AiD3nksng+V6erG0kwLfbmVYjoP9mq9Y16BCXr/7q9lekfogthqU614Q== 103 + dependencies: 104 + "@atproto/common-web" "^0.4.7" 105 + "@atproto/lexicon" "^0.6.0" 106 + "@atproto/syntax" "^0.4.2" 107 + "@atproto/xrpc" "^0.7.7" 108 + await-lock "^2.2.2" 109 + multiformats "^9.9.0" 110 + tlds "^1.234.0" 111 + zod "^3.23.8" 112 + 99 113 "@atproto/aws@^0.2.31": 100 114 version "0.2.31" 101 115 resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.31.tgz#e46d7db34ee57c4f9817269f1e73a7eddba2b9b8" ··· 187 201 dependencies: 188 202 "@atproto/lex-data" "0.0.2" 189 203 "@atproto/lex-json" "0.0.2" 204 + zod "^3.23.8" 205 + 206 + "@atproto/common-web@^0.4.7": 207 + version "0.4.7" 208 + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.7.tgz#6318f1daef105139449ff088ded6bfc97f3b6d59" 209 + integrity sha512-vjw2+81KPo2/SAbbARGn64Ln+6JTI0FTI4xk8if0ebBfDxFRmHb2oSN1y77hzNq/ybGHqA2mecfhS03pxC5+lg== 210 + dependencies: 211 + "@atproto/lex-data" "0.0.3" 212 + "@atproto/lex-json" "0.0.3" 190 213 zod "^3.23.8" 191 214 192 215 "@atproto/common@0.1.0": ··· 337 360 uint8arrays "3.0.0" 338 361 unicode-segmenter "^0.14.0" 339 362 363 + "@atproto/lex-data@0.0.3": 364 + version "0.0.3" 365 + resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.3.tgz#1ce1bd19e17af41b2991a2a02ad439a136dec4e9" 366 + integrity sha512-ivo1IpY/EX+RIpxPgCf4cPhQo5bfu4nrpa1vJCt8hCm9SfoonJkDFGa0n4SMw4JnXZoUcGcrJ46L+D8bH6GI2g== 367 + dependencies: 368 + "@atproto/syntax" "0.4.2" 369 + multiformats "^9.9.0" 370 + tslib "^2.8.1" 371 + uint8arrays "3.0.0" 372 + unicode-segmenter "^0.14.0" 373 + 340 374 "@atproto/lex-document@0.0.4": 341 375 version "0.0.4" 342 376 resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.4.tgz#2ada83e30bcef84cc0ff010c5dd8eb2514160731" ··· 352 386 integrity sha512-Pd72lO+l2rhOTutnf11omh9ZkoB/elbzE3HSmn2wuZlyH1mRhTYvoH8BOGokWQwbZkCE8LL3nOqMT3gHCD2l7g== 353 387 dependencies: 354 388 "@atproto/lex-data" "0.0.2" 389 + tslib "^2.8.1" 390 + 391 + "@atproto/lex-json@0.0.3": 392 + version "0.0.3" 393 + resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.3.tgz#c74aa0ede948b79ccc7fc8d9d1a3501a81c1cd8d" 394 + integrity sha512-ZVcY7XlRfdPYvQQ2WroKUepee0+NCovrSXgXURM3Xv+n5jflJCoczguROeRr8sN0xvT0ZbzMrDNHCUYKNnxcjw== 395 + dependencies: 396 + "@atproto/lex-data" "0.0.3" 355 397 tslib "^2.8.1" 356 398 357 399 "@atproto/lex-resolver@0.0.4": ··· 385 427 dependencies: 386 428 "@atproto/common-web" "^0.4.4" 387 429 "@atproto/syntax" "^0.4.1" 430 + iso-datestring-validator "^2.2.2" 431 + multiformats "^9.9.0" 432 + zod "^3.23.8" 433 + 434 + "@atproto/lexicon@^0.6.0": 435 + version "0.6.0" 436 + resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.6.0.tgz#444e340ba651f34725787d11916436f507770f09" 437 + integrity sha512-5veb8aD+J5M0qszLJ+73KSFsFrJBgAY/nM1TSAJvGY7fNc9ZAT+PSUlmIyrdye9YznAZ07yktalls/TwNV7cHQ== 438 + dependencies: 439 + "@atproto/common-web" "^0.4.7" 440 + "@atproto/syntax" "^0.4.2" 388 441 iso-datestring-validator "^2.2.2" 389 442 multiformats "^9.9.0" 390 443 zod "^3.23.8" ··· 619 672 integrity sha512-RvCf4j0JnKYWuz3QzsYCntJi3VuiAAybQsMIUw2wLWcHhchO9F7UaBZINLL2z0qc/cYWPv5NSwcVydMseoCZLA== 620 673 dependencies: 621 674 "@atproto/lexicon" "^0.5.2" 675 + zod "^3.23.8" 676 + 677 + "@atproto/xrpc@^0.7.7": 678 + version "0.7.7" 679 + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.7.7.tgz#c0e3106c854cb9bc7d3129de2f31b8256eb0ed11" 680 + integrity sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA== 681 + dependencies: 682 + "@atproto/lexicon" "^0.6.0" 622 683 zod "^3.23.8" 623 684 624 685 "@aws-crypto/crc32@5.2.0":