1- import type { OAuthClientMetadataInput , OAuthSession } from '@atproto/oauth-client-node'
1+ import type {
2+ OAuthClientMetadata ,
3+ OAuthRedirectUri ,
4+ OAuthSession ,
5+ WebUri ,
6+ } from '@atproto/oauth-client-node'
7+ import { JoseKey , Keyset , oauthRedirectUriSchema , webUriSchema } from '@atproto/oauth-client-node'
28import type { EventHandlerRequest , H3Event , SessionManager } from 'h3'
39import { NodeOAuthClient , AtprotoDohHandleResolver } from '@atproto/oauth-client-node'
4- import { parse } from 'valibot'
510import { getOAuthLock } from '#server/utils/atproto/lock'
611import { useOAuthStorage } from '#server/utils/atproto/storage'
712import { LIKES_SCOPE } from '#shared/utils/constants'
8- import { OAuthMetadataSchema } from '#shared/schemas/oauth'
13+ import type { NitroRuntimeConfig } from 'nitropack/types'
14+
915// @ts -expect-error virtual file from oauth module
1016import { clientUri } from '#oauth/config'
1117// TODO: If you add writing a new record you will need to add a scope for it
@@ -18,29 +24,42 @@ export const handleResolver = new AtprotoDohHandleResolver({
1824 dohEndpoint : 'https://cloudflare-dns.com/dns-query' ,
1925} )
2026
21- export function getOauthClientMetadata ( ) {
27+ /**
28+ * Generates the OAuth client metadata. pkAlg is used to signify that the OAuth client is confendital
29+ */
30+ export function getOauthClientMetadata ( pkAlg : string | undefined = undefined ) : OAuthClientMetadata {
2231 const dev = import . meta. dev
2332
2433 const client_uri = clientUri
25- const redirect_uri = `${ client_uri } /api/auth/atproto`
34+ const redirect_uri : OAuthRedirectUri = oauthRedirectUriSchema . parse (
35+ `${ client_uri } /api/auth/atproto` ,
36+ )
37+ const jwks_uri : WebUri | undefined = pkAlg
38+ ? webUriSchema . parse ( `${ client_uri } /.well-known/jwks.json` )
39+ : undefined
2640
2741 const client_id = dev
2842 ? `http://localhost?redirect_uri=${ encodeURIComponent ( redirect_uri ) } &scope=${ encodeURIComponent ( scope ) } `
2943 : `${ client_uri } /oauth-client-metadata.json`
3044
3145 // If anything changes here, please make sure to also update /shared/schemas/oauth.ts to match
32- return parse ( OAuthMetadataSchema , {
46+ return {
3347 client_name : 'npmx.dev' ,
3448 client_id,
3549 client_uri,
3650 scope,
37- redirect_uris : [ redirect_uri ] as [ string , ... string [ ] ] ,
51+ redirect_uris : [ redirect_uri ] ,
3852 grant_types : [ 'authorization_code' , 'refresh_token' ] ,
3953 application_type : 'web' ,
40- token_endpoint_auth_method : 'none' ,
4154 dpop_bound_access_tokens : true ,
4255 response_types : [ 'code' ] ,
43- } ) as OAuthClientMetadataInput
56+ subject_type : 'public' ,
57+ authorization_signed_response_alg : 'RS256' ,
58+ // confendital client values
59+ token_endpoint_auth_method : pkAlg ? 'private_key_jwt' : 'none' ,
60+ jwks_uri,
61+ token_endpoint_auth_signing_alg : pkAlg ,
62+ }
4463}
4564
4665type EventHandlerWithOAuthSession < T extends EventHandlerRequest , D > = (
@@ -49,29 +68,61 @@ type EventHandlerWithOAuthSession<T extends EventHandlerRequest, D> = (
4968 serverSession : SessionManager ,
5069) => Promise < D >
5170
71+ export async function getNodeOAuthClient (
72+ serverSession : SessionManager ,
73+ config : NitroRuntimeConfig ,
74+ ) : Promise < NodeOAuthClient > {
75+ const { stateStore, sessionStore } = useOAuthStorage ( serverSession )
76+
77+ // These are optional and not expected or can be used easily in local development, only in production
78+ const keyset = await loadJWKs ( config )
79+ // @ts -expect-error Taken from statusphere-example-app. Throws a ts error
80+ const pk = keyset ?. findPrivateKey ( { use : 'sig' } )
81+ console . log ( pk )
82+ const clientMetadata = getOauthClientMetadata ( pk ?. alg )
83+
84+ return new NodeOAuthClient ( {
85+ stateStore,
86+ sessionStore,
87+ clientMetadata,
88+ requestLock : getOAuthLock ( ) ,
89+ handleResolver,
90+ keyset,
91+ } )
92+ }
93+
94+ export async function loadJWKs ( config : NitroRuntimeConfig ) : Promise < Keyset | undefined > {
95+ // If we ever need to add multiple JWKs to rotate keys we will need to add a new one
96+ // under a new variable and update here
97+ const jwkOne = config . oauthJwkOne
98+ if ( ! jwkOne ) return undefined
99+
100+ // For multiple keys if we need to rotate
101+ // const keys = await Promise.all([JoseKey.fromImportable(jwkOne)])
102+
103+ const keys = await JoseKey . fromImportable ( jwkOne )
104+ return new Keyset ( [ keys ] )
105+ }
106+
52107async function getOAuthSession (
53108 event : H3Event ,
54109) : Promise < { oauthSession : OAuthSession | undefined ; serverSession : SessionManager } > {
55110 const serverSession = await useServerSession ( event )
111+ const config = useRuntimeConfig ( event )
56112
57113 try {
58- const clientMetadata = getOauthClientMetadata ( )
59- const { stateStore, sessionStore } = useOAuthStorage ( serverSession )
60-
61- const client = new NodeOAuthClient ( {
62- stateStore,
63- sessionStore,
64- clientMetadata,
65- requestLock : getOAuthLock ( ) ,
66- handleResolver,
67- } )
114+ const client = await getNodeOAuthClient ( serverSession , config )
68115
69116 const currentSession = serverSession . data
70117 // TODO (jg): why can a session be `{}`?
71118 if ( ! currentSession || ! currentSession . public ?. did ) {
72119 return { oauthSession : undefined , serverSession }
73120 }
74121
122+ if ( currentSession . oauthSession && currentSession . public . did ) {
123+ //TODO clear and redirect to login to clean up old sessions
124+ }
125+
75126 const oauthSession = await client . restore ( currentSession . public . did )
76127 return { oauthSession, serverSession }
77128 } catch ( error ) {
0 commit comments