#![cfg(not(target_arch = "wasm32"))]
use http::{Extensions, HeaderMap, Method, Uri};
use pocopine_core::{ServerError, ServerResult};
use crate::principal::Principal;
use crate::role::{Permission, Role};
use crate::user::AuthUser;
pub const SESSION_COOKIE: &str = "pocopine_session";
#[derive(Clone, Debug)]
pub struct RequestContext {
method: Method,
uri: Uri,
headers: HeaderMap,
pub user: Principal,
}
impl RequestContext {
pub fn new(method: Method, uri: Uri, headers: HeaderMap) -> Self {
Self {
method,
uri,
headers,
user: Principal::anonymous(),
}
}
pub fn from_parts(
method: Method,
uri: Uri,
headers: HeaderMap,
extensions: Extensions,
) -> Self {
let user = extensions
.get::<Principal>()
.cloned()
.or_else(|| {
extensions
.get::<AuthUser>()
.cloned()
.map(Principal::from_user)
})
.unwrap_or_else(Principal::anonymous);
Self {
method,
uri,
headers,
user,
}
}
pub fn with_user(mut self, user: AuthUser) -> Self {
self.user = Principal::from_user(user);
self
}
pub fn with_principal(mut self, principal: Principal) -> Self {
self.user = principal;
self
}
pub fn method(&self) -> &Method {
&self.method
}
pub fn uri(&self) -> &Uri {
&self.uri
}
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
pub fn header(&self, name: &str) -> Option<&str> {
self.headers.get(name).and_then(|value| value.to_str().ok())
}
pub fn bearer_token(&self) -> Option<&str> {
let value = self.header("authorization")?;
let (scheme, token) = value.split_once(' ')?;
if scheme.eq_ignore_ascii_case("bearer") && !token.trim().is_empty() {
Some(token.trim())
} else {
None
}
}
pub fn cookie(&self, name: &str) -> Option<&str> {
let cookies = self.header("cookie")?;
for part in cookies.split(';') {
if let Some((key, value)) = part.trim().split_once('=') {
if key.trim() == name {
return Some(value.trim());
}
}
}
None
}
pub fn session_id(&self) -> Option<&str> {
self.cookie(SESSION_COOKIE)
}
pub fn require_user(&self) -> ServerResult<&AuthUser> {
self.user.require_user()
}
}
pub fn ensure_login(ctx: &RequestContext) -> ServerResult<()> {
ctx.require_user().map(|_| ())
}
pub fn ensure_role(ctx: &RequestContext, role: &Role) -> ServerResult<()> {
if !ctx.user.is_authenticated() {
return Err(ServerError::unauthorized("login required"));
}
if ctx.user.has_role(role) {
Ok(())
} else {
Err(ServerError::forbidden(format!(
"missing role `{}`",
role.as_str()
)))
}
}
pub fn ensure_permission(ctx: &RequestContext, permission: &Permission) -> ServerResult<()> {
if !ctx.user.is_authenticated() {
return Err(ServerError::unauthorized("login required"));
}
if ctx.user.has_permission(permission) {
Ok(())
} else {
Err(ServerError::forbidden(format!(
"missing permission `{}`",
permission.as_str()
)))
}
}
pub async fn require_login(ctx: RequestContext) -> ServerResult<()> {
ensure_login(&ctx)
}
pub async fn require_admin(ctx: RequestContext) -> ServerResult<()> {
ensure_role(&ctx, &Role::admin())
}
pub async fn require_staff(ctx: RequestContext) -> ServerResult<()> {
ensure_role(&ctx, &Role::staff())
}