ddtkey / protect-endpoints Goto Github PK
View Code? Open in Web Editor NEWAuthorization extension for popular web-frameworks to protect your endpoints
License: Apache License 2.0
Authorization extension for popular web-frameworks to protect your endpoints
License: Apache License 2.0
Message on failing a test with common::test_body(left, right)
fn
look like this:
---- proc_macro::different_fn_types::test_str stdout ----
thread '...' panicked at 'assertion failed: `(left == right)`
left: `"Hi!"`,
right: `"Hello!"`', tests/file...
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
which corrosponds to
common::test_body("Hello!", "Hi!").await;
The lhs/rhs mismatch between compiler's message and the function params is due to the underlying assert_eq!
swap of expected
and received
args
pub async fn test_body(resp: ServiceResponse, expected_body: &str) {
│ let body = test::read_body(resp).await;
│ assert_eq!(expected_body, String::from_utf8(body.to_vec()).unwrap());
}
Swapping lhs
and rhs
pub async fn test_body(resp: ServiceResponse, expected_body: &str) {
│ let body = test::read_body(resp).await;
│ assert_eq!(String::from_utf8(body.to_vec()).unwrap(), expected_body);
}
fixes this issue.
This follows The Rust Book convention of
assert_eq!(actual, expected);
I have a PR ready to go if this feels like a worthwhile issue and doesn't violate library wide chosen convention.
poem-grants v2.0.0
I remember that in the ^1 version, there was no permission. The error message returned by interception was Forbidden 403.
Why is the current request Unauthorized?
This is completely inconsistent with the operational logic.
Because Unauthorized means that the server cannot obtain the identity of the request, but my current request is that I have logged in and got the Token issued by the server, but I just don’t have permission to access a certain resource, so I think Forbidden 403 is right
401 Unauthorized
403 Forbidden
They are two different things
The access denied response is currently fixed/hardcoded for actix-grants-proc-macro
: 403 Forbidden
(see this line)
It makes sense, but I found it helpful to let users specify their response.
My suggestion: add new optional attribute (denied
or error
for example) for macros (which will apply expression syn::Expr
)
As a result, the user can specify response something like this:
#[get("/secured")]
#[has_role("ADMIN", error = "access_denied()")]
async fn secured() -> HttpResponse {
HttpResponse::Ok().body("some response for allowed requests")
}
fn access_denied() -> HttpResponse {
HttpResponse::with_body(StatusCode::FORBIDDEN, BoxBody::new("This resource allowed only for ADMIN"))
}
With poem_openapi, the openapi definition of every endpoint is generated from the endpoint definition. The poem-grants crate integrates seamlessly with poem_openapi, but sadly, I'm unable to show the required authentication in the openapi.json.
I did manage this with poem_openapi itself, but it's not nearly as ergonomic as poem-grants.
Is there any solution I'm missing? Or is this something that needs contribution?
hi im new user for this framework
i want return cutom type if user no permission,
return json format not 403 only , how to do?
Hi,
Is it possible to use an enum value in the has_permissions attribute at all? I understand that what i have below is not valid syntax but want to understand if something like this is possible?
from:
#[has_permissions("CanRead")]
to:
#[has_permissions(Permissions::CanRead.to_string())]
More of a question than an issue.
Was hoping to do something like the following:
#[get("/read")] #[has_permissions("UserPermissions::UserRead")] #[has_any_role("UserRole::Admin", "UserRole::User")]
where I can check 2 different types (a UserPermissions enumeration and a UserRole enumeration on a singular endpoint.
Aka, I'd like for the permission to be UserRead, but also the user's role to be either an Admin or a User.
Given the 2 approaches to macros (has or has_any), I can't shove all of these into the same enumeration to solve this? (since the has_permissions/has_roles would exclude the 'or' logic for the roles, and has_any_role/has_any_permission, would just return true if any one is true.
And so far what I can see, the has_any_role/has_roles proc macros are the same logic as the permissions ones, just looking for a ROLES_ prefix in the string.
Trying to do the above on a struct provides me with:
identifier
auth_details is bound more than once in this parameter list used as parameter more than once
Am I going about this/thinking about this wrong? I'd like to have a user role AND set of associated permissions checked.
Hello,
this is a bit related to #37. Sometimes there are routes in the same scope, but some of them need authentication and some not.
Is there a strait way in actix-web-grants
to implement this? My idea was, to map all users which send requests without authentication header to role Guest and use this in the route as:
#[has_any_role("Role::Admin", "Role::Guest", type = "Role")]
#[get("/my_route/")]
async fn my_route( ...
My only solution is s very bad workaround:
async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, Error> {
match auth::decode_jwt(credentials.token()).await {
Ok(_) => req.attach(vec![Role::Admin]),
Err(e) => {
error!("validation: {e}");
req.attach(vec![Role::Guest])
}
};
req.attach(vec![Role::Admin]);
Ok(req)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
let auth = HttpAuthentication::bearer(validator);
App::new()
.wrap_fn(|mut req, srv| {
if req.headers().get("authorization").is_none() {
req.headers_mut().append(
actix_web::http::header::HeaderName::from_static("authorization"),
actix_web::http::header::HeaderValue::from_static("Bearer guest"),
);
}
srv.call(req).map(|res| res)
})
.service(
web::scope("/api/v2")
.wrap(auth)
.service(my_route)
...
is there a better way to solve the problem here?
Hi there, seems that the old exaple does not compile due to mismatch return types, specifically here:
https://github.com/DDtKey/actix-web-grants/blob/92d97c9bebb4b42beff62e829ca6b3e64edbb75b/examples/jwt-httpauth/src/main.rs#L50
Wondering if there are plans for supporting other frameworks specially salvo?
Hello,
first of all thanks for your nice and simple to use crate!
Is it possible, to check the login user name in a route? I have only seen that AuthDetails
has only a permissions value.
I ask because I would like to implement a function that a user can change his credentials, but for that I have to be sure, that the user can only change his own ones, and not from other users.
I know it's not a documented use case, but it works and somehow returns 404 error instead of 403.
.service(
web::scope("/admin")
.service(index)
.service(users)
.guard(PermissionGuard::new(ROLE_ADMIN.to_string()))
);
Was checked on v3.0.0-beta.1
As far as I can tell, the only way to get a hold of a ServiceRequest
's inner HttpRequest
is through into_parts
, which takes self
and parts_mut
, which takes &mut self
. Since actix-web-grants hands you a &ServiceRequest
, this HttpRequest
is therefore inaccessible, limiting the usefulness.
I know I can make the redirect manually, but I think it's possible to make a redirect look good in macro, something like that:
#[has_permissions("ROLE_ADMIN", redirect = "/login")]
That is the response, right? We can make a redirect there, if it was passed in macro. Does it make sense?
Upd: I guess not, that's repeating. We need middleware for that I guess.
I would like to request a new major release (3.0.0) with support for the stabilized Actix Web 4.0. Most of the work for such a release has already been done, with the changes added in 3.0.0-beta.x
. The required changes have been done in #30.
PS:
Thank you for your awesome work on Actix Web grants. I've recently started to use Actix Web for a personal project, and the permission guarding with the grant macros was very helpful for getting everything started.
This issue exists to inform about the status and plans.
There is an initiative to reorganize structure of existing crates:
The interface of these crate is really close to each other and gonna be aligned as long as possible.
It's possible to extract some reusable stuff, documentation, CI and improve code quality.
In addition it should improve maintainability, will be much easier to manage issues and reflect changes into related crates
.
For example, that's really helpful to implement DDtKey/rocket-grants#3 for all crates.
After trying to update to beta.14, I get the following error. The second one looks like a quick fix. Not so sure about the first one.
error[E0599]: no method named `extensions` found for reference `&RequestHead` in the current scope
--> /home/.cargo/git/checkouts/actix-web-grants-fc366f5a984b4e1f/f2f9964/src/guards.rs:44:14
|
44 | .extensions()
| ^^^^^^^^^^ method not found in `&RequestHead`
error[E0599]: no method named `extensions` found for struct `HttpRequest` in the current scope
--> /home/.cargo/git/checkouts/actix-web-grants-fc366f5a984b4e1f/f2f9964/src/permissions/mod.rs:97:17
|
97 | req.extensions()
| ^^^^^^^^^^ method not found in `HttpRequest`
|
::: /home/.cargo/registry/src/github.com-1ecc6299db9ec823/actix-http-3.0.0-beta.15/src/http_message.rs:29:8
|
29 | fn extensions(&self) -> Ref<'_, Extensions>;
| ---------- the method is available for `HttpRequest` here
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
16 | use actix_web::HttpMessage;
I have reverted to an old Cargo.lock file which I'll stay with for now.
When I try to use "actix-web-grants" I get the following error:
error[E0437]: type Config
is not a member of trait FromRequest
--> C:\Users\myuser.cargo\registry\src\github.com-1ecc6299db9ec823\actix-web-grants-3.0.0-beta.3\src\permissions\mod.rs:92:5
What could be the reason to get this error?
Thank you for the amazing package!
This is more of a question than an issue.
I've been following the jwt-httpauth example:
let auth = HttpAuthentication::bearer(validator);
App::new().service(create_token).service(
web::scope("/api")
.wrap(auth)
.service(permission_secured)
.service(manager_secured),
)
The unguarded create_token
handler is outside the /api
scope.
The problem arises when you want to nest the unguarded route inside /api
:
App::new()
.service(
web::scope("/api/v1")
.wrap(HttpAuthentication::bearer(validator))
.service(
web::scope("/reservations")
.configure(reservations_controllers::secured_routes),
),
)
.service(
web::scope("/api/v1")
.service(web::scope("/auth").configure(auth_controller::routes)),
)
.app_data(data.clone())
Here, the first route works, but the second one returns 401.
Adding regexp {regex:$|/.*?}
on the second scope doesn't work either.
Is there a way to achieve this? (Other than wrapping the handlers individually)
I am using poem and poem-grants and copied the JWT example, but am getting the following error: custom attribute panicked message: called Option::unwrap()
on a None
on the #[poem_grants::protect("ADMIN")]
macro. The code is:
#[poem_grants::protect("ADMIN")]
#[handler]
fn post_blog(mut pool: Data<&PgPool>, Json(blog): Json<NewBlog>) -> Response {
let r = match blog.id {
Some(id) => Blog::update(pool_handler(pool)?.borrow_mut(), id, blog.into()),
None => Blog::create(pool_handler(pool)?.borrow_mut(), blog.into()),
};
match r {
Ok(blog) => ResponseBuilder::builder()
.status(StatusCode::OK)
.body(json!({
"blog": r.expect("Error")
})),
Err(e) => {
eprintln!("Failed to save or update blog: {}", e);
ResponseBuilder::builder()
.status(StatusCode::SERVICE_UNAVAILABLE)
.body(MessageResponse {
message: "Failed to update or insert".to_string(),
})
}
}
}
...
pub fn api() -> JwtMiddlewareImpl<Route> {
Route::new()
.at("/getBlogs", get(get_blogs))
.at("/getBlog", get(get_blog))
.at("/updateBlog", post(post_blog))
.at("/getFaq", get(get_faq))
.at("/getTestimonial", get(get_testimonial))
.with(JwtMiddleware)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let connection_pool = db_helpers::establish_connection();
let app = Route::new()
.nest("/api/content", api())
.with(AddData::new(connection_pool))
.with(Tracing);
Server::new(TcpListener::bind("0.0.0.0:3000"))
.run_with_graceful_shutdown(
app,
async move {
let _ = tokio::signal::ctrl_c().await;
},
Some(Duration::from_secs(1)),
)
.await?;
Ok(())
}
The Gofiber JWT middleware provides flexibility in configuring the token lookup location, allowing adherence to OpenAPI specifications. The TokenLookup
key supports options such as Cookie, Header, Path, and Query. The default setting is "header:Authorization"
, mirroring the behavior of this crate. However, in my frontend website, where I store the access token in cookies, ensuring compliance with OpenAPI specs, to transmit the access token to the Authorization header, the cookie cannot be set as HttpOnly
, compromising security. Therefore, I aim to maintain HttpOnly
while employing this crate to extract the token by reading cookies with a custom key.
It's proposed to add support for additional checking of custom conditions in proc-macro via an additional macro argument (e.g. secure
).
The responsibility for having access to the properties specified in the secure
section rests with the user of the library.
That is, the function must have arguments of the same name.
For example:
#[get("/users/{user_id}")]
#[has_permissions("READ_INFO", secure = "user_id==user.id")]
async fn secure_id(web::Path(user_id): web::Path<i32>, user: web::Data<User>) {
// ...
}
The function parameters already contain user_id
and user
structure, accordingly, the implementation of the macro will add (in generated code) the specified condition to check access.
This is acceptable, because if there are no fields, we will receive a compilation error when expanding macros.
Hi there!
I wanted to make it so the user either has ROLE_ADMIN OR has a specific permission, instead of the user having both. Is this supported?
// User should be ADMIN with OP_GET_SECRET permission
#[actix_web_grants::protect("ROLE_ADMIN", "OP_GET_SECRET")]
async fn macro_secured() -> &'static str {
"some secured info"
}
(taken from docs)
From what i understand here, the user woiuld have to have both ROLE_ADMIN and OP_GET_SECRET. I wanted it so a certain permission can inherit other permissions. I am writing a custom extractor so i could theoretically add all the inherited permissions when i see a certain permission, but I was wondering if there was a built-in way to do so.
Thanks!
when i want read cookie and from redis read it time
Hi!
I have been trying this for my own project and I have to say it is very convenient. However I am noticing that functions with lots of calls that can fail are a bit awkward to write since the ?-operator does not work.
#[get("/foo")]
#[has_permissions("foo")]
pub async fn foo() -> Result<HttpResponse, SomeError> {
let a = thing_that_might_fail().await?; //<-- Error: ? can only be used in a function that returns Result or...
let b = other_thing(a).await?; //<-- Error
let c = yet_other_thing(b).await?; //<-- Error
Ok(and_so_on(c).await)
}
Do you have any suggestions for how I should write a similar function? Or might I request some way of supporting the ?-operator :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.