Compare commits
14 Commits
e3229f41a4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9fa66899aa | |||
| 8a48ca0ad6 | |||
| 26301ac344 | |||
| 81023f2042 | |||
| f5f35855ce | |||
| 7018adf98d | |||
| a60713a4b0 | |||
| 9ef4176091 | |||
| 6cd6b12630 | |||
| 2a9ac271a7 | |||
| 8a7ff047b6 | |||
| e28a8c2893 | |||
| 73f5edf77f | |||
| c12d7fb01c |
835
Cargo.lock
generated
835
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "BitAuth"
|
name = "BitAuth"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
@ -16,10 +16,11 @@ jsonwebtoken = "9.2.0"
|
|||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rsa = "0.9.6"
|
rsa = "0.9.6"
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.111"
|
||||||
skytable = "0.8.6"
|
skytable = "0.8.10"
|
||||||
tokio = { version = "1.35.1", features = ["full"] }
|
tokio = { version = "1.35.1", features = ["full"] }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
uuid = { version = "1.6.1", features = ["v4", "v5"] }
|
uuid = { version = "1.6.1", features = ["v4", "v5"] }
|
||||||
|
webhook = "2.1.2"
|
||||||
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|||||||
28
TODO
28
TODO
@ -1,6 +1,4 @@
|
|||||||
############################
|
Forward user back when set `back_url`
|
||||||
### MAKE USER UUID TABLE ###
|
|
||||||
############################
|
|
||||||
|
|
||||||
|
|
||||||
Auth using tokens
|
Auth using tokens
|
||||||
@ -11,8 +9,26 @@ Auth using email
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### API v0
|
##### API v0 (unsafe)
|
||||||
# Auth link (device check url with this session and get data)
|
# Auth link (device check url with this session and get data)
|
||||||
/auth?session=<UNIQUE>
|
/api/v0/auth?session=<UNIQUE>&back_url=<OPTIONAL>
|
||||||
# Get auth data
|
# Get auth data
|
||||||
/auth_finish?session=<UNIQUE>
|
/api/v0/auth_finish?session=<UNIQUE>
|
||||||
|
|
||||||
|
|
||||||
|
##### API v1 (semi-safe)
|
||||||
|
# Auth link
|
||||||
|
/api/v1/auth?session=<UNIQUE>&back_url=<OPTIONAL>
|
||||||
|
# Get token
|
||||||
|
/api/v1/auth_finish?session=<UNIQUE>
|
||||||
|
# Refresh token
|
||||||
|
/api/v1/refresh?token=<REFRESH>
|
||||||
|
|
||||||
|
|
||||||
|
##### API v2 (safe)
|
||||||
|
# Auth link
|
||||||
|
/api/v2/auth?site_id=<REQUIRED>&session=<UNIQUE>&back_url=<OPTIONAL>&webhook=<OPTIONAL>
|
||||||
|
# Get token
|
||||||
|
/api/v2/auth_finish?site_id=<REQUIRED>&session=<UNIQUE>
|
||||||
|
# Refresh token
|
||||||
|
/api/v2/refresh?site_id=<REQUIRED>&token=<REFRESH>
|
||||||
|
|||||||
362
src/funcs.rs
362
src/funcs.rs
@ -7,10 +7,57 @@ use {
|
|||||||
UNIX_EPOCH,
|
UNIX_EPOCH,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
webhook::client::WebhookClient,
|
||||||
urlencoding::decode as url_decode,
|
urlencoding::decode as url_decode,
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
|
chrono::DateTime,
|
||||||
|
serde_json::{
|
||||||
|
Value as Json,
|
||||||
|
Map as JsonMap,
|
||||||
|
json,
|
||||||
|
},
|
||||||
|
rsa::{
|
||||||
|
pkcs1::{
|
||||||
|
EncodeRsaPublicKey,
|
||||||
|
EncodeRsaPrivateKey,
|
||||||
|
},
|
||||||
|
RsaPrivateKey,
|
||||||
|
RsaPublicKey,
|
||||||
|
},
|
||||||
|
jsonwebtoken as jwt,
|
||||||
|
jwt::{
|
||||||
|
Header,
|
||||||
|
Algorithm,
|
||||||
|
TokenData,
|
||||||
|
Validation,
|
||||||
|
EncodingKey,
|
||||||
|
DecodingKey,
|
||||||
|
encode as jwt_encode,
|
||||||
|
decode as jwt_decode,
|
||||||
|
},
|
||||||
|
skytable::{
|
||||||
|
query,
|
||||||
|
error::Error as SkyError,
|
||||||
|
},
|
||||||
|
http_body_util::BodyExt,
|
||||||
|
hyper::{
|
||||||
|
Request,
|
||||||
|
HeaderMap,
|
||||||
|
body::Incoming,
|
||||||
|
header::{
|
||||||
|
HeaderValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
crate::{
|
crate::{
|
||||||
|
Res,
|
||||||
|
Result,
|
||||||
|
DBPool,
|
||||||
|
REFRESH_LIFETIME,
|
||||||
|
TOKEN_LIFETIME,
|
||||||
html::*,
|
html::*,
|
||||||
|
Users,
|
||||||
|
Sites,
|
||||||
|
APIV0_LIFETIME,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,6 +75,64 @@ pub fn build_html(body: &str) -> String {
|
|||||||
format!("{}{}{}{}", HEADER_HTML, CSS3, body, FOOTER_HTML)
|
format!("{}{}{}{}", HEADER_HTML, CSS3, body, FOOTER_HTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn init_tables(pool: DBPool) -> Res<(), SkyError> {
|
||||||
|
let mut con = pool.get().await.unwrap();
|
||||||
|
|
||||||
|
let _ = con.query_parse::<()>(&query!("CREATE SPACE IF NOT EXISTS bitauth")).await;
|
||||||
|
let _ = con.query_parse::<()>(&query!(r#"
|
||||||
|
CREATE MODEL IF NOT EXISTS bitauth.users(
|
||||||
|
uuid: string,
|
||||||
|
login: string,
|
||||||
|
password: string,
|
||||||
|
email: string,
|
||||||
|
tokens: list {type: string}
|
||||||
|
)
|
||||||
|
"#)).await;
|
||||||
|
let _ = con.query_parse::<()>(&query!(r#"
|
||||||
|
CREATE MODEL IF NOT EXISTS bitauth.users_uuid(
|
||||||
|
login: string,
|
||||||
|
uuid: string
|
||||||
|
)
|
||||||
|
"#)).await;
|
||||||
|
let _ = con.query_parse::<()>(&query!(r#"
|
||||||
|
CREATE MODEL IF NOT EXISTS bitauth.sites(
|
||||||
|
uuid: string,
|
||||||
|
uid: string,
|
||||||
|
domain: string,
|
||||||
|
pkey: binary,
|
||||||
|
skey: binary
|
||||||
|
)
|
||||||
|
"#)).await;
|
||||||
|
let _ = con.query_parse::<()>(&query!(r#"
|
||||||
|
CREATE MODEL IF NOT EXISTS bitauth.tokens(
|
||||||
|
uuid: string,
|
||||||
|
uid: string,
|
||||||
|
sid: string,
|
||||||
|
ref: string,
|
||||||
|
refend: uint32
|
||||||
|
)
|
||||||
|
"#)).await;
|
||||||
|
let _ = con.query_parse::<()>(&query!(r#"
|
||||||
|
CREATE MODEL IF NOT EXISTS bitauth.v0(
|
||||||
|
session: string,
|
||||||
|
login: string,
|
||||||
|
uuid: string,
|
||||||
|
expire: uint32
|
||||||
|
)
|
||||||
|
"#)).await;
|
||||||
|
|
||||||
|
let q = con.query_parse::<Sites>(&query!("SELECT * FROM bitauth.sites WHERE uuid = 0")).await;
|
||||||
|
if q.is_err() {
|
||||||
|
let (skey, pkey) = rsa_gen();
|
||||||
|
let _ = con.query_parse::<()>(&query!(
|
||||||
|
"INSERT INTO bitauth.sites { uuid: ?, uid: ?, domain: ?, pkey: ?, skey: ? }",
|
||||||
|
"0", "0", "", pkey, skey
|
||||||
|
)).await;
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn double_split(body: String, first: &str, second: &str) -> HashMap<String, String> {
|
pub fn double_split(body: String, first: &str, second: &str) -> HashMap<String, String> {
|
||||||
body.split(first)
|
body.split(first)
|
||||||
.filter_map(|c| {
|
.filter_map(|c| {
|
||||||
@ -53,3 +158,260 @@ pub fn time_mcs() -> u128 {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.as_micros()
|
.as_micros()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn discord_wh_send(text: String) {
|
||||||
|
let url: &str = "https://discord.com/api/webhooks/1228566682902204537/RXaWpZplEGzU88O8c4mD4qzXp1PDBrrp93nGvdijaY7mBXp27xc0EsThHUeU0431PQOZ";
|
||||||
|
let client: WebhookClient = WebhookClient::new(url);
|
||||||
|
client.send(|message| message
|
||||||
|
.username("Bit.Auth")
|
||||||
|
// .avatar_url("")
|
||||||
|
.embed(|embed| embed
|
||||||
|
.title("Error")
|
||||||
|
.description(&text)
|
||||||
|
// .footer("Footer", Some(String::from(IMAGE_URL)))
|
||||||
|
// .image(IMAGE_URL)
|
||||||
|
// .thumbnail(IMAGE_URL)
|
||||||
|
// .author("bitheaven", Some(String::from(IMAGE_URL)), Some(String::from(IMAGE_URL)))
|
||||||
|
// .field("name", "value", false)
|
||||||
|
)
|
||||||
|
).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cookie(headers: &mut HeaderMap, key: &str, value: &str) {
|
||||||
|
let time = DateTime::from_timestamp((time() + REFRESH_LIFETIME) as i64, 0)
|
||||||
|
.expect("REASON")
|
||||||
|
.to_rfc2822();
|
||||||
|
let time = time.replace("+0000", "GMT");
|
||||||
|
headers.append(
|
||||||
|
hyper::header::SET_COOKIE,
|
||||||
|
HeaderValue::from_str(format!("{}={}; HttpOnly; Expires={}", key, value, time).as_str())
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_location(headers: &mut HeaderMap, location: &str) {
|
||||||
|
headers.append(
|
||||||
|
hyper::header::LOCATION,
|
||||||
|
HeaderValue::from_str(format!("{}", location).as_str()).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_body_from_request(mut req: Request<Incoming>) -> Result<Vec<u8>> {
|
||||||
|
let mut body: Vec<u8> = vec![];
|
||||||
|
while let Some(next) = req.frame().await {
|
||||||
|
let frame = next?;
|
||||||
|
if let Some(chunk) = frame.data_ref() {
|
||||||
|
body.extend_from_slice(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login_user(pool: DBPool, data: HashMap<String, String>) -> Res<(String, String), SkyError> {
|
||||||
|
let mut ret: (String, String) = Default::default();
|
||||||
|
let mut con = pool.get().await.unwrap();
|
||||||
|
|
||||||
|
let login = &data.get("login").unwrap().trim().to_lowercase();
|
||||||
|
let pass = data.get("password").unwrap().trim();
|
||||||
|
|
||||||
|
let q = con
|
||||||
|
.query_parse::<(String,)>(&query!("SELECT uuid FROM bitauth.users_uuid WHERE login = ?", login))
|
||||||
|
.await;
|
||||||
|
if q.is_err() { println!("{:?}", q.err()); return Ok(ret); };
|
||||||
|
let (uuid,) = q.unwrap();
|
||||||
|
|
||||||
|
let q = con
|
||||||
|
.query_parse::<Users>(&query!("SELECT * FROM bitauth.users WHERE uuid = ?", uuid.clone()))
|
||||||
|
.await;
|
||||||
|
if q.is_err() {
|
||||||
|
discord_wh_send(
|
||||||
|
format!("Пизда тут, ну тип, да, какая-то ебень с uuid, чел потерялся...\n`{}`\n**{}**", uuid, login)
|
||||||
|
).await;
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Say to user about trouble.
|
||||||
|
|
||||||
|
let q = q.unwrap();
|
||||||
|
if bcrypt::verify(pass, q.password.as_str()).unwrap() {
|
||||||
|
let token = format!("{}", uuid_v4().as_hyphenated());
|
||||||
|
let reftoken = format!("{}", uuid_v4().as_hyphenated());
|
||||||
|
let time = time();
|
||||||
|
let uuid: String = q.uuid;
|
||||||
|
let login: String = q.login;
|
||||||
|
|
||||||
|
let _ = con.query_parse::<()>(&query!(
|
||||||
|
"INSERT INTO bitauth.tokens { uuid: ?, uid: ?, sid: ?, ref: ?, refend: ? }",
|
||||||
|
token.clone(), uuid.clone(), "0", reftoken.clone(), time + REFRESH_LIFETIME
|
||||||
|
)).await;
|
||||||
|
let _ = con.query_parse::<()>(&query!(
|
||||||
|
"UPDATE bitauth.users SET tokens += ? WHERE login = ?",
|
||||||
|
token.clone(), login.clone()
|
||||||
|
)).await;
|
||||||
|
|
||||||
|
ret = (
|
||||||
|
jwt_sign(pool.clone(), json!({
|
||||||
|
"login": login.clone(),
|
||||||
|
"uuid": uuid.clone(),
|
||||||
|
"iat": time,
|
||||||
|
"exp": time + TOKEN_LIFETIME
|
||||||
|
})).await.unwrap(),
|
||||||
|
jwt_sign(pool.clone(), json!({
|
||||||
|
"uuid": token.clone(),
|
||||||
|
"iat": time,
|
||||||
|
"ref": reftoken.clone(),
|
||||||
|
"exp": time + REFRESH_LIFETIME
|
||||||
|
})).await.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn jwt_sign(pool: DBPool, data: Json) -> Result<String> {
|
||||||
|
let mut con = pool.get().await.unwrap();
|
||||||
|
|
||||||
|
let q = con
|
||||||
|
.query_parse::<Sites>(&query!("SELECT * FROM bitauth.sites WHERE uuid = ?", "0"))
|
||||||
|
.await;
|
||||||
|
if q.is_err() { return Ok("".to_owned()) };
|
||||||
|
let skey = q.unwrap().skey;
|
||||||
|
|
||||||
|
let skey = EncodingKey::from_rsa_der(&skey);
|
||||||
|
let header = Header::new(Algorithm::RS512);
|
||||||
|
let token = jwt_encode(&header, &data, &skey)?;
|
||||||
|
|
||||||
|
Ok(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rsa_gen() -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let bits = 2048;
|
||||||
|
let skey = RsaPrivateKey::new(&mut rng, bits).expect("RSA err");
|
||||||
|
let pkey = RsaPublicKey::from(&skey);
|
||||||
|
|
||||||
|
(
|
||||||
|
skey.to_pkcs1_der().unwrap().as_bytes().to_vec(),
|
||||||
|
pkey.to_pkcs1_der().unwrap().as_bytes().to_vec()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn jwt_verify(pool: DBPool, token: &str) -> Result<TokenData<Json>> {
|
||||||
|
let mut con = pool.get().await.unwrap();
|
||||||
|
let mut ret = TokenData { header: Header::new(Algorithm::RS512), claims: json![{}] };
|
||||||
|
|
||||||
|
let q = con
|
||||||
|
.query_parse::<Sites>(&query!("SELECT * FROM bitauth.sites WHERE uuid = ?", "0"))
|
||||||
|
.await;
|
||||||
|
if q.is_err() { return Ok(ret) };
|
||||||
|
let pkey = q.unwrap().pkey;
|
||||||
|
|
||||||
|
let pkey = DecodingKey::from_rsa_der(&pkey);
|
||||||
|
let token = jwt_decode::<Json>(&token, &pkey, &Validation::new(Algorithm::RS512));
|
||||||
|
|
||||||
|
match token.is_ok() {
|
||||||
|
true => ret = token.clone().unwrap(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_user(pool: DBPool, data: HashMap<String, String>) -> Res<bool, SkyError> {
|
||||||
|
let mut ret = true;
|
||||||
|
let mut con = pool.get().await.unwrap();
|
||||||
|
|
||||||
|
let login = &data.get("login").unwrap().trim().to_lowercase();
|
||||||
|
let email = &data.get("email").unwrap().trim().to_lowercase();
|
||||||
|
let pass = data.get("password").unwrap().trim();
|
||||||
|
let pass2 = data.get("password2").unwrap().trim();
|
||||||
|
|
||||||
|
if pass != pass2 { ret = false };
|
||||||
|
if pass.len() < 8 { ret = false };
|
||||||
|
|
||||||
|
let q = con
|
||||||
|
.query_parse::<(String,)>(&query!("SELECT uuid FROM bitauth.users_uuid WHERE login = ?", login))
|
||||||
|
.await;
|
||||||
|
if q.is_ok() { ret = false };
|
||||||
|
|
||||||
|
if ret {
|
||||||
|
let uuid = format!("{}", uuid_v4().as_hyphenated());
|
||||||
|
let pass = bcrypt::hash(pass, 12).unwrap();
|
||||||
|
|
||||||
|
let q = con.query_parse::<()>(&query!(
|
||||||
|
r#"INSERT INTO bitauth.users {
|
||||||
|
uuid: ?,
|
||||||
|
login: ?,
|
||||||
|
password: ?,
|
||||||
|
email: ?,
|
||||||
|
tokens: []
|
||||||
|
}"#,
|
||||||
|
uuid.clone(),
|
||||||
|
login.clone(),
|
||||||
|
pass,
|
||||||
|
email,
|
||||||
|
)).await;
|
||||||
|
if q.is_err() { ret = false }
|
||||||
|
|
||||||
|
let q = con.query_parse::<()>(&query!(
|
||||||
|
r#"INSERT INTO bitauth.users_uuid {
|
||||||
|
login: ?,
|
||||||
|
uuid: ?
|
||||||
|
}"#,
|
||||||
|
login.clone(),
|
||||||
|
uuid.clone()
|
||||||
|
)).await;
|
||||||
|
if q.is_err() { ret = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user(pool: DBPool, login: String) -> Res<(String,), SkyError> {
|
||||||
|
let mut con = pool.get().await.unwrap();
|
||||||
|
|
||||||
|
let q = con
|
||||||
|
.query_parse::<(String,)>(&query!("SELECT uuid FROM bitauth.users_uuid WHERE login = ?", login))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
q
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cookies(headers: HeaderMap) -> HashMap<String, String> {
|
||||||
|
let header = headers.get(hyper::header::COOKIE);
|
||||||
|
let cookies = match header.is_none() {
|
||||||
|
false => header.unwrap().to_str().unwrap(),
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
|
||||||
|
double_split(cookies.to_owned(), ";", "=")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn authorize_user(pool: DBPool, token: String, session: String) {
|
||||||
|
let mut con = pool.get().await.unwrap();
|
||||||
|
|
||||||
|
let data: JsonMap<String, Json> = jwt_verify(pool.clone(), &token)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.claims
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let login = data.get("login").unwrap().as_str();
|
||||||
|
let uuid = data.get("uuid").unwrap().as_str();
|
||||||
|
|
||||||
|
let _ = con.query_parse::<()>(&query!(
|
||||||
|
r#"INSERT INTO bitauth.v0 {
|
||||||
|
session: ?,
|
||||||
|
login: ?,
|
||||||
|
uuid: ?,
|
||||||
|
expire: ?
|
||||||
|
}"#,
|
||||||
|
session,
|
||||||
|
login,
|
||||||
|
uuid,
|
||||||
|
time() + APIV0_LIFETIME
|
||||||
|
)).await;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ pub const HEADER_HTML: &str = r#"
|
|||||||
<a href="/">index</a>
|
<a href="/">index</a>
|
||||||
<a href="/login">login</a>
|
<a href="/login">login</a>
|
||||||
<a href="/register">register</a>
|
<a href="/register">register</a>
|
||||||
|
<span>{USER_STATUS}</span>
|
||||||
<hr>
|
<hr>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
@ -16,7 +17,7 @@ pub const FOOTER_HTML: &str = r#"
|
|||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
<hr>
|
<hr>
|
||||||
Page time: PAGE_TIMEµs.
|
Page time: {PAGE_TIME}µs.
|
||||||
Made by <a href="//bitheaven.ru/">BitHeaven</a>.
|
Made by <a href="//bitheaven.ru/">BitHeaven</a>.
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
466
src/main.rs
466
src/main.rs
@ -1,7 +1,7 @@
|
|||||||
mod funcs;
|
mod funcs;
|
||||||
mod types;
|
mod types;
|
||||||
mod html;
|
mod html;
|
||||||
mod api;
|
mod url;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
std::{
|
std::{
|
||||||
@ -10,18 +10,13 @@ use {
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
process::exit,
|
process::exit,
|
||||||
},
|
},
|
||||||
chrono::{
|
|
||||||
DateTime,
|
|
||||||
},
|
|
||||||
http_body_util::{
|
http_body_util::{
|
||||||
Full,
|
Full,
|
||||||
BodyExt,
|
|
||||||
},
|
},
|
||||||
hyper::{
|
hyper::{
|
||||||
StatusCode,
|
StatusCode,
|
||||||
Request,
|
Request,
|
||||||
Response,
|
Response,
|
||||||
Method,
|
|
||||||
HeaderMap,
|
HeaderMap,
|
||||||
header::HeaderValue,
|
header::HeaderValue,
|
||||||
body::{
|
body::{
|
||||||
@ -37,28 +32,8 @@ use {
|
|||||||
tokio::{
|
tokio::{
|
||||||
net::TcpListener,
|
net::TcpListener,
|
||||||
},
|
},
|
||||||
rsa::{
|
|
||||||
pkcs1::{
|
|
||||||
EncodeRsaPublicKey,
|
|
||||||
EncodeRsaPrivateKey,
|
|
||||||
},
|
|
||||||
RsaPrivateKey,
|
|
||||||
RsaPublicKey,
|
|
||||||
},
|
|
||||||
jsonwebtoken as jwt,
|
|
||||||
jwt::{
|
|
||||||
Header,
|
|
||||||
Algorithm,
|
|
||||||
TokenData,
|
|
||||||
Validation,
|
|
||||||
EncodingKey,
|
|
||||||
DecodingKey,
|
|
||||||
encode as jwt_encode,
|
|
||||||
decode as jwt_decode,
|
|
||||||
},
|
|
||||||
serde_json::{
|
serde_json::{
|
||||||
Value as Json,
|
Value as Json,
|
||||||
Map as JsonMap,
|
|
||||||
json,
|
json,
|
||||||
},
|
},
|
||||||
skytable::{
|
skytable::{
|
||||||
@ -73,6 +48,9 @@ use {
|
|||||||
bb8::{
|
bb8::{
|
||||||
Pool,
|
Pool,
|
||||||
},
|
},
|
||||||
|
urlencoding::{
|
||||||
|
encode as url_encode,
|
||||||
|
},
|
||||||
crate::{
|
crate::{
|
||||||
types::{
|
types::{
|
||||||
users::Users,
|
users::Users,
|
||||||
@ -89,17 +67,19 @@ type DBPool = Arc<Pool<ConnectionMgrTcp>>;
|
|||||||
type FullBytes = Result<Response<Full<Bytes>>>;
|
type FullBytes = Result<Response<Full<Bytes>>>;
|
||||||
|
|
||||||
|
|
||||||
const PORT: u16 = 8083;
|
const PORT: u16 = 8051;
|
||||||
|
|
||||||
const DB_POOL: u32 = 32;
|
const DB_POOL: u32 = 32;
|
||||||
const DB_ADDR: &str = "192.168.1.49";
|
const DB_ADDR: &str = "127.0.0.1";
|
||||||
const DB_PORT: u16 = 2003;
|
const DB_PORT: u16 = 2003;
|
||||||
const DB_USER: &str = "root";
|
const DB_USER: &str = "root";
|
||||||
const DB_PASS: &str = "rootpass12345678";
|
const DB_PASS: &str = "dBk6wUAynGRRLsSF";
|
||||||
|
|
||||||
const TOKEN_LIFETIME: u32 = 300;
|
const TOKEN_LIFETIME: u32 = 300;
|
||||||
const REFRESH_LIFETIME: u32 = 2_678_400;
|
const REFRESH_LIFETIME: u32 = 2_678_400;
|
||||||
|
|
||||||
|
const APIV0_LIFETIME: u32 = 120;
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
@ -175,13 +155,6 @@ async fn handle_connection(req: Request<Incoming>, pool: DBPool, ip: String) ->
|
|||||||
x if x.starts_with("/api/") => {},
|
x if x.starts_with("/api/") => {},
|
||||||
_ => 'jwt_check: {
|
_ => 'jwt_check: {
|
||||||
if token == "" { break 'jwt_check; }
|
if token == "" { break 'jwt_check; }
|
||||||
/* if token != "" {
|
|
||||||
parts.status = StatusCode::FOUND;
|
|
||||||
set_cookie(&mut headers, "token", "");
|
|
||||||
set_location(&mut headers, "/login");
|
|
||||||
parts.headers = headers;
|
|
||||||
return Ok(Response::from_parts(parts, Full::new(Bytes::new())));
|
|
||||||
}*/
|
|
||||||
|
|
||||||
let is_live = jwt_verify(pool.clone(), &token)
|
let is_live = jwt_verify(pool.clone(), &token)
|
||||||
.await?
|
.await?
|
||||||
@ -217,15 +190,16 @@ async fn handle_connection(req: Request<Incoming>, pool: DBPool, ip: String) ->
|
|||||||
let newref = format!("{}", uuid_v4().as_hyphenated());
|
let newref = format!("{}", uuid_v4().as_hyphenated());
|
||||||
let time = time();
|
let time = time();
|
||||||
|
|
||||||
// TODO: FIX ERROR IF TOKEN INVALID
|
let q = con.query_parse::<(String,)>(&query!(
|
||||||
let (uuid,) = con.query_parse::<(String,)>(&query!(
|
|
||||||
r#"
|
r#"
|
||||||
SELECT uid
|
SELECT uid
|
||||||
FROM bitauth.tokens
|
FROM bitauth.tokens
|
||||||
WHERE uuid = ?
|
WHERE uuid = ?
|
||||||
"#,
|
"#,
|
||||||
tokenid
|
tokenid
|
||||||
)).await?;
|
)).await;
|
||||||
|
if q.is_err() { break 'ref_check; }
|
||||||
|
let (uuid,) = q.unwrap();
|
||||||
|
|
||||||
let (login,) = con.query_parse::<(String,)>(&query!(
|
let (login,) = con.query_parse::<(String,)>(&query!(
|
||||||
r#"
|
r#"
|
||||||
@ -265,414 +239,40 @@ async fn handle_connection(req: Request<Incoming>, pool: DBPool, ip: String) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if x == "/authorize" {
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(body, parts.status, restype) = match req.uri().path().as_ref() {
|
(body, parts.status, restype) = match req.uri().path().as_ref() {
|
||||||
"/" => uri_index(),
|
"/" => url::index::index(),
|
||||||
"/cabinet" => uri_login(req, pool.clone(), &mut headers).await?,
|
"/cabinet" => url::login::login(req, pool.clone(), &mut headers).await?,
|
||||||
"/login" => uri_login(req, pool.clone(), &mut headers).await?,
|
"/login" => url::login::login(req, pool.clone(), &mut headers).await?,
|
||||||
x if x == "/authorize" && logged => uri_authorize(req, pool.clone(), token).await?,
|
x if x == "/authorize" && logged => url::authorize::authorize(req, pool.clone(), token).await?,
|
||||||
// "/authorize" => uri_authorize(req, pool.clone()).await?,
|
"/authorize" => uri_auth_required(req, &mut headers).await?,
|
||||||
"/register" => uri_register(req, pool.clone(), &mut headers).await?,
|
"/register" => url::register::register(req, pool.clone(), &mut headers).await?,
|
||||||
"/recover" => uri_recover(),
|
"/recover" => url::recover::recover(),
|
||||||
x if x.starts_with("/@") => uri_user(req, pool.clone()).await?,
|
x if x.starts_with("/@") => url::user::user(req, pool.clone()).await?,
|
||||||
x if x.starts_with("/api/") => api::endpoint(req, pool.clone()).await,
|
x if x.starts_with("/api/") => url::api::endpoint(req, pool.clone()).await,
|
||||||
_ => uri_404()
|
_ => url::nf::nf()
|
||||||
};
|
};
|
||||||
|
|
||||||
headers.insert(hyper::header::CONTENT_TYPE, restype);
|
headers.insert(hyper::header::CONTENT_TYPE, restype);
|
||||||
parts.headers = headers;
|
parts.headers = headers;
|
||||||
|
|
||||||
let body = body.replace("PAGE_TIME", &format!("{}", time_mcs() - t));
|
let user_status = match logged {
|
||||||
|
true => "AUTHORIZED",
|
||||||
|
_ => "NOT AUTHORIZED"
|
||||||
|
};
|
||||||
|
let body = body.replace("{USER_STATUS}", user_status);
|
||||||
|
let body = body.replace("{PAGE_TIME}", &format!("{}", time_mcs() - t));
|
||||||
Ok(Response::from_parts(parts, Full::new(Bytes::from(body))))
|
Ok(Response::from_parts(parts, Full::new(Bytes::from(body))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_cookie(headers: &mut HeaderMap, key: &str, value: &str) {
|
|
||||||
let time = DateTime::from_timestamp((time() + REFRESH_LIFETIME) as i64, 0)
|
|
||||||
.expect("REASON")
|
|
||||||
.to_rfc2822();
|
|
||||||
let time = time.replace("+0000", "GMT");
|
|
||||||
headers.append(
|
|
||||||
hyper::header::SET_COOKIE,
|
|
||||||
HeaderValue::from_str(format!("{}={}; HttpOnly; Expires={}", key, value, time).as_str())
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cookies(headers: HeaderMap) -> HashMap<String, String> {
|
async fn uri_auth_required(req: Request<Incoming>, headers: &mut HeaderMap) -> Result<(String, StatusCode, HeaderValue)> {
|
||||||
let header = headers.get(hyper::header::COOKIE);
|
let url = url_encode(req.uri().path_and_query().unwrap().as_str());
|
||||||
let cookies = match header.is_none() {
|
|
||||||
false => header.unwrap().to_str().unwrap(),
|
|
||||||
_ => ""
|
|
||||||
};
|
|
||||||
|
|
||||||
double_split(cookies.to_owned(), ";", "=")
|
set_location(headers, format!("/login?q={}", url).as_str());
|
||||||
}
|
|
||||||
|
|
||||||
fn set_location(headers: &mut HeaderMap, location: &str) {
|
|
||||||
headers.append(
|
|
||||||
hyper::header::LOCATION,
|
|
||||||
HeaderValue::from_str(format!("{}", location).as_str()).unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn uri_login(req: Request<Incoming>, pool: DBPool, headers: &mut HeaderMap) -> Result<(String, StatusCode, HeaderValue)> {
|
|
||||||
if *req.method() == Method::POST {
|
|
||||||
let body = get_body_from_request(req).await?;
|
|
||||||
let body = String::from_utf8(body).unwrap();
|
|
||||||
let body = double_split(body, "&", "=");
|
|
||||||
|
|
||||||
let (access, refresh) = login_user(pool.clone(), body).await?;
|
|
||||||
|
|
||||||
set_cookie(headers, "token", &access);
|
|
||||||
set_cookie(headers, "refresh", &refresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
let restype: HeaderValue = "text/html".parse().unwrap();
|
let restype: HeaderValue = "text/html".parse().unwrap();
|
||||||
Ok((build_html(LOGIN_HTML), StatusCode::OK, restype))
|
Ok(("".to_owned(), StatusCode::FOUND, restype))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn uri_authorize(req: Request<Incoming>, pool: DBPool, token: String) -> Result<(String, StatusCode, HeaderValue)> {
|
|
||||||
if *req.method() == Method::POST {
|
|
||||||
let r = double_split(req.uri().query().or(Some("")).unwrap().to_owned(), "&", "=");
|
|
||||||
|
|
||||||
let session = r.get("session");
|
|
||||||
let session = match session.is_none() {
|
|
||||||
false => session.unwrap().to_owned(),
|
|
||||||
_ => "".to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
if session != "" {
|
|
||||||
authorize_user(pool.clone(), token, session).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let restype: HeaderValue = "text/html".parse().unwrap();
|
|
||||||
Ok((build_html(AUTHORIZE_HTML), StatusCode::OK, restype))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn authorize_user(pool: DBPool, token: String, session: String) {
|
|
||||||
let mut con = pool.get().await.unwrap();
|
|
||||||
|
|
||||||
let data: JsonMap<String, Json> = jwt_verify(pool.clone(), &token)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.claims
|
|
||||||
.as_object()
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let login = data.get("login").unwrap().as_str();
|
|
||||||
let uuid = data.get("uuid").unwrap().as_str();
|
|
||||||
|
|
||||||
let _ = con.query_parse::<()>(&query!(
|
|
||||||
r#"INSERT INTO bitauth.v0 {
|
|
||||||
session: ?,
|
|
||||||
login: ?,
|
|
||||||
uuid: ?
|
|
||||||
}"#,
|
|
||||||
session,
|
|
||||||
login,
|
|
||||||
uuid
|
|
||||||
)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn uri_index() -> (String, StatusCode, HeaderValue) {
|
|
||||||
let restype: HeaderValue = "text/html".parse().unwrap();
|
|
||||||
(build_html(INDEX_HTML), StatusCode::OK, restype)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn uri_register(req: Request<Incoming>, pool: DBPool, headers: &mut HeaderMap) -> Result<(String, StatusCode, HeaderValue)> {
|
|
||||||
let mut body = "".to_owned();
|
|
||||||
let mut status = StatusCode::OK;
|
|
||||||
let restype: HeaderValue = "text/html".parse().unwrap();
|
|
||||||
|
|
||||||
if *req.method() == Method::POST {
|
|
||||||
let request = get_body_from_request(req).await?;
|
|
||||||
let request = String::from_utf8(request).unwrap();
|
|
||||||
let request = double_split(request, "&", "=");
|
|
||||||
|
|
||||||
match create_user(pool.clone(), request).await? {
|
|
||||||
true => {
|
|
||||||
println!("Created");
|
|
||||||
set_location(headers, "/login");
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
println!("Failed");
|
|
||||||
set_location(headers, "/register");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
status = StatusCode::FOUND;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
body = build_html(REG_HTML);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((body, status, restype))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn uri_recover() -> (String, StatusCode, HeaderValue) {
|
|
||||||
let restype: HeaderValue = "text/html".parse().unwrap();
|
|
||||||
(build_html(RECOVER_HTML), StatusCode::OK, restype)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn uri_user(req: Request<Incoming>, pool: DBPool) -> Res<(String, StatusCode, HeaderValue), SkyError> {
|
|
||||||
let uri: &str = req.uri().path().as_ref();
|
|
||||||
let login = &uri[2..uri.len()];
|
|
||||||
let body: String;
|
|
||||||
|
|
||||||
let user = get_user(pool, login.to_string()).await;
|
|
||||||
if user.is_ok() {
|
|
||||||
body = format!("{}", user?.uuid);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
body = "Not fond :(".to_owned();
|
|
||||||
}
|
|
||||||
|
|
||||||
let restype: HeaderValue = "text/html".parse().unwrap();
|
|
||||||
Ok((build_html(&body), StatusCode::OK, restype))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn uri_404() -> (String, StatusCode, HeaderValue) {
|
|
||||||
let restype: HeaderValue = "text/html".parse().unwrap();
|
|
||||||
(build_html(NF_HTML), StatusCode::NOT_FOUND, restype)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn init_tables(pool: DBPool) -> Res<(), SkyError> {
|
|
||||||
let mut con = pool.get().await.unwrap();
|
|
||||||
|
|
||||||
let _ = con.query_parse::<()>(&query!("CREATE SPACE IF NOT EXISTS bitauth")).await;
|
|
||||||
let _ = con.query_parse::<()>(&query!(r#"
|
|
||||||
CREATE MODEL IF NOT EXISTS bitauth.users(
|
|
||||||
uuid: string,
|
|
||||||
login: string,
|
|
||||||
password: string,
|
|
||||||
email: string,
|
|
||||||
tokens: list {type: string}
|
|
||||||
)
|
|
||||||
"#)).await;
|
|
||||||
let _ = con.query_parse::<()>(&query!(r#"
|
|
||||||
CREATE MODEL IF NOT EXISTS bitauth.users_uuid(
|
|
||||||
login: string,
|
|
||||||
uuid: string
|
|
||||||
)
|
|
||||||
"#)).await;
|
|
||||||
let _ = con.query_parse::<()>(&query!(r#"
|
|
||||||
CREATE MODEL IF NOT EXISTS bitauth.sites(
|
|
||||||
uuid: string,
|
|
||||||
uid: string,
|
|
||||||
domain: string,
|
|
||||||
pkey: binary,
|
|
||||||
skey: binary
|
|
||||||
)
|
|
||||||
"#)).await;
|
|
||||||
let _ = con.query_parse::<()>(&query!(r#"
|
|
||||||
CREATE MODEL IF NOT EXISTS bitauth.tokens(
|
|
||||||
uuid: string,
|
|
||||||
uid: string,
|
|
||||||
sid: string,
|
|
||||||
ref: string,
|
|
||||||
refend: uint32
|
|
||||||
)
|
|
||||||
"#)).await;
|
|
||||||
let _ = con.query_parse::<()>(&query!(r#"
|
|
||||||
CREATE MODEL IF NOT EXISTS bitauth.v0(
|
|
||||||
session: string,
|
|
||||||
login: string,
|
|
||||||
uuid: string
|
|
||||||
)
|
|
||||||
"#)).await;
|
|
||||||
|
|
||||||
let q = con.query_parse::<Sites>(&query!("SELECT * FROM bitauth.sites WHERE uuid = 0")).await;
|
|
||||||
if q.is_err() {
|
|
||||||
let (skey, pkey) = rsa_gen();
|
|
||||||
let _ = con.query_parse::<()>(&query!(
|
|
||||||
"INSERT INTO bitauth.sites { uuid: ?, uid: ?, domain: ?, pkey: ?, skey: ? }",
|
|
||||||
"0", "0", "", pkey, skey
|
|
||||||
)).await;
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_user(pool: DBPool, login: String) -> Res<Users, SkyError> {
|
|
||||||
let mut con = pool.get().await.unwrap();
|
|
||||||
|
|
||||||
let q = con
|
|
||||||
.query_parse::<Users>(&query!("SELECT * FROM bitauth.users WHERE login = ?", login))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
Ok(q?)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn login_user(pool: DBPool, data: HashMap<String, String>) -> Res<(String, String), SkyError> {
|
|
||||||
let mut ret: (String, String) = Default::default();
|
|
||||||
let mut con = pool.get().await.unwrap();
|
|
||||||
|
|
||||||
let login = &data.get("login").unwrap().trim().to_lowercase();
|
|
||||||
let pass = data.get("password").unwrap().trim();
|
|
||||||
|
|
||||||
let q = con
|
|
||||||
.query_parse::<(String,)>(&query!("SELECT uuid FROM bitauth.users_uuid WHERE login = ?", login))
|
|
||||||
.await;
|
|
||||||
if q.is_err() { println!("{:?}", q.err()); return Ok(ret); };
|
|
||||||
let (uuid,) = q.unwrap();
|
|
||||||
|
|
||||||
let q = con
|
|
||||||
.query_parse::<Users>(&query!("SELECT * FROM bitauth.users WHERE uuid = ?", uuid.clone()))
|
|
||||||
.await;
|
|
||||||
// TODO: Send to admin notify about trouble!
|
|
||||||
if q.is_err() { return Ok(ret); };
|
|
||||||
|
|
||||||
let q = q.unwrap();
|
|
||||||
if bcrypt::verify(pass, q.password.as_str()).unwrap() {
|
|
||||||
let token = format!("{}", uuid_v4().as_hyphenated());
|
|
||||||
let reftoken = format!("{}", uuid_v4().as_hyphenated());
|
|
||||||
let time = time();
|
|
||||||
let uuid: String = q.uuid;
|
|
||||||
let login: String = q.login;
|
|
||||||
|
|
||||||
let _ = con.query_parse::<()>(&query!(
|
|
||||||
"INSERT INTO bitauth.tokens { uuid: ?, uid: ?, sid: ?, ref: ?, refend: ? }",
|
|
||||||
token.clone(), uuid.clone(), "0", reftoken.clone(), time + REFRESH_LIFETIME
|
|
||||||
)).await;
|
|
||||||
let _ = con.query_parse::<()>(&query!(
|
|
||||||
"UPDATE bitauth.users SET tokens += ? WHERE login = ?",
|
|
||||||
token.clone(), login.clone()
|
|
||||||
)).await;
|
|
||||||
|
|
||||||
ret = (
|
|
||||||
jwt_sign(pool.clone(), json!({
|
|
||||||
"login": login.clone(),
|
|
||||||
"uuid": uuid.clone(),
|
|
||||||
"iat": time,
|
|
||||||
"exp": time + TOKEN_LIFETIME
|
|
||||||
})).await.unwrap(),
|
|
||||||
jwt_sign(pool.clone(), json!({
|
|
||||||
"uuid": token.clone(),
|
|
||||||
"iat": time,
|
|
||||||
"ref": reftoken.clone(),
|
|
||||||
"exp": time + REFRESH_LIFETIME
|
|
||||||
})).await.unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_user(pool: DBPool, data: HashMap<String, String>) -> Res<bool, SkyError> {
|
|
||||||
let mut ret = true;
|
|
||||||
let mut con = pool.get().await.unwrap();
|
|
||||||
|
|
||||||
let login = &data.get("login").unwrap().trim().to_lowercase();
|
|
||||||
let email = &data.get("email").unwrap().trim().to_lowercase();
|
|
||||||
let pass = data.get("password").unwrap().trim();
|
|
||||||
let pass2 = data.get("password2").unwrap().trim();
|
|
||||||
|
|
||||||
if pass != pass2 { ret = false };
|
|
||||||
if pass.len() < 8 { ret = false };
|
|
||||||
|
|
||||||
let q = con
|
|
||||||
.query_parse::<(String,)>(&query!("SELECT uuid FROM bitauth.users_uuid WHERE login = ?", login))
|
|
||||||
.await;
|
|
||||||
if q.is_ok() { ret = false };
|
|
||||||
|
|
||||||
if ret {
|
|
||||||
let uuid = format!("{}", uuid_v4().as_hyphenated());
|
|
||||||
let pass = bcrypt::hash(pass, 12).unwrap();
|
|
||||||
|
|
||||||
let q = con.query_parse::<()>(&query!(
|
|
||||||
r#"INSERT INTO bitauth.users {
|
|
||||||
uuid: ?,
|
|
||||||
login: ?,
|
|
||||||
password: ?,
|
|
||||||
email: ?,
|
|
||||||
tokens: []
|
|
||||||
}"#,
|
|
||||||
uuid.clone(),
|
|
||||||
login.clone(),
|
|
||||||
pass,
|
|
||||||
email,
|
|
||||||
)).await;
|
|
||||||
if q.is_err() { ret = false }
|
|
||||||
|
|
||||||
let q = con.query_parse::<()>(&query!(
|
|
||||||
r#"INSERT INTO bitauth.users_uuid {
|
|
||||||
login: ?,
|
|
||||||
uuid: ?
|
|
||||||
}"#,
|
|
||||||
login.clone(),
|
|
||||||
uuid.clone()
|
|
||||||
)).await;
|
|
||||||
if q.is_err() { ret = false }
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_body_from_request(mut req: Request<Incoming>) -> Result<Vec<u8>> {
|
|
||||||
let mut body: Vec<u8> = vec![];
|
|
||||||
while let Some(next) = req.frame().await {
|
|
||||||
let frame = next?;
|
|
||||||
if let Some(chunk) = frame.data_ref() {
|
|
||||||
body.extend_from_slice(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rsa_gen() -> (Vec<u8>, Vec<u8>) {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let bits = 2048;
|
|
||||||
let skey = RsaPrivateKey::new(&mut rng, bits).expect("RSA err");
|
|
||||||
let pkey = RsaPublicKey::from(&skey);
|
|
||||||
|
|
||||||
(
|
|
||||||
skey.to_pkcs1_der().unwrap().as_bytes().to_vec(),
|
|
||||||
pkey.to_pkcs1_der().unwrap().as_bytes().to_vec()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn jwt_sign(pool: DBPool, data: Json) -> Result<String> {
|
|
||||||
let mut con = pool.get().await.unwrap();
|
|
||||||
|
|
||||||
let q = con
|
|
||||||
.query_parse::<Sites>(&query!("SELECT * FROM bitauth.sites WHERE uuid = ?", "0"))
|
|
||||||
.await;
|
|
||||||
if q.is_err() { return Ok("".to_owned()) };
|
|
||||||
let skey = q.unwrap().skey;
|
|
||||||
|
|
||||||
let skey = EncodingKey::from_rsa_der(&skey);
|
|
||||||
let header = Header::new(Algorithm::RS512);
|
|
||||||
let token = jwt_encode(&header, &data, &skey)?;
|
|
||||||
|
|
||||||
Ok(token)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn jwt_verify(pool: DBPool, token: &str) -> Result<TokenData<Json>> {
|
|
||||||
let mut con = pool.get().await.unwrap();
|
|
||||||
let mut ret = TokenData { header: Header::new(Algorithm::RS512), claims: json![{}] };
|
|
||||||
|
|
||||||
let q = con
|
|
||||||
.query_parse::<Sites>(&query!("SELECT * FROM bitauth.sites WHERE uuid = ?", "0"))
|
|
||||||
.await;
|
|
||||||
if q.is_err() { return Ok(ret) };
|
|
||||||
let pkey = q.unwrap().pkey;
|
|
||||||
|
|
||||||
let pkey = DecodingKey::from_rsa_der(&pkey);
|
|
||||||
let token = jwt_decode::<Json>(&token, &pkey, &Validation::new(Algorithm::RS512));
|
|
||||||
|
|
||||||
match token.is_ok() {
|
|
||||||
true => ret = token.clone().unwrap(),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -1,31 +1,27 @@
|
|||||||
mod v0;
|
mod v0;
|
||||||
|
mod v1;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
hyper::{
|
hyper::{
|
||||||
StatusCode,
|
StatusCode,
|
||||||
Request,
|
Request,
|
||||||
header::HeaderValue,
|
header::HeaderValue,
|
||||||
body::{
|
body::Incoming,
|
||||||
Incoming,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
serde_json::{
|
serde_json::{
|
||||||
Value as Json,
|
Value as Json,
|
||||||
json,
|
json,
|
||||||
},
|
},
|
||||||
skytable::pool::ConnectionMgrTcp,
|
crate::DBPool,
|
||||||
bb8::Pool,
|
|
||||||
std::sync::Arc,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type DBPool = Arc<Pool<ConnectionMgrTcp>>;
|
|
||||||
|
|
||||||
|
|
||||||
pub async fn endpoint(req: Request<Incoming>, pool: DBPool) -> (String, StatusCode, HeaderValue) {
|
pub async fn endpoint(req: Request<Incoming>, pool: DBPool) -> (String, StatusCode, HeaderValue) {
|
||||||
let uri: &str = req.uri().path().as_ref();
|
let uri: &str = req.uri().path().as_ref();
|
||||||
let res: Json = match &uri[4..uri.len()] {
|
let res: Json = match &uri[4..uri.len()] {
|
||||||
"/test" => json!({"error": false, "msg": "test"}),
|
"/test" => json!({"error": false, "msg": "test"}),
|
||||||
x if x.starts_with("/v0/") => v0::api(req, pool.clone()).await,
|
x if x.starts_with("/v0/") => v0::api(req, pool.clone()).await,
|
||||||
|
x if x.starts_with("/v1/") => v1::api(req, pool.clone()).await,
|
||||||
_ => json!({"error": true, "msg": "No endpoint"})
|
_ => json!({"error": true, "msg": "No endpoint"})
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1,27 +1,20 @@
|
|||||||
use {
|
use {
|
||||||
hyper::{
|
hyper::{
|
||||||
Request,
|
Request,
|
||||||
body::{
|
body::Incoming,
|
||||||
Incoming,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
serde_json::{
|
serde_json::{
|
||||||
Value as Json,
|
Value as Json,
|
||||||
json,
|
json,
|
||||||
},
|
},
|
||||||
skytable::{
|
skytable::query,
|
||||||
pool::ConnectionMgrTcp,
|
|
||||||
query,
|
|
||||||
},
|
|
||||||
bb8::Pool,
|
|
||||||
std::sync::Arc,
|
|
||||||
crate::{
|
crate::{
|
||||||
|
DBPool,
|
||||||
|
time,
|
||||||
double_split,
|
double_split,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type DBPool = Arc<Pool<ConnectionMgrTcp>>;
|
|
||||||
|
|
||||||
|
|
||||||
pub async fn api(req: Request<Incoming>, pool: DBPool) -> Json {
|
pub async fn api(req: Request<Incoming>, pool: DBPool) -> Json {
|
||||||
let uri: &str = req.uri().path().as_ref();
|
let uri: &str = req.uri().path().as_ref();
|
||||||
@ -40,10 +33,11 @@ async fn auth(req: Request<Incoming>, _pool: DBPool) -> Json {
|
|||||||
.or(Some(&"".to_string()))
|
.or(Some(&"".to_string()))
|
||||||
.unwrap());
|
.unwrap());
|
||||||
match sess.as_str() {
|
match sess.as_str() {
|
||||||
"" => json!({"error": true, "msg": "No session in url"}),
|
"" => json!({"error": true, "msg": "No session in request"}),
|
||||||
|
x if x.len() > 128 => json!({"error": true, "msg": "Session len is too long"}),
|
||||||
_ => json!({
|
_ => json!({
|
||||||
"error": false,
|
"error": false,
|
||||||
"link": format!("https://auth.bitheaven.ru/authorize?session={}", sess)
|
"link": format!("https://auth.bitheaven.ru/authorize?v=0&session={}", sess)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,8 +52,8 @@ async fn auth_get(req: Request<Incoming>, pool: DBPool) -> Json {
|
|||||||
_ => ""
|
_ => ""
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = con.query_parse::<(String, String)>(&query!(
|
let res = con.query_parse::<(String, String, u32)>(&query!(
|
||||||
"SELECT login, uuid FROM bitauth.v0 WHERE session = ?",
|
"SELECT login, uuid, expire FROM bitauth.v0 WHERE session = ?",
|
||||||
session
|
session
|
||||||
)).await;
|
)).await;
|
||||||
let _ = con.query_parse::<()>(&query!(
|
let _ = con.query_parse::<()>(&query!(
|
||||||
@ -67,13 +61,15 @@ async fn auth_get(req: Request<Incoming>, pool: DBPool) -> Json {
|
|||||||
session
|
session
|
||||||
)).await;
|
)).await;
|
||||||
|
|
||||||
let (login, uuid) = match res.is_ok() {
|
let (login, uuid, exp) = match res.is_ok() {
|
||||||
false => ("".to_owned(), "".to_owned()),
|
false => ("".to_owned(), "".to_owned(), 0),
|
||||||
_ => res.unwrap()
|
_ => res.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
match login {
|
if login.as_str() == "" || exp < time() {
|
||||||
"" => json!({"error": true, "msg": "Not auth yet"}),
|
json!({"error": true, "msg": "Not auth yet"})
|
||||||
_ => json!({"error": false, "login": login, "uuid": uuid})
|
}
|
||||||
|
else {
|
||||||
|
json!({"error": false, "login": login, "uuid": uuid})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
75
src/url/api/v1.rs
Normal file
75
src/url/api/v1.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use {
|
||||||
|
hyper::{
|
||||||
|
Request,
|
||||||
|
body::Incoming,
|
||||||
|
},
|
||||||
|
serde_json::{
|
||||||
|
Value as Json,
|
||||||
|
json,
|
||||||
|
},
|
||||||
|
skytable::query,
|
||||||
|
crate::{
|
||||||
|
DBPool,
|
||||||
|
time,
|
||||||
|
double_split,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn api(req: Request<Incoming>, pool: DBPool) -> Json {
|
||||||
|
let uri: &str = req.uri().path().as_ref();
|
||||||
|
match &uri[7..uri.len()] {
|
||||||
|
"/auth" => auth(req, pool.clone()).await,
|
||||||
|
"/auth_get" => auth_get(req, pool.clone()).await,
|
||||||
|
_ => json!({"error": true, "msg": "No endpoint"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn auth(req: Request<Incoming>, _pool: DBPool) -> Json {
|
||||||
|
let query = req.uri().query().or(Some("")).unwrap();
|
||||||
|
let query = double_split(query.to_string(), "&", "=");
|
||||||
|
let sess = std::string::String::from(query
|
||||||
|
.get("session")
|
||||||
|
.or(Some(&"".to_string()))
|
||||||
|
.unwrap());
|
||||||
|
match sess.as_str() {
|
||||||
|
"" => json!({"error": true, "msg": "No session in request"}),
|
||||||
|
x if x.len() > 128 => json!({"error": true, "msg": "Session len is too long"}),
|
||||||
|
_ => json!({
|
||||||
|
"error": false,
|
||||||
|
"link": format!("https://auth.bitheaven.ru/authorize?v=0&session={}", sess)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn auth_get(req: Request<Incoming>, pool: DBPool) -> Json {
|
||||||
|
let mut con = pool.get().await.unwrap();
|
||||||
|
|
||||||
|
let query = req.uri().query().or(Some("")).unwrap();
|
||||||
|
let query = double_split(query.to_string(), "&", "=");
|
||||||
|
let session = match query.get("session").is_none() {
|
||||||
|
false => query.get("session").unwrap(),
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = con.query_parse::<(String, String, u32)>(&query!(
|
||||||
|
"SELECT login, uuid, expire FROM bitauth.v0 WHERE session = ?",
|
||||||
|
session
|
||||||
|
)).await;
|
||||||
|
let _ = con.query_parse::<()>(&query!(
|
||||||
|
"DELETE FROM bitauth.v0 WHERE session = ?",
|
||||||
|
session
|
||||||
|
)).await;
|
||||||
|
|
||||||
|
let (login, uuid, exp) = match res.is_ok() {
|
||||||
|
false => ("".to_owned(), "".to_owned(), 0),
|
||||||
|
_ => res.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
if login.as_str() == "" || exp < time() {
|
||||||
|
json!({"error": true, "msg": "Not auth yet"})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
json!({"error": false, "login": login, "uuid": uuid})
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/url/authorize.rs
Normal file
38
src/url/authorize.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use {
|
||||||
|
hyper::{
|
||||||
|
Request,
|
||||||
|
body::Incoming,
|
||||||
|
Method,
|
||||||
|
StatusCode,
|
||||||
|
header::HeaderValue,
|
||||||
|
},
|
||||||
|
crate::{
|
||||||
|
AUTHORIZE_HTML,
|
||||||
|
DBPool,
|
||||||
|
Result,
|
||||||
|
build_html,
|
||||||
|
double_split,
|
||||||
|
authorize_user,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn authorize(req: Request<Incoming>, pool: DBPool, token: String) -> Result<(String, StatusCode, HeaderValue)> {
|
||||||
|
// TODO: Forward for versions.
|
||||||
|
if *req.method() == Method::POST {
|
||||||
|
let r = double_split(req.uri().query().or(Some("")).unwrap().to_owned(), "&", "=");
|
||||||
|
|
||||||
|
let session = r.get("session");
|
||||||
|
let session = match session.is_none() {
|
||||||
|
false => session.unwrap().to_owned(),
|
||||||
|
_ => "".to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
if session != "" && session.len() <= 128 {
|
||||||
|
authorize_user(pool.clone(), token, session).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let restype: HeaderValue = "text/html".parse().unwrap();
|
||||||
|
Ok((build_html(AUTHORIZE_HTML), StatusCode::OK, restype))
|
||||||
|
}
|
||||||
16
src/url/index.rs
Normal file
16
src/url/index.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use {
|
||||||
|
hyper::{
|
||||||
|
StatusCode,
|
||||||
|
header::HeaderValue,
|
||||||
|
},
|
||||||
|
crate::{
|
||||||
|
INDEX_HTML,
|
||||||
|
build_html,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub fn index() -> (String, StatusCode, HeaderValue) {
|
||||||
|
let restype: HeaderValue = "text/html".parse().unwrap();
|
||||||
|
(build_html(INDEX_HTML), StatusCode::OK, restype)
|
||||||
|
}
|
||||||
53
src/url/login.rs
Normal file
53
src/url/login.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use {
|
||||||
|
hyper::{
|
||||||
|
StatusCode,
|
||||||
|
Request,
|
||||||
|
Method,
|
||||||
|
HeaderMap,
|
||||||
|
header::HeaderValue,
|
||||||
|
body::{
|
||||||
|
Incoming,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
urlencoding::{
|
||||||
|
decode as url_decode,
|
||||||
|
},
|
||||||
|
crate::{
|
||||||
|
Result,
|
||||||
|
DBPool,
|
||||||
|
html::*,
|
||||||
|
funcs::*,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn login(req: Request<Incoming>, pool: DBPool, headers: &mut HeaderMap) -> Result<(String, StatusCode, HeaderValue)> {
|
||||||
|
let mut body = build_html(LOGIN_HTML);
|
||||||
|
let mut status = StatusCode::OK;
|
||||||
|
let restype: HeaderValue = "text/html".parse().unwrap();
|
||||||
|
|
||||||
|
if *req.method() == Method::POST {
|
||||||
|
let r = double_split(req.uri().query().or(Some("")).unwrap().to_owned(), "&", "=");
|
||||||
|
|
||||||
|
let post = get_body_from_request(req).await?;
|
||||||
|
let post = String::from_utf8(post).unwrap();
|
||||||
|
let post = double_split(post, "&", "=");
|
||||||
|
|
||||||
|
let (access, refresh) = login_user(pool.clone(), post).await?;
|
||||||
|
|
||||||
|
set_cookie(headers, "token", &access);
|
||||||
|
set_cookie(headers, "refresh", &refresh);
|
||||||
|
|
||||||
|
let r = r.get("q");
|
||||||
|
match r.is_some() {
|
||||||
|
true => {
|
||||||
|
status = StatusCode::FOUND;
|
||||||
|
body = "".to_owned();
|
||||||
|
set_location(headers, format!("{}", url_decode(r.unwrap())?).as_str());
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((body, status, restype))
|
||||||
|
}
|
||||||
8
src/url/mod.rs
Normal file
8
src/url/mod.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
pub mod api;
|
||||||
|
pub mod login;
|
||||||
|
pub mod index;
|
||||||
|
pub mod authorize;
|
||||||
|
pub mod nf;
|
||||||
|
pub mod register;
|
||||||
|
pub mod user;
|
||||||
|
pub mod recover;
|
||||||
16
src/url/nf.rs
Normal file
16
src/url/nf.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use {
|
||||||
|
hyper::{
|
||||||
|
StatusCode,
|
||||||
|
header::HeaderValue,
|
||||||
|
},
|
||||||
|
crate::{
|
||||||
|
NF_HTML,
|
||||||
|
build_html,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub fn nf() -> (String, StatusCode, HeaderValue) {
|
||||||
|
let restype: HeaderValue = "text/html".parse().unwrap();
|
||||||
|
(build_html(NF_HTML), StatusCode::NOT_FOUND, restype)
|
||||||
|
}
|
||||||
16
src/url/recover.rs
Normal file
16
src/url/recover.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use {
|
||||||
|
hyper::{
|
||||||
|
StatusCode,
|
||||||
|
header::HeaderValue,
|
||||||
|
},
|
||||||
|
crate::{
|
||||||
|
build_html,
|
||||||
|
RECOVER_HTML,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub fn recover() -> (String, StatusCode, HeaderValue) {
|
||||||
|
let restype: HeaderValue = "text/html".parse().unwrap();
|
||||||
|
(build_html(RECOVER_HTML), StatusCode::OK, restype)
|
||||||
|
}
|
||||||
51
src/url/register.rs
Normal file
51
src/url/register.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use {
|
||||||
|
hyper::{
|
||||||
|
body::Incoming,
|
||||||
|
HeaderMap,
|
||||||
|
Method,
|
||||||
|
Request,
|
||||||
|
header::HeaderValue,
|
||||||
|
StatusCode,
|
||||||
|
},
|
||||||
|
crate::{
|
||||||
|
Result,
|
||||||
|
DBPool,
|
||||||
|
REG_HTML,
|
||||||
|
build_html,
|
||||||
|
set_location,
|
||||||
|
create_user,
|
||||||
|
double_split,
|
||||||
|
get_body_from_request,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn register(req: Request<Incoming>, pool: DBPool, headers: &mut HeaderMap) -> Result<(String, StatusCode, HeaderValue)> {
|
||||||
|
let mut body = "".to_owned();
|
||||||
|
let mut status = StatusCode::OK;
|
||||||
|
let restype: HeaderValue = "text/html".parse().unwrap();
|
||||||
|
|
||||||
|
if *req.method() == Method::POST {
|
||||||
|
let request = get_body_from_request(req).await?;
|
||||||
|
let request = String::from_utf8(request).unwrap();
|
||||||
|
let request = double_split(request, "&", "=");
|
||||||
|
|
||||||
|
match create_user(pool.clone(), request).await? {
|
||||||
|
true => {
|
||||||
|
println!("Created");
|
||||||
|
set_location(headers, "/login");
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
println!("Failed");
|
||||||
|
set_location(headers, "/register");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = StatusCode::FOUND;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
body = build_html(REG_HTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((body, status, restype))
|
||||||
|
}
|
||||||
34
src/url/user.rs
Normal file
34
src/url/user.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use {
|
||||||
|
hyper::{
|
||||||
|
Request,
|
||||||
|
StatusCode,
|
||||||
|
body::Incoming,
|
||||||
|
header::HeaderValue,
|
||||||
|
},
|
||||||
|
crate::{
|
||||||
|
Res,
|
||||||
|
DBPool,
|
||||||
|
SkyError,
|
||||||
|
get_user,
|
||||||
|
build_html,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn user(req: Request<Incoming>, pool: DBPool) -> Res<(String, StatusCode, HeaderValue), SkyError> {
|
||||||
|
let uri: &str = req.uri().path().as_ref();
|
||||||
|
let login = &uri[2..uri.len()];
|
||||||
|
let body: String;
|
||||||
|
|
||||||
|
let user = get_user(pool, login.to_string()).await;
|
||||||
|
if user.is_ok() {
|
||||||
|
let (uuid,) = user.unwrap();
|
||||||
|
body = format!("{}", uuid);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
body = "Not fond :(".to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
|
let restype: HeaderValue = "text/html".parse().unwrap();
|
||||||
|
Ok((build_html(&body), StatusCode::OK, restype))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user