-
-
Notifications
You must be signed in to change notification settings - Fork 892
/
Copy pathclaims.rs
136 lines (121 loc) Β· 4.11 KB
/
claims.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use crate::context::LemmyContext;
use actix_web::{http::header::USER_AGENT, HttpRequest};
use chrono::Utc;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use lemmy_db_schema::{
newtypes::LocalUserId,
sensitive::SensitiveString,
source::login_token::{LoginToken, LoginTokenCreateForm},
};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct Claims {
/// local_user_id, standard claim by RFC 7519.
pub sub: String,
pub iss: String,
/// Time when this token was issued as UNIX-timestamp in seconds
pub iat: i64,
}
impl Claims {
pub async fn validate(jwt: &str, context: &LemmyContext) -> LemmyResult<LocalUserId> {
let mut validation = Validation::default();
validation.validate_exp = false;
validation.required_spec_claims.remove("exp");
let jwt_secret = &context.secret().jwt_secret;
let key = DecodingKey::from_secret(jwt_secret.as_ref());
let claims =
decode::<Claims>(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
let user_id = LocalUserId(claims.claims.sub.parse()?);
let is_valid = LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
if !is_valid {
Err(LemmyErrorType::NotLoggedIn)?
} else {
Ok(user_id)
}
}
pub async fn generate(
user_id: LocalUserId,
req: HttpRequest,
context: &LemmyContext,
) -> LemmyResult<SensitiveString> {
let hostname = context.settings().hostname.clone();
let my_claims = Claims {
sub: user_id.0.to_string(),
iss: hostname,
iat: Utc::now().timestamp(),
};
let secret = &context.secret().jwt_secret;
let key = EncodingKey::from_secret(secret.as_ref());
let token: SensitiveString = encode(&Header::default(), &my_claims, &key)?.into();
let ip = req
.connection_info()
.realip_remote_addr()
.map(ToString::to_string);
let user_agent = req
.headers()
.get(USER_AGENT)
.and_then(|ua| ua.to_str().ok())
.map(ToString::to_string);
let form = LoginTokenCreateForm {
token: token.clone(),
user_id,
ip,
user_agent,
};
LoginToken::create(&mut context.pool(), form).await?;
Ok(token)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
use crate::{claims::Claims, context::LemmyContext};
use actix_web::test::TestRequest;
use lemmy_db_schema::{
source::{
instance::Instance,
local_user::{LocalUser, LocalUserInsertForm},
person::{Person, PersonInsertForm},
secret::Secret,
},
traits::Crud,
utils::build_db_pool_for_tests,
};
use lemmy_utils::rate_limit::RateLimitCell;
use pretty_assertions::assert_eq;
use reqwest::Client;
use reqwest_middleware::ClientBuilder;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_should_not_validate_user_token_after_password_change() {
let pool_ = build_db_pool_for_tests().await;
let pool = &mut (&pool_).into();
let secret = Secret::init(pool).await.unwrap().unwrap();
let context = LemmyContext::create(
pool_.clone(),
ClientBuilder::new(Client::default()).build(),
secret,
RateLimitCell::with_test_config(),
);
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
.await
.unwrap();
let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812");
let inserted_person = Person::create(pool, &new_person).await.unwrap();
let local_user_form = LocalUserInsertForm::test_form(inserted_person.id);
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
.await
.unwrap();
let req = TestRequest::default().to_http_request();
let jwt = Claims::generate(inserted_local_user.id, req, &context)
.await
.unwrap();
let valid = Claims::validate(&jwt, &context).await;
assert!(valid.is_ok());
let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap();
assert_eq!(1, num_deleted);
}
}