-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathuser.rs
134 lines (125 loc) · 4.17 KB
/
user.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
use crate::messages::{UserCreateMsg, UserLoginMsg};
use crate::resources::ResponseData;
use crate::state::ServerState;
use entities::{access_mask, User, UserCreatePayload, UserLoginPayload};
use sqlx::postgres::{PgDone, PgPool, PgRow};
use sqlx::Row;
use tide::{Request, StatusCode};
pub fn from_row(row: &PgRow) -> Result<User, sqlx::Error> {
Ok(User {
email: row.try_get("email")?,
access_mask: row.try_get("access_mask")?,
access_token: row.try_get("access_token")?,
})
}
pub async fn check_access_token(access_token: &str, db_pool: &PgPool) -> Result<bool, sqlx::Error> {
Ok(
sqlx::query(r#"SELECT EXISTS(SELECT 1 FROM "user" WHERE access_token=$1)"#)
.bind(access_token)
.fetch_one(db_pool)
.await?
.try_get::<bool, usize>(0)?,
)
}
pub async fn check_access_mask(
token: &str,
target_mask: i32,
db_pool: &PgPool,
) -> Result<bool, sqlx::Error> {
if let Some(row) = sqlx::query(r#"SELECT access_mask FROM "user" WHERE access_token=$1"#)
.bind(token)
.fetch_optional(db_pool)
.await?
{
let requester_access_mask = row.try_get::<i32, usize>(0)?;
Ok((target_mask & requester_access_mask) == target_mask)
} else {
Ok(false)
}
}
#[inline(always)]
pub async fn create_session(msg: &UserLoginMsg) -> Result<ResponseData<User>, sqlx::Error> {
let row = sqlx::query(r#"SELECT * FROM "user" WHERE email=$1"#)
.bind(&msg.payload.email)
.fetch_optional(msg.db_pool)
.await?;
if let Some(row) = row {
Ok(ResponseData(StatusCode::Created, Some(from_row(&row)?)))
} else {
Ok(ResponseData(StatusCode::NotFound, None))
}
}
#[inline(always)]
async fn extract_login(req: &mut Request<ServerState>) -> tide::Result<UserLoginPayload> {
Ok(req.body_json::<UserLoginPayload>().await?)
}
actor_response_handler::generate!({
name: login,
actor: User,
response_type: User,
tag: Login
});
#[inline(always)]
pub async fn create(
msg: &UserCreateMsg,
) -> Result<ResponseData<Result<User, String>>, sqlx::Error> {
let is_authorized = match &msg.payload.requester_access_token {
Some(token) => check_access_mask(token, access_mask::ADMIN, msg.db_pool).await?,
None => msg.payload.access_mask == access_mask::USER,
};
if !is_authorized {
return Ok(ResponseData(StatusCode::Forbidden, None));
}
let result = sqlx::query(
r#"INSERT INTO "user" (email, access_mask, access_token) VALUES ($1, $2, $3) RETURNING *"#,
)
.bind(&msg.payload.email)
.bind(&msg.payload.access_mask)
// FIXME have an actual access token generation strategy
.bind(&msg.payload.email)
.fetch_one(msg.db_pool)
.await;
match result {
Ok(row) => Ok(ResponseData(StatusCode::Created, Some(Ok(from_row(&row)?)))),
Err(err) => match err {
sqlx::Error::Database(err) => match err.code() {
Some(code) => {
if code == "23505" {
Ok(ResponseData(
StatusCode::UnprocessableEntity,
Some(Err(format!("{} already exists", &msg.payload.email))),
))
} else {
Err(sqlx::Error::Database(err))
}
}
_ => Err(sqlx::Error::Database(err)),
},
_ => Err(err),
},
}
}
#[inline(always)]
async fn extract_post(req: &mut Request<ServerState>) -> tide::Result<UserCreatePayload> {
Ok(req.body_json::<UserCreatePayload>().await?)
}
actor_response_handler::generate!({
name: post,
actor: User,
response_type: Result<User, String>,
tag: Create
});
endpoint_actor::generate!({ actor: User }, {
Create: create,
Login: create_session,
});
pub async fn create_super_user(email: &str, db_pool: &PgPool) -> Result<PgDone, sqlx::Error> {
Ok(
sqlx::query(r#"INSERT INTO "user" (email, access_token, access_mask) VALUES ($1, $2, $3)"#)
.bind(email)
.bind(email)
.bind(access_mask::ADMIN)
.execute(db_pool)
.await?,
)
}