-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcreate_link.rs
119 lines (109 loc) · 4.66 KB
/
create_link.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
use crate::{base58::Base58Chars, responses::created_link::CreatedLink, ServiceState};
use axum::{
extract::{ConnectInfo, State},
http::{StatusCode, Uri},
TypedHeader,
};
use chrono::{DateTime, Utc};
use headers::{authorization::Bearer, Authorization};
use rand::prelude::*;
use std::{net::SocketAddr, str::FromStr};
pub async fn create_link_route(
State(ServiceState { db, config }): State<ServiceState>,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
auth_header: Option<TypedHeader<Authorization<Bearer>>>,
url: String,
) -> Result<CreatedLink, StatusCode> {
if let Some(tok_config) = &config.tokens {
if tok_config.creation_requires_auth {
match auth_header {
Some(auth) => {
if auth.token() != tok_config.master_token.as_ref() {
let (admin_perm, create_link_perm, expiry_date): (bool, bool, DateTime<Utc>) = sqlx::query_as("SELECT admin_perm, create_link_perm, expires_at FROM tokens WHERE token = ?").bind(auth.token()).fetch_one(db.as_ref()).await.map_err(|e| {
match e {
sqlx::Error::RowNotFound => {
log::warn!("Attempt to use invalid credentials from {}: `{}`", addr.ip(), auth.token());
StatusCode::UNAUTHORIZED},
_ => {
log::error!("Error fetching token for permission check: {e}");
StatusCode::INTERNAL_SERVER_ERROR},
}
})?;
if Utc::now() > expiry_date {
log::warn!(
"Attempt to use expired token `{}` from {}: expired at {}",
auth.token(),
addr.ip(),
expiry_date
);
return Err(StatusCode::UNAUTHORIZED);
}
if !(admin_perm || create_link_perm) {
return Err(StatusCode::FORBIDDEN);
}
}
}
None => return Err(StatusCode::UNAUTHORIZED),
}
}
}
if config.ip_recording.is_some() {
let created_by =
bincode::serialize(&addr.ip()).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if let Some((strikes,)) =
sqlx::query_as::<_, (u16,)>("SELECT amount FROM strikes WHERE origin = ?")
.bind(created_by)
.fetch_optional(db.as_ref())
.await
.map_err(|e| {
log::error!("Error looking up strikes for {}: {}", addr.ip(), e);
StatusCode::INTERNAL_SERVER_ERROR
})?
{
if strikes >= config.max_strikes {
return Err(StatusCode::FORBIDDEN);
}
}
}
let uri = Uri::from_str(&url).map_err(|_| StatusCode::BAD_REQUEST)?;
let uri_hash = blake3::hash(uri.to_string().as_ref());
let uri_hash_bytes: [u8; 32] = uri_hash.into();
if let Some((id,)) = sqlx::query_as("SELECT id FROM links WHERE hash = ?")
.bind(uri_hash_bytes.as_ref())
.fetch_optional(db.as_ref())
.await
.map_err(|e| {
log::error!("Error when looking for existing link: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?
{
Ok(CreatedLink { id })
} else {
let created_by =
bincode::serialize(&addr.ip()).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let rng = StdRng::from_entropy();
let new_link_id: String = rng.sample_iter(Base58Chars).take(7).collect();
sqlx::query("INSERT INTO links (id, hash, link) values (?, ?, ?)")
.bind(&new_link_id)
.bind(uri_hash_bytes.as_ref())
.bind(uri.to_string())
.execute(db.as_ref())
.await
.map_err(|e| {
log::error!("Error when inserting new link: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?;
if config.ip_recording.is_some() {
sqlx::query("INSERT INTO origins (id, created_by) values (?, ?)")
.bind(&new_link_id)
.bind(created_by)
.execute(db.as_ref())
.await
.map_err(|e| {
log::error!("Error when inserting link origin: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?;
}
Ok(CreatedLink { id: new_link_id })
}
}