Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.9.1] - 2025-12-27

### Added
- OpenAPI security scheme parsing and RestConnector authentication support.
- `with_auth` builder method for OpenAPI parser to configure API key and bearer token credentials.
- Updated documentation and tests for security configuration.

## [0.9.0] - 2025-12-27

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "grpc_graphql_gateway"
version = "0.9.0"
version = "0.9.1"
edition = "2021"
authors = ["Protocol Lattice"]
description = "A Rust implementation of gRPC-GraphQL gateway - generates GraphQL execution code from gRPC services"
Expand Down
12 changes: 12 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

## [0.9.1] - 2025-12-27

### Added
- OpenAPI security scheme parsing and RestConnector authentication support.
- `with_auth` builder method for OpenAPI parser to configure API key and bearer token credentials.
- Updated documentation and tests for security configuration.
156 changes: 155 additions & 1 deletion src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@
//! ```

use crate::rest_connector::{
HttpMethod, RestConnector, RestEndpoint, RestFieldType, RestResponseField, RestResponseSchema,
ApiKeyInterceptor, BearerAuthInterceptor, HttpMethod, RestConnector, RestEndpoint,
RestFieldType, RestResponseField, RestResponseSchema,
};
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;

/// OpenAPI specification parser
Expand All @@ -56,6 +58,7 @@ pub struct OpenApiParser {
operation_filter: Option<Box<dyn Fn(&str, &str) -> bool + Send + Sync>>,
tag_filter: Option<Vec<String>>,
prefix: Option<String>,
auth_config: HashMap<String, String>,
}

impl std::fmt::Debug for OpenApiParser {
Expand Down Expand Up @@ -94,9 +97,16 @@ pub struct OpenApiSpec {
/// Component schemas
#[serde(default)]
pub components: Option<Components>,
/// Global security requirements
#[serde(default)]
pub security: Vec<HashMap<String, Vec<String>>>,
/// Swagger 2.0 definitions (equivalent to components.schemas)
#[serde(default)]
pub definitions: Option<HashMap<String, SchemaObject>>,
/// Swagger 2.0 security definitions
#[serde(default)]
#[serde(rename = "securityDefinitions")]
pub security_definitions: Option<HashMap<String, SecurityScheme>>,
/// Swagger 2.0 host
#[serde(default)]
pub host: Option<String>,
Expand Down Expand Up @@ -179,6 +189,9 @@ pub struct Operation {
/// Response definitions
#[serde(default)]
pub responses: HashMap<String, Response>,
/// Security requirements override
#[serde(default)]
pub security: Option<Vec<HashMap<String, Vec<String>>>>,
/// Whether the operation is deprecated
#[serde(default)]
pub deprecated: bool,
Expand Down Expand Up @@ -248,6 +261,10 @@ pub struct Components {
/// Schema definitions
#[serde(default)]
pub schemas: HashMap<String, SchemaObject>,
/// Security schemes
#[serde(default)]
#[serde(rename = "securitySchemes")]
pub security_schemes: HashMap<String, SecurityScheme>,
}

/// Schema object definition
Expand Down Expand Up @@ -291,6 +308,31 @@ pub struct SchemaObject {
pub any_of: Option<Vec<SchemaObject>>,
}

/// Security scheme definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityScheme {
/// Scheme type (apiKey, http, oauth2, openIdConnect)
#[serde(rename = "type")]
pub scheme_type: String,
/// Scheme description
#[serde(default)]
pub description: Option<String>,
/// Header/query parameter name (for apiKey)
#[serde(default)]
pub name: Option<String>,
/// Location (query, header, cookie) (for apiKey)
#[serde(default)]
#[serde(rename = "in")]
pub location: Option<String>,
/// HTTP scheme (bearer, basic) (for http)
#[serde(default)]
pub scheme: Option<String>,
/// Bearer format hint (for http/bearer)
#[serde(default)]
#[serde(rename = "bearerFormat")]
pub bearer_format: Option<String>,
}

impl OpenApiParser {
/// Create a parser from a file path
///
Expand Down Expand Up @@ -337,6 +379,7 @@ impl OpenApiParser {
operation_filter: None,
tag_filter: None,
prefix: None,
auth_config: HashMap::new(),
})
}

Expand All @@ -352,6 +395,7 @@ impl OpenApiParser {
operation_filter: None,
tag_filter: None,
prefix: None,
auth_config: HashMap::new(),
})
}

Expand Down Expand Up @@ -413,6 +457,19 @@ impl OpenApiParser {
self
}

/// Set credentials for a security scheme
///
/// The `scheme_name` must match the security scheme name in the OpenAPI spec.
///
/// # Info
/// - For `apiKey` (header): The value is the key itself
/// - For `http` (bearer): The value is the token (without "Bearer " prefix)
/// - For `http` (basic): The value is the encoded string or unused (not yet fully supported)
pub fn with_auth(mut self, scheme_name: impl Into<String>, value: impl Into<String>) -> Self {
self.auth_config.insert(scheme_name.into(), value.into());
self
}

/// Get the parsed OpenAPI spec
pub fn spec(&self) -> &OpenApiSpec {
&self.spec
Expand All @@ -430,6 +487,38 @@ impl OpenApiParser {
.base_url(&base_url)
.timeout(self.timeout);

// Configure authentication
let security_schemes = self.get_security_schemes();
for (name, scheme) in &security_schemes {
if let Some(value) = self.auth_config.get(name) {
match scheme.scheme_type.as_str() {
"apiKey" => {
if let (Some(ref param_name), Some(ref location)) =
(&scheme.name, &scheme.location)
{
if location == "header" {
builder = builder.interceptor(Arc::new(ApiKeyInterceptor::new(
param_name.clone(),
value.clone(),
)));
}
// TODO: Support query param auth if needed
}
}
"http" => {
if let Some(ref auth_scheme) = scheme.scheme {
if auth_scheme.eq_ignore_ascii_case("bearer") {
builder = builder.interceptor(Arc::new(
BearerAuthInterceptor::new(value.clone()),
));
}
}
}
_ => {}
}
}
}

// Get schemas for reference resolution
let schemas = self.get_schemas();

Expand Down Expand Up @@ -524,6 +613,23 @@ impl OpenApiParser {
schemas
}

/// Get all security schemes (from components or definitions)
fn get_security_schemes(&self) -> HashMap<String, SecurityScheme> {
let mut schemes = HashMap::new();

// OpenAPI 3.0+ components.securitySchemes
if let Some(ref components) = self.spec.components {
schemes.extend(components.security_schemes.clone());
}

// Swagger 2.0 securityDefinitions
if let Some(ref definitions) = self.spec.security_definitions {
schemes.extend(definitions.clone());
}

schemes
}

/// Create an endpoint from an operation
fn create_endpoint(
&self,
Expand Down Expand Up @@ -1061,4 +1167,52 @@ mod tests {
let pet_schema = schemas.get("Pet").unwrap();
assert_eq!(pet_schema.schema_type.as_deref(), Some("object"));
}

#[test]
fn test_security_scheme_parsing_and_config() {
let json = r##"{
"openapi": "3.0.0",
"info": {
"title": "Secure API",
"version": "1.0.0"
},
"servers": [
{"url": "https://api.secure.com"}
],
"paths": {
"/secure": {
"get": {
"operationId": "getSecure",
"responses": {
"200": {"description": "OK"}
}
}
}
},
"components": {
"securitySchemes": {
"apiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key"
},
"bearerAuth": {
"type": "http",
"scheme": "bearer"
}
}
}
}"##;

let parser = OpenApiParser::from_string(json, false)
.unwrap()
.with_auth("apiKeyAuth", "secret-key")
.with_auth("bearerAuth", "some-token");

let connector = parser.build().unwrap();

assert_eq!(connector.base_url(), "https://api.secure.com");
}


}