1
1
use crate :: { ctx:: Ctx , error:: Error , error:: Result , ApiResult , Db } ;
2
2
use axum:: { extract:: State , http:: Request , middleware:: Next , response:: Response } ;
3
- use hmac:: Hmac ;
4
- use jwt:: VerifyWithKey ;
5
- use sha2:: Sha256 ;
6
- use std:: collections:: BTreeMap ;
3
+ use jsonwebtoken:: { decode, DecodingKey , EncodingKey , Validation } ;
4
+ use serde:: { Deserialize , Serialize } ;
7
5
use tower_cookies:: { Cookie , Cookies } ;
8
6
use uuid:: Uuid ;
9
7
10
8
#[ derive( Clone ) ]
11
9
pub struct CtxState {
12
10
// NOTE: with DB, because a real login would check the DB
13
11
pub _db : Db ,
14
- pub key : Hmac < Sha256 > ,
12
+ pub key_enc : EncodingKey ,
13
+ pub key_dec : DecodingKey ,
15
14
}
16
15
17
16
pub const JWT_KEY : & str = "jwt" ;
18
- pub const JWT_AUTH : & str = "auth" ;
17
+ #[ derive( Debug , Serialize , Deserialize ) ]
18
+ pub struct Claims {
19
+ pub exp : usize ,
20
+ pub auth : String ,
21
+ }
19
22
20
23
pub async fn mw_require_auth < B > ( ctx : Ctx , req : Request < B > , next : Next < B > ) -> ApiResult < Response > {
21
24
println ! ( "->> {:<12} - mw_require_auth - {ctx:?}" , "MIDDLEWARE" ) ;
@@ -24,15 +27,15 @@ pub async fn mw_require_auth<B>(ctx: Ctx, req: Request<B>, next: Next<B>) -> Api
24
27
}
25
28
26
29
pub async fn mw_ctx_constructor < B > (
27
- State ( CtxState { _db, key } ) : State < CtxState > ,
30
+ State ( CtxState { _db, key_dec , .. } ) : State < CtxState > ,
28
31
cookies : Cookies ,
29
32
mut req : Request < B > ,
30
33
next : Next < B > ,
31
34
) -> Response {
32
35
println ! ( "->> {:<12} - mw_ctx_constructor" , "MIDDLEWARE" ) ;
33
36
34
37
let uuid = Uuid :: new_v4 ( ) ;
35
- let result_user_id: Result < String > = extract_token ( key , & cookies) . map_err ( |err| {
38
+ let result_user_id: Result < String > = extract_token ( key_dec , & cookies) . map_err ( |err| {
36
39
// Remove an invalid cookie
37
40
if let Error :: AuthFailJwtInvalid { .. } = err {
38
41
cookies. remove ( Cookie :: named ( JWT_KEY ) )
@@ -48,14 +51,12 @@ pub async fn mw_ctx_constructor<B>(
48
51
next. run ( req) . await
49
52
}
50
53
51
- fn verify_token ( key : Hmac < Sha256 > , token : & str ) -> Result < String > {
52
- let claims: BTreeMap < String , String > = token. verify_with_key ( & key) ?;
53
- claims
54
- . get ( JWT_AUTH )
55
- . ok_or ( Error :: AuthFailJwtWithoutAuth )
56
- . map ( String :: from)
54
+ fn verify_token ( key : DecodingKey , token : & str ) -> Result < String > {
55
+ Ok ( decode :: < Claims > ( token, & key, & Validation :: default ( ) ) ?
56
+ . claims
57
+ . auth )
57
58
}
58
- fn extract_token ( key : Hmac < Sha256 > , cookies : & Cookies ) -> Result < String > {
59
+ fn extract_token ( key : DecodingKey , cookies : & Cookies ) -> Result < String > {
59
60
cookies
60
61
. get ( JWT_KEY )
61
62
. ok_or ( Error :: AuthFailNoJwtCookie )
@@ -64,31 +65,78 @@ fn extract_token(key: Hmac<Sha256>, cookies: &Cookies) -> Result<String> {
64
65
65
66
#[ cfg( test) ]
66
67
mod tests {
67
- use crate :: mw_ctx:: JWT_AUTH ;
68
- use hmac :: { Hmac , Mac } ;
69
- use jwt :: SignWithKey ;
70
- use sha2 :: Sha256 ;
71
- use std :: collections :: BTreeMap ;
68
+ use crate :: mw_ctx:: Claims ;
69
+ use chrono :: { Duration , Utc } ;
70
+ use jsonwebtoken :: {
71
+ decode , encode , errors :: ErrorKind , DecodingKey , EncodingKey , Header , Validation ,
72
+ } ;
72
73
73
74
const SECRET : & [ u8 ] = b"some-secret" ;
74
75
const SOMEONE : & str = "someone" ;
75
- const TOKEN : & str =
76
76
// cspell:disable-next-line
77
- "eyJhbGciOiJIUzI1NiJ9.eyJhdXRoIjoic29tZW9uZSJ9.1g78DkCARXRPLRlRbzv_nKZZuykVr5_nwPaifpVTvvM" ;
77
+ const TOKEN_EXPIRED : & str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEsImF1dGgiOiJzb21lb25lIn0.XXHVHu2IsUPA175aQ-noWbQK4Wu-2prk3qTXjwaWBvE" ;
78
+
79
+ #[ test]
80
+ fn jwt_sign_expired ( ) {
81
+ let my_claims = Claims {
82
+ exp : 1 ,
83
+ auth : SOMEONE . to_string ( ) ,
84
+ } ;
85
+ let token_str = encode (
86
+ & Header :: default ( ) ,
87
+ & my_claims,
88
+ & EncodingKey :: from_secret ( SECRET ) ,
89
+ )
90
+ . unwrap ( ) ;
91
+ assert_eq ! ( token_str, TOKEN_EXPIRED ) ;
92
+ }
93
+
94
+ #[ test]
95
+ fn jwt_verify_expired_ignore ( ) {
96
+ let mut validation = Validation :: default ( ) ;
97
+ validation. validate_exp = false ;
98
+ let token = decode :: < Claims > (
99
+ TOKEN_EXPIRED ,
100
+ & DecodingKey :: from_secret ( SECRET ) ,
101
+ & validation,
102
+ )
103
+ . unwrap ( ) ;
104
+ assert_eq ! ( token. claims. auth, SOMEONE ) ;
105
+ }
78
106
79
107
#[ test]
80
- fn jwt_sign ( ) {
81
- let key: Hmac < Sha256 > = Hmac :: new_from_slice ( SECRET ) . unwrap ( ) ;
82
- let mut claims = BTreeMap :: new ( ) ;
83
- claims. insert ( JWT_AUTH , SOMEONE ) ;
84
- let token_str = claims. sign_with_key ( & key) . unwrap ( ) ;
85
- assert_eq ! ( token_str, TOKEN ) ;
108
+ fn jwt_verify_expired_fail ( ) {
109
+ let token_result = decode :: < Claims > (
110
+ TOKEN_EXPIRED ,
111
+ & DecodingKey :: from_secret ( SECRET ) ,
112
+ & Validation :: default ( ) ,
113
+ ) ;
114
+ assert ! ( token_result. is_err( ) ) ;
115
+ let kind = token_result. map_err ( |e| e. into_kind ( ) ) . err ( ) ;
116
+ assert_eq ! ( kind, Some ( ErrorKind :: ExpiredSignature ) ) ;
86
117
}
87
118
88
119
#[ test]
89
- fn jwt_verify ( ) {
90
- let key: Hmac < Sha256 > = Hmac :: new_from_slice ( SECRET ) . unwrap ( ) ;
91
- let user_id = super :: verify_token ( key, TOKEN ) . unwrap ( ) ;
92
- assert_eq ! ( user_id, SOMEONE ) ;
120
+ fn jwt_sign_and_verify_with_chrono ( ) {
121
+ let exp = Utc :: now ( ) + Duration :: minutes ( 1 ) ;
122
+ let my_claims = Claims {
123
+ exp : exp. timestamp ( ) as usize ,
124
+ auth : SOMEONE . to_string ( ) ,
125
+ } ;
126
+ // Sign
127
+ let token_str = encode (
128
+ & Header :: default ( ) ,
129
+ & my_claims,
130
+ & EncodingKey :: from_secret ( SECRET ) ,
131
+ )
132
+ . unwrap ( ) ;
133
+ // Verify
134
+ let token_result = decode :: < Claims > (
135
+ & token_str,
136
+ & DecodingKey :: from_secret ( SECRET ) ,
137
+ & Validation :: default ( ) ,
138
+ )
139
+ . unwrap ( ) ;
140
+ assert_eq ! ( token_result. claims. auth, SOMEONE ) ;
93
141
}
94
142
}
0 commit comments