r_token/lib.rs
1#![deny(clippy::unwrap_used)]
2#![deny(clippy::expect_used)]
3#![deny(clippy::panic)]
4#![deny(clippy::todo)]
5#![deny(clippy::unimplemented)]
6#![deny(clippy::empty_loop)]
7#![deny(clippy::indexing_slicing)]
8#![deny(unused)]
9//! # r-token
10//!
11//! ## 日本語
12//!
13//! actix-web 向けの軽量なインメモリ token 認証ヘルパーです。
14//!
15//! このライブラリは主に次の 2 つを提供します:
16//! - [`RTokenManager`]: token(UUID v4)の発行/失効と、インメモリストアの管理
17//! - [`RUser`]: `Authorization` を自動検証する actix-web extractor
18//!
19//! ## 認証の流れ
20//!
21//! 1. ログイン処理で [`RTokenManager::login`] を呼び、ユーザー ID と TTL(秒)を渡します。
22//! 2. token をクライアントへ返します(多くはプレーンテキストまたは JSON)。
23//! 3. クライアントは `Authorization` header で token を送ります:
24//! - `Authorization: <token>`
25//! - `Authorization: Bearer <token>`
26//! 4. handler が [`RUser`] を引数に持つと保護されたエンドポイントになります。抽出が成功すれば認証済みとして扱われ、失敗すれば actix-web がエラーを返します。
27//!
28//! ## English
29//!
30//! A small, in-memory token authentication helper for actix-web.
31//!
32//! The library exposes two main building blocks:
33//! - [`RTokenManager`]: issues and revokes tokens (UUID v4) and keeps an in-memory store.
34//! - [`RUser`]: an actix-web extractor that validates `Authorization` automatically.
35//!
36//! ## How authentication works
37//!
38//! 1. Your login handler calls [`RTokenManager::login`] with a user id and a TTL (seconds).
39//! 2. The token is returned to the client (typically as plain text or JSON).
40//! 3. The client sends the token back via `Authorization` header:
41//! - `Authorization: <token>`
42//! - `Authorization: Bearer <token>`
43//! 4. Any handler that declares an [`RUser`] parameter becomes a protected endpoint. If extraction
44//! succeeds, the request is considered authenticated; otherwise actix-web returns an error.
45
46mod memory;
47mod models;
48#[cfg(feature = "redis")]
49mod redis;
50
51/// ## 日本語
52///
53/// token 送受信に使うデフォルトの Cookie 名です。
54///
55/// この名前は次で使用されます:
56/// - 例のサーバーが `/login` で Cookie をセットするとき
57/// - actix extractor が Cookie から token を読むとき
58///
59/// ## English
60///
61/// Default cookie name used for token transport.
62///
63/// This name is used by:
64/// - the example servers when setting cookies on `/login`
65/// - the actix extractors when reading the token from cookies
66pub const TOKEN_COOKIE_NAME: &str = "r_token";
67
68#[cfg(feature = "actix")]
69#[derive(Clone, Debug)]
70/// ## 日本語
71///
72/// 複数の token 供給元がある場合に、どちらを優先するかの設定です。
73///
74/// ## English
75///
76/// Priority for selecting which token source to use when multiple are present.
77pub enum TokenSourcePriority {
78 /// ## 日本語
79 ///
80 /// Header(例:`Authorization`)を Cookie より優先します。
81 ///
82 /// ## English
83 ///
84 /// Prefer headers (e.g. `Authorization`) over cookies.
85 HeaderFirst,
86 /// ## 日本語
87 ///
88 /// Cookie を header より優先します。
89 ///
90 /// ## English
91 ///
92 /// Prefer cookies over headers.
93 CookieFirst,
94}
95
96#[cfg(feature = "actix")]
97#[derive(Clone, Debug)]
98/// ## 日本語
99///
100/// actix extractor の token 取得元を設定します。
101///
102/// `app_data(web::Data<TokenSourceConfig>)` として登録すると、次をカスタマイズできます:
103/// - どの header 名を順に探索するか
104/// - どの cookie 名を順に探索するか
105/// - header/cookie の優先順位
106///
107/// ## English
108///
109/// Token source configuration for actix extractors.
110///
111/// You can register this as `app_data(web::Data<TokenSourceConfig>)` to customize:
112/// - which header names are scanned for a token
113/// - which cookie names are scanned for a token
114/// - the priority order between header/cookie
115pub struct TokenSourceConfig {
116 /// ## 日本語
117 ///
118 /// token 取得元の優先順位。
119 ///
120 /// ## English
121 ///
122 /// Priority of token sources.
123 pub priority: TokenSourcePriority,
124 /// ## 日本語
125 ///
126 /// 順にチェックする header 名の一覧。
127 ///
128 /// ## English
129 ///
130 /// Header names that will be checked in order.
131 pub header_names: Vec<String>,
132 /// ## 日本語
133 ///
134 /// 順にチェックする cookie 名の一覧。
135 ///
136 /// ## English
137 ///
138 /// Cookie names that will be checked in order.
139 pub cookie_names: Vec<String>,
140}
141
142#[cfg(feature = "actix")]
143impl Default for TokenSourceConfig {
144 fn default() -> Self {
145 Self {
146 priority: TokenSourcePriority::HeaderFirst,
147 header_names: vec!["Authorization".to_string()],
148 cookie_names: vec![TOKEN_COOKIE_NAME.to_string(), "token".to_string()],
149 }
150 }
151}
152
153#[cfg(feature = "actix")]
154/// ## 日本語
155///
156/// actix-web のリクエストから token を抽出します。
157///
158/// `app_data(web::Data<TokenSourceConfig>)` があればその設定を使い、なければ
159/// `TokenSourceConfig::default()` にフォールバックします。
160///
161/// ## English
162///
163/// Extracts a token from an actix-web request.
164///
165/// The function reads configuration from `app_data(web::Data<TokenSourceConfig>)` if present;
166/// otherwise it falls back to `TokenSourceConfig::default()`.
167pub fn extract_token_from_request(req: &actix_web::HttpRequest) -> Option<String> {
168 use actix_web::web;
169
170 if let Some(cfg) = req.app_data::<web::Data<TokenSourceConfig>>() {
171 extract_token_from_request_with_config(req, cfg.as_ref())
172 } else {
173 let default_cfg = TokenSourceConfig::default();
174 extract_token_from_request_with_config(req, &default_cfg)
175 }
176}
177
178#[cfg(feature = "actix")]
179/// ## 日本語
180///
181/// 明示的な設定を使って actix-web のリクエストから token を抽出します。
182///
183/// 解析のルール:
184/// - header は `Bearer <token>` と生の `<token>` の両方に対応します。
185/// - cookie は cookie value をそのまま token として扱います。
186///
187/// ## English
188///
189/// Extracts a token from an actix-web request using an explicit config.
190///
191/// Token parsing behavior:
192/// - Header values support both `Bearer <token>` and raw `<token>` formats.
193/// - Cookie values use the raw cookie value as the token.
194pub fn extract_token_from_request_with_config(
195 req: &actix_web::HttpRequest,
196 cfg: &TokenSourceConfig,
197) -> Option<String> {
198 // 日本語: header 名を順に見て、最初に見つかった token を返す。
199 // `Authorization: Bearer <token>` と `Authorization: <token>` の両方に対応する。
200 // - 値が UTF-8 でない header は無視する(to_str() が失敗するため)
201 // English: Scan header names in order and return the first token found.
202 // Supports both `Authorization: Bearer <token>` and raw `Authorization: <token>`.
203 // - Non-UTF8 headers are ignored (to_str() fails)
204 let from_headers = || {
205 cfg.header_names.iter().find_map(|name| {
206 req.headers()
207 .get(name)
208 .and_then(|h| h.to_str().ok())
209 .map(|token_str| {
210 token_str
211 .strip_prefix("Bearer ")
212 .unwrap_or(token_str)
213 .to_string()
214 })
215 })
216 };
217
218 // 日本語: cookie 名を順に見て、最初に見つかった token を返す(cookie value をそのまま使う)。
219 // English: Scan cookie names in order and return the first token found (uses cookie value as-is).
220 let from_cookies = || {
221 cfg.cookie_names
222 .iter()
223 .find_map(|name| req.cookie(name).map(|cookie| cookie.value().to_string()))
224 };
225
226 // 日本語: 設定された優先順位に従って header/cookie を選ぶ。
227 // 両方に token がある場合でも「どちらを優先するか」をここで決める。
228 // English: Choose header vs cookie by configured priority.
229 // This decides which source wins when both are present.
230 match cfg.priority {
231 TokenSourcePriority::HeaderFirst => from_headers().or_else(from_cookies),
232 TokenSourcePriority::CookieFirst => from_cookies().or_else(from_headers),
233 }
234}
235
236pub use crate::memory::RTokenManager;
237#[cfg(feature = "actix")]
238pub use crate::memory::RUser;
239pub use crate::models::RTokenError;
240#[cfg(all(feature = "redis", feature = "actix"))]
241pub use crate::redis::RRedisUser;
242#[cfg(feature = "redis")]
243pub use crate::redis::RTokenRedisManager;