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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
use diesel::prelude::*;
use log::{debug, error, trace};
use minerva_cache as cache;
use minerva_data::db::DBPool;
use minerva_data::encryption;
use minerva_data::schema::syslog;
use minerva_data::session as model;
use minerva_data::syslog::{NewLog, OpType};
use minerva_data::user::User;
use mongodb::bson::doc;
use mongodb::bson::oid::ObjectId;
use mongodb::Database as MongoDatabase;
use redis::Client as RedisClient;
use std::str;
use tonic::Status;
pub async fn create_session(
tenant: &str,
data: model::NewSession,
pool: DBPool,
mongo: MongoDatabase,
redis: &RedisClient,
) -> Result<String, Status> {
trace!("Create new session");
let usr = {
use minerva_data::schema::user::dsl::*;
let connection = pool.get().await.map_err(|e| {
error!("Database access error: {}", e);
Status::internal("There was an error while trying to access the database")
})?;
user.filter(login.eq(data.login.clone()))
.first::<User>(&*connection)
}
.map_err(|_| {
debug!("Invalid login or password");
Status::unauthenticated("Invalid login or password")
})?;
let pwhash = str::from_utf8(&usr.pwhash).map_err(|e| {
debug!("Error while performing authentication: {}", e);
Status::internal("There was an error while performing authentication")
})?;
if !encryption::check_hash(&data.password, pwhash) {
return Err(Status::unauthenticated("Invalid login or password"));
}
let collection = mongo.collection::<model::Session>("session");
let session: model::Session = data.into();
let result = collection
.insert_one(session.clone(), None)
.await
.map_err(|e| {
error!("Error while creating session: {}", e);
Status::internal("There was an error while creating a new session")
})?
.inserted_id
.as_object_id()
.ok_or_else(|| {
error!("Error while processing session token");
Status::internal("Error while processing session token")
})?
.to_hex();
let token = base64::encode(result);
let _ = serde_json::to_string(&session)
.map(|json| cache::auth::save_session(redis, tenant, &token, &json));
let connection = pool.get().await.map_err(|e| {
error!("Error while acessing database for logging: {}", e);
Status::internal("There was an error while trying to access the database")
})?;
let _ = diesel::insert_into(syslog::table)
.values(&NewLog {
service: "SESSION".to_string(),
requestor: session.login.clone(),
entity: "session".to_string(),
operation: OpType::Insert,
datetime: chrono::offset::Utc::now(),
description: Some("Create user session".to_string()),
})
.execute(&*connection);
Ok(token)
}
pub async fn recover_session(
tenant: &str,
token: String,
mongo: MongoDatabase,
redis: &RedisClient,
) -> Result<model::Session, Status> {
trace!("Recover session");
if let Ok(json) = cache::auth::get_session(redis, tenant, &token).await {
return serde_json::from_str(&json).map_err(|e| {
error!(
"Error while parsing session data from Redis as JSON: {:?}",
e
);
Status::internal("There was an error while recovering a cached session")
});
}
let collection = mongo.collection::<model::Session>("session");
let id = decode_session_token(token.clone())?;
let session = collection
.find_one(doc! { "_id": id }, None)
.await
.map_err(|e| {
error!("Could not recover session token from MongoDB: {}", e);
Status::internal("Error while trying to recover session")
})?
.ok_or_else(|| Status::not_found("Session does not exist"))?;
if let Ok(json) = serde_json::to_string(&session) {
if let Err(e) = cache::auth::save_session(redis, tenant, &token, &json) {
error!("Error while caching recovered user session: {:#?}", e);
}
}
Ok(session)
}
pub async fn remove_session(
tenant: &str,
token: String,
pool: DBPool,
mongo: MongoDatabase,
redis: &RedisClient,
) -> Result<(), Status> {
trace!("Removing user session");
cache::auth::remove_session(redis, tenant, &token)
.await
.map_err(|e| {
error!("Could not remove session from cache: {:?}", e);
Status::internal("Could not remove session from cache")
})?;
let collection = mongo.collection::<model::Session>("session");
let id = decode_session_token(token.clone())?;
if let Ok(session) = collection.find_one(doc! { "_id": id }, None).await {
collection
.delete_one(doc! { "_id": id }, None)
.await
.map_err(|e| {
error!("Error while deleting session data: {}", e);
Status::internal("There was an error while trying to delete the session")
})?;
let connection = pool.get().await.map_err(|e| {
error!("Error while accessing database for logging: {}", e);
Status::internal("There was an error while trying to access the database")
})?;
let _ = diesel::insert_into(syslog::table)
.values(&NewLog {
service: "SESSION".to_string(),
requestor: session
.map(|s| s.login)
.unwrap_or_else(|| "unknown".to_string()),
entity: "session".to_string(),
operation: OpType::Delete,
datetime: chrono::offset::Utc::now(),
description: Some("Remove user session".to_string()),
})
.execute(&*connection);
}
Ok(())
}
fn decode_session_token(token: String) -> Result<ObjectId, Status> {
let id = base64::decode(token.as_bytes()).map_err(|_| {
error!("Could not decode session token from Base64");
Status::internal("Unable to decode session token")
})?;
let id = String::from_utf8(id).map_err(|_| {
error!("Could not convert ID to UTF-8");
Status::internal("Unable to decode session token")
})?;
ObjectId::parse_str(&id).map_err(|_| {
error!("Could not parse session token as MongoDB Object ID");
Status::internal("Unable to decode session token")
})
}