/**
* Optional OAuth setup. Resend implements OAuth 2.1 with PKCE and Dynamic
* Client Registration, so a public client can register itself — no dashboard
* app is required. API keys are the default auth lane for this package.
*
* https://resend.com/docs/guides/building-a-resend-oauth-client
*/
import {
API_BASE_URL,
OAUTH_SCOPES,
ResendApiError,
oauthConnectUrl,
} from './resend-core.ts'
export type RegisterOauthClientInput = {
/** Must be exactly `https://kody.codes/connect/oauth` for hosted Kody OAuth. */
redirectUri: string
clientName?: string
scope?: string
}
/**
* POST /oauth/register — dynamically register a public OAuth client.
* Unauthenticated. Defaults to `full_access` so every helper works.
*/
export async function registerOauthClient(
input: RegisterOauthClientInput,
options: { fetchImpl?: typeof fetch } = {},
) {
if (!input?.redirectUri) {
throw new Error(
'registerOauthClient: redirectUri is required (for Kody hosted OAuth use https://kody.codes/connect/oauth)',
)
}
const fetchImpl = options.fetchImpl ?? fetch
const response = await fetchImpl(API_BASE_URL + '/oauth/register', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
client_name: input.clientName ?? 'Kody',
redirect_uris: [input.redirectUri],
grant_types: ['authorization_code', 'refresh_token'],
response_types: ['code'],
token_endpoint_auth_method: 'none',
scope: input.scope ?? OAUTH_SCOPES.fullAccess,
}),
})
const text = await response.text()
let parsed: unknown = null
try {
parsed = text ? JSON.parse(text) : null
} catch {
parsed = text
}
if (!response.ok) {
const record = (parsed ?? {}) as { message?: string; name?: string }
throw new ResendApiError(
record.message
? (record.name ? record.name + ': ' : '') + record.message
: 'Resend API ' + response.status + ' POST /oauth/register',
{ status: response.status, body: parsed, method: 'POST', path: '/oauth/register' },
)
}
const record = (parsed ?? {}) as {
client_id?: string
client_name?: string
redirect_uris?: string[]
scope?: string
}
if (!record.client_id) {
throw new ResendApiError('registerOauthClient: response had no client_id', {
body: parsed,
method: 'POST',
path: '/oauth/register',
})
}
return {
client_id: record.client_id,
client_name: record.client_name ?? '',
redirect_uris: record.redirect_uris ?? [],
scope: record.scope ?? '',
connectUrl: oauthConnectUrl(undefined, input.scope ?? OAUTH_SCOPES.fullAccess),
}
}
/**
* Hosted `/connect/oauth` URL for Resend (PKCE, no client secret).
* Defaults to https://kody.codes and the `full_access` scope.
*/
export function buildConnectUrl(input: { provider?: string; scope?: string } = {}) {
return oauthConnectUrl(input.provider ?? 'resend', input.scope ?? OAUTH_SCOPES.fullAccess)
}