torrust_index_backend/web/api/v1/
auth.rs

1//! API authentication.
2//!
3//! The API uses a [bearer token authentication scheme](https://datatracker.ietf.org/doc/html/rfc6750).
4//!
5//! API clients must have an account on the website to be able to use the API.
6//!
7//! # Authentication flow
8//!
9//! - [Registration](#registration)
10//! - [Login](#login)
11//! - [Using the token](#using-the-token)
12//!
13//! ## Registration
14//!
15//! ```bash
16//! curl \
17//!   --header "Content-Type: application/json" \
18//!   --request POST \
19//!   --data '{"username":"indexadmin","email":"indexadmin@torrust.com","password":"BenoitMandelbrot1924","confirm_password":"BenoitMandelbrot1924"}' \
20//!   http://127.0.0.1:3001/v1/user/register
21//! ```
22//!
23//! **NOTICE**: The first user is automatically an administrator. Currently,
24//! there is no way to change this. There is one administrator per instance.
25//! And you cannot delete the administrator account or make another user an
26//! administrator. For testing purposes, you can create a new administrator
27//! account by creating a new user and then manually changing the `administrator`
28//! field in the `torrust_users` table to `1`.
29//!
30//! ## Login
31//!
32//! ```bash
33//! curl \
34//!   --header "Content-Type: application/json" \
35//!   --request POST \
36//!   --data '{"login":"indexadmin","password":"BenoitMandelbrot1924"}' \
37//!   http://127.0.0.1:3001/v1/user/login
38//! ```
39//!
40//! **Response**
41//!
42//! ```json
43//! {
44//!     "data":{
45//!       "token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI",
46//!       "username":"indexadmin",
47//!       "admin":true
48//!     }
49//!   }
50//! ```
51//!
52//! **NOTICE**: The token is valid for 2 weeks (`1_209_600` seconds). After that,
53//! you will have to renew the token.
54//!
55//! **NOTICE**: The token is associated with the user role. If you change the
56//! user's role, you will have to log in again to get a new token with the new
57//! role.
58//!
59//! ## Using the token
60//!
61//! Some endpoints require authentication. To use the token, you must add the
62//! `Authorization` header to your request. For example, if you want to add a
63//! new category, you must do the following:
64//!
65//! ```bash
66//! curl \
67//!   --header "Content-Type: application/json" \
68//!   --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \
69//!   --request POST \
70//!   --data '{"name":"new category","icon":null}' \
71//!   http://127.0.0.1:3001/v1/category
72//! ```
73//!
74//! **Response**
75//!
76//! ```json
77//! {
78//!   "data": "new category"
79//! }
80//! ```
81use std::sync::Arc;
82
83use hyper::http::HeaderValue;
84
85use crate::common::AppData;
86use crate::errors::ServiceError;
87use crate::models::user::{UserClaims, UserCompact, UserId};
88use crate::services::authentication::JsonWebToken;
89use crate::web::api::v1::extractors::bearer_token::BearerToken;
90
91pub struct Authentication {
92    json_web_token: Arc<JsonWebToken>,
93}
94
95impl Authentication {
96    #[must_use]
97    pub fn new(json_web_token: Arc<JsonWebToken>) -> Self {
98        Self { json_web_token }
99    }
100
101    /// Create Json Web Token
102    pub async fn sign_jwt(&self, user: UserCompact) -> String {
103        self.json_web_token.sign(user).await
104    }
105
106    /// Verify Json Web Token
107    ///
108    /// # Errors
109    ///
110    /// This function will return an error if the JWT is not good or expired.
111    pub async fn verify_jwt(&self, token: &str) -> Result<UserClaims, ServiceError> {
112        self.json_web_token.verify(token).await
113    }
114
115    /// Get logged-in user ID from bearer token
116    ///
117    /// # Errors
118    ///
119    /// This function will return an error if it can get claims from the request
120    pub async fn get_user_id_from_bearer_token(&self, maybe_token: &Option<BearerToken>) -> Result<UserId, ServiceError> {
121        let claims = self.get_claims_from_bearer_token(maybe_token).await?;
122        Ok(claims.user.user_id)
123    }
124
125    /// Get Claims from bearer token
126    ///
127    /// # Errors
128    ///
129    /// This function will:
130    ///
131    /// - Return an `ServiceError::TokenNotFound` if `HeaderValue` is `None`.
132    /// - Pass through the `ServiceError::TokenInvalid` if unable to verify the JWT.
133    async fn get_claims_from_bearer_token(&self, maybe_token: &Option<BearerToken>) -> Result<UserClaims, ServiceError> {
134        match maybe_token {
135            Some(token) => match self.verify_jwt(&token.value()).await {
136                Ok(claims) => Ok(claims),
137                Err(e) => Err(e),
138            },
139            None => Err(ServiceError::TokenNotFound),
140        }
141    }
142}
143
144/// Parses the token from the `Authorization` header.
145///
146/// # Panics
147///
148/// This function will panic if the `Authorization` header is not a valid `String`.
149pub fn parse_token(authorization: &HeaderValue) -> String {
150    let split: Vec<&str> = authorization
151        .to_str()
152        .expect("variable `auth` contains data that is not visible ASCII chars.")
153        .split("Bearer")
154        .collect();
155    let token = split[1].trim();
156    token.to_string()
157}
158
159/// If the user is logged in, returns the user's ID. Otherwise, returns `None`.
160///
161/// # Errors
162///
163/// It returns an error if we cannot get the user from the bearer token.
164pub async fn get_optional_logged_in_user(
165    maybe_bearer_token: Option<BearerToken>,
166    app_data: Arc<AppData>,
167) -> Result<Option<UserId>, ServiceError> {
168    match maybe_bearer_token {
169        Some(bearer_token) => match app_data.auth.get_user_id_from_bearer_token(&Some(bearer_token)).await {
170            Ok(user_id) => Ok(Some(user_id)),
171            Err(error) => Err(error),
172        },
173        None => Ok(None),
174    }
175}