-
-
Notifications
You must be signed in to change notification settings - Fork 424
Expand file tree
/
Copy pathoauth.ts
More file actions
116 lines (102 loc) · 4.13 KB
/
oauth.ts
File metadata and controls
116 lines (102 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import type { OAuthClientMetadataInput, OAuthSession } from '@atproto/oauth-client-node'
import type { EventHandlerRequest, H3Event, SessionManager } from 'h3'
import { NodeOAuthClient, AtprotoDohHandleResolver } from '@atproto/oauth-client-node'
import { parse } from 'valibot'
import { getOAuthLock } from '#server/utils/atproto/lock'
import { useOAuthStorage } from '#server/utils/atproto/storage'
import { LIKES_SCOPE } from '#shared/utils/constants'
import { OAuthMetadataSchema } from '#shared/schemas/oauth'
// @ts-expect-error virtual file from oauth module
import { clientUri } from '#oauth/config'
// TODO: If you add writing a new record you will need to add a scope for it
export const scope = `atproto ${LIKES_SCOPE}`
/**
* Resolves a did to a handle via DoH or via the http website calls
*/
export const handleResolver = new AtprotoDohHandleResolver({
dohEndpoint: 'https://cloudflare-dns.com/dns-query',
})
export function getOauthClientMetadata() {
const dev = import.meta.dev
const client_uri = clientUri
const redirect_uri = `${client_uri}/api/auth/atproto`
const client_id = dev
? `http://localhost?redirect_uri=${encodeURIComponent(redirect_uri)}&scope=${encodeURIComponent(scope)}`
: `${client_uri}/oauth-client-metadata.json`
// If anything changes here, please make sure to also update /shared/schemas/oauth.ts to match
return parse(OAuthMetadataSchema, {
client_name: 'npmx.dev',
client_id,
client_uri,
scope,
redirect_uris: [redirect_uri] as [string, ...string[]],
grant_types: ['authorization_code', 'refresh_token'],
application_type: 'web',
token_endpoint_auth_method: 'none',
dpop_bound_access_tokens: true,
response_types: ['code'],
}) as OAuthClientMetadataInput
}
async function getOAuthSession(
event: H3Event,
): Promise<{ oauthSession: OAuthSession | undefined; serverSession: SessionManager }> {
const serverSession = await useServerSession(event)
try {
const clientMetadata = getOauthClientMetadata()
const { stateStore, sessionStore } = useOAuthStorage(serverSession)
const client = new NodeOAuthClient({
stateStore,
sessionStore,
clientMetadata,
requestLock: getOAuthLock(),
handleResolver,
})
const currentSession = serverSession.data
if (!currentSession) return { oauthSession: undefined, serverSession }
const oauthSession = await client.restore(currentSession.public.did)
return { oauthSession, serverSession }
} catch (error) {
// Log error safely without using util.inspect on potentially problematic objects
// The @atproto library creates error objects with getters that crash Node's util.inspect
// eslint-disable-next-line no-console
console.error(
'[oauth] Failed to get session:',
error instanceof Error ? error.message : 'Unknown error',
)
return { oauthSession: undefined, serverSession }
}
}
/**
* Throws if the logged in OAuth Session does not have the required scopes.
* As we add new scopes we need to check if the client has the ability to use it.
* If not need to let the client know to redirect the user to the PDS to upgrade their scopes.
* @param oAuthSession - The current OAuth session from the event
* @param requiredScopes - The required scope you are checking if you can use
*/
export async function throwOnMissingOAuthScope(oAuthSession: OAuthSession, requiredScopes: string) {
const tokenInfo = await oAuthSession.getTokenInfo()
if (!tokenInfo.scope.includes(requiredScopes)) {
throw createError({
status: 403,
message: ERROR_NEED_REAUTH,
})
}
}
type EventHandlerWithOAuthSession<T extends EventHandlerRequest, D> = (
event: H3Event<T>,
session: OAuthSession | undefined,
serverSession: SessionManager,
) => Promise<D>
/**
* Handler with a valid OAuth session that is ready to be used
* @param handler
* @returns
*/
export function eventHandlerWithOAuthSession<T extends EventHandlerRequest, D>(
handler: EventHandlerWithOAuthSession<T, D>,
) {
return defineEventHandler(async event => {
const { oauthSession, serverSession } = await getOAuthSession(event)
return await handler(event, oauthSession, serverSession)
})
}