1
1
import axios , { AxiosInstance , AxiosRequestConfig , AxiosResponse , CreateAxiosDefaults } from 'axios' ;
2
2
import { OAuthClient } from './oauth/oauth-client' ;
3
3
import { RestError } from './rest-error' ;
4
-
5
4
/*
6
5
* Confluent-Schema-Registry-TypeScript - Node.js wrapper for Confluent Schema Registry
7
6
*
@@ -11,14 +10,27 @@ import { RestError } from './rest-error';
11
10
* of the MIT license. See the LICENSE.txt file for details.
12
11
*/
13
12
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
+
14
25
export 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 ,
22
34
}
23
35
24
36
//TODO: Consider retry policy, may need additional libraries on top of Axios
@@ -28,33 +40,109 @@ export interface ClientConfig {
28
40
cacheLatestTtlSecs ?: number ,
29
41
isForward ?: boolean ,
30
42
createAxiosDefaults ?: CreateAxiosDefaults ,
43
+ basicAuthCredentials ?: BasicAuthCredentials ,
31
44
bearerAuthCredentials ?: BearerAuthCredentials ,
32
45
}
33
46
47
+ const toBase64 = ( str : string ) : string => Buffer . from ( str ) . toString ( 'base64' ) ;
48
+
34
49
export class RestService {
35
50
private client : AxiosInstance ;
36
51
private baseURLs : string [ ] ;
37
- private OAuthClient ?: OAuthClient ;
38
- private bearerAuth : boolean = false ;
52
+ private oauthClient ?: OAuthClient ;
53
+ private oauthBearer : boolean = false ;
39
54
40
55
constructor ( baseURLs : string [ ] , isForward ?: boolean , axiosDefaults ?: CreateAxiosDefaults ,
41
- bearerAuthCredentials ?: BearerAuthCredentials ) {
56
+ basicAuthCredentials ?: BasicAuthCredentials , bearerAuthCredentials ?: BearerAuthCredentials ) {
42
57
this . client = axios . create ( axiosDefaults ) ;
43
58
this . baseURLs = baseURLs ;
44
59
45
60
if ( isForward ) {
46
61
this . client . defaults . headers . common [ 'X-Forward' ] = 'true'
47
62
}
48
63
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 {
49
104
if ( bearerAuthCredentials ) {
50
- this . bearerAuth = true ;
51
105
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
+
52
114
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 !
55
117
} ) ;
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
+ }
58
146
}
59
147
}
60
148
@@ -65,8 +153,8 @@ export class RestService {
65
153
config ?: AxiosRequestConfig ,
66
154
) : Promise < AxiosResponse < T > > {
67
155
68
- if ( this . bearerAuth ) {
69
- await this . setBearerToken ( ) ;
156
+ if ( this . oauthBearer ) {
157
+ await this . setOAuthBearerToken ( ) ;
70
158
}
71
159
72
160
for ( let i = 0 ; i < this . baseURLs . length ; i ++ ) {
@@ -111,12 +199,12 @@ export class RestService {
111
199
}
112
200
}
113
201
114
- async setBearerToken ( ) : Promise < void > {
115
- if ( ! this . OAuthClient ) {
202
+ async setOAuthBearerToken ( ) : Promise < void > {
203
+ if ( ! this . oauthClient ) {
116
204
throw new Error ( 'OAuthClient not initialized' ) ;
117
205
}
118
206
119
- const bearerToken : string = await this . OAuthClient . getAccessToken ( ) ;
207
+ const bearerToken : string = await this . oauthClient . getAccessToken ( ) ;
120
208
this . setAuth ( undefined , bearerToken ) ;
121
209
}
122
210
0 commit comments