+
+
diff --git a/server/api/auth/atproto.get.ts b/server/api/auth/atproto.get.ts
index e19fe837a..39721941d 100644
--- a/server/api/auth/atproto.get.ts
+++ b/server/api/auth/atproto.get.ts
@@ -7,6 +7,15 @@ import { SLINGSHOT_HOST } from '#shared/utils/constants'
import { useServerSession } from '#server/utils/server-session'
import type { PublicUserSession } from '#shared/schemas/publicUserSession'
+interface ProfileRecord {
+ avatar?: {
+ $type: 'blob'
+ ref: { $link: string }
+ mimeType: string
+ size: number
+ }
+}
+
export default defineEventHandler(async event => {
const config = useRuntimeConfig(event)
if (!config.sessionPassword) {
@@ -58,8 +67,36 @@ export default defineEventHandler(async event => {
)
if (response.ok) {
const miniDoc: PublicUserSession = await response.json()
+
+ // Fetch the user's profile record to get their avatar blob reference
+ let avatar: string | undefined
+ const did = agent.did
+ try {
+ const pdsUrl = new URL(miniDoc.pds)
+ // Only fetch from HTTPS PDS endpoints to prevent SSRF
+ if (did && pdsUrl.protocol === 'https:') {
+ const profileResponse = await fetch(
+ `${pdsUrl.origin}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=app.bsky.actor.profile&rkey=self`,
+ { headers: { 'User-Agent': 'npmx' } },
+ )
+ if (profileResponse.ok) {
+ const record = (await profileResponse.json()) as { value: ProfileRecord }
+ const avatarBlob = record.value.avatar
+ if (avatarBlob?.ref?.$link) {
+ // Use Bluesky CDN for faster image loading
+ avatar = `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${avatarBlob.ref.$link}@jpeg`
+ }
+ }
+ }
+ } catch {
+ // Avatar fetch failed, continue without it
+ }
+
await session.update({
- public: miniDoc,
+ public: {
+ ...miniDoc,
+ avatar,
+ },
})
}
diff --git a/shared/schemas/publicUserSession.ts b/shared/schemas/publicUserSession.ts
index 2ead3080f..1c9e15a92 100644
--- a/shared/schemas/publicUserSession.ts
+++ b/shared/schemas/publicUserSession.ts
@@ -1,4 +1,4 @@
-import { object, string, pipe, url } from 'valibot'
+import { object, string, pipe, url, optional } from 'valibot'
import type { InferOutput } from 'valibot'
export const PublicUserSessionSchema = object({
@@ -6,6 +6,7 @@ export const PublicUserSessionSchema = object({
did: string(),
handle: string(),
pds: pipe(string(), url()),
+ avatar: optional(pipe(string(), url())),
})
export type PublicUserSession = InferOutput
diff --git a/shared/types/userSession.ts b/shared/types/userSession.ts
index 081c1b296..dea7decd3 100644
--- a/shared/types/userSession.ts
+++ b/shared/types/userSession.ts
@@ -5,6 +5,7 @@ export interface UserServerSession {
did: string
handle: string
pds: string
+ avatar?: string
}
// Only to be used in the atproto session and state stores
// Will need to change to Record and add a current logged in user if we ever want to support