@better-auth-ui/core
Version:
Authentication components and data utilities for [Better Auth](https://better-auth.com), available for React and Solid.
125 lines (116 loc) • 3.66 kB
text/typescript
import type { OAuthProviderAuthClient } from "./oauth-provider-auth-client"
export type OAuthClientOwner =
| { type: "user" }
| {
type: "organization"
organizationId: string
organizationSlug: string
}
export type ManagedOAuthClient = {
client_id: string
client_secret?: string
client_name?: string
client_uri?: string
logo_uri?: string
redirect_uris: string[]
post_logout_redirect_uris?: string[]
scope?: string
application_type?: "web" | "native"
token_endpoint_auth_method?: string
disabled?: boolean
client_id_issued_at?: number
}
export type OAuthClientInput = Omit<
ManagedOAuthClient,
"client_id" | "client_secret" | "client_id_issued_at" | "disabled"
>
export type OAuthClientUpdate = Partial<OAuthClientInput>
/**
* Provider-neutral operations used by the OAuth client settings UI.
*
* The owner is always passed explicitly. Organization implementations must
* authorize both the organization ID and slug on the server instead of
* relying on Better Auth's active-organization session state.
*/
export interface OAuthClientManager {
list(
owner: OAuthClientOwner,
signal?: AbortSignal
): Promise<ManagedOAuthClient[]>
create(
owner: OAuthClientOwner,
input: OAuthClientInput
): Promise<ManagedOAuthClient>
update(
owner: OAuthClientOwner,
clientId: string,
update: OAuthClientUpdate
): Promise<ManagedOAuthClient>
delete(owner: OAuthClientOwner, clientId: string): Promise<void>
rotateSecret(
owner: OAuthClientOwner,
clientId: string
): Promise<ManagedOAuthClient>
/** Optional because Better Auth 1.7 only exposes disable through admin APIs. */
setDisabled?(
owner: OAuthClientOwner,
clientId: string,
disabled: boolean
): Promise<ManagedOAuthClient>
}
/**
* Adapt Better Auth's signed-in user OAuth endpoints to the UI manager.
*
* This adapter intentionally rejects organization owners. Supply an
* application-owned manager for organization settings so every request can
* carry and authorize the explicit organization ID and slug.
*/
export function createBetterAuthOAuthClientManager<
TAuthClient extends OAuthProviderAuthClient
>(authClient: TAuthClient): OAuthClientManager {
const assertUserOwner = (owner: OAuthClientOwner) => {
if (owner.type !== "user") {
throw new Error(
"Better Auth's browser OAuth client endpoints cannot safely scope organization clients. Provide an organization OAuthClientManager instead."
)
}
}
return {
async list(owner, signal) {
assertUserOwner(owner)
const clients = await authClient.oauth2.getClients({
fetchOptions: { signal, throw: true }
})
return (clients ?? []) as ManagedOAuthClient[]
},
async create(owner, input) {
assertUserOwner(owner)
return authClient.oauth2.createClient({
...input,
fetchOptions: { throw: true }
}) as Promise<ManagedOAuthClient>
},
async update(owner, clientId, update) {
assertUserOwner(owner)
return authClient.oauth2.updateClient({
client_id: clientId,
update,
fetchOptions: { throw: true }
}) as Promise<ManagedOAuthClient>
},
async delete(owner, clientId) {
assertUserOwner(owner)
await authClient.oauth2.deleteClient({
client_id: clientId,
fetchOptions: { throw: true }
})
},
async rotateSecret(owner, clientId) {
assertUserOwner(owner)
return authClient.oauth2.client.rotateSecret({
client_id: clientId,
fetchOptions: { throw: true }
}) as Promise<ManagedOAuthClient>
}
}
}