Skip to content
28 changes: 25 additions & 3 deletions book/actix-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,37 @@

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<String, MyError> {
/*...*/
}
```

If you need to define an API key Authentication :
```rust
use paperclip::actix::Apiv2Security;

#[derive(Apiv2Security)]
#[openapi(
apiKey,
in = "header",
name = "Authorization",
description = "Use format 'Bearer TOKEN'"
name = "x-api-key",
description = "Use API Key"
)]
pub struct AccessToken;

Expand All @@ -22,7 +44,7 @@ async fn my_handler(access_token: AccessToken) -> Result<String, MyError> {
}
```

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.

Expand Down
6 changes: 6 additions & 0 deletions core/src/v2/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ pub struct SecurityScheme {
#[serde(rename = "in", skip_serializing_if = "Option::is_none")]
pub in_: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scheme: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bearer_format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub flow: Option<String>,
#[serde(rename = "authorizationUrl", skip_serializing_if = "Option::is_none")]
pub auth_url: Option<String>,
Expand All @@ -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);
Expand Down
39 changes: 14 additions & 25 deletions core/src/v3/security_scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,20 @@ use super::v2;
impl From<v2::SecurityScheme> 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
Expand Down Expand Up @@ -73,7 +62,7 @@ impl From<v2::SecurityScheme> for openapiv3::SecurityScheme {
_ => None,
},
},
description: None,
description: v2.description,
}
}
type_ => {
Expand Down
6 changes: 6 additions & 0 deletions macros/src/actix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,8 @@ pub fn emit_v2_security(input: TokenStream) -> TokenStream {
"description",
"name",
"in",
"scheme",
"bearer_format",
"flow",
"auth_url",
"token_url",
Expand Down Expand Up @@ -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"));
Expand All @@ -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,
Expand Down
62 changes: 62 additions & 0 deletions tests/test_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Result<Self, Self::Error>>;
#[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'"
)]
Expand Down Expand Up @@ -3441,9 +3464,15 @@ fn test_security_app() {
body
}

#[api_v2_operation]
async fn echo_pet_with_apikey(_: APIKeyToken, body: web::Json<Pet>) -> web::Json<Pet> {
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(
Expand Down Expand Up @@ -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": {
Expand Down