Skip to content

Commit 5da9972

Browse files
committed
I think that's most of it
1 parent 72b0507 commit 5da9972

9 files changed

Lines changed: 91 additions & 83 deletions

File tree

server/api/auth/atproto.get.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NodeOAuthClient } from '@atproto/oauth-client-node'
33
import { createError, getQuery, sendRedirect } from 'h3'
44
import { useOAuthStorage } from '#server/utils/atproto/storage'
55
import { SLINGSHOT_HOST } from '#shared/utils/constants'
6-
import type { UserSession } from '#shared/schemas/userSession'
6+
import { useServerSession } from '~~/server/utils/server-session'
77

88
export default defineEventHandler(async event => {
99
const config = useRuntimeConfig(event)
@@ -16,7 +16,8 @@ export default defineEventHandler(async event => {
1616

1717
const query = getQuery(event)
1818
const clientMetadata = getOauthClientMetadata()
19-
const { stateStore, sessionStore } = useOAuthStorage(event)
19+
const session = await useServerSession(event)
20+
const { stateStore, sessionStore } = useOAuthStorage(session)
2021

2122
const atclient = new NodeOAuthClient({
2223
stateStore,
@@ -48,17 +49,19 @@ export default defineEventHandler(async event => {
4849
const agent = new Agent(authSession)
4950
event.context.agent = agent
5051

51-
const session = await useSession(event, {
52-
password: config.sessionPassword,
53-
})
54-
5552
const response = await fetch(
5653
`https://${SLINGSHOT_HOST}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${agent.did}`,
5754
{ headers: { 'User-Agent': 'npmx' } },
5855
)
59-
const miniDoc = (await response.json()) as UserSession
56+
const miniDoc = await response.json()
6057

61-
await session.update(miniDoc)
58+
await session.update({
59+
public: {
60+
did: miniDoc.did,
61+
handle: miniDoc.handle,
62+
pds: miniDoc.pds,
63+
},
64+
})
6265

6366
return sendRedirect(event, '/')
6467
})

server/api/auth/session.get.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { UserSessionSchema } from '#shared/schemas/userSession'
1+
import { PublicUserSessionSchema } from '#shared/schemas/publicUserSession'
22
import { safeParse } from 'valibot'
33

44
export default eventHandlerWithOAuthSession(async (event, oAuthSession, serverSession) => {
5-
const result = safeParse(UserSessionSchema, serverSession.data)
5+
const result = safeParse(PublicUserSessionSchema, serverSession.data.public)
66
if (!result.success) {
77
return null
88
}
Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,28 @@
11
import type { NodeSavedSession, NodeSavedSessionStore } from '@atproto/oauth-client-node'
2-
import type { H3Event } from 'h3'
3-
4-
/**
5-
* Storage key prefix for oauth session storage.
6-
*/
7-
export const OAUTH_SESSION_CACHE_STORAGE_BASE = 'oauth-atproto-session'
2+
import type { UserServerSession } from '#shared/types/userSession'
3+
import type { SessionManager } from 'h3'
84

95
export class OAuthSessionStore implements NodeSavedSessionStore {
10-
// TODO: not sure if we will support multi accounts, but if we do in the future will need to change this around
11-
private readonly cookieKey = 'oauth:atproto:session'
12-
private readonly storage = useStorage(OAUTH_SESSION_CACHE_STORAGE_BASE)
6+
private readonly session: SessionManager<UserServerSession>
137

14-
constructor(private event: H3Event) {}
8+
constructor(session: SessionManager<UserServerSession>) {
9+
this.session = session
10+
}
1511

1612
async get(): Promise<NodeSavedSession | undefined> {
17-
const sessionKey = getCookie(this.event, this.cookieKey)
18-
if (!sessionKey) return
19-
const result = await this.storage.getItem<NodeSavedSession>(sessionKey)
20-
if (!result) return
21-
return result
13+
const sessionData = this.session.data
14+
if (!sessionData) return undefined
15+
return sessionData.oauthSession
2216
}
2317

24-
async set(key: string, val: NodeSavedSession) {
25-
setCookie(this.event, this.cookieKey, key, {
26-
httpOnly: true,
27-
secure: !import.meta.dev,
28-
sameSite: 'lax',
29-
})
30-
await this.storage.setItem<NodeSavedSession>(key, val)
18+
async set(_key: string, val: NodeSavedSession) {
19+
// We are ignoring the key since the mapping is already done in the session
20+
this.session.data.oauthSession = val
21+
await this.session.update(this.session.data)
3122
}
3223

3324
async del() {
34-
const sessionKey = getCookie(this.event, this.cookieKey)
35-
if (sessionKey) {
36-
await this.storage.del(sessionKey)
37-
}
38-
deleteCookie(this.event, this.cookieKey)
25+
this.session.data.oauthSession = undefined
26+
await this.session.update(this.session.data)
3927
}
4028
}
Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,28 @@
11
import type { NodeSavedState, NodeSavedStateStore } from '@atproto/oauth-client-node'
2-
import type { H3Event } from 'h3'
3-
4-
/**
5-
* Storage key prefix for oauth state storage.
6-
*/
7-
export const OAUTH_STATE_CACHE_STORAGE_BASE = 'oauth-atproto-state'
2+
import type { UserServerSession } from '#shared/types/userSession'
3+
import type { SessionManager } from 'h3'
84

95
export class OAuthStateStore implements NodeSavedStateStore {
10-
private readonly cookieKey = 'oauth:atproto:state'
11-
private readonly storage = useStorage(OAUTH_STATE_CACHE_STORAGE_BASE)
6+
private readonly session: SessionManager<UserServerSession>
127

13-
constructor(private event: H3Event) {}
8+
constructor(session: SessionManager<UserServerSession>) {
9+
this.session = session
10+
}
1411

1512
async get(): Promise<NodeSavedState | undefined> {
16-
const stateKey = getCookie(this.event, this.cookieKey)
17-
if (!stateKey) return
18-
const result = await this.storage.getItem<NodeSavedState>(stateKey)
19-
if (!result) return
20-
return result
13+
const sessionData = this.session.data
14+
if (!sessionData) return undefined
15+
return sessionData.oauthState
2116
}
2217

23-
async set(key: string, val: NodeSavedState) {
24-
setCookie(this.event, this.cookieKey, key, {
25-
httpOnly: true,
26-
secure: !import.meta.dev,
27-
sameSite: 'lax',
28-
})
29-
await this.storage.setItem<NodeSavedState>(key, val)
18+
async set(_key: string, val: NodeSavedState) {
19+
// We are ignoring the key since the mapping is already done in the session
20+
this.session.data.oauthState = val
21+
await this.session.update(this.session.data)
3022
}
3123

3224
async del() {
33-
const stateKey = getCookie(this.event, this.cookieKey)
34-
deleteCookie(this.event, this.cookieKey)
35-
if (stateKey) {
36-
await this.storage.del(stateKey)
37-
}
25+
this.session.data.oauthState = undefined
26+
await this.session.update(this.session.data)
3827
}
3928
}

server/utils/atproto/oauth.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { NodeOAuthClient } from '@atproto/oauth-client-node'
44
import { parse } from 'valibot'
55
import { getOAuthLock } from '#server/utils/atproto/lock'
66
import { useOAuthStorage } from '#server/utils/atproto/storage'
7-
import { UNSET_NUXT_SESSION_PASSWORD } from '#shared/utils/constants'
87
import { OAuthMetadataSchema } from '#shared/schemas/oauth'
98
// @ts-expect-error virtual file from oauth module
109
import { clientUri } from '#oauth/config'
10+
import { useServerSession } from '#server/utils/server-session'
1111
// TODO: limit scope as features gets added. atproto just allows login so no scary login screen till we have scopes
1212
export const scope = 'atproto'
1313

@@ -44,7 +44,8 @@ type EventHandlerWithOAuthSession<T extends EventHandlerRequest, D> = (
4444

4545
async function getOAuthSession(event: H3Event): Promise<OAuthSession | undefined> {
4646
const clientMetadata = getOauthClientMetadata()
47-
const { stateStore, sessionStore } = useOAuthStorage(event)
47+
const serverSession = await useServerSession(event)
48+
const { stateStore, sessionStore } = useOAuthStorage(serverSession)
4849

4950
const client = new NodeOAuthClient({
5051
stateStore,
@@ -64,18 +65,7 @@ export function eventHandlerWithOAuthSession<T extends EventHandlerRequest, D>(
6465
handler: EventHandlerWithOAuthSession<T, D>,
6566
) {
6667
return defineEventHandler(async event => {
67-
const config = useRuntimeConfig(event)
68-
69-
if (!config.sessionPassword) {
70-
throw createError({
71-
status: 500,
72-
message: UNSET_NUXT_SESSION_PASSWORD,
73-
})
74-
}
75-
76-
const serverSession = await useSession(event, {
77-
password: config.sessionPassword,
78-
})
68+
const serverSession = await useServerSession(event)
7969

8070
const oAuthSession = await getOAuthSession(event)
8171
return await handler(event, oAuthSession, serverSession)

server/utils/atproto/storage.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import type { H3Event } from 'h3'
1+
import type { SessionManager } from 'h3'
22
import { OAuthStateStore } from './oauth-state-store'
33
import { OAuthSessionStore } from './oauth-session-store'
4+
import type { UserServerSession } from '#shared/types/userSession'
45

5-
export const useOAuthStorage = (event: H3Event) => {
6+
export const useOAuthStorage = (session: SessionManager<UserServerSession>) => {
67
return {
7-
stateStore: new OAuthStateStore(event),
8-
sessionStore: new OAuthSessionStore(event),
8+
stateStore: new OAuthStateStore(session),
9+
sessionStore: new OAuthSessionStore(session),
910
}
1011
}

server/utils/server-session.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This is for getting the session on the npmx server and differs from the OAuthSession
2+
import type { H3Event } from 'h3'
3+
import type { UserServerSession } from '#shared/types/userSession'
4+
5+
/**
6+
* Get's the user's session that is stored on the server
7+
* @param event
8+
* @returns
9+
*/
10+
export const useServerSession = async (event: H3Event) => {
11+
const config = useRuntimeConfig(event)
12+
13+
if (!config.sessionPassword) {
14+
throw new Error('Session password is not configured')
15+
}
16+
17+
const serverSession = useSession<UserServerSession>(event, {
18+
password: config.sessionPassword,
19+
})
20+
21+
return serverSession
22+
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { object, string, pipe, url } from 'valibot'
22
import type { InferOutput } from 'valibot'
33

4-
export const UserSessionSchema = object({
4+
export const PublicUserSessionSchema = object({
5+
// Safe to pass to the frontend
56
did: string(),
67
handle: string(),
78
pds: pipe(string(), url()),
89
})
910

10-
export type UserSession = InferOutput<typeof UserSessionSchema>
11+
export type PublicUserSession = InferOutput<typeof PublicUserSessionSchema>

shared/types/userSession.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { NodeSavedSession, NodeSavedState } from '@atproto/oauth-client-node'
2+
3+
export interface UserServerSession {
4+
public: {
5+
did: string
6+
handle: string
7+
pds: string
8+
}
9+
// Only to be used in the atproto session and state stores
10+
// Will need to change to Record<string, T> and add a current logged in user if we ever want to support
11+
// multiple did logins per server session
12+
oauthSession: NodeSavedSession | undefined
13+
oauthState: NodeSavedState | undefined
14+
}

0 commit comments

Comments
 (0)