Allow proxying of getSession using OAuth session (#3820)

* Allow proxying of dpop bound requests by using service auth instead, for the `getSession` endpoint.

* Show `getSession` data in example app

* Add  scope

* strings

* cleanup

* tidy

* tidy

* Add transition:email scope to example app

* strings

* changeset

* pr comments
This commit is contained in:
Matthieu Sieben 2025-05-20 14:37:02 +02:00 committed by GitHub
parent 36dbd41551
commit 8318c57187
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 681 additions and 207 deletions

View File

@ -0,0 +1,5 @@
---
"@atproto/oauth-client-browser-example": patch
---
Add `com.atproto.server.getSession` query.

View File

@ -0,0 +1,5 @@
---
"@atproto/pds": patch
---
Allow access to `com.atproto.server.getSession` when authenticated using oauth credentials. Email info (`email`, `emailConfirmed`) will only be exposed if the credentials were issued with the `transition:email` scope.

View File

@ -0,0 +1,7 @@
---
"@atproto/oauth-provider-ui": patch
"@atproto/oauth-provider": patch
"@atproto/pds": patch
---
Add support for `transition:email` oauth scope

View File

@ -0,0 +1,5 @@
---
"@atproto/oauth-client-browser-example": patch
---
Allow configuration of loopback client id scope through query param

View File

@ -45,7 +45,11 @@
],
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"ignoreRestSiblings": true
}
],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-interface": "off",

View File

@ -26,7 +26,6 @@
"files": [
"dist"
],
"dependencies": {},
"devDependencies": {
"@atproto-labs/rollup-plugin-bundle-manifest": "workspace:*",
"@atproto/api": "workspace:*",
@ -35,17 +34,19 @@
"@atproto/oauth-types": "workspace:*",
"@atproto/xrpc": "workspace:*",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-html": "^1.0.4",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-swc": "^0.4.0",
"@swc/helpers": "^0.5.15",
"@tanstack/react-query": "^5.71.10",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"autoprefixer": "^10.4.17",
"postcss": "^8.4.33",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-json-view": "^1.21.3",
"rollup": "^4.13.0",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-serve": "^1.1.1",

View File

@ -1,4 +1,6 @@
import { useCallback, useEffect, useState } from 'react'
import { UseQueryResult, useQuery } from '@tanstack/react-query'
import { JSX, useEffect } from 'react'
import ReactJson from 'react-json-view'
import { Agent } from '@atproto/api'
import { OAuthSession } from '@atproto/oauth-client'
import { useAuthContext } from './auth/auth-provider.tsx'
@ -6,37 +8,7 @@ import { useAuthContext } from './auth/auth-provider.tsx'
function App() {
const { pdsAgent, signOut, refresh } = useAuthContext()
const hasTokenInfo = pdsAgent.sessionManager instanceof OAuthSession
const [tokeninfo, setTokeninfo] = useState<unknown>(undefined)
const loadTokeninfo = useCallback(async () => {
if (pdsAgent.sessionManager instanceof OAuthSession) {
setTokeninfo(await pdsAgent.sessionManager.getTokenInfo())
}
}, [pdsAgent])
// A call that requires to be authenticated
const [serviceAuth, setServiceAuth] = useState<unknown>(undefined)
const loadServiceAuth = useCallback(async () => {
const serviceAuth = await pdsAgent.com.atproto.server.getServiceAuth({
aud: pdsAgent.assertDid,
})
console.log('serviceAuth', serviceAuth)
setServiceAuth(serviceAuth.data)
}, [pdsAgent])
// This call does not require authentication
const [profile, setProfile] = useState<unknown>(undefined)
const loadProfile = useCallback(async () => {
const profile = await pdsAgent.com.atproto.repo.getRecord({
repo: pdsAgent.assertDid,
collection: 'app.bsky.actor.profile',
rkey: 'self',
})
console.log(profile)
setProfile(profile.data)
}, [pdsAgent])
// Expose agent globally
const global = window as { pdsAgent?: Agent }
useEffect(() => {
global.pdsAgent = pdsAgent
@ -49,42 +21,144 @@ function App() {
return (
<div>
<p>Logged in!</p>
<p>
Logged in!
<div className="ml-2 inline-flex flex-wrap space-x-2">
<Button onClick={refresh}>Refresh tokens</Button>
<Button onClick={signOut}>Sign-out</Button>
</div>
</p>
{hasTokenInfo && (
<>
<button onClick={loadTokeninfo}>Load token info</button>
<code>
<pre>
{tokeninfo !== undefined
? JSON.stringify(tokeninfo, undefined, 2)
: null}
</pre>
</code>
</>
)}
<button onClick={loadProfile}>Load profile</button>
<code>
<pre>
{profile !== undefined ? JSON.stringify(profile, undefined, 2) : null}
</pre>
</code>
<button onClick={loadServiceAuth}>Load service auth</button>
<code>
<pre>
{serviceAuth !== undefined
? JSON.stringify(serviceAuth, undefined, 2)
: null}
</pre>
</code>
<button onClick={refresh}>Refresh tokens</button>
<br />
<button onClick={signOut}>Sign-out</button>
{pdsAgent.sessionManager instanceof OAuthSession && <TokenInfo />}
<SessionInfo />
<ProfileInfo />
</div>
)
}
export default App
function TokenInfo() {
const { pdsAgent } = useAuthContext()
const { sessionManager } = pdsAgent
const oauthSession =
sessionManager instanceof OAuthSession ? sessionManager : null
const result = useQuery({
queryKey: ['tokeninfo', pdsAgent.assertDid],
queryFn: async () => oauthSession?.getTokenInfo(),
})
return (
<div>
<h2>
Token info
<Button
className="ml-2"
onClick={() => result.refetch({ throwOnError: false })}
>
refresh
</Button>
</h2>
<QueryResult result={result} />
</div>
)
}
function ProfileInfo() {
const { pdsAgent } = useAuthContext()
const result = useQuery({
queryKey: ['profile', pdsAgent.assertDid],
queryFn: async () => {
const { data } = await pdsAgent.com.atproto.repo.getRecord({
repo: pdsAgent.assertDid,
collection: 'app.bsky.actor.profile',
rkey: 'self',
})
return data
},
})
return (
<div>
<h2>
Profile
<Button
className="ml-2"
onClick={() => result.refetch({ throwOnError: false })}
>
refresh
</Button>
</h2>
<QueryResult result={result} />
</div>
)
}
function SessionInfo() {
const { pdsAgent } = useAuthContext()
const result = useQuery({
queryKey: ['session', pdsAgent.assertDid],
queryFn: async () => {
const { data } = await pdsAgent.com.atproto.server.getSession()
return data
},
})
return (
<div>
<h2>
getSession
<Button
className="ml-2"
onClick={() => result.refetch({ throwOnError: false })}
>
refresh
</Button>
</h2>
<QueryResult result={result} />
</div>
)
}
function QueryResult({ result }: { result: UseQueryResult }) {
return (
<div>
{result.data !== undefined ? (
result.data === null ? (
'null'
) : (
<ReactJson
src={result.data}
indentWidth={2}
displayDataTypes={false}
/>
)
) : result.isLoading ? (
<p>Loading...</p>
) : result.isError ? (
<p>Error: {String(result.error)}</p>
) : (
<p>Error: no-data</p>
)}
</div>
)
}
function Button({
children,
className,
...props
}: JSX.IntrinsicElements['button']) {
return (
<button
{...props}
className={`inline-block transform rounded bg-cyan-300 px-2 py-1 text-black shadow-lg transition duration-300 ease-in-out hover:scale-105 hover:bg-cyan-500 ${className || ''}`}
>
{children}
</button>
)
}

View File

@ -16,3 +16,9 @@ export const HANDLE_RESOLVER_URL: string =
export const SIGN_UP_URL: string =
searchParams.get('sign_up_url') ??
(ENV === 'development' ? 'http://localhost:2583' : 'https://bsky.social')
export const OAUTH_SCOPE: string =
searchParams.get('scope') ??
(ENV === 'development'
? 'atproto transition:generic transition:email transition:chat.bsky'
: 'atproto transition:generic')

View File

@ -1,5 +1,6 @@
import './index.css'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './app.tsx'
@ -7,33 +8,39 @@ import { AuthProvider } from './auth/auth-provider.tsx'
import {
ENV,
HANDLE_RESOLVER_URL,
OAUTH_SCOPE,
PLC_DIRECTORY_URL,
SIGN_UP_URL,
} from './constants.ts'
const clientId = `http://localhost?${new URLSearchParams({
scope: 'atproto transition:generic',
scope: OAUTH_SCOPE,
redirect_uri: Object.assign(new URL(window.location.origin), {
hostname: '127.0.0.1',
search: new URLSearchParams({
env: ENV,
handle_resolver: HANDLE_RESOLVER_URL,
sign_up_url: SIGN_UP_URL,
scope: OAUTH_SCOPE,
...(PLC_DIRECTORY_URL && { plc_directory_url: PLC_DIRECTORY_URL }),
}).toString(),
}).href,
})}`
const queryClient = new QueryClient()
createRoot(document.getElementById('root')!).render(
<StrictMode>
<AuthProvider
clientId={clientId}
plcDirectoryUrl={PLC_DIRECTORY_URL}
signUpUrl={SIGN_UP_URL}
handleResolver={HANDLE_RESOLVER_URL}
allowHttp={ENV === 'development' || ENV === 'test'}
>
<App />
</AuthProvider>
<QueryClientProvider client={queryClient}>
<AuthProvider
clientId={clientId}
plcDirectoryUrl={PLC_DIRECTORY_URL}
signUpUrl={SIGN_UP_URL}
handleResolver={HANDLE_RESOLVER_URL}
allowHttp={ENV === 'development' || ENV === 'test'}
>
<App />
</AuthProvider>
</QueryClientProvider>
</StrictMode>,
)

View File

@ -168,7 +168,8 @@
createdAt: '2023-10-01T00:00:00.000Z',
updatedAt: '2023-10-01T00:00:00.000Z',
clientId: 'https://bsky.app/oauth-client.json',
scope: 'atproto transition:generic transition:chat.bsky',
scope:
'atproto transition:generic transition:email transition:chat.bsky',
},
{
tokenId: 'token3',

View File

@ -128,6 +128,7 @@
scopeDetails: [
{ scope: 'atproto' },
{ scope: 'transition:generic' },
{ scope: 'transition:email' },
{ scope: 'transition:chat.bsky' },
],
}

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr "2FA Confirmation"
msgid "A second authentication factor is required"
msgstr "A second authentication factor is required"
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr "Access your account data (except chat messages)"
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr "Access your chat messages"
@ -314,6 +314,10 @@ msgstr "Please verify the domain name of the website before entering your passwo
msgid "Privacy Policy"
msgstr "Privacy Policy"
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr "Read your email address"
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr "Confirmation 2FA"
msgid "A second authentication factor is required"
msgstr "Un second facteur d'authentification est requis"
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr "Accéder aux données de votre compte (sauf les messages de chat)"
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr "Accéder à vos messages de chat"
@ -60,7 +60,7 @@ msgstr "Authoriser l'accès"
#: src/views/authorize/sign-in/sign-in-picker.tsx:68
msgid "Avatar"
msgstr ""
msgstr "Photo de profile"
#: src/views/authorize/sign-in/sign-in-picker.tsx:47
#: src/views/authorize/sign-in/sign-in-form.tsx:130
@ -314,6 +314,10 @@ msgstr "Veuillez vérifier le nom de domaine du site web avant de saisir votre m
msgid "Privacy Policy"
msgstr "Politique de Confidentialité"
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr "Lire votre adresse email"
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -25,11 +25,11 @@ msgstr ""
msgid "A second authentication factor is required"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
#: src/views/authorize/accept/accept-form.tsx:150
msgid "Access your account data (except chat messages)"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:150
#: src/views/authorize/accept/accept-form.tsx:152
msgid "Access your chat messages"
msgstr ""
@ -314,6 +314,10 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
#: src/views/authorize/accept/accept-form.tsx:148
msgid "Read your email address"
msgstr ""
#: src/views/authorize/sign-in/sign-in-form.tsx:205
#: src/views/authorize/sign-in/sign-in-form.tsx:210
msgid "Remember this account on this device"

View File

@ -144,6 +144,8 @@ function ScopeDescription({ scope }: ScopeDescriptionProps) {
switch (scope) {
case 'atproto':
return <Trans>Uniquely identify you</Trans>
case 'transition:email':
return <Trans>Read your email address</Trans>
case 'transition:generic':
return <Trans>Access your account data (except chat messages)</Trans>
case 'transition:chat.bsky':

View File

@ -1,10 +1,10 @@
import { ComAtprotoServerGetSession } from '@atproto/api'
import { INVALID_HANDLE } from '@atproto/syntax'
import { InvalidRequestError } from '@atproto/xrpc-server'
import { formatAccountStatus } from '../../../../account-manager/account-manager'
import { AuthScope } from '../../../../auth-verifier'
import { AccessOutput, AuthScope, OAuthOutput } from '../../../../auth-verifier'
import { AppContext } from '../../../../context'
import { Server } from '../../../../lexicon'
import { resultPassthru } from '../../../proxy'
import { didDocForSession } from './util'
export default function (server: Server, ctx: AppContext) {
@ -14,12 +14,25 @@ export default function (server: Server, ctx: AppContext) {
}),
handler: async ({ auth, req }) => {
if (ctx.entrywayAgent) {
return resultPassthru(
await ctx.entrywayAgent.com.atproto.server.getSession(
undefined,
ctx.entrywayPassthruHeaders(req),
),
// Allow proxying of dpop bound requests by using service auth instead
const headers =
auth.credentials.type === 'oauth' // DPoP bound tokens cannot be proxied
? await ctx.entrywayAuthHeaders(
req,
auth.credentials.did,
'com.atproto.server.getSession',
)
: ctx.entrywayPassthruHeaders(req)
const res = await ctx.entrywayAgent.com.atproto.server.getSession(
undefined,
headers,
)
return {
encoding: 'application/json',
body: output(auth, res.data),
}
}
const did = auth.credentials.did
@ -37,7 +50,7 @@ export default function (server: Server, ctx: AppContext) {
return {
encoding: 'application/json',
body: {
body: output(auth, {
handle: user.handle ?? INVALID_HANDLE,
did: user.did,
email: user.email ?? undefined,
@ -45,8 +58,30 @@ export default function (server: Server, ctx: AppContext) {
emailConfirmed: !!user.emailConfirmedAt,
active,
status,
},
}),
}
},
})
}
function output(
{ credentials }: AccessOutput | OAuthOutput,
data: ComAtprotoServerGetSession.OutputSchema,
): ComAtprotoServerGetSession.OutputSchema {
switch (credentials.type) {
case 'access':
return data
case 'oauth':
if (!credentials.oauthScopes.has('transition:email')) {
const { email, emailAuthFactor, emailConfirmed, ...rest } = data
return rest
}
return data
default:
// @ts-expect-error
throw new Error(`Unknown credentials type: ${credentials.type}`)
}
}

View File

@ -46,17 +46,17 @@ export enum RoleStatus {
Missing,
}
type NullOutput = {
export type NullOutput = {
credentials: null
}
type AdminTokenOutput = {
export type AdminTokenOutput = {
credentials: {
type: 'admin_token'
}
}
type ModServiceOutput = {
export type ModServiceOutput = {
credentials: {
type: 'mod_service'
aud: string
@ -64,29 +64,35 @@ type ModServiceOutput = {
}
}
type AccessOutput = {
export type AccessOutput = {
credentials: {
type: 'access'
did: string
scope: AuthScope
audience: string | undefined
isPrivileged: boolean
}
artifacts: string
}
type RefreshOutput = {
export type OAuthOutput = {
credentials: {
type: 'oauth'
did: string
scope: AuthScope
isPrivileged: boolean
oauthScopes: Set<string>
}
}
export type RefreshOutput = {
credentials: {
type: 'refresh'
did: string
scope: AuthScope
audience: string | undefined
tokenId: string
}
artifacts: string
}
type UserServiceAuthOutput = {
export type UserServiceAuthOutput = {
credentials: {
type: 'user_service_auth'
aud: string
@ -139,7 +145,7 @@ export class AuthVerifier {
accessStandard =
(opts: Partial<AccessOpts> = {}) =>
async (ctx: ReqCtx): Promise<AccessOutput> => {
async (ctx: ReqCtx): Promise<AccessOutput | OAuthOutput> => {
return this.validateAccessToken(
ctx,
[
@ -154,7 +160,7 @@ export class AuthVerifier {
accessFull =
(opts: Partial<AccessOpts> = {}) =>
(ctx: ReqCtx): Promise<AccessOutput> => {
(ctx: ReqCtx): Promise<AccessOutput | OAuthOutput> => {
return this.validateAccessToken(
ctx,
[AuthScope.Access, ...(opts.additional ?? [])],
@ -164,7 +170,7 @@ export class AuthVerifier {
accessPrivileged =
(opts: Partial<AccessOpts> = {}) =>
(ctx: ReqCtx): Promise<AccessOutput> => {
(ctx: ReqCtx): Promise<AccessOutput | OAuthOutput> => {
return this.validateAccessToken(ctx, [
AuthScope.Access,
AuthScope.AppPassPrivileged,
@ -173,34 +179,30 @@ export class AuthVerifier {
}
refresh = async (ctx: ReqCtx): Promise<RefreshOutput> => {
const { did, scope, token, tokenId, audience } =
await this.validateRefreshToken(ctx)
const { did, scope, tokenId } = await this.validateRefreshToken(ctx)
return {
credentials: {
type: 'refresh',
did,
scope,
audience,
tokenId,
},
artifacts: token,
}
}
refreshExpired = async (ctx: ReqCtx): Promise<RefreshOutput> => {
const { did, scope, token, tokenId, audience } =
await this.validateRefreshToken(ctx, { clockTolerance: Infinity })
const { did, scope, tokenId } = await this.validateRefreshToken(ctx, {
clockTolerance: Infinity,
})
return {
credentials: {
type: 'refresh',
did,
scope,
audience,
tokenId,
},
artifacts: token,
}
}
@ -213,7 +215,7 @@ export class AuthVerifier {
(opts: Partial<AccessOpts> = {}) =>
async (
ctx: ReqCtx,
): Promise<AccessOutput | AdminTokenOutput | NullOutput> => {
): Promise<AccessOutput | OAuthOutput | AdminTokenOutput | NullOutput> => {
if (isAccessToken(ctx.req)) {
return await this.accessStandard(opts)(ctx)
} else if (isBasicToken(ctx.req)) {
@ -258,7 +260,9 @@ export class AuthVerifier {
accessOrUserServiceAuth =
(opts: Partial<AccessOpts> = {}) =>
async (ctx: ReqCtx): Promise<UserServiceAuthOutput | AccessOutput> => {
async (
ctx: ReqCtx,
): Promise<UserServiceAuthOutput | AccessOutput | OAuthOutput> => {
const token = bearerTokenFromReq(ctx.req)
if (token) {
const payload = jose.decodeJwt(token)
@ -411,10 +415,10 @@ export class AuthVerifier {
checkTakedown = false,
checkDeactivated = false,
}: { checkTakedown?: boolean; checkDeactivated?: boolean } = {},
): Promise<AccessOutput> {
): Promise<AccessOutput | OAuthOutput> {
this.setAuthHeaders(ctx)
let accessOutput: AccessOutput
let accessOutput: AccessOutput | OAuthOutput
const [type] = parseAuthorizationHeader(ctx.req.headers.authorization)
switch (type) {
@ -467,7 +471,7 @@ export class AuthVerifier {
protected async validateDpopAccessToken(
ctx: ReqCtx,
scopes: AuthScope[],
): Promise<AccessOutput> {
): Promise<OAuthOutput> {
this.setAuthHeaders(ctx)
const { req } = ctx
@ -498,16 +502,16 @@ export class AuthVerifier {
throw new InvalidRequestError('Malformed token', 'InvalidToken')
}
const tokenScopes = new Set(result.claims.scope?.split(' '))
const oauthScopes = new Set(result.claims.scope?.split(' '))
if (!tokenScopes.has('transition:generic')) {
if (!oauthScopes.has('transition:generic')) {
throw new AuthRequiredError(
'Missing required scope: transition:generic',
'InvalidToken',
)
}
const scopeEquivalent: AuthScope = tokenScopes.has('transition:chat.bsky')
const scopeEquivalent: AuthScope = oauthScopes.has('transition:chat.bsky')
? AuthScope.AppPassPrivileged
: AuthScope.AppPass
@ -530,13 +534,12 @@ export class AuthVerifier {
return {
credentials: {
type: 'access',
type: 'oauth',
did: result.claims.sub,
scope: scopeEquivalent,
audience: this.dids.pds,
oauthScopes,
isPrivileged: scopeEquivalent === AuthScope.AppPassPrivileged,
},
artifacts: result.token,
}
} catch (err) {
// Make sure to include any WWW-Authenticate header in the response
@ -558,24 +561,21 @@ export class AuthVerifier {
ctx: ReqCtx,
scopes: AuthScope[],
): Promise<AccessOutput> {
const { did, scope, token, audience } = await this.validateBearerToken(
ctx,
scopes,
{ audience: this.dids.pds, typ: 'at+jwt' },
)
const isPrivileged = [
AuthScope.Access,
AuthScope.AppPassPrivileged,
].includes(scope)
const { did, scope } = await this.validateBearerToken(ctx, scopes, {
audience: this.dids.pds,
typ: 'at+jwt',
})
const isPrivileged =
scope === AuthScope.Access || scope === AuthScope.AppPassPrivileged
return {
credentials: {
type: 'access',
did,
scope,
audience,
isPrivileged,
},
artifacts: token,
}
}
@ -632,7 +632,7 @@ export class AuthVerifier {
}
isUserOrAdmin(
auth: AccessOutput | AdminTokenOutput | NullOutput,
auth: AccessOutput | OAuthOutput | AdminTokenOutput | NullOutput,
did: string,
): boolean {
if (!auth.credentials) {

View File

@ -363,7 +363,11 @@ export class AppContext {
safeFetch,
metadata: {
protected_resources: [new URL(cfg.oauth.issuer).origin],
scopes_supported: ['transition:generic', 'transition:chat.bsky'],
scopes_supported: [
'transition:email',
'transition:generic',
'transition:chat.bsky',
],
},
// If the PDS is both an authorization server & resource server (no
// entryway), there is no need to use JWTs as access tokens. Instead,

View File

@ -503,6 +503,7 @@ export const PROTECTED_METHODS = new Set<string>([
ids.ComAtprotoServerCreateAppPassword,
ids.ComAtprotoServerDeactivateAccount,
ids.ComAtprotoServerGetAccountInviteCodes,
ids.ComAtprotoServerGetSession,
ids.ComAtprotoServerListAppPasswords,
ids.ComAtprotoServerRequestAccountDelete,
ids.ComAtprotoServerRequestEmailConfirmation,

174
pnpm-lock.yaml generated
View File

@ -943,6 +943,9 @@ importers:
'@swc/helpers':
specifier: ^0.5.15
version: 0.5.15
'@tanstack/react-query':
specifier: ^5.71.10
version: 5.71.10(react@19.0.0)
'@types/react':
specifier: ^19.0.10
version: 19.0.10
@ -961,6 +964,9 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.0.0(react@19.0.0)
react-json-view:
specifier: ^1.21.3
version: 1.21.3(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0)
rollup:
specifier: ^4.13.0
version: 4.34.9
@ -9064,6 +9070,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
dev: true
/asn1.js@5.4.1:
resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==}
dependencies:
@ -9272,6 +9282,10 @@ packages:
dev: true
optional: true
/base16@1.0.0:
resolution: {integrity: sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==}
dev: true
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@ -9983,6 +9997,14 @@ packages:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/cross-fetch@3.2.0:
resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==}
dependencies:
node-fetch: 2.7.0
transitivePeerDependencies:
- encoding
dev: true
/cross-spawn@5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
dependencies:
@ -11634,6 +11656,32 @@ packages:
bser: 2.1.1
dev: true
/fbemitter@3.0.0:
resolution: {integrity: sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==}
dependencies:
fbjs: 3.0.5
transitivePeerDependencies:
- encoding
dev: true
/fbjs-css-vars@1.0.2:
resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==}
dev: true
/fbjs@3.0.5:
resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==}
dependencies:
cross-fetch: 3.2.0
fbjs-css-vars: 1.0.2
loose-envify: 1.4.0
object-assign: 4.1.1
promise: 7.3.1
setimmediate: 1.0.5
ua-parser-js: 1.0.38
transitivePeerDependencies:
- encoding
dev: true
/fd-slicer@1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
dependencies:
@ -11751,6 +11799,18 @@ packages:
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
dev: true
/flux@4.0.4(react@19.0.0):
resolution: {integrity: sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==}
peerDependencies:
react: ^15.0.2 || ^16.0.0 || ^17.0.0
dependencies:
fbemitter: 3.0.0
fbjs: 3.0.5
react: 19.0.0
transitivePeerDependencies:
- encoding
dev: true
/follow-redirects@1.15.5:
resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==}
engines: {node: '>=4.0'}
@ -13735,9 +13795,17 @@ packages:
/lodash.camelcase@4.3.0:
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
/lodash.curry@4.1.1:
resolution: {integrity: sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==}
dev: true
/lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
/lodash.flow@3.5.0:
resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==}
dev: true
/lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
@ -13816,6 +13884,13 @@ packages:
resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
dev: false
/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
dependencies:
js-tokens: 4.0.0
dev: true
/lru-cache@10.2.0:
resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==}
engines: {node: 14 || >=16.14}
@ -15523,6 +15598,12 @@ packages:
engines: {node: '>=0.12'}
dev: true
/promise@7.3.1:
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
dependencies:
asap: 2.0.6
dev: true
/prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@ -15633,6 +15714,10 @@ packages:
- utf-8-validate
dev: true
/pure-color@1.3.0:
resolution: {integrity: sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==}
dev: true
/qs@6.11.0:
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
engines: {node: '>=0.6'}
@ -15684,6 +15769,15 @@ packages:
minimist: 1.2.8
strip-json-comments: 2.0.1
/react-base16-styling@0.6.0:
resolution: {integrity: sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==}
dependencies:
base16: 1.0.0
lodash.curry: 4.1.1
lodash.flow: 3.5.0
pure-color: 1.3.0
dev: true
/react-dom@19.0.0(react@19.0.0):
resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
peerDependencies:
@ -15706,6 +15800,27 @@ packages:
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
dev: true
/react-json-view@1.21.3(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==}
peerDependencies:
react: ^17.0.0 || ^16.3.0 || ^15.5.4
react-dom: ^17.0.0 || ^16.3.0 || ^15.5.4
dependencies:
flux: 4.0.4(react@19.0.0)
react: 19.0.0
react-base16-styling: 0.6.0
react-dom: 19.0.0(react@19.0.0)
react-lifecycles-compat: 3.0.4
react-textarea-autosize: 8.5.9(@types/react@19.0.10)(react@19.0.0)
transitivePeerDependencies:
- '@types/react'
- encoding
dev: true
/react-lifecycles-compat@3.0.4:
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
dev: true
/react-remove-scroll-bar@2.3.8(@types/react@19.0.10)(react@19.0.0):
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
engines: {node: '>=10'}
@ -15757,6 +15872,20 @@ packages:
tslib: 2.8.1
dev: true
/react-textarea-autosize@8.5.9(@types/react@19.0.10)(react@19.0.0):
resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==}
engines: {node: '>=10'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
dependencies:
'@babel/runtime': 7.22.10
react: 19.0.0
use-composed-ref: 1.4.0(@types/react@19.0.10)(react@19.0.0)
use-latest: 1.3.0(@types/react@19.0.10)(react@19.0.0)
transitivePeerDependencies:
- '@types/react'
dev: true
/react@19.0.0:
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
engines: {node: '>=0.10.0'}
@ -16269,6 +16398,10 @@ packages:
has-property-descriptors: 1.0.2
dev: true
/setimmediate@1.0.5:
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
dev: true
/setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
@ -17346,7 +17479,6 @@ packages:
/ua-parser-js@1.0.38:
resolution: {integrity: sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==}
dev: false
/ua-parser-js@2.0.3:
resolution: {integrity: sha512-LZyXZdNttONW8LjzEH3Z8+6TE7RfrEiJqDKyh0R11p/kxvrV2o9DrT2FGZO+KVNs3k+drcIQ6C3En6wLnzJGpw==}
@ -17506,6 +17638,46 @@ packages:
tslib: 2.8.1
dev: true
/use-composed-ref@1.4.0(@types/react@19.0.10)(react@19.0.0):
resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.0.10
react: 19.0.0
dev: true
/use-isomorphic-layout-effect@1.2.0(@types/react@19.0.10)(react@19.0.0):
resolution: {integrity: sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.0.10
react: 19.0.0
dev: true
/use-latest@1.3.0(@types/react@19.0.10)(react@19.0.0):
resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 19.0.10
react: 19.0.0
use-isomorphic-layout-effect: 1.2.0(@types/react@19.0.10)(react@19.0.0)
dev: true
/use-sidecar@1.1.3(@types/react@19.0.10)(react@19.0.0):
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'}