@@ -105,6 +105,9 @@ import type {
105105 MFAVerifyWebauthnParamFields ,
106106 MFAVerifyWebauthnParams ,
107107 OAuthResponse ,
108+ AuthOAuthServerApi ,
109+ AuthOAuthAuthorizationDetailsResponse ,
110+ AuthOAuthConsentResponse ,
108111 Prettify ,
109112 Provider ,
110113 ResendParams ,
@@ -196,6 +199,12 @@ export default class GoTrueClient {
196199 * Namespace for the MFA methods.
197200 */
198201 mfa : GoTrueMFAApi
202+ /**
203+ * Namespace for the OAuth 2.1 authorization server methods.
204+ * Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
205+ * Used to implement the authorization code flow on the consent page.
206+ */
207+ oauth : AuthOAuthServerApi
199208 /**
200209 * The storage key used to identify the values saved in localStorage
201210 */
@@ -322,6 +331,12 @@ export default class GoTrueClient {
322331 webauthn : new WebAuthnApi ( this ) ,
323332 }
324333
334+ this . oauth = {
335+ getAuthorizationDetails : this . _getAuthorizationDetails . bind ( this ) ,
336+ approveAuthorization : this . _approveAuthorization . bind ( this ) ,
337+ denyAuthorization : this . _denyAuthorization . bind ( this ) ,
338+ }
339+
325340 if ( this . persistSession ) {
326341 if ( settings . storage ) {
327342 this . storage = settings . storage
@@ -3344,6 +3359,165 @@ export default class GoTrueClient {
33443359 } )
33453360 }
33463361
3362+ /**
3363+ * Retrieves details about an OAuth authorization request.
3364+ * Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
3365+ */
3366+ private async _getAuthorizationDetails (
3367+ authorizationId : string ,
3368+ options ?: { skipBrowserRedirect ?: boolean }
3369+ ) : Promise < AuthOAuthAuthorizationDetailsResponse > {
3370+ try {
3371+ return await this . _useSession ( async ( result ) => {
3372+ const {
3373+ data : { session } ,
3374+ error : sessionError ,
3375+ } = result
3376+
3377+ if ( sessionError ) {
3378+ return { data : null , error : sessionError }
3379+ }
3380+
3381+ if ( ! session ) {
3382+ return { data : null , error : new AuthSessionMissingError ( ) }
3383+ }
3384+
3385+ return await _request (
3386+ this . fetch ,
3387+ 'GET' ,
3388+ `${ this . url } /oauth/authorizations/${ authorizationId } ` ,
3389+ {
3390+ headers : this . headers ,
3391+ jwt : session . access_token ,
3392+ xform : ( data : any ) => {
3393+ // If the API returns redirect_uri, it means consent was already given
3394+ if ( data . redirect_uri ) {
3395+ // Automatically redirect in browser unless skipBrowserRedirect is true
3396+ if ( isBrowser ( ) && ! options ?. skipBrowserRedirect ) {
3397+ window . location . assign ( data . redirect_uri )
3398+ }
3399+ }
3400+
3401+ return { data, error : null }
3402+ } ,
3403+ }
3404+ )
3405+ } )
3406+ } catch ( error ) {
3407+ if ( isAuthError ( error ) ) {
3408+ return { data : null , error }
3409+ }
3410+
3411+ throw error
3412+ }
3413+ }
3414+
3415+ /**
3416+ * Approves an OAuth authorization request.
3417+ * Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
3418+ */
3419+ private async _approveAuthorization (
3420+ authorizationId : string ,
3421+ options ?: { skipBrowserRedirect ?: boolean }
3422+ ) : Promise < AuthOAuthConsentResponse > {
3423+ try {
3424+ return await this . _useSession ( async ( result ) => {
3425+ const {
3426+ data : { session } ,
3427+ error : sessionError ,
3428+ } = result
3429+
3430+ if ( sessionError ) {
3431+ return { data : null , error : sessionError }
3432+ }
3433+
3434+ if ( ! session ) {
3435+ return { data : null , error : new AuthSessionMissingError ( ) }
3436+ }
3437+
3438+ const response = await _request (
3439+ this . fetch ,
3440+ 'POST' ,
3441+ `${ this . url } /oauth/authorizations/${ authorizationId } /consent` ,
3442+ {
3443+ headers : this . headers ,
3444+ jwt : session . access_token ,
3445+ body : { action : 'approve' } ,
3446+ xform : ( data : any ) => ( { data, error : null } ) ,
3447+ }
3448+ )
3449+
3450+ if ( response . data && response . data . redirect_url ) {
3451+ // Automatically redirect in browser unless skipBrowserRedirect is true
3452+ if ( isBrowser ( ) && ! options ?. skipBrowserRedirect ) {
3453+ window . location . assign ( response . data . redirect_url )
3454+ }
3455+ }
3456+
3457+ return response
3458+ } )
3459+ } catch ( error ) {
3460+ if ( isAuthError ( error ) ) {
3461+ return { data : null , error }
3462+ }
3463+
3464+ throw error
3465+ }
3466+ }
3467+
3468+ /**
3469+ * Denies an OAuth authorization request.
3470+ * Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
3471+ */
3472+ private async _denyAuthorization (
3473+ authorizationId : string ,
3474+ options ?: { skipBrowserRedirect ?: boolean }
3475+ ) : Promise < AuthOAuthConsentResponse > {
3476+ try {
3477+ return await this . _useSession ( async ( result ) => {
3478+ const {
3479+ data : { session } ,
3480+ error : sessionError ,
3481+ } = result
3482+
3483+ if ( sessionError ) {
3484+ return { data : null , error : sessionError }
3485+ }
3486+
3487+ if ( ! session ) {
3488+ return { data : null , error : new AuthSessionMissingError ( ) }
3489+ }
3490+
3491+ const response = await _request (
3492+ this . fetch ,
3493+ 'POST' ,
3494+ `${ this . url } /oauth/authorizations/${ authorizationId } /consent` ,
3495+ {
3496+ headers : this . headers ,
3497+ jwt : session . access_token ,
3498+ body : { action : 'deny' } ,
3499+ xform : ( data : any ) => ( { data, error : null } ) ,
3500+ }
3501+ )
3502+
3503+ if ( response . data && response . data . redirect_url ) {
3504+ // Automatically redirect in browser unless skipBrowserRedirect is true
3505+ if ( isBrowser ( ) && ! options ?. skipBrowserRedirect ) {
3506+ window . location . assign ( response . data . redirect_url )
3507+ }
3508+ }
3509+
3510+ return response
3511+ } )
3512+ } catch ( error ) {
3513+ if ( isAuthError ( error ) ) {
3514+ return { data : null , error }
3515+ }
3516+
3517+ throw error
3518+ }
3519+ }
3520+
33473521 private async fetchJwk ( kid : string , jwks : { keys : JWK [ ] } = { keys : [ ] } ) : Promise < JWK | null > {
33483522 // try fetching from the supplied jwks
33493523 let jwk = jwks . keys . find ( ( key ) => key . kid === kid )
0 commit comments