Skip to content

Commit 8bf1eb5

Browse files
committed
Updated Api
1 parent 7d5e4bb commit 8bf1eb5

File tree

26 files changed

+645
-460
lines changed

26 files changed

+645
-460
lines changed

crates/codora-framework-identity/src/email.rs renamed to crates/codora-framework-identity/src/cframeworkidentity/email.rs

File renamed without changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use std::sync::Arc;
2+
3+
pub mod email;
4+
pub mod user;
5+
6+
pub struct CFrameworkIdentity {
7+
// extension
8+
inner: Arc<CFrameworkIdentityInner>,
9+
}
10+
11+
impl CFrameworkIdentity {
12+
// maanage cframework security
13+
// worker for background tasks perhaps
14+
}
15+
16+
pub struct CFrameworkIdentityInner {}
17+
18+
#[cfg(test)]
19+
mod tests {
20+
21+
#[tokio::test]
22+
async fn test_cf_identiy() -> anyhow::Result<()> {
23+
Ok(())
24+
}
25+
}

crates/codora-framework-identity/src/user/mod.rs renamed to crates/codora-framework-identity/src/cframeworkidentity/user/mod.rs

File renamed without changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod value;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod email;
2+
mod pii;
3+
4+
pub use email::*;
5+
pub use pii::*;

crates/codora-framework-identity/src/lib.rs

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,53 +19,6 @@ extern crate tracing;
1919
#[macro_use]
2020
extern crate derive_new;
2121

22-
pub mod domain {
23-
mod value {
24-
mod email;
25-
mod pii;
26-
}
27-
28-
pub use value::*;
29-
}
30-
pub mod email;
31-
pub mod user;
32-
33-
// use anyhow::anyhow;
34-
// use axum::{
35-
// Router,
36-
// body::Body,
37-
// http::Request,
38-
// response::{IntoResponse, Response},
39-
// routing::get,
40-
// };
41-
// use codora_framework::{Context, Startup, StartupError};
42-
// use std::{convert::Infallible, pin::Pin, sync::Arc};
43-
// use tokio::{net::TcpListener, runtime::Runtime};
44-
// use tower::Service;
45-
46-
// struct WebServiceFuture {}
47-
48-
// impl Future for WebServiceFuture {
49-
// type Output = Result<Response, Infallible>;
50-
51-
// fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
52-
// todo!()
53-
// }
54-
// }
55-
56-
// // implement tower_service::Service for WebService this wouled allow us to inject Webservice into AxumService built by Router
57-
// impl tower_service::Service<Request<Body>> for WebService {
58-
// type Response = Response;
59-
// type Error = Infallible;
60-
61-
// // Fix this later but that's the idea;
62-
// type Future = WebServiceFuture;
63-
64-
// fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
65-
// std::task::Poll::Ready(Ok(()))
66-
// }
67-
68-
// fn call(&mut self, req: Request<Body>) -> Self::Future {
69-
// WebServiceFuture {}
70-
// }
71-
// }
22+
pub mod cframeworkidentity;
23+
// API
24+
pub mod domain;

crates/codora-framework-security/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ categories = ["web-programming", "authentication"]
1111
[features]
1212
default = ["axum"]
1313
axum = ["dep:axum", "dep:tower-service"]
14+
# cookie = []
1415

1516
[dependencies]
1617
# third party crates
@@ -32,3 +33,4 @@ tokio = { workspace = true, features = ["rt-multi-thread"] }
3233
anyhow = { workspace = true }
3334
tower = { workspace = true, features = ["full"] }
3435
tracing-subscriber = { workspace = true, features = ["env-filter"] }
36+
serde = { workspace = true, features = ["derive"] }
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use crate::codoraframeworksecurity::{
2+
CFrameworkSecurity,
3+
context::{Context, Extension},
4+
};
5+
use axum::{extract::Request, response::Response};
6+
use pin_project_lite::pin_project;
7+
use std::{pin::Pin, task::Poll};
8+
use tower_service::Service;
9+
10+
#[derive(new, Clone)]
11+
pub struct CFrameworkService<S> {
12+
inner: S,
13+
extension: Extension,
14+
}
15+
16+
pin_project! {
17+
#[derive(Debug, new)]
18+
pub struct ServiceResponseFuture<F> {
19+
#[pin]
20+
future: F
21+
}
22+
}
23+
24+
impl<S> Service<Request> for CFrameworkService<S>
25+
where
26+
S: Service<Request, Response = Response> + Send + 'static,
27+
S::Future: Send + 'static,
28+
{
29+
type Response = S::Response;
30+
type Error = S::Error;
31+
type Future = ServiceResponseFuture<S::Future>;
32+
33+
fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
34+
self.inner.poll_ready(cx)
35+
}
36+
37+
fn call(&mut self, mut request: Request) -> Self::Future {
38+
// Insert a context with empty request
39+
let prev = request
40+
.extensions_mut()
41+
.insert(Context::new((), self.extension.clone()));
42+
debug_assert!(prev.is_none(), "Context already present in request extensions");
43+
ServiceResponseFuture::new(self.inner.call(request))
44+
}
45+
}
46+
47+
impl<S> tower_layer::Layer<S> for CFrameworkSecurity {
48+
type Service = CFrameworkService<S>;
49+
50+
fn layer(&self, inner: S) -> Self::Service {
51+
CFrameworkService::new(inner, self.extension().clone())
52+
}
53+
}
54+
55+
impl<F> Future for ServiceResponseFuture<F>
56+
where
57+
F: Future,
58+
{
59+
type Output = F::Output;
60+
61+
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
62+
let this = self.project();
63+
64+
// Forward the inner future's poll result directly.
65+
match this.future.poll(cx) {
66+
Poll::Ready(output) => Poll::Ready(output),
67+
Poll::Pending => Poll::Pending,
68+
}
69+
}
70+
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
use crate::codoraframeworksecurity::context::Context;
2+
use axum::{
3+
extract::{FromRequest, FromRequestParts, Request},
4+
response::{IntoResponse, Response},
5+
};
6+
use http::request::Parts;
7+
8+
#[derive(Debug)]
9+
pub struct MissingContext;
10+
11+
#[derive(Debug)]
12+
pub enum ContextRejection<T> {
13+
MissingContext(MissingContext),
14+
InnerRejection(T),
15+
}
16+
17+
impl IntoResponse for MissingContext {
18+
fn into_response(self) -> Response {
19+
use http::StatusCode;
20+
21+
StatusCode::INTERNAL_SERVER_ERROR.into_response()
22+
}
23+
}
24+
25+
impl<T> IntoResponse for ContextRejection<T>
26+
where
27+
T: IntoResponse,
28+
{
29+
fn into_response(self) -> Response {
30+
match self {
31+
ContextRejection::MissingContext(missing_context) => missing_context.into_response(),
32+
ContextRejection::InnerRejection(t) => t.into_response(),
33+
}
34+
}
35+
}
36+
37+
#[derive(Debug, new)]
38+
pub struct TypedRequest<T> {
39+
pub parts: Parts,
40+
pub value: T,
41+
}
42+
43+
impl<T> TypedRequest<T> {
44+
pub fn unpack(self) -> (http::request::Parts, T) {
45+
(self.parts, self.value)
46+
}
47+
48+
#[inline]
49+
pub fn parts(&self) -> &http::request::Parts {
50+
&self.parts
51+
}
52+
53+
#[inline]
54+
pub fn parts_mut(&mut self) -> &http::request::Parts {
55+
&mut self.parts
56+
}
57+
58+
#[inline]
59+
pub fn value(&self) -> &T {
60+
&self.value
61+
}
62+
63+
#[inline]
64+
pub fn value_mut(&mut self) -> &mut T {
65+
&mut self.value
66+
}
67+
}
68+
69+
impl<S> FromRequestParts<S> for Context<Parts>
70+
where
71+
S: Send + Sync,
72+
{
73+
type Rejection = MissingContext;
74+
75+
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
76+
let context_with_empty_request = parts
77+
.extensions
78+
.get::<Context<()>>()
79+
.cloned()
80+
.ok_or(MissingContext)?;
81+
82+
Ok(context_with_empty_request.map_request(|_| parts.clone()))
83+
}
84+
}
85+
86+
impl<T, S> FromRequest<S> for Context<TypedRequest<T>>
87+
where
88+
S: Send + Sync,
89+
T: FromRequest<S>,
90+
{
91+
type Rejection = ContextRejection<T::Rejection>;
92+
93+
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
94+
let (mut parts, body) = req.into_parts();
95+
let context = Context::from_request_parts(&mut parts, state)
96+
.await
97+
.map_err(ContextRejection::MissingContext)?;
98+
99+
let inner_value = T::from_request(Request::from_parts(parts.clone(), body), state)
100+
.await
101+
.map_err(ContextRejection::InnerRejection)?;
102+
103+
Ok(context.map_request(|_| TypedRequest::new(parts, inner_value)))
104+
}
105+
}
106+
107+
#[cfg(test)]
108+
mod axum_tests {
109+
use crate::{adapter::TypedRequest, prelude::*};
110+
use anyhow::Result;
111+
use axum::{
112+
Json, Router,
113+
body::Body,
114+
extract::Request,
115+
response::{IntoResponse, Response},
116+
routing::{get, post},
117+
};
118+
use http::{StatusCode, request::Parts};
119+
use tower::ServiceExt;
120+
121+
#[allow(dead_code)]
122+
#[derive(Debug, serde::Deserialize)]
123+
pub struct JwtBody {
124+
token: String,
125+
}
126+
127+
#[tokio::test]
128+
async fn test_context_if_it_compiles_it_works() -> Result<()> {
129+
let app = Router::new()
130+
.route("/signup", get(|_context: Context<Parts>| async { () }))
131+
.route("/signin", get(|_context: Context<TypedRequest<Json<JwtBody>>>| async { () }))
132+
.layer(CFrameworkSecurity::default());
133+
134+
let req = Request::builder()
135+
.uri("/")
136+
.body(Body::empty())?;
137+
138+
let res = app.oneshot(req).await?;
139+
assert_eq!(res.status(), StatusCode::OK);
140+
Ok(())
141+
}
142+
143+
#[tokio::test]
144+
async fn test_cookie_sign_in_with_axum_parts() -> Result<()> {
145+
let cf = CFrameworkSecurity::default().add_cookie(|option| {
146+
// setup option here
147+
148+
option
149+
});
150+
151+
async fn authentication_handler(ctx: Context<Parts>) -> Result<Response, Response> {
152+
// cookie response
153+
let _res = ctx
154+
.sign_in_with_cookie(CookieState {}, CookiePayload {})
155+
.await
156+
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response())?;
157+
158+
Ok(String::from("Yet to be implemented").into_response())
159+
}
160+
161+
let app = Router::new()
162+
.route("/", post(authentication_handler))
163+
.layer(cf);
164+
165+
let req = Request::builder()
166+
.uri("/")
167+
.body(Body::empty())?;
168+
169+
let res = app.oneshot(req).await?;
170+
assert_eq!(res.status(), StatusCode::OK);
171+
172+
Ok(())
173+
}
174+
175+
#[tokio::test]
176+
async fn test_cookie_sign_in_with_axum_typed_request() -> Result<()> {
177+
let cf = CFrameworkSecurity::default().add_cookie(|option| {
178+
// setup option here
179+
180+
option
181+
});
182+
183+
async fn authentication_handler(ctx: Context<TypedRequest<Json<JwtBody>>>) -> Result<Response, Response> {
184+
// cookie response
185+
let _res = ctx
186+
.sign_in_with_cookie(CookieState {}, CookiePayload {})
187+
.await
188+
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response())?;
189+
190+
let (request, _) = ctx.unpack();
191+
192+
Ok(String::from("Yet to be implemented").into_response())
193+
}
194+
195+
let app = Router::new()
196+
.route("/", post(authentication_handler))
197+
.layer(cf);
198+
199+
let req = Request::builder()
200+
.uri("/")
201+
.body(Body::empty())?;
202+
203+
let res = app.oneshot(req).await?;
204+
assert_eq!(res.status(), StatusCode::OK);
205+
206+
Ok(())
207+
}
208+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub(super) mod codoraframeworksecurity;
2+
pub(super) mod context;
3+
pub mod request;

0 commit comments

Comments
 (0)