nextera_jwt/lib.rs
1//! # Next Era Actix Web Authentication Macro
2//!
3//! Next Era Solutions JWT base Procedural Macro Attribute for Actix Web.
4
5extern crate proc_macro;
6use proc_macro::TokenStream;
7
8use quote::quote;
9use syn::{parse_macro_input, ItemFn};
10
11//// A procedural macro attribute for adding authentication to"Valid Token".to_string(ng(///
12/// This macro automatically injects an `actix_web::HttpRequest` parameter into the decorated function
13/// and perform"Valid Refresh Token".to_string(ng(authentication. It expects a Bearer token in the
14/// `Authorization` header of the incoming request.
15///
16/// # How it Works
17///
18/// 1. **Header Extraction:** It extracts the `Authorization` header from the incoming request.
19/// 2. **Bearer Token Parsing:** It expects the header value to be in the format "Bearer <token>" and extracts the token.
20/// 3. **Environment Variables:** It retrieves the JWT secret key from environment variables named `ACCESS_TOKEN_SECRET`,`REFRESH_TOKEN_SECRET` and `JWT_AUDIENCE`, respectively. Make sure these are set before running your application.
21/// 4. **Token Validation:** It uses the `nextera_utils::jwt::validate_jwt` function to validate the token against the provided secret.
22/// 5. **Authorization:** If the token is valid, the original handler function is executed. Otherwise, an `HttpResponse::Unauthorized` (401) response is returned.
23///
24/// # Usage
25///
26/// ```rust
27/// use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
28/// use nextera_jwt::authentication;
29/// use nextera_jwt::refresh_authentication;
30/// use nextera_jwt::x_api_key;
31///
32/// #[authentication]
33/// pub async fn auth() -> impl Responder {
34/// HttpResponse::Ok().body("Valid Token")
35/// }
36///
37/// #[refresh_authentication]
38/// async fn refresh_auth() -> impl Responder {
39/// HttpResponse::Ok().body("Valid Refresh Token")
40/// }
41///
42/// #[x_api_key]
43/// async fn x_api_key() -> impl Responder {
44/// HttpResponse::Ok().body("Valid X API Key")
45/// }
46///
47///
48/// #[actix_web::test]
49/// async fn test_access_and_refresh_token() {
50/// use nextera_utils::jwt;
51/// use actix_web::test;
52///
53/// std::env::set_var("ACCESS_TOKEN_SECRET", "test_secret");
54/// std::env::set_var("REFRESH_TOKEN_SECRET", "test_secret");
55/// std::env::set_var("JWT_AUDIENCE", "test_jwt_audience");
56///
57/// let app =
58/// test::init_service(App::new().route("/auth", web::get().to(auth)).route("/refresh", web::get().to(refresh_auth))).await;
59///
60/// let (token, _) =
61/// jwt::generate_jwt(1, 1, "test_secret", 3600, "session_uuid", "test_jwt_audience")
62/// .expect("Failed to generate access token");
63///
64/// let req = test::TestRequest::get()
65/// .uri("/auth")
66/// .insert_header(("Authorization", format!("Bearer {}", token)))
67/// .to_request();
68///
69/// let resp = test::call_service(&app, req).await;
70/// assert_eq!(resp.status(), 200);
71///
72/// let ref_req = test::TestRequest::get()
73/// .uri("/refresh")
74/// .insert_header(("Authorization", format!("Bearer {}", token)))
75/// .to_request();
76///
77/// let ref_resp = test::call_service(&app, ref_req).await;
78/// assert_eq!(ref_resp.status(), 200);
79/// }
80///
81/// #[actix_web::test]
82/// async fn test_api_key() {
83/// use actix_web::test;
84///
85/// std::env::set_var("X_API_KEY", "test_api_key");
86///
87/// let app = test::init_service(App::new().route("/apikey", web::get().to(x_api_key))).await;
88///
89/// let req = test::TestRequest::get()
90/// .uri("/apikey")
91/// .insert_header(("X-API-Key", "test_api_key"))
92/// .to_request();
93///
94/// let resp = test::call_service(&app, req).await;
95/// assert_eq!(resp.status(), 200);
96/// }
97/// ```
98///
99/// # Important Considerations
100///
101/// * **Error Handling:** The current implementation uses `unwrap_or("")` on the header value and `expect` for environment variables. For production, more robust error handling should be implemented (e.g., returning `HttpResponse::BadRequest` for malformed headers).
102/// * **Dependency:** This macro depends on the `nextera_utils::jwt` crate, which needs to be included in your `Cargo.toml`.
103/// * **Environment Variables:** Ensure `ACCESS_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `JWT_AUDIENCE` and `X_API_KEY` are set in your environment. Never hardcode secrets in your source code. Consider using a secrets management solution for production.
104/// * **HttpRequest Injection:** The macro automatically injects `actix_web::HttpRequest` as the first argument of the decorated function. Make sure your handler function signature is compatible.
105/// * **Async Functions:** This macro supports `async` functions.
106#[proc_macro_attribute]
107pub fn authentication(_args: TokenStream, input: TokenStream) -> TokenStream {
108 let input_fn = parse_macro_input!(input as ItemFn);
109
110 let fn_name = &input_fn.sig.ident;
111 let fn_async_ness = &input_fn.sig.asyncness;
112 let fn_visibility = &input_fn.vis;
113 let fn_inputs = &input_fn.sig.inputs;
114 let fn_output = &input_fn.sig.output;
115 let fn_body = &input_fn.block;
116
117 // Wrap the function logic with authentication logic
118 let expanded = quote! {
119
120 #fn_visibility #fn_async_ness fn #fn_name(
121 actix_web_req: actix_web::HttpRequest, // Automatically inject HttpRequest
122 #fn_inputs
123 ) #fn_output {
124 use actix_web::HttpResponse;
125
126 // Get response language
127 let default = actix_web::http::header::HeaderValue::from_static("en");
128 let c = actix_web_req.headers().get("Content-Language").unwrap_or(&default).to_str().unwrap_or("en");
129 let invalid_credentials = match c {
130 "zh-CN" => "凭证无效",
131 "th" => "ข้อมูลเข้าสู่ระบบไม่ถูกต้อง",
132 "mm" => "အထောက်အထားများ မှားယွင်းနေပါသည်",
133 _ => "Invalid credentials"
134 };
135
136 let session_expired = match c {
137 "zh-CN" => "会话已过期",
138 "th" => "เซสชั่นหมดอายุแล้ว",
139 "mm" => "စက်ရှင် သက်တမ်းကုန်သွားပါပြီ",
140 _ => "Session Expired"
141 };
142 // Extract and validate the Authorization header
143 if let Some(auth_header) = actix_web_req.headers().get("Authorization") {
144 let token = auth_header.to_str().unwrap_or("").trim();
145
146 // Ensure token starts with "Bearer "
147 let token = token.trim_start_matches("Bearer ").trim();
148
149 // Load environment variables
150 let access_token_secret = std::env::var("ACCESS_TOKEN_SECRET")
151 .expect("Failed to get ACCESS_TOKEN_SECRET from environment");
152 let audience = std::env::var("JWT_AUDIENCE")
153 .expect("Failed to get JWT_AUDIENCE from environment");
154
155 // Validate the token
156 if let Err(e) = nextera_utils::jwt::validate_jwt(token, &access_token_secret, &audience) {
157 // You've already got the error 'e' here, no need to call unwrap_err()
158 return if e.kind() == &jsonwebtoken::errors::ErrorKind::ExpiredSignature {
159 HttpResponse::build(actix_web::http::StatusCode::from_u16(419).unwrap())
160 .json(nextera_utils::models::response_message::ResponseMessage {
161 message: String::from(session_expired)
162 })
163 } else {
164 HttpResponse::Unauthorized().json(nextera_utils::models::response_message::ResponseMessage {
165 message: String::from(invalid_credentials)
166 })
167 };
168 }
169 } else {
170 // Respond with Unauthorized if no Authorization header is present
171 return HttpResponse::Unauthorized().json(nextera_utils::models::response_message::ResponseMessage { message: String::from(invalid_credentials)});
172 }
173
174
175 // Proceed with the original function body
176 #fn_body
177 }
178 };
179
180 TokenStream::from(expanded)
181}
182
183#[proc_macro_attribute]
184pub fn refresh_authentication(_args: TokenStream, input: TokenStream) -> TokenStream {
185 let input_fn = parse_macro_input!(input as ItemFn);
186
187 let fn_name = &input_fn.sig.ident;
188 let fn_async_ness = &input_fn.sig.asyncness;
189 let fn_visibility = &input_fn.vis;
190 let fn_inputs = &input_fn.sig.inputs;
191 let fn_output = &input_fn.sig.output;
192 let fn_body = &input_fn.block;
193
194 // Wrap the function logic with refresh token authentication logic
195 let expanded = quote! {
196
197 #fn_visibility #fn_async_ness fn #fn_name(
198 actix_web_req: actix_web::HttpRequest, // Automatically inject HttpRequest
199 #fn_inputs
200 ) #fn_output {
201 use actix_web::HttpResponse;
202
203 // Get response language
204 let default = actix_web::http::header::HeaderValue::from_static("en");
205 let c = actix_web_req.headers().get("Content-Language").unwrap_or(&default).to_str().unwrap_or("en");
206 let invalid_credentials = match c {
207 "zh-CN" => "凭证无效",
208 "th" => "ข้อมูลเข้าสู่ระบบไม่ถูกต้อง",
209 "mm" => "အထောက်အထားများ မှားယွင်းနေပါသည်",
210 _ => "Invalid credentials"
211 };
212
213 let session_expired = match c {
214 "zh-CN" => "会话已过期",
215 "th" => "เซสชั่นหมดอายุแล้ว",
216 "mm" => "စက်ရှင် သက်တမ်းကုန်သွားပါပြီ",
217 _ => "Session Expired"
218 };
219
220 // Extract and validate the Authorization header
221 if let Some(auth_header) = actix_web_req.headers().get("Authorization") {
222 let token = auth_header.to_str().unwrap_or("").trim();
223
224 // Ensure token starts with "Bearer "
225 let token = token.trim_start_matches("Bearer ").trim();
226
227 // Load environment variables
228 let refresh_token_secret = std::env::var("REFRESH_TOKEN_SECRET")
229 .expect("Failed to get REFRESH_TOKEN_SECRET from environment");
230 let audience = std::env::var("JWT_AUDIENCE")
231 .expect("Failed to get JWT_AUDIENCE from environment");
232
233 // Validate the token
234 if let Err(e) = nextera_utils::jwt::validate_jwt(token, &refresh_token_secret, &audience) {
235 // You've already got the error 'e' here, no need to call unwrap_err()
236 return if e.kind() == &jsonwebtoken::errors::ErrorKind::ExpiredSignature {
237 HttpResponse::build(actix_web::http::StatusCode::from_u16(419).unwrap())
238 .json(nextera_utils::models::response_message::ResponseMessage {
239 message: String::from(session_expired)
240 })
241 } else {
242 HttpResponse::Unauthorized().json(nextera_utils::models::response_message::ResponseMessage {
243 message: String::from(invalid_credentials)
244 })
245 };
246 }
247 } else {
248 // Respond with Unauthorized if no Authorization header is present
249 return HttpResponse::Unauthorized().json(nextera_utils::models::response_message::ResponseMessage { message: String::from(invalid_credentials)});
250 }
251
252 // Proceed with the original function body
253 #fn_body
254 }
255 };
256
257 TokenStream::from(expanded)
258}
259
260#[proc_macro_attribute]
261pub fn x_api_key(_args: TokenStream, input: TokenStream) -> TokenStream {
262 let input_fn = parse_macro_input!(input as ItemFn);
263
264 let fn_name = &input_fn.sig.ident;
265 let fn_async_ness = &input_fn.sig.asyncness;
266 let fn_visibility = &input_fn.vis;
267 let fn_inputs = &input_fn.sig.inputs;
268 let fn_output = &input_fn.sig.output;
269 let fn_body = &input_fn.block;
270
271 // Wrap the function logic with refresh token authentication logic
272 let expanded = quote! {
273
274 #fn_visibility #fn_async_ness fn #fn_name(
275 actix_web_req: actix_web::HttpRequest, // Automatically inject HttpRequest
276 #fn_inputs
277 ) #fn_output {
278 use actix_web::HttpResponse;
279
280 // Get response language
281 let default = actix_web::http::header::HeaderValue::from_static("en");
282 let c = actix_web_req.headers().get("Content-Language").unwrap_or(&default).to_str().unwrap_or("en");
283 let invalid_credentials = match c {
284 "zh-CN" => "凭证无效",
285 "th" => "ข้อมูลเข้าสู่ระบบไม่ถูกต้อง",
286 "mm" => "အထောက်အထားများ မှားယွင်းနေပါသည်",
287 _ => "Invalid API Key"
288 };
289
290 // Extract and validate the Authorization header
291 if let Some(auth_header) = actix_web_req.headers().get("X-API-Key") {
292 let xapikey = auth_header.to_str().unwrap_or("").trim();
293
294 // Load environment variables
295 let env_xapikey = std::env::var("X_API_KEY").expect("Failed to get X_API_KEY from environment");
296
297 // Validate the token
298 if xapikey.ne(&env_xapikey) {
299 return HttpResponse::Unauthorized().json(nextera_utils::models::response_message::ResponseMessage { message: String::from(invalid_credentials)});
300 }
301 } else {
302 // Respond with Unauthorized if no Authorization header is present
303 return HttpResponse::Unauthorized().json(nextera_utils::models::response_message::ResponseMessage { message: String::from(invalid_credentials)});
304 }
305
306 // Proceed with the original function body
307 #fn_body
308 }
309 };
310
311 TokenStream::from(expanded)
312}