rocket_apitoken/
lib.rs

1//! A very simple API Authorization module for Rocket web applications
2//!
3//! # Overview
4//! This module provides a simple token-based authorization system for Rocket web applications.
5//! It supports both enabled and disabled states, and validates Bearer tokens against a predefined set.
6//!
7//! # Usage Example
8//! ```no_run
9//! use rocket;
10//! use rocket_apitoken::{ApiToken, Authorized};
11//!
12//! #[post("/<method>?<json>", data = "<data>")]
13//! async fn protected_endpoint(_auth: Authorized, /* other params */) {
14//!     // If this executes, the request was authorized
15//!     // ...
16//! }
17//!
18//! #[launch]
19//! fn rocket() -> _ {
20//!     let tokens = vec!["secret-token".to_string()];
21//!     rocket::build()
22//!         .manage(ApiToken::new(tokens, true))
23//!         .mount("/api", routes![protected_endpoint])
24//! }
25//! ```
26//!
27//! # Configuration
28//! - Create an `ApiToken` instance with a list of valid tokens and enabled state
29//! - Add it to Rocket's state using `.manage()`
30//! - Use the `Authorized` guard in your route handlers
31//!
32//! When enabled, requests must include a valid token in the Authorization header.
33//! When disabled, all requests are authorized automatically.
34
35#![warn(missing_docs)]
36
37use rocket::http::Status;
38use rocket::request::{FromRequest, Outcome};
39use rocket::Request;
40use std::collections::HashSet;
41
42/// Configuration for API token authorization
43pub struct ApiToken {
44    tokens: HashSet<String>,
45    enabled: bool,
46}
47
48impl ApiToken {
49    /// Create a new `ApiToken` instance
50    pub fn new(tokens: Vec<String>, enabled: bool) -> Self {
51        Self {
52            tokens: tokens
53                .into_iter()
54                .map(|token| format!("Bearer {}", token))
55                .collect(),
56            enabled,
57        }
58    }
59
60    /// Add bearer tokens to the list of valid tokens
61    pub fn add_bearer(&mut self, token: &str) {
62        self.tokens.insert(format!("Bearer {}", token));
63    }
64}
65
66/// Request guard that ensures requests are authorized
67///
68/// This guard will succeed if either:
69/// - Authorization is disabled (`enabled = false` in ApiToken)
70/// - A valid bearer token is provided in the Authorization header
71///
72/// # Errors
73/// Returns 401 Unauthorized if:
74/// - Authorization is enabled and no Authorization header is present
75/// - The provided token is invalid
76#[derive(Debug)]
77pub struct Authorized;
78
79#[rocket::async_trait]
80impl<'r> FromRequest<'r> for Authorized {
81    type Error = &'static str;
82
83    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
84        let token = request
85            .rocket()
86            .state::<ApiToken>()
87            .expect("Token state not available.");
88        if !token.enabled {
89            return Outcome::Success(Authorized);
90        }
91        match request.headers().get_one("Authorization") {
92            Some(value) => {
93                // Check the Bearer token
94                if token.tokens.contains(value) {
95                    Outcome::Success(Authorized)
96                } else {
97                    Outcome::Error((Status::Unauthorized, "invalid token"))
98                }
99            }
100            _ => Outcome::Error((Status::Unauthorized, "Authorization header not found")),
101        }
102    }
103}