use actix_web::cookie::{Cookie, SameSite};
use actix_web::{HttpRequest, HttpResponse, HttpServer, get, post, web};
use r_token::RTokenRedisManager;
fn extract_token(req: &HttpRequest) -> Result<String, actix_web::Error> {
let header_token = req
.headers()
.get("Authorization")
.and_then(|h| h.to_str().ok())
.map(|token| token.strip_prefix("Bearer ").unwrap_or(token).to_string());
if let Some(token) = header_token {
return Ok(token);
}
if let Some(cookie) = req.cookie(r_token::TOKEN_COOKIE_NAME) {
return Ok(cookie.value().to_string());
}
Err(actix_web::error::ErrorUnauthorized("Unauthorized"))
}
#[post("/login")]
async fn do_login(
manager: web::Data<RTokenRedisManager>,
body: String,
) -> Result<HttpResponse, actix_web::Error> {
let user_id = body.trim();
if user_id.is_empty() {
return Err(actix_web::error::ErrorBadRequest("Empty user id"));
}
let token = manager
.login(user_id, 3600)
.await
.map_err(|_| actix_web::error::ErrorInternalServerError("Redis error"))?;
Ok(HttpResponse::Ok()
.cookie(
Cookie::build(r_token::TOKEN_COOKIE_NAME, token.clone())
.path("/")
.http_only(true)
.secure(true)
.same_site(SameSite::Lax)
.finish(),
)
.body(token))
}
#[get("/info")]
async fn do_info(
manager: web::Data<RTokenRedisManager>,
req: HttpRequest,
) -> Result<HttpResponse, actix_web::Error> {
let token = extract_token(&req)?;
#[cfg(feature = "rbac")]
let user_info = manager
.validate_with_roles(&token)
.await
.map_err(|_| actix_web::error::ErrorInternalServerError("Redis error"))?;
#[cfg(not(feature = "rbac"))]
let user_info = manager
.validate(&token)
.await
.map_err(|_| actix_web::error::ErrorInternalServerError("Redis error"))?;
#[cfg(feature = "rbac")]
match user_info {
Some((user_id, roles)) => {
Ok(HttpResponse::Ok().body(format!("info: user_id={}, roles={:?}", user_id, roles)))
}
None => Err(actix_web::error::ErrorUnauthorized("Invalid token")),
}
#[cfg(not(feature = "rbac"))]
match user_info {
Some(user_id) => Ok(HttpResponse::Ok().body(format!("info: {}", user_id))),
None => Err(actix_web::error::ErrorUnauthorized("Invalid token")),
}
}
#[post("/logout")]
async fn do_logout(
manager: web::Data<RTokenRedisManager>,
req: HttpRequest,
) -> Result<HttpResponse, actix_web::Error> {
let token = extract_token(&req)?;
manager
.logout(&token)
.await
.map_err(|_| actix_web::error::ErrorInternalServerError("Redis error"))?;
Ok(HttpResponse::Ok().body("logout success"))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let redis_url = std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1/".to_string());
let prefix = std::env::var("R_TOKEN_PREFIX").unwrap_or_else(|_| "r_token:token:".to_string());
let manager = RTokenRedisManager::connect(&redis_url, prefix)
.await
.map_err(|_| std::io::Error::other("Redis connect failed"))?;
println!("r-token (redis) server started at http://127.0.0.1:8081");
HttpServer::new(move || {
actix_web::App::new()
.app_data(web::Data::new(manager.clone()))
.service(do_login)
.service(do_info)
.service(do_logout)
})
.bind("127.0.0.1:8081")?
.run()
.await
}