authentication

This commit is contained in:
Aiden McClelland
2021-07-29 12:29:01 -06:00
committed by Aiden McClelland
parent 01ed3318cb
commit 2dc896ef04
7 changed files with 277 additions and 81 deletions

View File

@@ -1,22 +1,25 @@
use anyhow::anyhow;
use basic_cookies::Cookie;
use chrono::Utc;
use digest::Digest;
use futures::future::BoxFuture;
use futures::FutureExt;
use rpc_toolkit::command_helpers::prelude::RequestParts;
use rpc_toolkit::hyper::header::COOKIE;
use rpc_toolkit::hyper::http::Error as HttpError;
use rpc_toolkit::hyper::{Body, Request, Response, StatusCode};
use rpc_toolkit::hyper::{Body, Request, Response};
use rpc_toolkit::rpc_server_helpers::{
noop2, noop3, DynMiddleware, DynMiddlewareStage2, DynMiddlewareStage3,
noop3, noop4, DynMiddleware, DynMiddlewareStage2, DynMiddlewareStage3,
};
use rpc_toolkit::yajrc::RpcMethod;
use rpc_toolkit::Metadata;
use serde::Deserialize;
use sha2::Sha256;
use crate::context::RpcContext;
use crate::{Error, ResultExt};
async fn is_authed(ctx: &RpcContext, req: &Request<Body>) -> Result<bool, Error> {
if let Some(cookie_header) = req.headers().get(COOKIE) {
pub fn get_id(req: &RequestParts) -> Result<String, Error> {
if let Some(cookie_header) = req.headers.get(COOKIE) {
let cookies = Cookie::parse(
cookie_header
.to_str()
@@ -24,80 +27,69 @@ async fn is_authed(ctx: &RpcContext, req: &Request<Body>) -> Result<bool, Error>
)
.with_kind(crate::ErrorKind::Authorization)?;
if let Some(session) = cookies.iter().find(|c| c.get_name() == "session") {
let id = session.get_value();
let exp = sqlx::query!("SELECT expires_at FROM session WHERE id = ?", id)
.fetch_one(&mut ctx.secret_store.acquire().await?)
.await?;
if exp.expires_at < Utc::now().naive_utc() {
return Ok(true);
}
return Ok(hash_token(session.get_value()));
}
}
Ok(false)
Err(Error::new(
anyhow!("UNAUTHORIZED"),
crate::ErrorKind::Authorization,
))
}
pub async fn auth<Params: for<'de> Deserialize<'de> + 'static, M: Metadata>(
ctx: RpcContext,
) -> DynMiddleware<Params, M> {
pub fn hash_token(token: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(token.as_bytes());
base32::encode(
base32::Alphabet::RFC4648 { padding: false },
hasher.finalize().as_slice(),
)
.to_lowercase()
}
async fn is_authed(ctx: &RpcContext, req: &RequestParts) -> Result<(), Error> {
let id = get_id(req)?;
let exp = sqlx::query!("SELECT logged_out FROM session WHERE id = ?", id)
.fetch_one(&mut ctx.secret_store.acquire().await?)
.await?;
match exp.logged_out {
Some(exp) if exp >= Utc::now().naive_utc() => Err(Error::new(
anyhow!("UNAUTHORIZED"),
crate::ErrorKind::Authorization,
)),
_ => Ok(()),
}
}
pub async fn auth<M: Metadata>(ctx: RpcContext) -> DynMiddleware<M> {
Box::new(
|req: &mut Request<Body>,
metadata: M|
-> BoxFuture<
Result<Result<DynMiddlewareStage2<Params>, Response<Body>>, HttpError>,
> {
-> BoxFuture<Result<Result<DynMiddlewareStage2, Response<Body>>, HttpError>> {
async move {
match is_authed(&ctx, req).await {
Ok(true) => Ok(Ok(noop2())),
Ok(false) => Ok(Ok({
let mut fake_req = Request::new(Body::empty());
*fake_req.headers_mut() = req.headers().clone();
let m2: DynMiddlewareStage2<Params> =
Box::new(move |rpc_req| {
let method = rpc_req.method.as_str();
let res: Result<
Result<DynMiddlewareStage3, Response<Body>>,
HttpError,
> = if metadata.get(method, "login").unwrap_or(false) {
todo!("set cookie on success")
} else if !metadata.get(method, "authenticated").unwrap_or(true) {
Ok(Ok(noop3()))
} else {
rpc_toolkit::rpc_server_helpers::to_response(
&fake_req,
Ok((
rpc_req.id.clone(),
Err(Error::new(
anyhow!("UNAUTHORIZED"),
crate::ErrorKind::Authorization,
)
.into()),
)),
|_| StatusCode::OK,
)
.map(|a| Err(a))
};
async { res }.boxed()
});
m2
})),
Err(e) => Ok(Ok({
let mut fake_req = Request::new(Body::empty());
*fake_req.headers_mut() = req.headers().clone();
let m2: DynMiddlewareStage2<Params> = Box::new(move |rpc_req| {
let res: Result<
Result<DynMiddlewareStage3, Response<Body>>,
HttpError,
> = rpc_toolkit::rpc_server_helpers::to_response(
&fake_req,
Ok((rpc_req.id.clone(), Err(e.into()))),
|_| StatusCode::OK,
)
.map(|a| Err(a));
async { res }.boxed()
});
m2
})),
}
let mut header_stub = Request::new(Body::empty());
*header_stub.headers_mut() = req.headers().clone();
let m2: DynMiddlewareStage2 = Box::new(move |req, rpc_req| {
async move {
if metadata
.get(rpc_req.method.as_str(), "authenticated")
.unwrap_or(true)
{
if let Err(e) = is_authed(&ctx, req).await {
let m3: DynMiddlewareStage3 = Box::new(|_, rpc_res| {
async move {
*rpc_res = Err(e.into());
Ok(Ok(noop4()))
}
.boxed()
});
return Ok(Ok(m3));
}
}
Ok(Ok(noop3()))
}
.boxed()
});
Ok(Ok(m2))
}
.boxed()
},

View File

@@ -4,11 +4,12 @@ use rpc_toolkit::hyper::{Body, Method, Request, Response};
use rpc_toolkit::rpc_server_helpers::{
DynMiddlewareStage2, DynMiddlewareStage3, DynMiddlewareStage4,
};
use serde::Deserialize;
use rpc_toolkit::Metadata;
pub async fn cors<Params: for<'de> Deserialize<'de> + 'static, Metadata>(
pub async fn cors<M: Metadata>(
req: &mut Request<Body>,
) -> Result<Result<DynMiddlewareStage2<Params>, Response<Body>>, HttpError> {
_metadata: M,
) -> Result<Result<DynMiddlewareStage2, Response<Body>>, HttpError> {
if req.method() == Method::OPTIONS {
Ok(Err(Response::builder()
.header(
@@ -24,9 +25,9 @@ pub async fn cors<Params: for<'de> Deserialize<'de> + 'static, Metadata>(
.header("Access-Control-Allow-Credentials", "true")
.body(Body::empty())?))
} else {
Ok(Ok(Box::new(|_| {
Ok(Ok(Box::new(|_, _| {
async move {
let res: DynMiddlewareStage3 = Box::new(|_| {
let res: DynMiddlewareStage3 = Box::new(|_, _| {
async move {
let res: DynMiddlewareStage4 = Box::new(|res| {
async move {