11import { Request , Server } from '@hapi/hapi'
2- import axios , { isAxiosError } from 'axios'
32import chalk from 'chalk'
43import Conf from 'conf'
54import { createHash , randomUUID } from 'crypto'
@@ -33,6 +32,16 @@ type CloudConfig = {
3332 'rcc.expiresAt' ?: Date
3433}
3534
35+ class CloudAuthRequestError extends Error {
36+ constructor (
37+ public readonly status : number ,
38+ public readonly body : unknown ,
39+ message ?: string ,
40+ ) {
41+ super ( message ?? `Request failed with status ${ status } ` )
42+ }
43+ }
44+
3645export class CloudAuth {
3746 private config ?: Conf < CloudConfig >
3847 private codeVerifier : string
@@ -134,14 +143,7 @@ export class CloudAuth {
134143 code_verifier : this . codeVerifier ,
135144 }
136145
137- const res = await axios . post < ICloudToken > (
138- `${ cloudUrl } /api/oauth/token` ,
139- new URLSearchParams ( request ) . toString ( ) ,
140- {
141- headers : { 'Content-Type' : 'application/x-www-form-urlencoded' } ,
142- } ,
143- )
144- const tokenInfo = res . data
146+ const tokenInfo = await this . postForm < ICloudToken > ( '/api/oauth/token' , request )
145147
146148 const expiresAt = new Date ( )
147149 expiresAt . setSeconds ( expiresAt . getSeconds ( ) + tokenInfo . expires_in )
@@ -155,12 +157,8 @@ export class CloudAuth {
155157
156158 return tokenInfo
157159 } catch ( err ) {
158- if ( isAxiosError ( err ) && err . response ) {
159- const { status, data} = err . response
160- const errorMessage = ( data as { error ?: string ; requestId ?: string } ) || { }
161- this . logError (
162- `[${ status } ] error getting token: ${ errorMessage . error ?? 'unknown' } (${ errorMessage . requestId ?? 'n/a' } )` ,
163- )
160+ if ( err instanceof CloudAuthRequestError ) {
161+ this . logHttpError ( 'error getting token' , err )
164162 } else {
165163 this . logError ( `Error getting token: ${ this . formatError ( err ) } ` )
166164 }
@@ -181,14 +179,7 @@ export class CloudAuth {
181179 }
182180
183181 try {
184- const res = await axios . post < ICloudToken > (
185- `${ cloudUrl } /api/oauth/token` ,
186- new URLSearchParams ( request ) . toString ( ) ,
187- {
188- headers : { 'Content-Type' : 'application/x-www-form-urlencoded' } ,
189- } ,
190- )
191- const tokenInfo = res . data
182+ const tokenInfo = await this . postForm < ICloudToken > ( '/api/oauth/token' , request )
192183
193184 const expiresAt = new Date ( )
194185 expiresAt . setSeconds ( expiresAt . getSeconds ( ) + tokenInfo . expires_in )
@@ -200,12 +191,8 @@ export class CloudAuth {
200191 config . set ( 'rcc.token.token_type' , tokenInfo . token_type )
201192 config . set ( 'rcc.expiresAt' , expiresAt )
202193 } catch ( err ) {
203- if ( isAxiosError ( err ) && err . response ) {
204- const { status, data} = err . response
205- const errorMessage = ( data as { error ?: string ; requestId ?: string } ) || { }
206- this . logError (
207- `[${ status } ] error refreshing token: ${ errorMessage . error ?? 'unknown' } (${ errorMessage . requestId ?? 'n/a' } )` ,
208- )
194+ if ( err instanceof CloudAuthRequestError ) {
195+ this . logHttpError ( 'error refreshing token' , err )
209196 } else {
210197 this . logError ( `Error refreshing token: ${ this . formatError ( err ) } ` )
211198 }
@@ -224,22 +211,16 @@ export class CloudAuth {
224211 }
225212
226213 try {
227- await axios . post ( `${ cloudUrl } /api/oauth/revoke` , new URLSearchParams ( request ) . toString ( ) , {
228- headers : { 'Content-Type' : 'application/x-www-form-urlencoded' } ,
229- } )
214+ await this . postForm ( '/api/oauth/revoke' , request )
230215 this . getConfig ( ) . delete ( 'rcc' )
231216 } catch ( err ) {
232- if ( isAxiosError ( err ) && err . response ) {
233- const { status, data} = err . response
234- if ( status === 401 ) {
217+ if ( err instanceof CloudAuthRequestError ) {
218+ if ( err . status === 401 ) {
235219 this . getConfig ( ) . delete ( 'rcc' )
236220 return
237221 }
238222
239- const errorMessage = ( data as { error ?: string ; requestId ?: string } ) || { }
240- this . logError (
241- `[${ status } ] error revoking the token: ${ errorMessage . error ?? 'unknown' } (${ errorMessage . requestId ?? 'n/a' } )` ,
242- )
223+ this . logHttpError ( 'error revoking the token' , err )
243224 } else {
244225 this . logError ( `Error revoking token: ${ this . formatError ( err ) } ` )
245226 }
@@ -302,6 +283,60 @@ export class CloudAuth {
302283 return url . replace ( / \+ / g, '-' ) . replace ( / \/ / g, '_' ) . replace ( / = + $ / , '' )
303284 }
304285
286+ private async postForm < T > ( path : string , payload : Record < string , string > ) : Promise < T > {
287+ const response = await fetch ( `${ cloudUrl } ${ path } ` , {
288+ method : 'POST' ,
289+ headers : { 'Content-Type' : 'application/x-www-form-urlencoded' } ,
290+ body : new URLSearchParams ( payload ) . toString ( ) ,
291+ } )
292+
293+ const rawBody = await response . text ( )
294+ let parsedBody : unknown = undefined
295+
296+ if ( rawBody ) {
297+ try {
298+ parsedBody = JSON . parse ( rawBody )
299+ } catch {
300+ parsedBody = rawBody
301+ }
302+ }
303+
304+ if ( ! response . ok ) {
305+ throw new CloudAuthRequestError ( response . status , parsedBody ?? rawBody )
306+ }
307+
308+ return parsedBody as T
309+ }
310+
311+ private logHttpError ( prefix : string , err : CloudAuthRequestError ) : void {
312+ const { error, requestId} = this . extractErrorDetails ( err . body )
313+ this . logError ( `[${ err . status } ] ${ prefix } : ${ error ?? 'unknown' } (${ requestId ?? 'n/a' } )` )
314+ }
315+
316+ private extractErrorDetails ( body : unknown ) : { error ?: string ; requestId ?: string } {
317+ if ( body && typeof body === 'object' ) {
318+ const info = body as Record < string , unknown >
319+ return {
320+ error : typeof info . error === 'string' ? info . error : undefined ,
321+ requestId : typeof info . requestId === 'string' ? info . requestId : undefined ,
322+ }
323+ }
324+
325+ if ( typeof body === 'string' ) {
326+ try {
327+ const parsed = JSON . parse ( body ) as Record < string , unknown >
328+ return {
329+ error : typeof parsed . error === 'string' ? parsed . error : undefined ,
330+ requestId : typeof parsed . requestId === 'string' ? parsed . requestId : undefined ,
331+ }
332+ } catch {
333+ return { error : body }
334+ }
335+ }
336+
337+ return { }
338+ }
339+
305340 private getConfig ( ) : Conf < CloudConfig > {
306341 if ( ! this . config ) {
307342 throw new Error ( 'Cloud configuration not initialized' )
0 commit comments