···11export default eventHandlerWithOAuthSession(async (event, oAuthSession, serverSession) => {
22- await Promise.all([oAuthSession?.signOut(), serverSession.clear()])
33-22+ // Even tho the signOut also clears part of the server cache should be done in order
33+ // to let the oAuth package do any other clean up it may need
44+ await oAuthSession?.signOut()
55+ await serverSession.clear()
46 return 'Session cleared'
57})
+2-2
server/api/auth/session.get.ts
···11-import { UserSessionSchema } from '#shared/schemas/userSession'
11+import { PublicUserSessionSchema } from '#shared/schemas/publicUserSession'
22import { safeParse } from 'valibot'
3344export default eventHandlerWithOAuthSession(async (event, oAuthSession, serverSession) => {
55- const result = safeParse(UserSessionSchema, serverSession.data)
55+ const result = safeParse(PublicUserSessionSchema, serverSession.data.public)
66 if (!result.success) {
77 return null
88 }
+16-26
server/utils/atproto/oauth-session-store.ts
···11import type { NodeSavedSession, NodeSavedSessionStore } from '@atproto/oauth-client-node'
22-import type { H3Event } from 'h3'
33-44-/**
55- * Storage key prefix for oauth session storage.
66- */
77-export const OAUTH_SESSION_CACHE_STORAGE_BASE = 'oauth-atproto-session'
22+import type { UserServerSession } from '#shared/types/userSession'
33+import type { SessionManager } from 'h3'
8495export class OAuthSessionStore implements NodeSavedSessionStore {
1010- // TODO: not sure if we will support multi accounts, but if we do in the future will need to change this around
1111- private readonly cookieKey = 'oauth:atproto:session'
1212- private readonly storage = useStorage(OAUTH_SESSION_CACHE_STORAGE_BASE)
66+ private readonly session: SessionManager<UserServerSession>
1371414- constructor(private event: H3Event) {}
88+ constructor(session: SessionManager<UserServerSession>) {
99+ this.session = session
1010+ }
15111612 async get(): Promise<NodeSavedSession | undefined> {
1717- const sessionKey = getCookie(this.event, this.cookieKey)
1818- if (!sessionKey) return
1919- const result = await this.storage.getItem<NodeSavedSession>(sessionKey)
2020- if (!result) return
2121- return result
1313+ const sessionData = this.session.data
1414+ if (!sessionData) return undefined
1515+ return sessionData.oauthSession
2216 }
23172424- async set(key: string, val: NodeSavedSession) {
2525- setCookie(this.event, this.cookieKey, key, {
2626- httpOnly: true,
2727- secure: !import.meta.dev,
2828- sameSite: 'lax',
1818+ async set(_key: string, val: NodeSavedSession) {
1919+ // We are ignoring the key since the mapping is already done in the session
2020+ await this.session.update({
2121+ oauthSession: val,
2922 })
3030- await this.storage.setItem<NodeSavedSession>(key, val)
3123 }
32243325 async del() {
3434- const sessionKey = getCookie(this.event, this.cookieKey)
3535- if (sessionKey) {
3636- await this.storage.del(sessionKey)
3737- }
3838- deleteCookie(this.event, this.cookieKey)
2626+ await this.session.update({
2727+ oauthSession: undefined,
2828+ })
3929 }
4030}
+16-25
server/utils/atproto/oauth-state-store.ts
···11import type { NodeSavedState, NodeSavedStateStore } from '@atproto/oauth-client-node'
22-import type { H3Event } from 'h3'
33-44-/**
55- * Storage key prefix for oauth state storage.
66- */
77-export const OAUTH_STATE_CACHE_STORAGE_BASE = 'oauth-atproto-state'
22+import type { UserServerSession } from '#shared/types/userSession'
33+import type { SessionManager } from 'h3'
8495export class OAuthStateStore implements NodeSavedStateStore {
1010- private readonly cookieKey = 'oauth:atproto:state'
1111- private readonly storage = useStorage(OAUTH_STATE_CACHE_STORAGE_BASE)
66+ private readonly session: SessionManager<UserServerSession>
1271313- constructor(private event: H3Event) {}
88+ constructor(session: SessionManager<UserServerSession>) {
99+ this.session = session
1010+ }
14111512 async get(): Promise<NodeSavedState | undefined> {
1616- const stateKey = getCookie(this.event, this.cookieKey)
1717- if (!stateKey) return
1818- const result = await this.storage.getItem<NodeSavedState>(stateKey)
1919- if (!result) return
2020- return result
1313+ const sessionData = this.session.data
1414+ if (!sessionData) return undefined
1515+ return sessionData.oauthState
2116 }
22172323- async set(key: string, val: NodeSavedState) {
2424- setCookie(this.event, this.cookieKey, key, {
2525- httpOnly: true,
2626- secure: !import.meta.dev,
2727- sameSite: 'lax',
1818+ async set(_key: string, val: NodeSavedState) {
1919+ // We are ignoring the key since the mapping is already done in the session
2020+ await this.session.update({
2121+ oauthState: val,
2822 })
2929- await this.storage.setItem<NodeSavedState>(key, val)
3023 }
31243225 async del() {
3333- const stateKey = getCookie(this.event, this.cookieKey)
3434- deleteCookie(this.event, this.cookieKey)
3535- if (stateKey) {
3636- await this.storage.del(stateKey)
3737- }
2626+ await this.session.update({
2727+ oauthState: undefined,
2828+ })
3829 }
3930}
+4-14
server/utils/atproto/oauth.ts
···44import { parse } from 'valibot'
55import { getOAuthLock } from '#server/utils/atproto/lock'
66import { useOAuthStorage } from '#server/utils/atproto/storage'
77-import { UNSET_NUXT_SESSION_PASSWORD } from '#shared/utils/constants'
87import { OAuthMetadataSchema } from '#shared/schemas/oauth'
98// @ts-expect-error virtual file from oauth module
109import { clientUri } from '#oauth/config'
1010+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
1212export const scope = 'atproto'
1313···44444545async function getOAuthSession(event: H3Event): Promise<OAuthSession | undefined> {
4646 const clientMetadata = getOauthClientMetadata()
4747- const { stateStore, sessionStore } = useOAuthStorage(event)
4747+ const serverSession = await useServerSession(event)
4848+ const { stateStore, sessionStore } = useOAuthStorage(serverSession)
48494950 const client = new NodeOAuthClient({
5051 stateStore,
···6465 handler: EventHandlerWithOAuthSession<T, D>,
6566) {
6667 return defineEventHandler(async event => {
6767- const config = useRuntimeConfig(event)
6868-6969- if (!config.sessionPassword) {
7070- throw createError({
7171- status: 500,
7272- message: UNSET_NUXT_SESSION_PASSWORD,
7373- })
7474- }
7575-7676- const serverSession = await useSession(event, {
7777- password: config.sessionPassword,
7878- })
6868+ const serverSession = await useServerSession(event)
79698070 const oAuthSession = await getOAuthSession(event)
8171 return await handler(event, oAuthSession, serverSession)
+5-4
server/utils/atproto/storage.ts
···11-import type { H3Event } from 'h3'
11+import type { SessionManager } from 'h3'
22import { OAuthStateStore } from './oauth-state-store'
33import { OAuthSessionStore } from './oauth-session-store'
44+import type { UserServerSession } from '#shared/types/userSession'
4555-export const useOAuthStorage = (event: H3Event) => {
66+export const useOAuthStorage = (session: SessionManager<UserServerSession>) => {
67 return {
77- stateStore: new OAuthStateStore(event),
88- sessionStore: new OAuthSessionStore(event),
88+ stateStore: new OAuthStateStore(session),
99+ sessionStore: new OAuthSessionStore(session),
910 }
1011}
+22
server/utils/server-session.ts
···11+// This is for getting the session on the npmx server and differs from the OAuthSession
22+import type { H3Event } from 'h3'
33+import type { UserServerSession } from '#shared/types/userSession'
44+55+/**
66+ * Get's the user's session that is stored on the server
77+ * @param event
88+ * @returns
99+ */
1010+export const useServerSession = async (event: H3Event) => {
1111+ const config = useRuntimeConfig(event)
1212+1313+ if (!config.sessionPassword) {
1414+ throw new Error('Session password is not configured')
1515+ }
1616+1717+ const serverSession = useSession<UserServerSession>(event, {
1818+ password: config.sessionPassword,
1919+ })
2020+2121+ return serverSession
2222+}
+11
shared/schemas/publicUserSession.ts
···11+import { object, string, pipe, url } from 'valibot'
22+import type { InferOutput } from 'valibot'
33+44+export const PublicUserSessionSchema = object({
55+ // Safe to pass to the frontend
66+ did: string(),
77+ handle: string(),
88+ pds: pipe(string(), url()),
99+})
1010+1111+export type PublicUserSession = InferOutput<typeof PublicUserSessionSchema>
-10
shared/schemas/userSession.ts
···11-import { object, string, pipe, url } from 'valibot'
22-import type { InferOutput } from 'valibot'
33-44-export const UserSessionSchema = object({
55- did: string(),
66- handle: string(),
77- pds: pipe(string(), url()),
88-})
99-1010-export type UserSession = InferOutput<typeof UserSessionSchema>
+14
shared/types/userSession.ts
···11+import type { NodeSavedSession, NodeSavedState } from '@atproto/oauth-client-node'
22+33+export interface UserServerSession {
44+ public: {
55+ did: string
66+ handle: string
77+ pds: string
88+ }
99+ // Only to be used in the atproto session and state stores
1010+ // Will need to change to Record<string, T> and add a current logged in user if we ever want to support
1111+ // multiple did logins per server session
1212+ oauthSession: NodeSavedSession | undefined
1313+ oauthState: NodeSavedState | undefined
1414+}