Added defolt auth w/o ref token

This commit is contained in:
bitheaven 2024-01-20 13:45:06 +05:00
parent aa4db89f97
commit 1577486736
5 changed files with 2242 additions and 3 deletions

1671
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,27 @@
[package]
name = "bitauth"
name = "BitAuth"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bb8 = "0.8.1"
bcrypt = "0.15.0"
chrono = "0.4.31"
http-body-util = "0.1.0"
hyper = { version = "1.1.0", features = ["full"] }
hyper-util = { version = "0.1.2", features = ["full"] }
jsonwebtoken = "9.2.0"
rand = "0.8.5"
rsa = "0.9.6"
serde_json = "1.0.111"
skytable = "0.8.3"
tokio = { version = "1.35.1", features = ["full"] }
urlencoding = "2.1.3"
uuid = { version = "1.6.1", features = ["v4", "v5"] }
[[bin]]
name = "bitauth"
path = "src/main.rs"

View File

@ -1,3 +1,474 @@
fn main() {
println!("Hello, world!");
use std::{
sync::Arc,
net::SocketAddr,
collections::HashMap,
time::{SystemTime, UNIX_EPOCH},
};
use chrono::{
DateTime,
};
use http_body_util::{
Full,
BodyExt,
};
use hyper::{
StatusCode,
Request,
Response,
Method,
HeaderMap,
header::HeaderValue,
body::{Bytes, Incoming},
server::conn::http1 as Server,
service::service_fn,
};
use hyper_util::{
rt::TokioIo,
};
use tokio::{
net::TcpListener,
};
use rsa::{
pkcs1::{EncodeRsaPublicKey, EncodeRsaPrivateKey},
RsaPrivateKey,
RsaPublicKey,
};
use urlencoding::{
decode as url_decode,
};
use jsonwebtoken as jwt;
use jwt::{
Header,
Algorithm,
TokenData,
Validation,
EncodingKey,
DecodingKey,
encode as jwt_encode,
decode as jwt_decode,
};
use serde_json::{
Value as Json,
json,
};
use uuid::{
Uuid,
};
use skytable::{
Config,
pool::{self, ConnectionMgrTcp},
query,
};
use bb8::{
Pool,
};
mod type_users;
mod type_sites;
use crate::{
type_users::Users,
type_sites::Sites,
};
type Res<T, E> = std::result::Result<T, E>;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
const PORT: u16 = 8081;
const DB_POOL: u32 = 32;
const DB_ADDR: &str = "192.168.1.49";
const DB_PORT: u16 = 2003;
const DB_USER: &str = "root";
const DB_PASS: &str = "rootpass12345678";
const TOKEN_LIFETIME: u32 = 300;
const REFRESH_LIFETIME: u32 = 2_678_400;
const HEADER_HTML: &str = r#"
<!DOCTYPE html>
<html>
<head></head>
<body>
<header>
<a href="/">index</a>
<a href="/login">login</a>
<a href="/register">register</a>
<hr>
</header>
<main>
"#;
const FOOTER_HTML: &str = r#"
<footer>
<hr>
Made by BitHeaven.
</footer>
</body>
</html>
"#;
const INDEX_HTML: &str = "<h1>main</h1>";
const LOGIN_HTML: &str = r#"
<h1>login</h1>
<form method="POST">
<input name="login" type="text" placeholder="login / email" required>
<br>
<input name="password" type="password" placeholder="password" required>
<br>
<br>
<button type="submit">login</button>
</form>
"#;
const REG_HTML: &str = r#"
<h1>register</h1>
<form method="POST">
<input name="login" type="text" placeholder="login" required>
<br>
<input name="email" type="email" placeholder="email (opt)">
<br>
<input name="password" type="password" placeholder="password" required>
<br>
<input name="password2" type="password" placeholder="password again" required>
<br>
<br>
<button type="submit">register</button>
</form>
"#;
const RECOVER_HTML: &str = "<h1>recover</h1>";
const NF_HTML: &str = "<h1>404</h1>think about it.";
const CSS3: &str = r#"<style>
:root { color-scheme: dark; }
</style>"#;
#[tokio::main]
async fn main() -> Result<()> {
let addr = SocketAddr::from(([0, 0, 0, 0], PORT));
let listener = TcpListener::bind(addr).await?;
let pool = Arc::new(pool::get_async(DB_POOL, Config::new(DB_ADDR, DB_PORT, DB_USER, DB_PASS)).await.unwrap());
init_tables(pool.clone()).await?;
println!("Server started on port: {}", PORT);
loop {
let (stream, _) = listener.accept().await?;
let ip = format!("{:?}", stream.peer_addr().unwrap().ip());
let io = TokioIo::new(stream);
let this_pool = pool.clone();
tokio::task::spawn(async move {
if let Err(err) = Server::Builder::new().serve_connection(
io, service_fn(move |req| handle_connection(req, this_pool.clone(), ip.clone()))
).await {
println!("Error serving connection: {:?}", err);
}
});
}
}
async fn handle_connection(req: Request<Incoming>, pool: Arc<Pool<ConnectionMgrTcp>>, ip: String) -> Result<Response<Full<Bytes>>> {
if ip == "1.1.1.1" {
}
// let mut con = pool.get().await?;
// let q = con.query_parse::<Forwards>(&query!("SELECT * FROM bitproxy.forwards WHERE domain = ?", host)).await;
// if !q.is_ok() {
// }
// let target_url = q.unwrap().address.to_owned()
// .parse::<hyper::Uri>().unwrap();
let res = Response::new(Full::new(Bytes::new()));
let (mut parts, _) = res.into_parts();
let body: String;
let restype: HeaderValue;
let mut headers = parts.headers.clone();
let cookies = get_cookies(req.headers().clone());
let token = cookies.get("token");
let token = match token.is_none() {
false => token.unwrap(),
_ => ""
};
if token != "" && jwt_verify(pool.clone(), token).await?.claims.as_object().unwrap().len() == 0 {
parts.status = StatusCode::FOUND;
set_cookie(&mut headers, "token", "");
set_location(&mut headers, "/");
parts.headers = headers;
return Ok(Response::from_parts(parts, Full::new(Bytes::new())));
}
(body, parts.status, restype) = match req.uri().path().as_ref() {
"/" => uri_index(),
"/cabinet" => uri_login(req, pool.clone(), &mut headers).await?,
"/login" => uri_login(req, pool.clone(), &mut headers).await?,
"/register" => uri_register(req, pool.clone()).await?,
"/recover" => uri_recover(),
x if x.starts_with("/api") => uri_api(req),
_ => uri_404()
};
headers.insert(hyper::header::CONTENT_TYPE, restype);
parts.headers = headers;
Ok(Response::from_parts(parts, Full::new(Bytes::from(body))))
}
fn build_html(body: &str) -> String {
format!("{}{}{}{}", HEADER_HTML, CSS3, body, FOOTER_HTML)
}
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> {
let header = headers.get(hyper::header::COOKIE);
let cookies = match header.is_none() {
false => header.unwrap().to_str().unwrap(),
_ => ""
};
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: Arc<Pool<ConnectionMgrTcp>>, headers: &mut HeaderMap) -> Result<(String, StatusCode, HeaderValue)> {
let mut cookie: String = Default::default();
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, "&", "=");
cookie = login_user(pool.clone(), body).await?;
}
set_cookie(headers, "token", &cookie);
let restype: HeaderValue = "text/html".parse().unwrap();
Ok((build_html(LOGIN_HTML), StatusCode::OK, restype))
}
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: Arc<Pool<ConnectionMgrTcp>>) -> 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, "&", "=");
match create_user(pool.clone(), body).await? {
true => println!("Created"),
_ => println!("Failed")
}
}
let restype: HeaderValue = "text/html".parse().unwrap();
Ok((build_html(REG_HTML), StatusCode::OK, restype))
}
fn uri_recover() -> (String, StatusCode, HeaderValue) {
let restype: HeaderValue = "text/html".parse().unwrap();
(build_html(RECOVER_HTML), StatusCode::OK, restype)
}
fn uri_api(req: Request<Incoming>) -> (String, StatusCode, HeaderValue) {
let uri_api: &str = req.uri().path().as_ref();
let res: Json = match &uri_api[4..uri_api.len()] {
"/test" => json!({"error": false, "msg": "test"}),
_ => json!({"error": true, "msg": "No endpoint"})
};
let restype: HeaderValue = "application/json".parse().unwrap();
(res.to_string(), StatusCode::IM_A_TEAPOT, 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: Arc<Pool<ConnectionMgrTcp>>) -> Res<(), skytable::error::Error> {
let mut con = pool.get().await.unwrap();
let _ = con.query_parse::<()>(&query!("CREATE SPACE IF NOT EXISTS bitauth")).await;
let _ = con.query_parse::<()>(&query!(
"CREATE MODEL IF NOT EXISTS bitauth.users(login: string, uuid: string, password: string, null email: string)"
)).await;
let _ = con.query_parse::<()>(&query!(
"CREATE MODEL IF NOT EXISTS bitauth.sites(uuid: string, uid: string, domain: string, pkey: binary, skey: binary)"
)).await;
let _ = con.query_parse::<()>(&query!(
"CREATE MODEL IF NOT EXISTS bitauth.tokens(uuid: string, uid: string, sid: string, ref: string, refend: 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 login_user(pool: Arc<Pool<ConnectionMgrTcp>>, data: HashMap<String, String>) -> Res<String, skytable::error::Error> {
let mut err = false;
let mut ret: String = Default::default();
let mut con = pool.get().await.unwrap();
let login = data.get("login").unwrap();
let pass = data.get("password").unwrap().trim();
let q = con.query_parse::<Users>(&query!("SELECT * FROM bitauth.users WHERE login = ?", login)).await;
if q.is_err() { err = true };
if !err {
let q = q.unwrap();
if bcrypt::verify(pass, q.password.as_str()).unwrap() {
ret = jwt_sign(pool.clone(), json!({
"login": q.login,
"uuid": q.uuid,
// "refresh": q.ref,
// "refexp": q.refend,
"iat": time(),
"exp": time() + TOKEN_LIFETIME
})).await.unwrap();
}
}
Ok(ret)
}
async fn create_user(pool: Arc<Pool<ConnectionMgrTcp>>, data: HashMap<String, String>) -> Res<bool, skytable::error::Error> {
let mut ret = true;
let mut con = pool.get().await.unwrap();
let login = data.get("login").unwrap();
let email = data.get("email").unwrap();
let pass = data.get("password").unwrap();
let pass2 = data.get("password2").unwrap();
if pass != pass2 { ret = false };
if pass.len() < 8 { ret = false };
let q = con.query_parse::<(String,)>(&query!("SELECT uuid FROM bitauth.users 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: ?
}"#,
uuid,
login,
pass,
email
)).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 double_split(body: String, first: &str, second: &str) -> HashMap<String, String> {
body.split(first)
.filter_map(|c| {
c.split_once(second)
.map(|(l, r)| (l.trim().to_owned(), format!("{}", url_decode(r).expect("UTF-8")).trim().to_owned()))})
.collect::<HashMap<String, String>>()
}
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);
let skey = skey.to_pkcs1_der().unwrap().as_bytes().to_vec();
let pkey = pkey.to_pkcs1_der().unwrap().as_bytes().to_vec();
(skey, pkey)
}
async fn jwt_sign(pool: Arc<Pool<ConnectionMgrTcp>>, 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: Arc<Pool<ConnectionMgrTcp>>, 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)
}
fn uuid_v4() -> Uuid {
Uuid::new_v4()
}
fn time() -> u32 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as u32
}
/*fn time_ns() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u128
}*/

40
src/type_sites.rs Normal file
View File

@ -0,0 +1,40 @@
use skytable::{
query::SQParam,
response::{FromResponse, Response},
ClientResult,
};
pub struct Sites {
pub uuid: String,
pub uid: String,
pub domain: String,
pub pkey: Vec<u8>,
pub skey: Vec<u8>,
}
impl Sites {
pub fn new(uuid: String, uid: String, domain: String, pkey: Vec<u8>, skey: Vec<u8>) -> Self {
Self {
uuid,
uid,
domain,
pkey,
skey,
}
}
}
impl SQParam for Sites {
fn append_param(&self, q: &mut Vec<u8>) -> usize {
self.uuid.append_param(q)
+ self.domain.append_param(q)
}
}
impl FromResponse for Sites {
fn from_response(resp: Response) -> ClientResult<Self> {
let (uuid, uid, domain, pkey, skey) = FromResponse::from_response(resp)?;
Ok(Self::new(uuid, uid, domain, pkey, skey))
}
}

38
src/type_users.rs Normal file
View File

@ -0,0 +1,38 @@
use skytable::{
query::SQParam,
response::{FromResponse, Response},
ClientResult,
};
pub struct Users {
pub login: String,
pub uuid: String,
pub password: String,
pub email: String,
}
impl Users {
pub fn new(login: String, uuid: String, password: String, email: String) -> Self {
Self {
login,
uuid,
password,
email,
}
}
}
impl SQParam for Users {
fn append_param(&self, q: &mut Vec<u8>) -> usize {
self.login.append_param(q)
+ self.uuid.append_param(q)
}
}
impl FromResponse for Users {
fn from_response(resp: Response) -> ClientResult<Self> {
let (login, uuid, password, email) = FromResponse::from_response(resp)?;
Ok(Self::new(login, uuid, password, email))
}
}