11use crate :: axum:: AxumState ;
22pub ( crate ) use crate :: panel_user:: Moderator ;
3- use crate :: panel_user:: PanelUserService ;
3+ use crate :: panel_user:: { PanelUser , PanelUserService } ;
44use anyhow:: Context ;
5- use axum:: extract:: FromRequestParts ;
5+ use axum:: extract:: { FromRequestParts , Request , State } ;
66use axum:: http:: request:: Parts ;
77use axum:: http:: { HeaderMap , HeaderValue , StatusCode } ;
8+ use axum:: middleware:: Next ;
9+ use axum:: response:: IntoResponse ;
10+ use axum:: response:: Result as AxResult ;
811use reqwest:: Method ;
912use serde:: Deserialize ;
1013use std:: time:: Instant ;
@@ -18,6 +21,66 @@ struct ValidationReturn {
1821 user_id : String
1922}
2023
24+ const FORBIDDEN : fn ( ) -> ( StatusCode , String ) = || ( StatusCode :: FORBIDDEN , "user lacks required permissions" . to_owned ( ) ) ;
25+
26+ pub async fn auth_user ( State ( state) : State < AxumState > , mut request : Request , next : Next ) -> AxResult < impl IntoResponse > {
27+ let ( mut request, user) = authenticate_user ( state, request) . await ?;
28+
29+ request. extensions_mut ( ) . insert ( user) ;
30+ Ok ( next. run ( request) . await )
31+ }
32+
33+ pub async fn auth_mod ( State ( state) : State < AxumState > , request : Request , next : Next ) -> AxResult < impl IntoResponse > {
34+ let ( mut request, user) = match request
35+ . extensions ( )
36+ . get :: < PanelUser > ( )
37+ . cloned ( ) {
38+ Some ( user) => ( request, user) ,
39+ None => authenticate_user ( state, request) . await ?,
40+ } ;
41+
42+ let moderator = Moderator :: new ( user) . ok_or ( FORBIDDEN ( ) ) ?;
43+ request. extensions_mut ( ) . insert ( moderator) ;
44+ Ok ( next. run ( request) . await )
45+ }
46+
47+ async fn authenticate_user ( state : AxumState , request : Request ) -> AxResult < ( Request , PanelUser ) > {
48+ let access_token = get_header ( request. headers ( ) , "token" ) ?;
49+ let user_agent = get_header ( request. headers ( ) , "User-Agent" ) ?;
50+
51+ //we would still want to implement the authentication bypass, although handling those anonymous users for extractors would be a challenge
52+ match state. session_service . get_by_access_token ( & access_token) {
53+ Some ( session) => {
54+ if session. user_agent != user_agent {
55+ Err ( ( StatusCode :: UNAUTHORIZED , "Reauthenticate with access token" . to_string ( ) ) ) ?
56+ } else if session. last_refreshed_at + state. session_service . session_timeout ( ) < Instant :: now ( ) {
57+ state. session_service . delete_by_access_token ( & access_token) ;
58+ Err ( ( StatusCode :: UNAUTHORIZED , "Reauthenticate with access token" . to_string ( ) ) ) ?
59+ } else {
60+ _ = state. session_service . refresh_session ( access_token) ;
61+ Ok ( ( request, session. panel_user ) )
62+ }
63+ }
64+ None => {
65+ let validated = validate_token ( & access_token) . await
66+ . map_err ( |_err| {
67+ // log errors
68+ ( StatusCode :: INTERNAL_SERVER_ERROR , "Internal server error. Retry authentication" . to_string ( ) )
69+ } ) ?
70+ . ok_or ( ( StatusCode :: UNAUTHORIZED , "Invalid access token, Reauthenticate" . to_string ( ) ) ) ?;
71+ let user = PanelUserService :: find_by_id ( & state. prod_db , validated. user_id )
72+ . await
73+ . map_err ( |_err| {
74+ // log error
75+ ( StatusCode :: INTERNAL_SERVER_ERROR , "Internal server error. Retry authentication" . to_string ( ) )
76+ } ) ?
77+ . ok_or ( FORBIDDEN ( ) ) ?;
78+ state. session_service . create_session ( user. clone ( ) , access_token, user_agent) ;
79+ Ok ( ( request, user) )
80+ }
81+ }
82+ }
83+
2184/// Validate an accessToken with Twitch. Ok(None) represents a successful validation, but the token being invalid
2285async fn validate_token ( access_token : & str ) -> anyhow:: Result < Option < ValidationReturn > > {
2386 // prob move client into app state
@@ -41,57 +104,24 @@ async fn validate_token(access_token: &str) -> anyhow::Result<Option<ValidationR
41104 Ok ( Some ( validation_return) )
42105}
43106
107+ fn get_header ( headers : & HeaderMap < HeaderValue > , key : & str ) -> Result < String , ( StatusCode , String ) > {
108+ headers
109+ . get ( key) . ok_or ( ( StatusCode :: UNAUTHORIZED , format ! ( "Missing '{}' authentication header" , key) ) )
110+ . map ( |value| value. to_str ( ) . map_err ( |_| ( StatusCode :: BAD_REQUEST , "malformed authentication header token, non ascii string" . to_string ( ) ) ) )
111+ . and_then ( |value| value)
112+ . map ( |s| s. to_string ( ) )
113+ }
114+
44115impl FromRequestParts < AxumState > for Moderator {
45116 type Rejection = ( StatusCode , String ) ;
46117
47- //this is correct for routes that would definitely need to know which user this was, but we would still want to implement the authentication bypass
48- fn from_request_parts ( parts : & mut Parts , state : & AxumState ) -> impl Future < Output =Result < Self , Self :: Rejection > > + Send {
49-
50- fn get_header ( headers : & HeaderMap < HeaderValue > , key : & str ) -> Result < String , ( StatusCode , String ) > {
51- headers
52- . get ( key) . ok_or ( ( StatusCode :: UNAUTHORIZED , format ! ( "Missing '{}' authentication header" , key) ) )
53- . map ( |value| value. to_str ( ) . map_err ( |_| ( StatusCode :: BAD_REQUEST , "malformed authentication header token, non ascii string" . to_string ( ) ) ) )
54- . and_then ( |value| value)
55- . map ( |s| s. to_string ( ) )
56- }
57-
58- async {
59- let access_token = get_header ( & parts. headers , "token" ) ?;
60- let user_agent = get_header ( & parts. headers , "User-Agent" ) ?;
61-
62- match state. session_service . get_by_access_token ( & access_token) {
63- Some ( session) => {
64- if session. user_agent != user_agent {
65- Err ( ( StatusCode :: UNAUTHORIZED , "Reauthenticate with access token" . to_string ( ) ) )
66- } else if session. last_refreshed_at + state. session_service . session_timeout ( ) < Instant :: now ( ) {
67- state. session_service . delete_by_access_token ( & access_token) ;
68- Err ( ( StatusCode :: UNAUTHORIZED , "Reauthenticate with access token" . to_string ( ) ) )
69- } else {
70- let moderator = Moderator :: new ( session. panel_user )
71- . ok_or ( ( StatusCode :: FORBIDDEN , "user lacks required permissions" . to_string ( ) ) ) ?;
72- _ = state. session_service . refresh_session ( access_token) ;
73- Ok ( moderator)
74- }
75- }
76- None => {
77- let validated = validate_token ( & access_token) . await
78- . map_err ( |_err| {
79- // log errors
80- ( StatusCode :: INTERNAL_SERVER_ERROR , "Internal server error. Retry authentication" . to_string ( ) )
81- } ) ?
82- . ok_or ( ( StatusCode :: UNAUTHORIZED , "Invalid access token, Reauthenticate" . to_string ( ) ) ) ?;
83- let user = PanelUserService :: find_by_id ( & state. prod_db , validated. user_id )
84- . await
85- . map_err ( |_err| {
86- // log error
87- ( StatusCode :: INTERNAL_SERVER_ERROR , "Internal server error. Retry authentication" . to_string ( ) )
88- } ) ?
89- . ok_or ( ( StatusCode :: FORBIDDEN , "user lacks required permissions" . to_string ( ) ) ) ?;
90- let moderator = Moderator :: new ( user. clone ( ) )
91- . ok_or ( ( StatusCode :: FORBIDDEN , "user lacks required permissions" . to_string ( ) ) ) ?;
92- state. session_service . create_session ( user, access_token, user_agent) ;
93- Ok ( moderator)
94- }
118+ async fn from_request_parts ( parts : & mut Parts , _state : & AxumState ) -> Result < Self , Self :: Rejection > {
119+ match parts. extensions . get :: < Moderator > ( ) {
120+ Some ( extension) => Ok ( extension. clone ( ) ) ,
121+ None => match parts. extensions . get :: < PanelUser > ( ) {
122+ None => Err ( FORBIDDEN ( ) ) ,
123+ Some ( u) => Moderator :: new ( u. clone ( ) )
124+ . ok_or ( FORBIDDEN ( ) ) ,
95125 }
96126 }
97127 }
0 commit comments