diff --git a/Dockerfile b/Dockerfile index c6452ac45..46c359b2f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,6 +59,9 @@ RUN pnpm install --frozen-lockfile && \ rm -rf /usr/local/bin/pnpm* /usr/local/bin/corepack && \ rm -rf $HOME/.local && \ rm -rf $HOME/.cache && \ + sed -i 's/\r//' ./docker-entrypoint.sh && \ + sed -i 's/\r//' ./process-environment.sh && \ + sed -i 's/\r//' ./setup-ssl.sh && \ chmod a+x ./process-environment.sh && \ chmod a+x ./setup-ssl.sh && \ chmod a+x ./docker-entrypoint.sh diff --git a/packages/graph-explorer-proxy-server/src/app.ts b/packages/graph-explorer-proxy-server/src/app.ts index c3ebaaaf3..a7804a9d5 100644 --- a/packages/graph-explorer-proxy-server/src/app.ts +++ b/packages/graph-explorer-proxy-server/src/app.ts @@ -50,6 +50,7 @@ const DbQueryHeadersSchema = z.object({ .optional() .default(DEFAULT_SERVICE_TYPE), "db-query-logging-enabled": z.stringbool().optional().default(false), + authorization: z.string().optional(), }); /** Validates and extracts database query headers. Throws {@link RequestValidationError} on failure. */ @@ -76,6 +77,7 @@ function parseDbQueryHeaders(headers: IncomingHttpHeaders) { queryId: parsed.queryid, graphDbConnectionUrl: parsed["graph-db-connection-url"], shouldLogDbQuery: parsed["db-query-logging-enabled"], + authorization: parsed.authorization, ...authOptions, }; } @@ -285,6 +287,7 @@ export function createApp({ isIamEnabled, region, serviceType, + authorization, } = parseDbQueryHeaders(req.headers); assertAllowedDbOrigin(graphDbConnectionUrl, allowedDbOrigins); @@ -355,6 +358,7 @@ export function createApp({ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/sparql-results+json", + ...(authorization && { Authorization: authorization }), }, body, }; @@ -380,6 +384,7 @@ export function createApp({ isIamEnabled, region, serviceType, + authorization, } = parseDbQueryHeaders(req.headers); assertAllowedDbOrigin(graphDbConnectionUrl, allowedDbOrigins); @@ -441,6 +446,7 @@ export function createApp({ headers: { "Content-Type": "application/json", Accept: "application/vnd.gremlin-v3.0+json", + ...(authorization && { Authorization: authorization }), }, body: JSON.stringify(body), }; @@ -465,6 +471,7 @@ export function createApp({ isIamEnabled, region, serviceType, + authorization, } = parseDbQueryHeaders(req.headers); assertAllowedDbOrigin(graphDbConnectionUrl, allowedDbOrigins); @@ -488,6 +495,7 @@ export function createApp({ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", + ...(authorization && { Authorization: authorization }), }, body: `query=${encodeURIComponent(queryString)}`, }; @@ -505,7 +513,7 @@ export function createApp({ // GET endpoint to retrieve PropertyGraph statistics summary for Neptune Analytics. app.get("/summary", async (req, res, next) => { - const { graphDbConnectionUrl, isIamEnabled, region, serviceType } = + const { graphDbConnectionUrl, isIamEnabled, region, serviceType, authorization } = parseDbQueryHeaders(req.headers); assertAllowedDbOrigin(graphDbConnectionUrl, allowedDbOrigins); const rawUrl = resolveEndpointUrl( @@ -517,7 +525,7 @@ export function createApp({ res, next, rawUrl, - { method: "GET" }, + { method: "GET", headers: { ...(authorization && { Authorization: authorization }) } }, isIamEnabled, region, serviceType, @@ -526,7 +534,7 @@ export function createApp({ // GET endpoint to retrieve PropertyGraph statistics summary for Neptune DB. app.get("/pg/statistics/summary", async (req, res, next) => { - const { graphDbConnectionUrl, isIamEnabled, region, serviceType } = + const { graphDbConnectionUrl, isIamEnabled, region, serviceType, authorization } = parseDbQueryHeaders(req.headers); assertAllowedDbOrigin(graphDbConnectionUrl, allowedDbOrigins); const rawUrl = resolveEndpointUrl( @@ -538,7 +546,7 @@ export function createApp({ res, next, rawUrl, - { method: "GET" }, + { method: "GET", headers: { ...(authorization && { Authorization: authorization }) } }, isIamEnabled, region, serviceType, @@ -547,7 +555,7 @@ export function createApp({ // GET endpoint to retrieve RDF statistics summary. app.get("/rdf/statistics/summary", async (req, res, next) => { - const { graphDbConnectionUrl, isIamEnabled, region, serviceType } = + const { graphDbConnectionUrl, isIamEnabled, region, serviceType, authorization } = parseDbQueryHeaders(req.headers); assertAllowedDbOrigin(graphDbConnectionUrl, allowedDbOrigins); const rawUrl = resolveEndpointUrl( @@ -559,7 +567,7 @@ export function createApp({ res, next, rawUrl, - { method: "GET" }, + { method: "GET", headers: { ...(authorization && { Authorization: authorization }) } }, isIamEnabled, region, serviceType, diff --git a/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts b/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts index 80696820f..45cfde464 100644 --- a/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts +++ b/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts @@ -63,6 +63,9 @@ function getAuthHeaders( headers["aws-neptune-region"] = connection.awsRegion || ""; headers["service-type"] = connection.serviceType || DEFAULT_SERVICE_TYPE; } + if (connection.username && connection.password) { + headers["Authorization"] = `Basic ${btoa(`${connection.username}:${connection.password}`)}`; + } if (typeHeaders) { Object.assign(headers, Object.fromEntries(new Headers(typeHeaders))); diff --git a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx index 637a0dc26..0e9e6b9c3 100644 --- a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx +++ b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx @@ -47,6 +47,8 @@ type ConnectionForm = { fetchTimeoutMs?: number; nodeExpansionLimitEnabled: boolean; nodeExpansionLimit?: number; + username?: string; + password?: string; }; const CONNECTIONS_OP: { @@ -76,6 +78,8 @@ function mapToConnection(data: Required): ConnectionConfig { nodeExpansionLimit: data.nodeExpansionLimitEnabled ? data.nodeExpansionLimit : undefined, + username: data.username || undefined, + password: data.password || undefined, }; } @@ -193,6 +197,8 @@ const CreateConnection = ({ fetchTimeoutMs: initialData?.fetchTimeoutMs, nodeExpansionLimitEnabled: initialData?.nodeExpansionLimitEnabled || false, nodeExpansionLimit: initialData?.nodeExpansionLimit, + username: initialData?.username || "", + password: initialData?.password || "", }); const [hasError, setError] = useState(false); @@ -295,6 +301,31 @@ const CreateConnection = ({ /> + + + + + + + + +