@@ -24,27 +24,23 @@ export type UserInfo = {
2424 * including obtaining access tokens and refreshing them.
2525 */
2626class GoogleAuthService {
27- // Define the constructor with parameters if needed{
28- private _tokenUrl : string = 'https://oauth2.googleapis.com/token' ;
29- private _clientId : string ;
27+ private _googleAuthBackendUrl : string ;
28+ private _handshakeToken : string | null = null ;
3029 private _isLogin : boolean = false ;
3130 private _isAdmin : boolean = false ;
32- private _clientSecret : string ;
33- private _redirectUri : string ;
3431 private _code : string | null = null ;
3532 private _refreshToken : string | null = null ;
3633 private _expiresIn : number | null = null ;
3734 private _accessToken : string | null = null ;
38- private _timeoutId : NodeJS . Timeout | undefined
35+ private _timeoutId : NodeJS . Timeout | undefined ;
3936 private _userProfile : UserProfile ;
4037 private _modeLogger = logger . child ( { module : 'googleapi' } ) ;
4138
4239
4340 constructor ( ) {
44- this . _clientId = import . meta. env . GOOGLE_CLIENT_ID || '' ;
45- this . _clientSecret = import . meta. env . GOOGLE_CLIENT_SECRET || '' ;
46- this . _redirectUri = import . meta. env . GOOGLE_REDIRECT_URI || '' ;
41+ this . _googleAuthBackendUrl = import . meta. env . VITE_GOOGLE_AUTH_URL ;
4742 this . _userProfile = { id : '' , email : '' , name : '' , picture : '' } ;
43+ this . initHandshake ( ) ; // Initiate handshake on service creation
4844 }
4945
5046 dispose ( ) {
@@ -111,21 +107,39 @@ class GoogleAuthService {
111107 this . _isAdmin = isAdmin ;
112108 }
113109
110+ private async initHandshake ( ) {
111+ try {
112+ const response = await fetch ( `${ this . _googleAuthBackendUrl } /google-auth/handshake` , {
113+ credentials : 'include' , // Include cookies in cross-origin requests
114+ } ) ;
115+ if ( ! response . ok ) {
116+ throw new Error ( `Handshake failed: ${ response . statusText } ` ) ;
117+ }
118+ const data = await response . json ( ) ;
119+ this . _handshakeToken = data . handshake_token ;
120+ } catch ( error ) {
121+ if ( error instanceof Error ) {
122+ this . _modeLogger . error ( `Error during Google Auth handshake: ${ error . stack ?? error . message } ` ) ;
123+ } else {
124+ this . _modeLogger . error ( `Error during Google Auth handshake: ${ String ( error ) } ` ) ;
125+ }
126+ }
127+ }
128+
114129 /**
115130 * Refreshes the access token using the stored refresh token.
116131 * @returns A Promise that resolves to the new access token.
117132 */
118133 private async refreshToken ( )
119134 {
120- if ( this . _refreshToken ) {
135+ if ( this . _refreshToken && this . _handshakeToken ) {
121136 this . getAccessToken ( ) . then ( ( token ) => {
122137 this . _accessToken = token ;
123- this . _modeLogger . debug ( `Access Token refreshed: , ${ this . _accessToken } ` ) ;
124138 } ) . catch ( ( error ) => {
125139 this . _modeLogger . error ( 'Error refreshing access token:' , error ) ;
126140 } ) ;
127141 } else {
128- this . _modeLogger . warn ( 'No refresh token available to refresh access token.' ) ;
142+ this . _modeLogger . warn ( 'No refresh token or handshake token available to refresh access token.' ) ;
129143 }
130144 // Set a timeout to refresh the token again after the current token expires
131145 this . _timeoutId = setTimeout ( this . refreshToken . bind ( this ) , this . _expiresIn ? this . _expiresIn * 1000 * .95 : 3600000 ) ; // Default to 1 hour if expires_in is not set
@@ -146,38 +160,58 @@ class GoogleAuthService {
146160 // Logic to handle Google logout
147161 googleLogout ( ) ;
148162 this . _isLogin = false ;
163+ // Also notify backend to clean up session
164+ if ( this . _handshakeToken ) {
165+ try {
166+ await fetch ( `${ this . _googleAuthBackendUrl } /google-auth/session/${ this . _handshakeToken } ` , {
167+ method : 'DELETE' ,
168+ headers : {
169+ 'X-Handshake-Token' : this . _handshakeToken ,
170+ } ,
171+ credentials : 'include' , // Include cookies in cross-origin requests
172+ } ) ;
173+ } catch ( error ) {
174+ if ( error instanceof Error ) {
175+ this . _modeLogger . error ( `Error cleaning up backend session: ${ error . stack ?? error . message } ` ) ;
176+ } else {
177+ this . _modeLogger . error ( `Error cleaning up backend session: ${ String ( error ) } ` ) ;
178+ }
179+ }
180+ }
149181 }
150182
151183 /**
152184 * Retrieves the access token using the stored refresh token.
153185 * @returns A Promise that resolves to the access token.
154186 */
155187 async getAccessToken ( ) {
156- const payload = {
157- grant_type : 'refresh_token' ,
158- refresh_token : this . _refreshToken ?? '' ,
159- client_id : this . _clientId ,
160- client_secret : this . _clientSecret ,
161- } ;
188+ if ( ! this . _handshakeToken || ! this . _refreshToken ) {
189+ throw new Error ( 'Handshake token or refresh token not available.' ) ;
190+ }
162191
163192 try {
164- const response = fetch ( this . _tokenUrl , {
193+ const response = await fetch ( ` ${ this . _googleAuthBackendUrl } /google-auth/refresh-token` , {
165194 method : 'POST' ,
166195 headers : {
167- 'Content-Type' : 'application/x-www-form-urlencoded' ,
196+ 'Content-Type' : 'application/json' ,
197+ 'X-Handshake-Token' : this . _handshakeToken ,
168198 } ,
169- body : new URLSearchParams ( payload ) . toString ( ) ,
199+ body : JSON . stringify ( { session_id : this . _handshakeToken } ) , // Sending handshakeToken as session_id
200+ credentials : 'include' , // Include cookies in cross-origin requests
170201 } ) ;
171202
172- return ( await response ) . json ( ) . then ( data => {
173- if ( data . access_token ) {
174- this . _accessToken = data . access_token ;
175- this . _expiresIn = data . expires_in ;
176- return data . access_token ;
177- } else {
178- throw new Error ( 'Failed to get access token' ) ;
179- }
180- } ) ;
203+ if ( ! response . ok ) {
204+ throw new Error ( `Failed to refresh access token from backend: ${ response . statusText } ` ) ;
205+ }
206+ const data = await response . json ( ) ;
207+
208+ if ( data . access_token ) {
209+ this . _accessToken = data . access_token ;
210+ this . _expiresIn = data . expires_in ;
211+ return data . access_token ;
212+ } else {
213+ throw new Error ( 'Failed to get access token from backend response' ) ;
214+ }
181215 }
182216 catch ( error ) {
183217 if ( error instanceof Error ) {
@@ -192,34 +226,35 @@ class GoogleAuthService {
192226 * @returns A Promise that resolves to an object containing access_token, refresh_token, and expires_in.
193227 */
194228 async getRefreshToken ( ) {
195- const payload = {
196- grant_type : 'authorization_code' ,
197- code : this . _code ?? '' ,
198- client_id : this . _clientId ,
199- client_secret : this . _clientSecret ,
200- redirect_uri : this . _redirectUri ,
201- } ;
229+ if ( ! this . _handshakeToken || ! this . _code ) {
230+ throw new Error ( 'Handshake token or authorization code not available.' ) ;
231+ }
202232
203233 try {
204- const response = fetch ( this . _tokenUrl , {
234+ const response = await fetch ( ` ${ this . _googleAuthBackendUrl } /google-auth/exchange-code` , {
205235 method : 'POST' ,
206236 headers : {
207- 'Content-Type' : 'application/x-www-form-urlencoded' ,
237+ 'Content-Type' : 'application/json' ,
238+ 'X-Handshake-Token' : this . _handshakeToken ,
208239 } ,
209- body : new URLSearchParams ( payload ) . toString ( ) ,
240+ body : JSON . stringify ( { code : this . _code } ) ,
241+ credentials : 'include' , // Include cookies in cross-origin requests
210242 } ) ;
211243
212- return ( await response ) . json ( ) . then ( data => {
213- if ( data . access_token ) {
214- this . _timeoutId = setTimeout ( ( ) => this . refreshToken ( ) , 1000 ) ;
215- this . _accessToken = data . access_token ;
216- this . _refreshToken = data . refresh_token ;
217- this . _expiresIn = data . expires_in ;
218- return { access_token : data . access_token , refresh_token : data . refresh_token , expires_in : data . expires_in } ;
219- } else {
220- throw new Error ( 'Failed to refresh access token' ) ;
221- }
222- } ) ;
244+ if ( ! response . ok ) {
245+ throw new Error ( `Failed to exchange code for tokens from backend: ${ response . statusText } ` ) ;
246+ }
247+ const data = await response . json ( ) ;
248+
249+ if ( data . access_token ) {
250+ this . _timeoutId = setTimeout ( ( ) => this . refreshToken ( ) , 1000 ) ;
251+ this . _accessToken = data . access_token ;
252+ this . _refreshToken = data . refresh_token ; // Backend returns refresh token on first exchange
253+ this . _expiresIn = data . expires_in ;
254+ return { access_token : data . access_token , refresh_token : data . refresh_token , expires_in : data . expires_in } ;
255+ } else {
256+ throw new Error ( 'Failed to get refresh token from backend response' ) ;
257+ }
223258 }
224259 catch ( error ) {
225260 if ( error instanceof Error ) {
@@ -230,4 +265,4 @@ class GoogleAuthService {
230265 }
231266}
232267
233- export default GoogleAuthService ;
268+ export default GoogleAuthService ;
0 commit comments