11import axios , { AxiosInstance , AxiosRequestConfig , AxiosResponse , CreateAxiosDefaults } from 'axios' ;
22import { OAuthClient } from './oauth/oauth-client' ;
33import { RestError } from './rest-error' ;
4-
54/*
65 * Confluent-Schema-Registry-TypeScript - Node.js wrapper for Confluent Schema Registry
76 *
@@ -11,14 +10,27 @@ import { RestError } from './rest-error';
1110 * of the MIT license. See the LICENSE.txt file for details.
1211 */
1312
13+ export interface BasicAuthCredentials {
14+ credentialsSource : 'USER_INFO' | 'URL' | 'SASL_INHERIT' ,
15+ userInfo ?: string ,
16+ saslInfo ?: SaslInfo
17+ }
18+
19+ export interface SaslInfo {
20+ mechanism ?: string ,
21+ username : string ,
22+ password : string
23+ }
24+
1425export interface BearerAuthCredentials {
15- clientId : string ,
16- clientSecret : string ,
17- tokenHost : string ,
18- tokenPath : string ,
19- schemaRegistryLogicalCluster : string ,
20- identityPool : string ,
21- scope : string
26+ credentialsSource : 'STATIC_TOKEN' | 'OAUTHBEARER' ,
27+ token ?: string ,
28+ issuerEndpointUrl ?: string ,
29+ clientId ?: string ,
30+ clientSecret ?: string ,
31+ scope ?: string ,
32+ logicalCluster ?: string ,
33+ identityPoolId ?: string ,
2234}
2335
2436//TODO: Consider retry policy, may need additional libraries on top of Axios
@@ -28,33 +40,109 @@ export interface ClientConfig {
2840 cacheLatestTtlSecs ?: number ,
2941 isForward ?: boolean ,
3042 createAxiosDefaults ?: CreateAxiosDefaults ,
43+ basicAuthCredentials ?: BasicAuthCredentials ,
3144 bearerAuthCredentials ?: BearerAuthCredentials ,
3245}
3346
47+ const toBase64 = ( str : string ) : string => Buffer . from ( str ) . toString ( 'base64' ) ;
48+
3449export class RestService {
3550 private client : AxiosInstance ;
3651 private baseURLs : string [ ] ;
37- private OAuthClient ?: OAuthClient ;
38- private bearerAuth : boolean = false ;
52+ private oauthClient ?: OAuthClient ;
53+ private oauthBearer : boolean = false ;
3954
4055 constructor ( baseURLs : string [ ] , isForward ?: boolean , axiosDefaults ?: CreateAxiosDefaults ,
41- bearerAuthCredentials ?: BearerAuthCredentials ) {
56+ basicAuthCredentials ?: BasicAuthCredentials , bearerAuthCredentials ?: BearerAuthCredentials ) {
4257 this . client = axios . create ( axiosDefaults ) ;
4358 this . baseURLs = baseURLs ;
4459
4560 if ( isForward ) {
4661 this . client . defaults . headers . common [ 'X-Forward' ] = 'true'
4762 }
4863
64+ this . handleBasicAuth ( basicAuthCredentials ) ;
65+ this . handleBearerAuth ( bearerAuthCredentials ) ;
66+
67+ if ( ! basicAuthCredentials && ! bearerAuthCredentials ) {
68+ throw new Error ( 'No auth credentials provided' ) ;
69+ }
70+ }
71+
72+ handleBasicAuth ( basicAuthCredentials ?: BasicAuthCredentials ) : void {
73+ if ( basicAuthCredentials ) {
74+ switch ( basicAuthCredentials . credentialsSource ) {
75+ case 'USER_INFO' :
76+ if ( ! basicAuthCredentials . userInfo ) {
77+ throw new Error ( 'User info not provided' ) ;
78+ }
79+ this . setAuth ( toBase64 ( basicAuthCredentials . userInfo ! ) ) ;
80+ break ;
81+ case 'SASL_INHERIT' :
82+ if ( ! basicAuthCredentials . saslInfo ) {
83+ throw new Error ( 'Sasl info not provided' ) ;
84+ }
85+ if ( basicAuthCredentials . saslInfo . mechanism ?. toUpperCase ( ) === 'GSSAPI' ) {
86+ throw new Error ( 'SASL_INHERIT support PLAIN and SCRAM SASL mechanisms only' ) ;
87+ }
88+ this . setAuth ( toBase64 ( `${ basicAuthCredentials . saslInfo . username } :${ basicAuthCredentials . saslInfo . password } ` ) ) ;
89+ break ;
90+ case 'URL' :
91+ if ( ! basicAuthCredentials . userInfo ) {
92+ throw new Error ( 'User info not provided' ) ;
93+ }
94+ const basicAuthUrl = new URL ( basicAuthCredentials . userInfo ) ;
95+ this . setAuth ( toBase64 ( `${ basicAuthUrl . username } :${ basicAuthUrl . password } ` ) ) ;
96+ break ;
97+ default :
98+ throw new Error ( 'Invalid basic auth credentials source' ) ;
99+ }
100+ }
101+ }
102+
103+ handleBearerAuth ( bearerAuthCredentials ?: BearerAuthCredentials ) : void {
49104 if ( bearerAuthCredentials ) {
50- this . bearerAuth = true ;
51105 delete this . client . defaults . auth ;
106+
107+ const headers = [ 'logicalCluster' , 'identityPoolId' ] ;
108+ const missingHeaders = headers . find ( header => bearerAuthCredentials [ header as keyof typeof bearerAuthCredentials ] ) ;
109+
110+ if ( missingHeaders ) {
111+ throw new Error ( `Bearer auth header '${ missingHeaders } ' not provided` ) ;
112+ }
113+
52114 this . setHeaders ( {
53- 'Confluent-Identity-Pool-Id' : bearerAuthCredentials . identityPool ,
54- 'target-sr-cluster' : bearerAuthCredentials . schemaRegistryLogicalCluster
115+ 'Confluent-Identity-Pool-Id' : bearerAuthCredentials . identityPoolId ! ,
116+ 'target-sr-cluster' : bearerAuthCredentials . logicalCluster !
55117 } ) ;
56- this . OAuthClient = new OAuthClient ( bearerAuthCredentials . clientId , bearerAuthCredentials . clientSecret ,
57- bearerAuthCredentials . tokenHost , bearerAuthCredentials . tokenPath , bearerAuthCredentials . scope ) ;
118+
119+ switch ( bearerAuthCredentials . credentialsSource ) {
120+ case 'STATIC_TOKEN' :
121+ if ( ! bearerAuthCredentials . token ) {
122+ throw new Error ( 'Bearer token not provided' ) ;
123+ }
124+ this . setAuth ( undefined , bearerAuthCredentials . token ) ;
125+ break ;
126+ case 'OAUTHBEARER' :
127+ this . oauthBearer = true ;
128+ const requiredFields = [
129+ 'clientId' ,
130+ 'clientSecret' ,
131+ 'issuerEndpointUrl' ,
132+ 'scope'
133+ ] ;
134+ const missingField = requiredFields . find ( field => bearerAuthCredentials [ field as keyof typeof bearerAuthCredentials ] ) ;
135+
136+ if ( missingField ) {
137+ throw new Error ( `OAuth credential '${ missingField } ' not provided` ) ;
138+ }
139+ const issuerEndPointUrl = new URL ( bearerAuthCredentials . issuerEndpointUrl ! ) ;
140+ this . oauthClient = new OAuthClient ( bearerAuthCredentials . clientId ! , bearerAuthCredentials . clientSecret ! ,
141+ issuerEndPointUrl . host , issuerEndPointUrl . pathname , bearerAuthCredentials . scope ! ) ;
142+ break ;
143+ default :
144+ throw new Error ( 'Invalid bearer auth credentials source' ) ;
145+ }
58146 }
59147 }
60148
@@ -65,8 +153,8 @@ export class RestService {
65153 config ?: AxiosRequestConfig ,
66154 ) : Promise < AxiosResponse < T > > {
67155
68- if ( this . bearerAuth ) {
69- await this . setBearerToken ( ) ;
156+ if ( this . oauthBearer ) {
157+ await this . setOAuthBearerToken ( ) ;
70158 }
71159
72160 for ( let i = 0 ; i < this . baseURLs . length ; i ++ ) {
@@ -111,12 +199,12 @@ export class RestService {
111199 }
112200 }
113201
114- async setBearerToken ( ) : Promise < void > {
115- if ( ! this . OAuthClient ) {
202+ async setOAuthBearerToken ( ) : Promise < void > {
203+ if ( ! this . oauthClient ) {
116204 throw new Error ( 'OAuthClient not initialized' ) ;
117205 }
118206
119- const bearerToken : string = await this . OAuthClient . getAccessToken ( ) ;
207+ const bearerToken : string = await this . oauthClient . getAccessToken ( ) ;
120208 this . setAuth ( undefined , bearerToken ) ;
121209 }
122210
0 commit comments