1
use uuid::Uuid;
2
use web::token::{
3
    TokenClaims, TokenDetails, TokenType, generate_jwt_token, generate_jwt_token_with_uuid,
4
    verify_jwt_token,
5
};
6

            
7
const TEST_PRIVATE_KEY_B64: &str = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRREptUmdUU1pEY3QySGsKQys5L2xNa1poQWNQL09zQWpQU1B5LytoYzZYNHhkMzFUT0RrS1lzeGhqQXE1RkFmQzJjZTk4VmY3ZmhWYlgyMwpwYU91WVp2ZTY1UUEyRW81VlM1SFFKWUsxUXNSWTVUVzFDQ05MZVJmV3pWSWY4c01YMlNuN09XYStWakZaVW0rCkhPd3pBL0xIUW84QzFzUWZsYUJVWVVIUEJkMTE5a25TU0pIYlNhdTFsZ1ZsUVlvY1FndWVnZ29MVktjcnlZMTgKYitNSXlTRWJCT1lUUDk1YlNKbjlPK3lhcHZPamhhQ3VuSlQzakVjOVhVRytiakJBam9CVTZpV3NncTVtTnljYQpPbHd3N253S0ZoaEhjUEZhRWY4bHZ3WFoxVkxHTDNlQ0FMR0VrakhteFdBWEdVUnNMMWxIOTYvN2JvQjhBUXBDCkxPaXIwb0s5QWdNQkFBRUNnZ0VBQkVoZWtOQlVCY0JXa2Nub0gxbDVrVE56RS9XZlIyNTNKcFBpblJzd0dESVoKNWtOcEZxZkVSM3ZPaTZhNGhnUlB2MEpydkw4NU1wVm1JWS9oQTR3YXk5aE0xMXpBN09sZ2w0NXBjWmIzaW5uSApXRzMrdUFrVXpDckxuYnZyUkJyRnNHU1J0eUpiYnF3WmJqSyttTkhaWno3RWUzNjFBV2ZFRlc3UWYxV2tVQVJ1Ci9zTXVqUzdVem5RZFBwNnhzWnJzbGRrRmlJSStWVXA4UitzUDhVTFJuZlhKRnliZXNuQXRDRUZiM053U0EzVWQKSnhjcGlNUzlHOHhGY2VKK3kxNlNnc3FpZG55dGtZdm1UUVZ3N0NEZmtHZHZFT0lXdkdNNTFVNjdNbVpyZW15OApnZ1p2ZkcyUlgyaG4yNVdPTlFaY1hrRDJmZ2UwWXZvMjVKRnQzZ3lhY1FLQmdRRHh4ajl0UUtDankrd3d5Yk9iCmw5bFp4Uzh1aUM0YTY1OExOU3VoNGZYbkRJTE83VmRBbW9lY2hNeXMwaXZxSnhxWTBva2QvVzAwUVg0RWx5Ui8KRG9QWHM5N01DaHRMc1dTZUJpN3VsZWhtbTJab1F6d09xSkR6Ynd2TmZGTWVJYlNVTG5nTEFYT1lyQUZNVGhjWQpHMmFxRFRCRjBUUkRNd3hoMGhLL1NYRS9VUUtCZ1FEVmRhOXlPV2wxUkJrNDZoU1AveFB3ZEUrR0RRR0dycDNKCkNzRXRYeUovMVBrajVKZ25HTTZ4dWdHTHhuaGs2YW1TN0czUS95QzBwalZXOVkrSUg5RUpNRUdYcTNyUjlKWkEKcFdZNkQ5SnM1K2dCT3h4Y3ZxaCtDQ1VWZnk2b0pRUGpJK3VxUXNEYjQ0TVRHUGJQckhxbU10dUE5M3pkQ2ZXOQpOYlZvOW9ycHJRS0JnRUs3am53d1QyYUdmYTNGcm41dXZqNUo3OGp2SzVLZ29HaHVoNW1LRGQ1MUZKSGE3cTlWCk44TWE0SWQvQjBIOUF4bFZXeVZjOHN4dW0wTFhHT3E4N2VVV3I1TXY0dkxVaHNvYk9NNy9yNExLdDh4bGFtazkKVzZ6bFpLT1dBamNaNUliV0FLcEEvMUFQZ2RnMjRhYjB3VFNFcVdOTDZCbjRPQjJ6NXhySFFhdlJBb0dBRFRVZAo2T3hpZVE0QW5ZUG1SODZabGp3c0czZzhpdS9NOVg2RDIySFNpYVJNMGdxMzIxdHVscEtTdStwSTByMmViMmZQCmw2bmhoU3Z2aXZUZ3I2U0FVNWczeHNHbWRNMDBhc1dSSUxDUDdZc2YwTXV1Z3BLTmJGYm1ySURWQ3pSWEhEdkIKdmlRcE9MSElEMnR4QWdLRENEdUhWMkI0eWxodWF3bWlzdDdtVTNVQ2dZQitFVlJGUlVjQnhYSm8xaXg2bXZlQQo1blgwa2s2L1F3YVZWWUhlLzFlYm1ISDNZYUZWQzhvYTJYQWpmNDRuRkN6cHFGWjlsTnZYMHR0eFo3VzlzV1E3CjFjOXU1VTQxekdDZzkreVVwcUdYNTZXdG4zUWJkL0dUdGJlTXd5c2o3V0NlSFltU0xUcklaU001aE0zNDBaeHkKNzZZRTV0SGRKazhHUGVzNzNwR29lUT09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K";
8

            
9
const TEST_PUBLIC_KEY_B64: &str = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF5WmtZRTBtUTNMZGg1QXZ2ZjVUSgpHWVFIRC96ckFJejBqOHYvb1hPbCtNWGQ5VXpnNUNtTE1ZWXdLdVJRSHd0bkh2ZkZYKzM0VlcxOXQ2V2pybUdiCjN1dVVBTmhLT1ZVdVIwQ1dDdFVMRVdPVTF0UWdqUzNrWDFzMVNIL0xERjlrcCt6bG12bFl4V1ZKdmh6c013UHkKeDBLUEF0YkVINVdnVkdGQnp3WGRkZlpKMGtpUjIwbXJ0WllGWlVHS0hFSUxub0lLQzFTbks4bU5mRy9qQ01raApHd1RtRXovZVcwaVovVHZzbXFiem80V2dycHlVOTR4SFBWMUJ2bTR3UUk2QVZPb2xySUt1WmpjbkdqcGNNTzU4CkNoWVlSM0R4V2hIL0piOEYyZFZTeGk5M2dnQ3hoSkl4NXNWZ0Z4bEViQzlaUi9ldisyNkFmQUVLUWl6b3E5S0MKdlFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==";
10

            
11
#[test]
12
1
fn test_token_details_creation() {
13
1
    let user_id = Uuid::new_v4();
14
1
    let token_uuid = Uuid::new_v4();
15

            
16
1
    let token_details = TokenDetails {
17
1
        token: Some("test_token".to_string()),
18
1
        token_uuid,
19
1
        user_id,
20
1
        token_type: Some(TokenType::Access),
21
1
        expires_in: Some(1234567890),
22
1
    };
23

            
24
1
    assert_eq!(token_details.user_id, user_id);
25
1
    assert_eq!(token_details.token_uuid, token_uuid);
26
1
    assert_eq!(token_details.token, Some("test_token".to_string()));
27
1
    assert_eq!(token_details.expires_in, Some(1234567890));
28
1
}
29

            
30
#[test]
31
1
fn test_token_claims_creation() {
32
1
    let user_id = Uuid::new_v4();
33
1
    let token_uuid = Uuid::new_v4();
34

            
35
1
    let claims = TokenClaims {
36
1
        sub: user_id.to_string(),
37
1
        token_uuid: token_uuid.to_string(),
38
1
        token_type: Some(TokenType::Refresh),
39
1
        exp: 1234567890,
40
1
        iat: 1234567000,
41
1
        nbf: 1234567000,
42
1
    };
43

            
44
1
    assert_eq!(claims.sub, user_id.to_string());
45
1
    assert_eq!(claims.token_uuid, token_uuid.to_string());
46
1
    assert_eq!(claims.exp, 1234567890);
47
1
    assert_eq!(claims.iat, 1234567000);
48
1
    assert_eq!(claims.nbf, 1234567000);
49
1
}
50

            
51
#[test]
52
1
fn test_generate_jwt_token_round_trip() {
53
1
    let user_id = Uuid::new_v4();
54
1
    let details =
55
1
        generate_jwt_token(user_id, 15, TEST_PRIVATE_KEY_B64, TokenType::Access).expect("encode");
56
1
    let token = details.token.as_deref().expect("token");
57

            
58
1
    let verified = verify_jwt_token(TEST_PUBLIC_KEY_B64, token).expect("verify");
59

            
60
1
    assert_eq!(verified.user_id, user_id);
61
1
    assert_eq!(verified.token_uuid, details.token_uuid);
62
1
    assert_eq!(
63
        verified.token_type,
64
        Some(TokenType::Access),
65
        "token_type claim must survive the sign/verify round trip"
66
    );
67
1
}
68

            
69
#[test]
70
1
fn test_generate_jwt_token_with_uuid_preserves_uuid() {
71
1
    let user_id = Uuid::new_v4();
72
1
    let fixed_uuid = Uuid::new_v4();
73

            
74
1
    let details = generate_jwt_token_with_uuid(
75
1
        user_id,
76
1
        fixed_uuid,
77
        15,
78
1
        TEST_PRIVATE_KEY_B64,
79
1
        TokenType::Refresh,
80
    )
81
1
    .expect("encode");
82

            
83
1
    assert_eq!(details.token_uuid, fixed_uuid);
84

            
85
1
    let verified =
86
1
        verify_jwt_token(TEST_PUBLIC_KEY_B64, details.token.as_deref().unwrap()).expect("verify");
87
1
    assert_eq!(verified.token_uuid, fixed_uuid);
88
1
    assert_eq!(verified.user_id, user_id);
89
1
}
90

            
91
#[test]
92
1
fn test_remint_with_same_uuid_extends_expiry() {
93
1
    let user_id = Uuid::new_v4();
94
1
    let shared_uuid = Uuid::new_v4();
95

            
96
1
    let first = generate_jwt_token_with_uuid(
97
1
        user_id,
98
1
        shared_uuid,
99
        15,
100
1
        TEST_PRIVATE_KEY_B64,
101
1
        TokenType::Refresh,
102
    )
103
1
    .expect("first");
104

            
105
1
    std::thread::sleep(std::time::Duration::from_secs(1));
106

            
107
1
    let second = generate_jwt_token_with_uuid(
108
1
        user_id,
109
1
        shared_uuid,
110
        60,
111
1
        TEST_PRIVATE_KEY_B64,
112
1
        TokenType::Refresh,
113
    )
114
1
    .expect("second");
115

            
116
1
    assert_eq!(first.token_uuid, second.token_uuid);
117
1
    assert!(
118
1
        second.expires_in.unwrap() > first.expires_in.unwrap(),
119
        "re-mint must push exp forward: first={:?}, second={:?}",
120
        first.expires_in,
121
        second.expires_in,
122
    );
123
1
    assert_ne!(
124
        first.token, second.token,
125
        "JWT string must differ (different iat/exp claims)"
126
    );
127

            
128
1
    let verified_first =
129
1
        verify_jwt_token(TEST_PUBLIC_KEY_B64, first.token.as_deref().unwrap()).expect("v1");
130
1
    let verified_second =
131
1
        verify_jwt_token(TEST_PUBLIC_KEY_B64, second.token.as_deref().unwrap()).expect("v2");
132
1
    assert_eq!(verified_first.token_uuid, verified_second.token_uuid);
133
1
}
134

            
135
#[test]
136
1
fn test_generate_jwt_token_unique_uuid_per_call() {
137
1
    let user_id = Uuid::new_v4();
138

            
139
1
    let a = generate_jwt_token(user_id, 15, TEST_PRIVATE_KEY_B64, TokenType::Access).expect("a");
140
1
    let b = generate_jwt_token(user_id, 15, TEST_PRIVATE_KEY_B64, TokenType::Access).expect("b");
141

            
142
1
    assert_ne!(
143
        a.token_uuid, b.token_uuid,
144
        "generate_jwt_token must mint fresh uuid each call"
145
    );
146
1
}
147

            
148
#[test]
149
1
fn test_unverified_user_id_reads_sub_without_signature() {
150
1
    let user_id = Uuid::new_v4();
151
1
    let details =
152
1
        generate_jwt_token(user_id, 15, TEST_PRIVATE_KEY_B64, TokenType::Access).expect("encode");
153
1
    let token = details.token.as_deref().expect("token");
154

            
155
    // The routing hint reads the sub from an unverified token; it must equal the
156
    // signer's user id (and the token still verifies normally with the key).
157
1
    let routed = web::token::unverified_user_id(token).expect("read sub");
158
1
    assert_eq!(routed, user_id);
159
1
}