diff --git a/book/actix-security.md b/book/actix-security.md index 017997688..ad8ad56d0 100644 --- a/book/actix-security.md +++ b/book/actix-security.md @@ -2,6 +2,28 @@ You can use `Apiv2Security` derive macro for structs which can then be used as handler parameters to have those handlers marked as requiring authorization. +If you need to define a Bearer Authentication : +```rust +use paperclip::actix::Apiv2Security; + +#[derive(Apiv2Security)] +#[openapi( + http, + scheme = "bearer", + bearer_format = "JWT", + description = "Use JWT Bearer token for authentification" +)] +pub struct AccessToken; + +impl FromRequest for Accesstoken { /*...*/ } + +#[api_v2_operation] +async fn my_handler(access_token: AccessToken) -> Result { + /*...*/ +} +``` + +If you need to define an API key Authentication : ```rust use paperclip::actix::Apiv2Security; @@ -9,8 +31,8 @@ use paperclip::actix::Apiv2Security; #[openapi( apiKey, in = "header", - name = "Authorization", - description = "Use format 'Bearer TOKEN'" + name = "x-api-key", + description = "Use API Key" )] pub struct AccessToken; @@ -22,7 +44,7 @@ async fn my_handler(access_token: AccessToken) -> Result { } ``` -First parameter is the type of security, currently supported types are "apiKey" and "oauth2". Possible parameters are `alias`, `description`, `name`, `in`, `flow`, `auth_url`, `token_url` or `parent`. +First parameter is the type of security, currently supported types are "http", "apiKey" and "oauth2". Possible parameters are `alias`, `description`, `name`, `in`, `bearer_format`, `scheme` `flow`, `auth_url`, `token_url` or `parent`. Use `alias` parameter if you need to have two different security definitions of the same type. diff --git a/core/src/v2/models.rs b/core/src/v2/models.rs index d72fabdf9..658d10987 100644 --- a/core/src/v2/models.rs +++ b/core/src/v2/models.rs @@ -323,6 +323,10 @@ pub struct SecurityScheme { #[serde(rename = "in", skip_serializing_if = "Option::is_none")] pub in_: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub scheme: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub bearer_format: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub flow: Option, #[serde(rename = "authorizationUrl", skip_serializing_if = "Option::is_none")] pub auth_url: Option, @@ -343,6 +347,8 @@ impl SecurityScheme { existing.type_ = self.type_; } existing.in_ = existing.in_.take().or(self.in_); + existing.scheme = existing.scheme.take().or(self.scheme); + existing.bearer_format = existing.bearer_format.take().or(self.bearer_format); existing.flow = existing.flow.take().or(self.flow); existing.auth_url = existing.auth_url.take().or(self.auth_url); existing.token_url = existing.token_url.take().or(self.token_url); diff --git a/core/src/v3/security_scheme.rs b/core/src/v3/security_scheme.rs index 51ea3bab3..27859a891 100644 --- a/core/src/v3/security_scheme.rs +++ b/core/src/v3/security_scheme.rs @@ -3,31 +3,20 @@ use super::v2; impl From for openapiv3::SecurityScheme { fn from(v2: v2::SecurityScheme) -> Self { match v2.type_.as_str() { - "basic" => openapiv3::SecurityScheme::HTTP { - scheme: "basic".to_string(), - bearer_format: None, - description: None, + "http" => openapiv3::SecurityScheme::HTTP { + scheme: v2.scheme.unwrap_or("basic".to_string()), + bearer_format: v2.bearer_format, + description: v2.description, + }, + "apiKey" => openapiv3::SecurityScheme::APIKey { + location: match v2.in_.unwrap_or_default().as_str() { + "query" => openapiv3::APIKeyLocation::Query, + "header" => openapiv3::APIKeyLocation::Header, + _ => openapiv3::APIKeyLocation::Query, + }, + name: v2.name.unwrap_or_default(), + description: v2.description, }, - "apiKey" => { - // how to determine when it should be JWT? - if v2.in_ == Some("header".into()) { - openapiv3::SecurityScheme::HTTP { - scheme: "bearer".to_string(), - bearer_format: Some("JWT".into()), - description: None, - } - } else { - openapiv3::SecurityScheme::APIKey { - location: match v2.in_.unwrap_or_default().as_str() { - "query" => openapiv3::APIKeyLocation::Query, - "header" => openapiv3::APIKeyLocation::Header, - _ => openapiv3::APIKeyLocation::Query, - }, - name: v2.name.unwrap_or_default(), - description: None, - } - } - } "oauth2" => { let scopes = v2 .scopes @@ -73,7 +62,7 @@ impl From for openapiv3::SecurityScheme { _ => None, }, }, - description: None, + description: v2.description, } } type_ => { diff --git a/macros/src/actix.rs b/macros/src/actix.rs index 0be04752b..58c95359d 100644 --- a/macros/src/actix.rs +++ b/macros/src/actix.rs @@ -963,6 +963,8 @@ pub fn emit_v2_security(input: TokenStream) -> TokenStream { "description", "name", "in", + "scheme", + "bearer_format", "flow", "auth_url", "token_url", @@ -1068,6 +1070,8 @@ pub fn emit_v2_security(input: TokenStream) -> TokenStream { let quoted_description = quote_option(security_attrs.get("description")); let quoted_name = quote_option(security_attrs.get("name")); let quoted_in = quote_option(security_attrs.get("in")); + let quoted_scheme = quote_option(security_attrs.get("scheme")); + let quoted_bearer_format = quote_option(security_attrs.get("bearer_format")); let quoted_flow = quote_option(security_attrs.get("flow")); let quoted_auth_url = quote_option(security_attrs.get("auth_url")); let quoted_token_url = quote_option(security_attrs.get("token_url")); @@ -1078,6 +1082,8 @@ pub fn emit_v2_security(input: TokenStream) -> TokenStream { type_: #type_.to_string(), name: #quoted_name, in_: #quoted_in, + scheme: #quoted_scheme, + bearer_format: #quoted_bearer_format, flow: #quoted_flow, auth_url: #quoted_auth_url, token_url: #quoted_token_url, diff --git a/tests/test_app.rs b/tests/test_app.rs index 4c26d4ad9..23c30bdf1 100644 --- a/tests/test_app.rs +++ b/tests/test_app.rs @@ -3377,8 +3377,31 @@ fn test_security_app() { #[derive(Apiv2Security, Deserialize)] #[openapi( apiKey, + alias = "apiKeyToken", + in = "header", + name = "x-api-key", + description = "Use apiKey token" + )] + struct APIKeyToken; + + impl FromRequest for APIKeyToken { + type Error = Error; + type Future = Ready>; + #[cfg(not(feature = "actix4"))] + type Config = (); + + fn from_request(_: &HttpRequest, _payload: &mut actix_web::dev::Payload) -> Self::Future { + ready(Ok(Self {})) + } + } + + #[derive(Apiv2Security, Deserialize)] + #[openapi( + http, alias = "JWT", in = "header", + scheme = "bearer", + bearer_format = "JWT" name = "Authorization", description = "Use format 'Bearer TOKEN'" )] @@ -3441,9 +3464,15 @@ fn test_security_app() { body } + #[api_v2_operation] + async fn echo_pet_with_apikey(_: APIKeyToken, body: web::Json) -> web::Json { + body + } + fn config(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("/echo1").route(web::post().to(echo_pet_with_jwt))) .service(web::resource("/echo2").route(web::post().to(echo_pet_with_petstore))); + .service(web::resource("/echo3").route(web::post().to(echo_pet_with_apikey))); } run_and_check_app( @@ -3548,12 +3577,45 @@ fn test_security_app() { ] } }, + "/api/echo3": { + "post": { + "parameters": [{ + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + }], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Pet" + } + }, + }, + "security": [ + { + "apiKeyToken": [] + } + ] + } + }, }, "securityDefinitions": { "JWT": { "description":"Use format 'Bearer TOKEN'", "in": "header", + "scheme": "bearer", + "bearerFormat": "JWT", "name": "Authorization", + "type": "http" + }, + "apiKeyToken": { + "description":"Use apiKey token", + "in": "header", + "name": "x-api-key", "type": "apiKey" }, "MyOAuth2": {