4
4
*/
5
5
6
6
import * as grpc from "@grpc/grpc-js"
7
+ import * as url from "url"
8
+ import * as querystring from "querystring"
7
9
8
10
import * as messages from "../generated/api_pb"
9
11
@@ -13,6 +15,11 @@ import { Txn, TxnOptions } from "./txn"
13
15
import * as types from "./types"
14
16
import { isUnauthenticatedError , stringifyMessage } from "./util"
15
17
18
+ const dgraphScheme = "dgraph:"
19
+ const sslModeDisable = "disable"
20
+ const sslModeRequire = "require"
21
+ const sslModeVerifyCA = "verify-ca"
22
+
16
23
/**
17
24
* Client is a transaction aware client to a set of Dgraph server instances.
18
25
*/
@@ -127,3 +134,102 @@ export function deleteEdges(mu: types.Mutation, uid: string, ...predicates: stri
127
134
mu . addDel ( nquad )
128
135
}
129
136
}
137
+
138
+ function addApiKeyToCredentials (
139
+ baseCreds : grpc . ChannelCredentials ,
140
+ apiKey : string ,
141
+ ) : grpc . ChannelCredentials {
142
+ const metaCreds = grpc . credentials . createFromMetadataGenerator ( ( _ , callback ) => {
143
+ const metadata = new grpc . Metadata ( )
144
+ metadata . add ( "authorization" , apiKey )
145
+ callback ( null , metadata )
146
+ } )
147
+ return grpc . credentials . combineChannelCredentials ( baseCreds , metaCreds )
148
+ }
149
+
150
+ function addBearerTokenToCredentials (
151
+ baseCreds : grpc . ChannelCredentials ,
152
+ bearerToken : string ,
153
+ ) : grpc . ChannelCredentials {
154
+ const metaCreds = grpc . credentials . createFromMetadataGenerator ( ( _ , callback ) => {
155
+ const metadata = new grpc . Metadata ( )
156
+ metadata . add ( "Authorization" , `Bearer ${ bearerToken } ` )
157
+ callback ( null , metadata )
158
+ } )
159
+ return grpc . credentials . combineChannelCredentials ( baseCreds , metaCreds )
160
+ }
161
+
162
+ export async function Open ( connStr : string ) : Promise < DgraphClient > {
163
+ const parsedUrl = url . parse ( connStr )
164
+
165
+ if ( parsedUrl . protocol !== dgraphScheme ) {
166
+ throw new Error ( "Invalid scheme: must start with dgraph://" )
167
+ }
168
+
169
+ const host = parsedUrl . hostname
170
+ const port = parsedUrl . port
171
+ if ( ! host ) {
172
+ throw new Error ( "Invalid connection string: hostname required" )
173
+ }
174
+ if ( ! port ) {
175
+ throw new Error ( "Invalid connection string: port required" )
176
+ }
177
+
178
+ const queryParams : Record < string , string > = { }
179
+ if ( parsedUrl . query ) {
180
+ const parsedQuery = querystring . parse ( parsedUrl . query )
181
+ Object . entries ( parsedQuery ) . forEach ( ( [ key , value ] ) => {
182
+ queryParams [ key ] = Array . isArray ( value ) ? value [ 0 ] : value
183
+ } )
184
+ }
185
+
186
+ if ( queryParams . apikey && queryParams . bearertoken ) {
187
+ throw new Error ( "Both apikey and bearertoken cannot be provided" )
188
+ }
189
+
190
+ let sslMode = queryParams . sslmode
191
+ if ( sslMode === undefined ) {
192
+ sslMode = sslModeDisable
193
+ }
194
+
195
+ let credentials
196
+ switch ( sslMode ) {
197
+ case sslModeDisable :
198
+ credentials = grpc . credentials . createInsecure ( )
199
+ break
200
+ case sslModeRequire :
201
+ credentials = grpc . credentials . createSsl ( null , null , null , {
202
+ checkServerIdentity : ( ) => undefined , // Skip certificate verification
203
+ } )
204
+ break
205
+ case sslModeVerifyCA :
206
+ credentials = grpc . credentials . createSsl ( ) // Use system CA for verification
207
+ break
208
+ default :
209
+ throw new Error ( `Invalid SSL mode: ${ sslMode } (must be one of disable, require, verify-ca)` )
210
+ }
211
+
212
+ // Add API key or Bearer token to credentials if provided
213
+ if ( queryParams . apikey ) {
214
+ credentials = addApiKeyToCredentials ( credentials , queryParams . apikey )
215
+ } else if ( queryParams . bearertoken ) {
216
+ credentials = addBearerTokenToCredentials ( credentials , queryParams . bearertoken )
217
+ }
218
+
219
+ const clientStub = new DgraphClientStub ( `${ host } :${ port } ` , credentials )
220
+
221
+ if ( parsedUrl . auth ) {
222
+ const [ username , password ] = parsedUrl . auth . split ( ":" )
223
+ if ( ! password ) {
224
+ throw new Error ( "Invalid connection string: password required when username is provided" )
225
+ }
226
+
227
+ try {
228
+ await clientStub . login ( username , password )
229
+ } catch ( err ) {
230
+ throw new Error ( `Failed to sign in user: ${ err . message } ` )
231
+ }
232
+ }
233
+
234
+ return new DgraphClient ( clientStub )
235
+ }
0 commit comments