Skip to content

Commit

Permalink
Return "401 - Unauthorized" if not logged in (#59)
Browse files Browse the repository at this point in the history
Co-authored-by: Nathan Ruiz <[email protected]>
  • Loading branch information
nathanruiz and Nathan Ruiz authored Jan 2, 2025
1 parent 946106d commit 572f82c
Show file tree
Hide file tree
Showing 8 changed files with 37 additions and 27 deletions.
2 changes: 1 addition & 1 deletion docs/docs/controllers/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Rwf has multiple authentication and authorization mechanisms. Different kinds of

## Session authentication

[Session](sessions.md) authentication checks that the user-supplied session cookie is valid (not expired) and contains an authenticated session. If that's not the case, the request is either rejected with a `403 - Forbidden` or provided an endpoint to re-authenticate, e.g., using a username and password, with a `302 - Found` redirect.
[Session](sessions.md) authentication checks that the user-supplied session cookie is valid (not expired) and contains an authenticated session. If that's not the case, the request is either rejected with a `401 - Unauthorized` or provided an endpoint to re-authenticate, e.g., using a username and password, with a `302 - Found` redirect.

### Enable session authentication

Expand Down
20 changes: 14 additions & 6 deletions docs/docs/controllers/response.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,25 +87,33 @@ This automatically sets the `Location` and `Cache-Control` headers, and returns

Common errors have their own methods which will return the correct HTTP response code and built-in response body.

##### 404 - Not found
##### 401 - Unauthorized

Commonly used when some resource doesn't exist, HTTP response code `404 - Not Found` can be returned with:
When your users have failed some authentication challenge, you can block access to a resource with HTTP response code `401 - Unauthorized`:

```rust
let response = Response::not_found();
let resonse = Response::unauthorized();
```

HTTP 404 is returned automatically by Rwf when a user requests a route that doesn't have a controller.
Use this one if your frontend can handle it gracefully. If not, a gentle [redirect](#redirect) to your login page may be preferable.

##### 403 - Forbidden

When your users have failed some authentication challenge, you can block access to a resource with HTTP response code `403 - Forbidden`:
When your user is logged in, but doesn't have access to the request resource, you can block access to it with HTTP response code `403 - Forbidden`:

```rust
let resonse = Response::forbidden();
```

Use this one if your frontend can handle it gracefully. If not, a gentle [redirect](#redirect) to your login page may be preferable.
##### 404 - Not found

Commonly used when some resource doesn't exist, HTTP response code `404 - Not Found` can be returned with:

```rust
let response = Response::not_found();
```

HTTP 404 is returned automatically by Rwf when a user requests a route that doesn't have a controller.

## Syntactic sugar

Expand Down
4 changes: 2 additions & 2 deletions examples/auth/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

# Authentication & sessions

Rwf has a customizable authentication and authorization system. All HTTP requests can be checked against some conditions, e.g. a header or a cookie value, and allowed access to a controller. If authorization fails, a default HTTP response, like a redirect or a `403 - Forbidden` can be returned.
Rwf has a customizable authentication and authorization system. All HTTP requests can be checked against some conditions, e.g. a header or a cookie value, and allowed access to a controller. If authorization fails, a default HTTP response, like a redirect or a `401 - Unauthorized` can be returned.

## Included authentication

Expand Down Expand Up @@ -133,7 +133,7 @@ impl Authentication for NoWorkSundays {
}

/// Optional access denied response.
/// The default is 403 - Forbidden.
/// The default is 401 - Unauthorized
async fn denied(&self) -> Result<Response, Error> {
Ok(Response::redirect("https://www.nps.gov"))
}
Expand Down
10 changes: 5 additions & 5 deletions rwf/src/controller/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ pub trait Authentication: Sync + Send {
async fn authorize(&self, request: &Request) -> Result<bool, Error>;

/// If the request is denied, return a specific response.
/// Default is `403 - Forbidden`.
/// Default is `401 - Unauthorized`.
async fn denied(&self, request: &Request) -> Result<Response, Error> {
Ok(Response::forbidden())
Ok(Response::unauthorized(None))
}

/// Returns an authentication handler used when configuring
Expand Down Expand Up @@ -111,7 +111,7 @@ impl Authentication for BasicAuth {
}

async fn denied(&self, _request: &Request) -> Result<Response, Error> {
Ok(Response::unauthorized("Basic"))
Ok(Response::unauthorized(Some("Basic")))
}
}

Expand Down Expand Up @@ -318,7 +318,7 @@ pub struct SessionAuth {

impl SessionAuth {
/// Create session authentication which redirects to this URL instead
/// of just returning `403 - Unauthorized`.
/// of just returning `401 - Unauthorized`.
pub fn redirect(url: impl ToString) -> Self {
Self {
redirect: Some(url.to_string()),
Expand All @@ -336,7 +336,7 @@ impl Authentication for SessionAuth {
if let Some(ref redirect) = self.redirect {
Ok(Response::new().redirect(redirect))
} else {
Ok(Response::forbidden())
Ok(Response::unauthorized(None))
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion rwf/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ pub trait Controller: Sync + Send {
let response = match err {
Error::HttpError(err) => match err.code() {
400 => Response::bad_request(),
403 => Response::forbidden(),
401 => Response::unauthorized(None),
413 => Response::content_too_large(),
_ => Response::internal_error(err),
},
Expand Down
8 changes: 4 additions & 4 deletions rwf/src/http/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ pub enum Error {
#[error("database error: {0}")]
Orm(#[from] crate::model::Error),

/// The user is not allowed to access the content.
#[error("forbidden")]
Forbidden,
/// The user isn't logged in.
#[error("unauthorized")]
Unauthorized,

/// HTTP request exceeds configured size.
#[error("content too large")]
Expand All @@ -75,7 +75,7 @@ impl Error {
pub fn code(&self) -> u16 {
match self {
Self::MissingParameter => 400,
Self::Forbidden => 403,
Self::Unauthorized => 401,
Self::ContentTooLarge(_) => 413,
_ => 500,
}
Expand Down
8 changes: 4 additions & 4 deletions rwf/src/http/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,11 @@ impl Request {
}

/// Get the authenticated user's ID. Combined with the `?` operator,
/// will return `403 - Unauthorized` if not logged in.
/// will return `401 - Unauthorized` if not logged in.
pub fn user_id(&self) -> Result<i64, Error> {
match self.session_id() {
SessionId::Authenticated(id) => Ok(id),
_ => Err(Error::Forbidden),
_ => Err(Error::Unauthorized),
}
}

Expand Down Expand Up @@ -279,14 +279,14 @@ impl Request {
}

/// Same function as [`Request::user`], except if returns a [`Result`] instead of an [`Option`].
/// If used with the `?` operator, returns `403 - Unauthorized` automatically.
/// If used with the `?` operator, returns `401 - Unauthorized` automatically.
pub async fn user_required<T: Model>(
&self,
conn: impl ToConnectionRequest<'_>,
) -> Result<T, Error> {
match self.user(conn).await? {
Some(user) => Ok(user),
None => Err(Error::Forbidden),
None => Err(Error::Unauthorized),
}
}

Expand Down
10 changes: 6 additions & 4 deletions rwf/src/http/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,10 +512,12 @@ impl Response {
}

/// Create `401 - Unauthorized` response.
pub fn unauthorized(auth: &str) -> Self {
Self::error_pretty("401 - Unauthorized", "")
.code(401)
.header("www-authenticate", auth)
pub fn unauthorized(auth: Option<&str>) -> Self {
let response = Self::error_pretty("401 - Unauthorized", "").code(401);
match auth {
Some(auth) => response.header("www-authenticate", auth),
None => response,
}
}

/// Create `429 - Too Many` response.
Expand Down

0 comments on commit 572f82c

Please sign in to comment.