-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(openIntProxyLink): Adding Openint proxy link #39
Changes from 11 commits
12d4e43
8340ea4
4feb3cf
00f37c1
02d314a
5e03a24
f34d9ad
e20fed1
4434f24
9e79f96
9fbcc5a
1356b24
2787124
bc25c89
01ebc72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { | ||
ClientAuthOptions, | ||
mergeHeaders, | ||
modifyRequest, | ||
openIntProxyLink, | ||
} from '@opensdks/runtime' | ||
import {Link} from '../link.js' | ||
|
||
export function authLink(auth: ClientAuthOptions, baseUrl: string): Link { | ||
if (!auth) { | ||
// No Op | ||
return (req, next) => next(req) | ||
} | ||
|
||
if (auth.openInt) { | ||
Check failure on line 15 in packages/fetch-links/links/authLink.ts GitHub Actions / Run type checks, lint, and tests
|
||
return openIntProxyLink(auth.openInt, baseUrl) | ||
Check failure on line 16 in packages/fetch-links/links/authLink.ts GitHub Actions / Run type checks, lint, and tests
|
||
} | ||
|
||
const headers = { | ||
['authorization']: auth.oauth?.accessToken | ||
Check failure on line 20 in packages/fetch-links/links/authLink.ts GitHub Actions / Run type checks, lint, and tests
|
||
? `Bearer ${auth.oauth.accessToken}` | ||
Check failure on line 21 in packages/fetch-links/links/authLink.ts GitHub Actions / Run type checks, lint, and tests
|
||
: auth?.bearer | ||
Check failure on line 22 in packages/fetch-links/links/authLink.ts GitHub Actions / Run type checks, lint, and tests
|
||
? `Bearer ${auth.bearer}` | ||
Check failure on line 23 in packages/fetch-links/links/authLink.ts GitHub Actions / Run type checks, lint, and tests
|
||
: auth?.basic | ||
Check failure on line 24 in packages/fetch-links/links/authLink.ts GitHub Actions / Run type checks, lint, and tests
|
||
? `Basic ${btoa(`${auth.basic?.username}:${auth.basic?.password}`)}` | ||
Check failure on line 25 in packages/fetch-links/links/authLink.ts GitHub Actions / Run type checks, lint, and tests
Check failure on line 25 in packages/fetch-links/links/authLink.ts GitHub Actions / Run type checks, lint, and tests
Check failure on line 25 in packages/fetch-links/links/authLink.ts GitHub Actions / Run type checks, lint, and tests
|
||
: '', | ||
} satisfies HeadersInit | ||
|
||
return async (req, next) => { | ||
req.headers.delete('authorization') | ||
const res = await next( | ||
modifyRequest(req, { | ||
headers: mergeHeaders(req.headers, headers, {}), | ||
body: req.body, | ||
}), | ||
) | ||
return res | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import {mergeHeaders, modifyRequest} from '../index.js' | ||
import type {Link} from '../link.js' | ||
|
||
export type OpenIntProxyLinkOptions = { | ||
token?: string | ||
apiKey?: string | ||
resourceId?: string | ||
endUserId?: string | ||
connectorName?: string | ||
} | ||
|
||
// TODO: Use something like this to validate and generate the types too | ||
// const AuthClientOptionsSchema = z.union([ | ||
// z.object({ | ||
// openInt: z.object({ | ||
// // Add OpenIntProxyLinkOptions fields here | ||
// }).optional() | ||
// }), | ||
// z.object({ | ||
// oauth: z.object({ | ||
// accessToken: z.string(), | ||
// refreshToken: z.string().optional(), | ||
// expiresAt: z.number().optional() | ||
// }).optional() | ||
// }), | ||
// z.object({ | ||
// basic: z.object({ | ||
// username: z.string(), | ||
// password: z.string() | ||
// }).optional() | ||
// }), | ||
// z.object({ | ||
// bearer: z.string().optional() | ||
// }) | ||
// ]) | ||
|
||
export function validateOpenIntProxyLinkOptions( | ||
options: OpenIntProxyLinkOptions, | ||
): boolean { | ||
const {token, apiKey, resourceId, endUserId, connectorName} = options ?? {} | ||
|
||
const hasToken = !!token | ||
const hasApiKey = !!apiKey | ||
const hasResourceId = !!resourceId | ||
const hasEndUserId = !!endUserId | ||
const hasConnectorName = !!connectorName | ||
|
||
const expectsAuthProxy = | ||
hasToken || hasApiKey || hasResourceId || hasEndUserId || hasConnectorName | ||
if ( | ||
expectsAuthProxy && | ||
!( | ||
(hasToken && hasResourceId) || | ||
(hasToken && hasConnectorName) || | ||
(hasApiKey && hasResourceId) || | ||
(hasApiKey && hasEndUserId && hasConnectorName) | ||
) | ||
) { | ||
throw new Error( | ||
'Invalid configuration for proxy authentication. You must provide one of the following combinations: ' + | ||
'1) token AND resourceId, ' + | ||
'2) token AND connectorName, ' + | ||
'3) apiKey AND resourceId, ' + | ||
'4) apiKey AND endUserId AND connectorName, ' + | ||
'or none of these options and instead authenticate directly.', | ||
) | ||
} | ||
return expectsAuthProxy | ||
} | ||
|
||
interface OpenIntProxyHeaders { | ||
authorization?: `Bearer ${string}` | ||
'x-apikey'?: string | ||
'x-resource-id'?: string | ||
'x-resource-end-user-id'?: string | ||
'x-resource-connector-name'?: string | ||
} | ||
|
||
function removeEmptyHeaders(headers: OpenIntProxyHeaders): HeadersInit { | ||
return Object.fromEntries( | ||
Object.entries(headers).filter(([_, value]) => value && value !== ''), | ||
) satisfies HeadersInit | ||
} | ||
|
||
export function openIntProxyLink( | ||
opts: OpenIntProxyLinkOptions, | ||
baseUrl: string, | ||
): Link { | ||
validateOpenIntProxyLinkOptions(opts) | ||
const {apiKey, token, resourceId, endUserId, connectorName} = opts | ||
|
||
const headers = removeEmptyHeaders({ | ||
['x-apikey']: apiKey || '', | ||
['authorization']: token ? `Bearer ${token}` : undefined, | ||
['x-resource-id']: resourceId || '', | ||
['x-resource-end-user-id']: endUserId || '', | ||
['x-resource-connector-name']: connectorName || '', | ||
}) satisfies HeadersInit | ||
|
||
return async (req, next) => { | ||
// TODO: Check if we are already proxying and throw an error if so | ||
// if (req.url.includes(proxyUrl)) { | ||
// // Was previously necessary as link called twice leading to /api/proxy/api/proxy/? | ||
// return next(req) | ||
// } | ||
const proxyUrl = 'https://api.openint.dev/proxy' | ||
|
||
// Remove the authorization header because it will be overwritten by the proxy anyways | ||
// TODO: Think about using non-standard header for auth to avoid this maybe? | ||
req.headers.delete('authorization') | ||
const res = await next( | ||
modifyRequest(req, { | ||
url: req.url.replace(baseUrl, proxyUrl), | ||
headers: mergeHeaders(req.headers, headers, {}), | ||
body: req.body, | ||
}), | ||
) | ||
return res | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
OpenIntProxyHeaders
interface is defined twice in this file. Consider removing the duplicate definition to avoid confusion.