44 */
55
66import * as grpc from "@grpc/grpc-js"
7+ import * as url from "url"
8+ import * as querystring from "querystring"
79
810import * as messages from "../generated/api_pb"
911
@@ -13,6 +15,11 @@ import { Txn, TxnOptions } from "./txn"
1315import * as types from "./types"
1416import { isUnauthenticatedError , stringifyMessage } from "./util"
1517
18+ const dgraphScheme = "dgraph:"
19+ const sslModeDisable = "disable"
20+ const sslModeRequire = "require"
21+ const sslModeVerifyCA = "verify-ca"
22+
1623/**
1724 * Client is a transaction aware client to a set of Dgraph server instances.
1825 */
@@ -127,3 +134,102 @@ export function deleteEdges(mu: types.Mutation, uid: string, ...predicates: stri
127134 mu . addDel ( nquad )
128135 }
129136}
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