[READ-ONLY] a fast, modern browser for the npm registry

refactor: added back support for did/PDS logins and passes the locale onto the oauth screen (#1187)

authored by baileytownsend.dev and committed by

GitHub cb040c4b 3030bc0a

+43 -11
+27 -7
app/components/Header/AuthModal.client.vue
··· 1 1 <script setup lang="ts"> 2 2 import { useAtproto } from '~/composables/atproto/useAtproto' 3 3 import { authRedirect } from '~/utils/atproto/helpers' 4 + import { ensureValidAtIdentifier } from '@atproto/syntax' 4 5 5 6 const handleInput = shallowRef('') 7 + const errorMessage = shallowRef('') 6 8 const route = useRoute() 7 9 const { user, logout } = useAtproto() 8 10 ··· 13 15 const atprotoLink = `https://atproto.com/${localeSubPath}` 14 16 15 17 async function handleBlueskySignIn() { 16 - await authRedirect('https://bsky.social', { redirectTo: route.fullPath }) 18 + await authRedirect('https://bsky.social', { redirectTo: route.fullPath, locale: locale.value }) 17 19 } 18 20 19 21 async function handleCreateAccount() { 20 - await authRedirect('https://npmx.social', { create: true, redirectTo: route.fullPath }) 22 + await authRedirect('https://npmx.social', { 23 + create: true, 24 + redirectTo: route.fullPath, 25 + locale: locale.value, 26 + }) 21 27 } 22 28 23 29 async function handleLogin() { 24 30 if (handleInput.value) { 25 - await authRedirect(handleInput.value) 31 + // URLS to PDSs are valid for oauth redirects 32 + if (!handleInput.value.startsWith('https://')) { 33 + try { 34 + ensureValidAtIdentifier(handleInput.value) 35 + } catch (error) { 36 + errorMessage.value = 37 + error instanceof Error ? error.message : $t('auth.modal.default_input_error') 38 + return 39 + } 40 + } 41 + await authRedirect(handleInput.value, { 42 + redirectTo: route.fullPath, 43 + locale: locale.value, 44 + }) 26 45 } 27 46 } 28 47 29 48 watch(handleInput, newHandleInput => { 49 + errorMessage.value = '' 30 50 if (!newHandleInput) return 31 51 32 - const normalized = newHandleInput 33 - .trim() 34 - .toLowerCase() 35 - .replace(/[^a-z0-9.-]/g, '') 52 + const normalized = newHandleInput.trim().toLowerCase().replace(/@/g, '') 36 53 37 54 if (normalized !== newHandleInput) { 38 55 handleInput.value = normalized ··· 81 98 v-bind="noCorrect" 82 99 class="w-full px-3 py-2 font-mono text-sm bg-bg-subtle border border-border rounded-md text-fg placeholder:text-fg-subtle transition-colors duration-200 hover:border-fg-subtle focus:border-accent focus-visible:(outline-2 outline-accent/70)" 83 100 /> 101 + <p v-if="errorMessage" class="text-red-500 text-xs mt-1" role="alert"> 102 + {{ errorMessage }} 103 + </p> 84 104 </div> 85 105 86 106 <details class="text-sm">
+2 -1
app/utils/atproto/helpers.ts
··· 4 4 interface AuthRedirectOptions { 5 5 create?: boolean 6 6 redirectTo?: string 7 + locale?: string 7 8 } 8 9 9 10 /** 10 11 * Redirect user to ATProto authentication 11 12 */ 12 13 export async function authRedirect(identifier: string, options: AuthRedirectOptions = {}) { 13 - let query: LocationQueryRaw = { handle: identifier } 14 + let query: LocationQueryRaw = { handle: identifier, locale: options.locale || 'en' } 14 15 if (options.create) { 15 16 query = { ...query, create: 'true' } 16 17 }
+2 -1
i18n/locales/en.json
··· 796 796 "create_account": "Create a new account", 797 797 "connect_bluesky": "Connect with Bluesky", 798 798 "what_is_atmosphere": "What is an Atmosphere account?", 799 - "atmosphere_explanation": "{npmx} uses the {atproto} to power many of its social features, allowing users to own their data and use one account for all compatible applications. Once you create an account, you can use other apps like {bluesky} and {tangled} with the same account." 799 + "atmosphere_explanation": "{npmx} uses the {atproto} to power many of its social features, allowing users to own their data and use one account for all compatible applications. Once you create an account, you can use other apps like {bluesky} and {tangled} with the same account.", 800 + "default_input_error": "Please enter a valid handle, DID, or a full PDS URL" 800 801 } 801 802 }, 802 803 "header": {
+2 -1
lunaria/files/en-GB.json
··· 796 796 "create_account": "Create a new account", 797 797 "connect_bluesky": "Connect with Bluesky", 798 798 "what_is_atmosphere": "What is an Atmosphere account?", 799 - "atmosphere_explanation": "{npmx} uses the {atproto} to power many of its social features, allowing users to own their data and use one account for all compatible applications. Once you create an account, you can use other apps like {bluesky} and {tangled} with the same account." 799 + "atmosphere_explanation": "{npmx} uses the {atproto} to power many of its social features, allowing users to own their data and use one account for all compatible applications. Once you create an account, you can use other apps like {bluesky} and {tangled} with the same account.", 800 + "default_input_error": "Please enter a valid handle, DID, or a full PDS URL" 800 801 } 801 802 }, 802 803 "header": {
+2 -1
lunaria/files/en-US.json
··· 796 796 "create_account": "Create a new account", 797 797 "connect_bluesky": "Connect with Bluesky", 798 798 "what_is_atmosphere": "What is an Atmosphere account?", 799 - "atmosphere_explanation": "{npmx} uses the {atproto} to power many of its social features, allowing users to own their data and use one account for all compatible applications. Once you create an account, you can use other apps like {bluesky} and {tangled} with the same account." 799 + "atmosphere_explanation": "{npmx} uses the {atproto} to power many of its social features, allowing users to own their data and use one account for all compatible applications. Once you create an account, you can use other apps like {bluesky} and {tangled} with the same account.", 800 + "default_input_error": "Please enter a valid handle, DID, or a full PDS URL" 800 801 } 801 802 }, 802 803 "header": {
+1
nuxt.config.ts
··· 262 262 'semver', 263 263 'validate-npm-package-name', 264 264 '@atproto/lex', 265 + '@atproto/syntax', 265 266 'fast-npm-meta', 266 267 '@floating-ui/vue', 267 268 ],
+7
server/api/auth/atproto.get.ts
··· 13 13 // @ts-expect-error virtual file from oauth module 14 14 import { clientUri } from '#oauth/config' 15 15 16 + //I did not have luck with other ones than these. I got this list from the PDS language picker 17 + const OAUTH_LOCALES = new Set(['en', 'fr-FR', 'ja-JP']) 18 + 16 19 /** 17 20 * Fetch the user's profile record to get their avatar blob reference 18 21 * @param did ··· 98 101 }) 99 102 } 100 103 104 + const localeFromQuery = query.locale?.toString() ?? 'en' 105 + const locale = OAUTH_LOCALES.has(localeFromQuery) ? localeFromQuery : 'en' 106 + 101 107 const redirectUrl = await atclient.authorize(handle, { 102 108 scope, 103 109 prompt: create ? 'create' : undefined, 110 + ui_locales: locale, 104 111 }) 105 112 return sendRedirect(event, redirectUrl.toString()) 106 113 } catch (error) {