Move a lot funcs...
This commit is contained in:
parent
2a9ac271a7
commit
6cd6b12630
23
TODO
23
TODO
@ -9,17 +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)
|
||||||
/v0/auth?session=<UNIQUE>&back_url=<OPTIONAL>
|
/api/v0/auth?session=<UNIQUE>&back_url=<OPTIONAL>
|
||||||
# Get auth data
|
# Get auth data
|
||||||
/v0/auth_finish?session=<UNIQUE>
|
/api/v0/auth_finish?session=<UNIQUE>
|
||||||
|
|
||||||
|
|
||||||
##### API v1
|
##### API v1 (semi-safe)
|
||||||
# Auth link
|
# Auth link
|
||||||
/v1/auth?session=<UNIQUE>&back_url=<OPTIONAL>
|
/api/v1/auth?session=<UNIQUE>&back_url=<OPTIONAL>
|
||||||
# Get token
|
# Get token
|
||||||
/v1/auth_finish?session=<UNIQUE>
|
/api/v1/auth_finish?session=<UNIQUE>
|
||||||
# Refresh token
|
# Refresh token
|
||||||
/v1/refresh?token=<REFRESH>
|
/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>
|
||||||
|
306
src/funcs.rs
306
src/funcs.rs
@ -1,5 +1,6 @@
|
|||||||
use {
|
use {
|
||||||
std::{
|
std::{
|
||||||
|
sync::Arc,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
any::type_name,
|
any::type_name,
|
||||||
time::{
|
time::{
|
||||||
@ -10,8 +11,54 @@ use {
|
|||||||
webhook::client::WebhookClient,
|
webhook::client::WebhookClient,
|
||||||
urlencoding::decode as url_decode,
|
urlencoding::decode as url_decode,
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
|
chrono::DateTime,
|
||||||
|
serde_json::{
|
||||||
|
Value as Json,
|
||||||
|
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,
|
||||||
|
pool::ConnectionMgrTcp,
|
||||||
|
error::Error as SkyError,
|
||||||
|
},
|
||||||
|
bb8::Pool,
|
||||||
|
http_body_util::BodyExt,
|
||||||
|
hyper::{
|
||||||
|
Request,
|
||||||
|
HeaderMap,
|
||||||
|
body::Incoming,
|
||||||
|
header::{
|
||||||
|
HeaderValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
crate::{
|
crate::{
|
||||||
|
Res,
|
||||||
|
Result,
|
||||||
|
DBPool,
|
||||||
|
REFRESH_LIFETIME,
|
||||||
|
TOKEN_LIFETIME,
|
||||||
html::*,
|
html::*,
|
||||||
|
Users,
|
||||||
|
Sites,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -29,6 +76,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| {
|
||||||
@ -72,3 +177,204 @@ pub async fn discord_wh_send(text: String) {
|
|||||||
)
|
)
|
||||||
).await.unwrap();
|
).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<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?)
|
||||||
|
}
|
||||||
|
296
src/main.rs
296
src/main.rs
@ -86,6 +86,7 @@ use {
|
|||||||
funcs::*,
|
funcs::*,
|
||||||
url::{
|
url::{
|
||||||
api,
|
api,
|
||||||
|
login,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -273,8 +274,8 @@ async fn handle_connection(req: Request<Incoming>, pool: DBPool, ip: String) ->
|
|||||||
|
|
||||||
(body, parts.status, restype) = match req.uri().path().as_ref() {
|
(body, parts.status, restype) = match req.uri().path().as_ref() {
|
||||||
"/" => uri_index(),
|
"/" => uri_index(),
|
||||||
"/cabinet" => uri_login(req, pool.clone(), &mut headers).await?,
|
"/cabinet" => login::uri_login(req, pool.clone(), &mut headers).await?,
|
||||||
"/login" => uri_login(req, pool.clone(), &mut headers).await?,
|
"/login" => login::uri_login(req, pool.clone(), &mut headers).await?,
|
||||||
x if x == "/authorize" && logged => uri_authorize(req, pool.clone(), token).await?,
|
x if x == "/authorize" && logged => uri_authorize(req, pool.clone(), token).await?,
|
||||||
"/authorize" => uri_auth_required(req, &mut headers).await?,
|
"/authorize" => uri_auth_required(req, &mut headers).await?,
|
||||||
"/register" => uri_register(req, pool.clone(), &mut headers).await?,
|
"/register" => uri_register(req, pool.clone(), &mut headers).await?,
|
||||||
@ -291,18 +292,6 @@ async fn handle_connection(req: Request<Incoming>, pool: DBPool, ip: String) ->
|
|||||||
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> {
|
fn get_cookies(headers: HeaderMap) -> HashMap<String, String> {
|
||||||
let header = headers.get(hyper::header::COOKIE);
|
let header = headers.get(hyper::header::COOKIE);
|
||||||
let cookies = match header.is_none() {
|
let cookies = match header.is_none() {
|
||||||
@ -313,44 +302,6 @@ fn get_cookies(headers: HeaderMap) -> HashMap<String, String> {
|
|||||||
double_split(cookies.to_owned(), ";", "=")
|
double_split(cookies.to_owned(), ";", "=")
|
||||||
}
|
}
|
||||||
|
|
||||||
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)> {
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn uri_authorize(req: Request<Incoming>, pool: DBPool, token: String) -> Result<(String, StatusCode, HeaderValue)> {
|
async fn uri_authorize(req: Request<Incoming>, pool: DBPool, token: String) -> Result<(String, StatusCode, HeaderValue)> {
|
||||||
// TODO: Forward for versions.
|
// TODO: Forward for versions.
|
||||||
if *req.method() == Method::POST {
|
if *req.method() == Method::POST {
|
||||||
@ -469,244 +420,3 @@ fn uri_404() -> (String, StatusCode, HeaderValue) {
|
|||||||
let restype: HeaderValue = "text/html".parse().unwrap();
|
let restype: HeaderValue = "text/html".parse().unwrap();
|
||||||
(build_html(NF_HTML), StatusCode::NOT_FOUND, restype)
|
(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,
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 +1,2 @@
|
|||||||
pub mod api;
|
pub mod api;
|
||||||
|
pub mod login;
|
||||||
|
124
src/url/login.rs
Normal file
124
src/url/login.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
use {
|
||||||
|
std::{
|
||||||
|
sync::Arc,
|
||||||
|
net::SocketAddr,
|
||||||
|
collections::HashMap,
|
||||||
|
process::exit,
|
||||||
|
},
|
||||||
|
chrono::{
|
||||||
|
DateTime,
|
||||||
|
},
|
||||||
|
http_body_util::{
|
||||||
|
Full,
|
||||||
|
BodyExt,
|
||||||
|
},
|
||||||
|
hyper::{
|
||||||
|
StatusCode,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
Method,
|
||||||
|
HeaderMap,
|
||||||
|
header::HeaderValue,
|
||||||
|
body::{
|
||||||
|
Bytes,
|
||||||
|
Incoming,
|
||||||
|
},
|
||||||
|
server::conn::http1 as Server,
|
||||||
|
service::service_fn,
|
||||||
|
},
|
||||||
|
hyper_util::{
|
||||||
|
rt::TokioIo,
|
||||||
|
},
|
||||||
|
tokio::{
|
||||||
|
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::{
|
||||||
|
Value as Json,
|
||||||
|
Map as JsonMap,
|
||||||
|
json,
|
||||||
|
},
|
||||||
|
skytable::{
|
||||||
|
query,
|
||||||
|
Config,
|
||||||
|
pool::{
|
||||||
|
self,
|
||||||
|
ConnectionMgrTcp
|
||||||
|
},
|
||||||
|
error::Error as SkyError,
|
||||||
|
},
|
||||||
|
bb8::{
|
||||||
|
Pool,
|
||||||
|
},
|
||||||
|
urlencoding::{
|
||||||
|
encode as url_encode,
|
||||||
|
decode as url_decode,
|
||||||
|
},
|
||||||
|
crate::{
|
||||||
|
types::{
|
||||||
|
users::Users,
|
||||||
|
sites::Sites,
|
||||||
|
},
|
||||||
|
html::*,
|
||||||
|
funcs::*,
|
||||||
|
url::{
|
||||||
|
api,
|
||||||
|
login,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type Res<T, E> = std::result::Result<T, E>;
|
||||||
|
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||||
|
type DBPool = Arc<Pool<ConnectionMgrTcp>>;
|
||||||
|
type FullBytes = Result<Response<Full<Bytes>>>;
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn uri_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))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user