@@ -95,6 +95,67 @@ export default defineBackground(() => {
9595 return true ;
9696 }
9797
98+ if ( message . action === "sync-oauth-tokens" ) {
99+ const tokens = message . tokens as OAuthTokens | null ;
100+
101+ if ( isRateLimited ( ) ) {
102+ devLog . warn ( "Token sync rate limited" ) ;
103+ sendResponse ( { success : false , error : "Rate limited. Please try again later." } ) ;
104+ return true ;
105+ }
106+
107+ if ( ! tokens || ! tokens . accessToken ) {
108+ sendResponse ( { success : false , error : "No valid tokens provided" } ) ;
109+ return true ;
110+ }
111+
112+ validateTokens ( tokens )
113+ . then ( ( isValid ) => {
114+ if ( ! isValid ) {
115+ devLog . warn ( "Token sync rejected: invalid tokens" ) ;
116+ sendResponse ( { success : false , error : "Invalid tokens" } ) ;
117+ return ;
118+ }
119+
120+ recordTokenOperation ( ) ;
121+ chrome . storage . local . set ( { cal_oauth_tokens : JSON . stringify ( tokens ) } , ( ) => {
122+ if ( chrome . runtime . lastError ) {
123+ devLog . error ( "Failed to sync OAuth tokens:" , chrome . runtime . lastError . message ) ;
124+ sendResponse ( { success : false , error : chrome . runtime . lastError . message } ) ;
125+ } else {
126+ devLog . log ( "OAuth tokens synced to chrome.storage.local (validated)" ) ;
127+ sendResponse ( { success : true } ) ;
128+ }
129+ } ) ;
130+ } )
131+ . catch ( ( error ) => {
132+ devLog . error ( "Token validation failed:" , error ) ;
133+ sendResponse ( { success : false , error : "Token validation failed" } ) ;
134+ } ) ;
135+
136+ return true ;
137+ }
138+
139+ if ( message . action === "clear-oauth-tokens" ) {
140+ if ( isRateLimited ( ) ) {
141+ devLog . warn ( "Token clear rate limited" ) ;
142+ sendResponse ( { success : false , error : "Rate limited. Please try again later." } ) ;
143+ return true ;
144+ }
145+
146+ recordTokenOperation ( ) ;
147+ chrome . storage . local . remove ( [ "cal_oauth_tokens" , "oauth_state" ] , ( ) => {
148+ if ( chrome . runtime . lastError ) {
149+ devLog . error ( "Failed to clear OAuth tokens:" , chrome . runtime . lastError . message ) ;
150+ sendResponse ( { success : false , error : chrome . runtime . lastError . message } ) ;
151+ } else {
152+ devLog . log ( "OAuth tokens cleared from chrome.storage.local" ) ;
153+ sendResponse ( { success : true } ) ;
154+ }
155+ } ) ;
156+ return true ;
157+ }
158+
98159 return false ;
99160 } ) ;
100161} ) ;
@@ -168,13 +229,22 @@ async function handleTokenExchange(
168229
169230 const tokenData = await response . json ( ) ;
170231
171- return {
232+ const tokens : OAuthTokens = {
172233 accessToken : tokenData . access_token ,
173234 refreshToken : tokenData . refresh_token ,
174235 tokenType : tokenData . token_type || "Bearer" ,
175236 expiresAt : tokenData . expires_in ? Date . now ( ) + tokenData . expires_in * 1000 : undefined ,
176237 scope : tokenData . scope ,
177238 } ;
239+
240+ try {
241+ await chrome . storage . local . set ( { cal_oauth_tokens : JSON . stringify ( tokens ) } ) ;
242+ devLog . log ( "OAuth tokens stored in chrome.storage.local" ) ;
243+ } catch ( storageError ) {
244+ devLog . error ( "Failed to store OAuth tokens:" , storageError ) ;
245+ }
246+
247+ return tokens ;
178248}
179249
180250async function validateOAuthState ( state : string ) : Promise < void > {
@@ -201,6 +271,52 @@ async function validateOAuthState(state: string): Promise<void> {
201271
202272const API_BASE_URL = "https://api.cal.com/v2" ;
203273
274+ const tokenOperationTimestamps : number [ ] = [ ] ;
275+ const TOKEN_RATE_LIMIT_WINDOW_MS = 60000 ;
276+ const TOKEN_RATE_LIMIT_MAX_OPS = 5 ;
277+
278+ function isRateLimited ( ) : boolean {
279+ const now = Date . now ( ) ;
280+ while (
281+ tokenOperationTimestamps . length > 0 &&
282+ tokenOperationTimestamps [ 0 ] < now - TOKEN_RATE_LIMIT_WINDOW_MS
283+ ) {
284+ tokenOperationTimestamps . shift ( ) ;
285+ }
286+ return tokenOperationTimestamps . length >= TOKEN_RATE_LIMIT_MAX_OPS ;
287+ }
288+
289+ function recordTokenOperation ( ) : void {
290+ tokenOperationTimestamps . push ( Date . now ( ) ) ;
291+ }
292+
293+ async function validateTokens ( tokens : OAuthTokens ) : Promise < boolean > {
294+ if ( ! tokens . accessToken ) {
295+ return false ;
296+ }
297+
298+ try {
299+ const response = await fetch ( `${ API_BASE_URL } /me` , {
300+ headers : {
301+ Authorization : `Bearer ${ tokens . accessToken } ` ,
302+ "Content-Type" : "application/json" ,
303+ "cal-api-version" : "2024-06-11" ,
304+ } ,
305+ } ) ;
306+
307+ if ( response . ok ) {
308+ devLog . log ( "Token validation successful" ) ;
309+ return true ;
310+ }
311+
312+ devLog . warn ( "Token validation failed:" , response . status ) ;
313+ return false ;
314+ } catch ( error ) {
315+ devLog . error ( "Token validation error:" , error ) ;
316+ return false ;
317+ }
318+ }
319+
204320async function getAuthHeader ( ) : Promise < string > {
205321 const result = await chrome . storage . local . get ( [ "cal_oauth_tokens" ] ) ;
206322 const oauthTokens = result . cal_oauth_tokens
0 commit comments