11import pkceChallenge from "pkce-challenge" ;
22import { SESSION_KEYS } from "./constants" ;
3+ import { z } from "zod" ;
34
4- export interface OAuthMetadata {
5- authorization_endpoint : string ;
6- token_endpoint : string ;
7- }
5+ export const OAuthMetadataSchema = z . object ( {
6+ authorization_endpoint : z . string ( ) ,
7+ token_endpoint : z . string ( ) ,
8+ } ) ;
9+
10+ export type OAuthMetadata = z . infer < typeof OAuthMetadataSchema > ;
11+
12+ export const OAuthTokensSchema = z . object ( {
13+ access_token : z . string ( ) ,
14+ refresh_token : z . string ( ) . optional ( ) ,
15+ expires_in : z . number ( ) . optional ( ) ,
16+ } ) ;
17+
18+ export type OAuthTokens = z . infer < typeof OAuthTokensSchema > ;
819
920export async function discoverOAuthMetadata (
1021 serverUrl : string ,
@@ -15,21 +26,23 @@ export async function discoverOAuthMetadata(
1526
1627 if ( response . ok ) {
1728 const metadata = await response . json ( ) ;
18- return {
29+ const validatedMetadata = OAuthMetadataSchema . parse ( {
1930 authorization_endpoint : metadata . authorization_endpoint ,
2031 token_endpoint : metadata . token_endpoint ,
21- } ;
32+ } ) ;
33+ return validatedMetadata ;
2234 }
2335 } catch ( error ) {
2436 console . warn ( "OAuth metadata discovery failed:" , error ) ;
2537 }
2638
2739 // Fall back to default endpoints
2840 const baseUrl = new URL ( serverUrl ) ;
29- return {
41+ const defaultMetadata = {
3042 authorization_endpoint : new URL ( "/authorize" , baseUrl ) . toString ( ) ,
3143 token_endpoint : new URL ( "/token" , baseUrl ) . toString ( ) ,
3244 } ;
45+ return OAuthMetadataSchema . parse ( defaultMetadata ) ;
3346}
3447
3548export async function startOAuthFlow ( serverUrl : string ) : Promise < string > {
@@ -60,7 +73,7 @@ export async function startOAuthFlow(serverUrl: string): Promise<string> {
6073export async function handleOAuthCallback (
6174 serverUrl : string ,
6275 code : string ,
63- ) : Promise < string > {
76+ ) : Promise < OAuthTokens > {
6477 // Get stored code verifier
6578 const codeVerifier = sessionStorage . getItem ( SESSION_KEYS . CODE_VERIFIER ) ;
6679 if ( ! codeVerifier ) {
@@ -69,7 +82,6 @@ export async function handleOAuthCallback(
6982
7083 // Discover OAuth endpoints
7184 const metadata = await discoverOAuthMetadata ( serverUrl ) ;
72-
7385 // Exchange code for tokens
7486 const response = await fetch ( metadata . token_endpoint , {
7587 method : "POST" ,
@@ -88,6 +100,35 @@ export async function handleOAuthCallback(
88100 throw new Error ( "Token exchange failed" ) ;
89101 }
90102
91- const data = await response . json ( ) ;
92- return data . access_token ;
103+ const tokens = await response . json ( ) ;
104+ return OAuthTokensSchema . parse ( tokens ) ;
105+ }
106+
107+ export async function refreshAccessToken (
108+ serverUrl : string ,
109+ ) : Promise < OAuthTokens > {
110+ const refreshToken = sessionStorage . getItem ( SESSION_KEYS . REFRESH_TOKEN ) ;
111+ if ( ! refreshToken ) {
112+ throw new Error ( "No refresh token available" ) ;
113+ }
114+
115+ const metadata = await discoverOAuthMetadata ( serverUrl ) ;
116+
117+ const response = await fetch ( metadata . token_endpoint , {
118+ method : "POST" ,
119+ headers : {
120+ "Content-Type" : "application/json" ,
121+ } ,
122+ body : JSON . stringify ( {
123+ grant_type : "refresh_token" ,
124+ refresh_token : refreshToken ,
125+ } ) ,
126+ } ) ;
127+
128+ if ( ! response . ok ) {
129+ throw new Error ( "Token refresh failed" ) ;
130+ }
131+
132+ const tokens = await response . json ( ) ;
133+ return OAuthTokensSchema . parse ( tokens ) ;
93134}
0 commit comments