Compare commits
	
		
			21 Commits
		
	
	
		
			abb2ac6c12
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9fa66899aa | |||
| 8a48ca0ad6 | |||
| 26301ac344 | |||
| 81023f2042 | |||
| f5f35855ce | |||
| 7018adf98d | |||
| a60713a4b0 | |||
| 9ef4176091 | |||
| 6cd6b12630 | |||
| 2a9ac271a7 | |||
| 8a7ff047b6 | |||
| e28a8c2893 | |||
| 73f5edf77f | |||
| c12d7fb01c | |||
| e3229f41a4 | |||
| c877b02286 | |||
| 030dd43ddf | |||
| b577904d75 | |||
| aeceb98ad5 | |||
| 6a64f87bc5 | |||
| 0ad0a8cf0b | 
							
								
								
									
										835
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										835
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,6 +1,6 @@ | ||||
| [package] | ||||
| name = "BitAuth" | ||||
| version = "0.1.0" | ||||
| version = "0.1.1" | ||||
| edition = "2021" | ||||
|  | ||||
| # 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" | ||||
| rsa = "0.9.6" | ||||
| serde_json = "1.0.111" | ||||
| skytable = "0.8.6" | ||||
| skytable = "0.8.10" | ||||
| tokio = { version = "1.35.1", features = ["full"] } | ||||
| urlencoding = "2.1.3" | ||||
| uuid = { version = "1.6.1", features = ["v4", "v5"] } | ||||
| webhook = "2.1.2" | ||||
|  | ||||
|  | ||||
| [[bin]] | ||||
|  | ||||
							
								
								
									
										27
									
								
								TODO
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								TODO
									
									
									
									
									
								
							| @ -1,3 +1,6 @@ | ||||
| Forward user back when set `back_url` | ||||
|  | ||||
|  | ||||
| Auth using tokens | ||||
| Auth using uniq id on device | ||||
| Auth using QR code | ||||
| @ -6,8 +9,26 @@ Auth using email | ||||
|  | ||||
|  | ||||
|  | ||||
| ##### API v0 | ||||
| ##### API v0 (unsafe) | ||||
| # 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 | ||||
| /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> | ||||
|  | ||||
| @ -1,42 +0,0 @@ | ||||
| use { | ||||
| 	hyper::{ | ||||
| 		Request, | ||||
| 		body::{ | ||||
| 			Incoming, | ||||
| 		}, | ||||
| 	}, | ||||
| 	serde_json::{ | ||||
| 		Value as Json, | ||||
| 		json, | ||||
| 	}, | ||||
| 	skytable::pool::ConnectionMgrTcp, | ||||
| 	bb8::Pool, | ||||
| 	std::sync::Arc, | ||||
| 	crate::double_split, | ||||
| }; | ||||
|  | ||||
| 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>>; | ||||
|  | ||||
|  | ||||
| pub async fn api(req: Request<Incoming>, pool: DBPool) -> Json { | ||||
| 	let uri: &str = req.uri().path().as_ref(); | ||||
| 	match &uri[7..uri.len()] { | ||||
| 		"/test" => json!({"error": false, "msg": "test"}), | ||||
| 		"/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 { | ||||
| 	json!({"error": false, "msg": "test auth endpoint v0"}) | ||||
| } | ||||
|  | ||||
| async fn auth_get(req: Request<Incoming>, pool: DBPool) -> Json { | ||||
| 	let query = req.uri().query().or(Some("")).unwrap(); | ||||
| 	let query = double_split(query.to_string(), "&", "="); | ||||
| 	println!("{:?}", query); | ||||
| 	json!({"error": false, "msg": "test auth_get endpoint v0"}) | ||||
| } | ||||
							
								
								
									
										417
									
								
								src/funcs.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								src/funcs.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,417 @@ | ||||
| use { | ||||
| 	std::{ | ||||
| 		collections::HashMap, | ||||
| 		any::type_name, | ||||
| 		time::{ | ||||
| 			SystemTime, | ||||
| 			UNIX_EPOCH, | ||||
| 		}, | ||||
| 	}, | ||||
| 	webhook::client::WebhookClient, | ||||
| 	urlencoding::decode as url_decode, | ||||
| 	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::{ | ||||
| 		Res, | ||||
| 		Result, | ||||
| 		DBPool, | ||||
| 		REFRESH_LIFETIME, | ||||
| 		TOKEN_LIFETIME, | ||||
| 		html::*, | ||||
| 		Users, | ||||
| 		Sites, | ||||
| 		APIV0_LIFETIME, | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
|  | ||||
| #[allow(dead_code)] | ||||
| pub fn type_of<T>(_: T) -> &'static str { | ||||
| 	type_name::<T>() | ||||
| } | ||||
|  | ||||
| pub fn uuid_v4() -> Uuid { | ||||
| 	Uuid::new_v4() | ||||
| } | ||||
|  | ||||
| pub fn build_html(body: &str) -> String { | ||||
| 	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> { | ||||
| 	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>>() | ||||
| } | ||||
|  | ||||
| pub fn time() -> u32 { | ||||
| 	SystemTime::now() | ||||
| 		.duration_since(UNIX_EPOCH) | ||||
| 		.unwrap() | ||||
| 		.as_secs() as u32 | ||||
| } | ||||
|  | ||||
| pub fn time_mcs() -> u128 { | ||||
| 	SystemTime::now() | ||||
| 		.duration_since(UNIX_EPOCH) | ||||
| 		.unwrap() | ||||
| 		.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; | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/html.rs
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								src/html.rs
									
									
									
									
									
								
							| @ -7,6 +7,7 @@ pub const HEADER_HTML: &str = r#" | ||||
| 			<a href="/">index</a> | ||||
| 			<a href="/login">login</a> | ||||
| 			<a href="/register">register</a> | ||||
| 			<span>{USER_STATUS}</span> | ||||
| 			<hr> | ||||
| 		</header> | ||||
| 		<main> | ||||
| @ -16,7 +17,8 @@ pub const FOOTER_HTML: &str = r#" | ||||
| 		</main> | ||||
| 		<footer> | ||||
| 			<hr> | ||||
| 			Render time: RENDER_TIMEµs. Made by BitHeaven. | ||||
| 			Page time: {PAGE_TIME}µs. | ||||
| 			Made by <a href="//bitheaven.ru/">BitHeaven</a>. | ||||
| 		</footer> | ||||
| 	</body> | ||||
| 	</html> | ||||
| @ -36,6 +38,15 @@ pub const LOGIN_HTML: &str = r#" | ||||
| 	</form> | ||||
| "#; | ||||
|  | ||||
| pub const AUTHORIZE_HTML: &str = r#" | ||||
| 	<h1>authorize</h1> | ||||
| 	<h2>you authorizing in unknown service</h2> | ||||
| 	<h3>yes?</h3> | ||||
| 	<form method="POST"> | ||||
| 		<button type="submit">yes</button> | ||||
| 	</form> | ||||
| "#; | ||||
|  | ||||
| pub const REG_HTML: &str = r#" | ||||
| 	<h1>register</h1> | ||||
| 	<form method="POST"> | ||||
| @ -58,7 +69,7 @@ pub const RECOVER_HTML: &str = "<h1>recover</h1>"; | ||||
| pub const NF_HTML: &str = "<h1>404</h1>think about it."; | ||||
|  | ||||
| pub const CSS3: &str = r#"<style> | ||||
| 	:root { color-scheme: dark; } | ||||
| 	:root { color-scheme: dark; font-family: monospace; font-size: 16px; } | ||||
| 	body { margin: auto; max-width: 768px; } | ||||
| 	footer { text-align: right; } | ||||
| </style>"#; | ||||
|  | ||||
							
								
								
									
										527
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										527
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -1,6 +1,7 @@ | ||||
| mod funcs; | ||||
| mod types; | ||||
| mod html; | ||||
| mod api; | ||||
| mod url; | ||||
|  | ||||
| use { | ||||
| 	std::{ | ||||
| @ -8,23 +9,14 @@ use { | ||||
| 		net::SocketAddr, | ||||
| 		collections::HashMap, | ||||
| 		process::exit, | ||||
| 		time::{ | ||||
| 			SystemTime, | ||||
| 			UNIX_EPOCH, | ||||
| 		}, | ||||
| 	}, | ||||
| 	chrono::{ | ||||
| 		DateTime, | ||||
| 	}, | ||||
| 	http_body_util::{ | ||||
| 		Full, | ||||
| 		BodyExt, | ||||
| 	}, | ||||
| 	hyper::{ | ||||
| 		StatusCode, | ||||
| 		Request, | ||||
| 		Response, | ||||
| 		Method, | ||||
| 		HeaderMap, | ||||
| 		header::HeaderValue, | ||||
| 		body::{ | ||||
| @ -40,35 +32,10 @@ use { | ||||
| 	tokio::{ | ||||
| 		net::TcpListener, | ||||
| 	}, | ||||
| 	rsa::{ | ||||
| 		pkcs1::{ | ||||
| 			EncodeRsaPublicKey, | ||||
| 			EncodeRsaPrivateKey, | ||||
| 		}, | ||||
| 		RsaPrivateKey, | ||||
| 		RsaPublicKey, | ||||
| 	}, | ||||
| 	urlencoding::{ | ||||
| 		decode as url_decode, | ||||
| 	}, | ||||
| 	jsonwebtoken as jwt, | ||||
| 	jwt::{ | ||||
| 		Header, | ||||
| 		Algorithm, | ||||
| 		TokenData, | ||||
| 		Validation, | ||||
| 		EncodingKey, | ||||
| 		DecodingKey, | ||||
| 		encode as jwt_encode, | ||||
| 		decode as jwt_decode, | ||||
| 	}, | ||||
| 	serde_json::{ | ||||
| 		Value as Json, | ||||
| 		json, | ||||
| 	}, | ||||
| 	uuid::{ | ||||
| 		Uuid, | ||||
| 	}, | ||||
| 	skytable::{ | ||||
| 		query, | ||||
| 		Config, | ||||
| @ -81,12 +48,16 @@ use { | ||||
| 	bb8::{ | ||||
| 		Pool, | ||||
| 	}, | ||||
| 	urlencoding::{ | ||||
| 		encode as url_encode, | ||||
| 	}, | ||||
| 	crate::{ | ||||
| 		types::{ | ||||
| 			users::Users, | ||||
| 			sites::Sites, | ||||
| 		}, | ||||
| 		html::*, | ||||
| 		funcs::*, | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
| @ -96,17 +67,19 @@ type DBPool = Arc<Pool<ConnectionMgrTcp>>; | ||||
| type FullBytes = Result<Response<Full<Bytes>>>; | ||||
|  | ||||
|  | ||||
| const PORT: u16 = 8083; | ||||
| const PORT: u16 = 8051; | ||||
|  | ||||
| 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_USER: &str = "root"; | ||||
| const DB_PASS: &str = "rootpass12345678"; | ||||
| const DB_PASS: &str = "dBk6wUAynGRRLsSF"; | ||||
|  | ||||
| const TOKEN_LIFETIME: u32 = 300; | ||||
| const REFRESH_LIFETIME: u32 = 2_678_400; | ||||
|  | ||||
| const APIV0_LIFETIME: u32 = 120; | ||||
|  | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<()> { | ||||
| @ -159,7 +132,7 @@ async fn main() -> Result<()> { | ||||
| } | ||||
|  | ||||
| async fn handle_connection(req: Request<Incoming>, pool: DBPool, ip: String) -> FullBytes { | ||||
| 	let t = time_ns(); | ||||
| 	let t = time_mcs(); | ||||
|  | ||||
| 	if ip == "1.1.1.1" { | ||||
| 	} | ||||
| @ -172,392 +145,134 @@ async fn handle_connection(req: Request<Incoming>, pool: DBPool, ip: String) -> | ||||
| 	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(), | ||||
| 	let mut token = match cookies.get("token") { | ||||
| 		x if x.is_none() => "".to_owned(), | ||||
| 		x => x.unwrap().to_owned() | ||||
| 	}; | ||||
| 	let mut logged = false; | ||||
|  | ||||
| 	match <str as AsRef<str>>::as_ref(req.uri().path()) { | ||||
| 		x if x.starts_with("/api/") => {}, | ||||
| 		_ => 'jwt_check: { | ||||
| 			if token == "" { break 'jwt_check; } | ||||
|  | ||||
| 			let is_live = jwt_verify(pool.clone(), &token) | ||||
| 				.await? | ||||
| 				.claims | ||||
| 				.as_object() | ||||
| 				.unwrap() | ||||
| 				.len() != 0; | ||||
|  | ||||
| 			match is_live { | ||||
| 				true => { logged = true; }, | ||||
| 				_ => 'ref_check: { | ||||
| 					let reftoken = cookies.get("refresh"); | ||||
| 					let reftoken = match reftoken.is_none() { | ||||
| 						false => reftoken.unwrap(), | ||||
| 						_ => "" | ||||
| 					}; | ||||
|  | ||||
| 	match <str as AsRef<str>>::as_ref(req.uri().path()) { | ||||
| 		x if x.starts_with("/api/") => {} | ||||
| 		_ => { | ||||
| //			println!("{}", token); | ||||
| 					if reftoken == "" { break 'ref_check; } | ||||
|  | ||||
| 			if token != "" && jwt_verify(pool.clone(), token) | ||||
| 				.await?.claims.as_object().unwrap().len() == 0 | ||||
| 			{ | ||||
| 				println!("Invalid suka"); | ||||
| 				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()))); | ||||
| 					let ref_data: HashMap<String, Json> = jwt_verify(pool.clone(), reftoken) | ||||
| 						.await? | ||||
| 						.claims | ||||
| 						.as_object() | ||||
| 						.unwrap() | ||||
| 						.clone() | ||||
| 						.into_iter() | ||||
| 						.collect(); | ||||
|  | ||||
| 					if ref_data.clone().len() != 0 { | ||||
| 						let mut con = pool.get().await.unwrap(); | ||||
|  | ||||
| 						let tokenid = ref_data.get("uuid").unwrap().as_str().unwrap(); | ||||
| 						let newref = format!("{}", uuid_v4().as_hyphenated()); | ||||
| 						let time = time(); | ||||
|  | ||||
| 						let q = con.query_parse::<(String,)>(&query!( | ||||
| 							r#" | ||||
| 								SELECT uid | ||||
| 								FROM bitauth.tokens | ||||
| 								WHERE uuid = ? | ||||
| 							"#, | ||||
| 							tokenid | ||||
| 						)).await; | ||||
| 						if q.is_err() { break 'ref_check; } | ||||
| 						let (uuid,) = q.unwrap(); | ||||
|  | ||||
| 						let (login,) = con.query_parse::<(String,)>(&query!( | ||||
| 							r#" | ||||
| 								SELECT login | ||||
| 								FROM bitauth.users | ||||
| 								WHERE uuid = ? | ||||
| 							"#, | ||||
| 							uuid.clone() | ||||
| 						)).await?;					 | ||||
| 						let _ = con.query_parse::<()>(&query!( | ||||
| 							r#" | ||||
| 								UPDATE bitauth.tokens | ||||
| 								SET ref = ?, refend = ? | ||||
| 								WHERE uuid = ? | ||||
| 							"#, | ||||
| 							newref.clone(), time + REFRESH_LIFETIME, tokenid | ||||
| 						)).await; | ||||
|  | ||||
| 						token = jwt_sign(pool.clone(), json!({ | ||||
| 							"login": login.clone(), | ||||
| 							"uuid": uuid.clone(), | ||||
| 							"iat": time, | ||||
| 							"exp": time + TOKEN_LIFETIME | ||||
| 						})).await.unwrap(); | ||||
|  | ||||
| 						set_cookie(&mut headers, "token", &token); | ||||
| 						set_cookie(&mut headers, "refresh", | ||||
| 							&jwt_sign(pool.clone(), json!({ | ||||
| 								"uuid": tokenid, | ||||
| 								"iat": time, | ||||
| 								"ref": newref.clone(), | ||||
| 								"exp": time + REFRESH_LIFETIME | ||||
| 							})).await.unwrap() | ||||
| 						); | ||||
|  | ||||
| 						logged = true; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	(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(), &mut headers).await?, | ||||
| 		"/recover" => uri_recover(), | ||||
| 		x if x.starts_with("/@") => uri_user(req, pool.clone()).await?, | ||||
| 		x if x.starts_with("/api/") => api::endpoint(req, pool.clone()).await, | ||||
| 		_ => uri_404() | ||||
| 		"/" => url::index::index(), | ||||
| 		"/cabinet" => url::login::login(req, pool.clone(), &mut headers).await?, | ||||
| 		"/login" => url::login::login(req, pool.clone(), &mut headers).await?, | ||||
| 		x if x == "/authorize" && logged => url::authorize::authorize(req, pool.clone(), token).await?, | ||||
| 		"/authorize" => uri_auth_required(req, &mut headers).await?, | ||||
| 		"/register" => url::register::register(req, pool.clone(), &mut headers).await?, | ||||
| 		"/recover" => url::recover::recover(), | ||||
| 		x if x.starts_with("/@") => url::user::user(req, pool.clone()).await?, | ||||
| 		x if x.starts_with("/api/") => url::api::endpoint(req, pool.clone()).await, | ||||
| 		_ => url::nf::nf() | ||||
| 	}; | ||||
|  | ||||
| 	headers.insert(hyper::header::CONTENT_TYPE, restype); | ||||
| 	parts.headers = headers; | ||||
|  | ||||
| 	let body = body.replace("RENDER_TIME", &format!("{}", time_ns() - 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)))) | ||||
| } | ||||
|  | ||||
| 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() | ||||
| 	); | ||||
| } | ||||
| async fn uri_auth_required(req: Request<Incoming>, headers: &mut HeaderMap) -> Result<(String, StatusCode, HeaderValue)> { | ||||
| 	let url = url_encode(req.uri().path_and_query().unwrap().as_str()); | ||||
|  | ||||
| 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: 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); | ||||
| 	} | ||||
| 	set_location(headers, format!("/login?q={}", url).as_str()); | ||||
|  | ||||
| 	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: 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( | ||||
| 			login: string, | ||||
| 			uuid: string, | ||||
| 			password: string, | ||||
| 			email: string, | ||||
| 			tokens: list {type: 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 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 err = false; | ||||
| 	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::<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() { | ||||
| 			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 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 { | ||||
| 				login: ?, | ||||
| 				uuid: ?, | ||||
| 				password: ?, | ||||
| 				email: ?, | ||||
| 				tokens: [] | ||||
| 			}"#, | ||||
| 			login, | ||||
| 			uuid, | ||||
| 			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); | ||||
|  | ||||
| 	( | ||||
| 		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) | ||||
| } | ||||
|  | ||||
| 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_micros() | ||||
| 	Ok(("".to_owned(), StatusCode::FOUND, restype)) | ||||
| } | ||||
|  | ||||
| @ -10,18 +10,18 @@ use skytable::{ | ||||
|  | ||||
|  | ||||
| pub struct Users { | ||||
| 	pub login: String, | ||||
| 	pub uuid: String, | ||||
| 	pub login: String, | ||||
| 	pub password: String, | ||||
| 	pub email: String, | ||||
| 	pub tokens: Vec<Value>, | ||||
| } | ||||
|  | ||||
| impl Users { | ||||
| 	pub fn new(login: String, uuid: String, password: String, email: String, tokens: Vec<Value>) -> Self { | ||||
| 	pub fn new(uuid: String, login: String, password: String, email: String, tokens: Vec<Value>) -> Self { | ||||
| 		Self { | ||||
| 			login, | ||||
| 			uuid, | ||||
| 			login, | ||||
| 			password, | ||||
| 			email, | ||||
| 			tokens, | ||||
| @ -31,8 +31,8 @@ impl Users { | ||||
|  | ||||
| impl SQParam for Users { | ||||
| 	fn append_param(&self, q: &mut Vec<u8>) -> usize { | ||||
| 		self.login.append_param(q) | ||||
| 			+ self.uuid.append_param(q) | ||||
| 		self.uuid.append_param(q) | ||||
| 			+ self.login.append_param(q) | ||||
| 			+ self.password.append_param(q) | ||||
| 			+ self.email.append_param(q) | ||||
| 	} | ||||
| @ -40,7 +40,7 @@ impl SQParam for Users { | ||||
|  | ||||
| impl FromResponse for Users { | ||||
| 	fn from_response(resp: Response) -> ClientResult<Self> { | ||||
| 		let (login, uuid, password, email, tokens) = FromResponse::from_response(resp)?; | ||||
| 		Ok(Self::new(login, uuid, password, email, tokens)) | ||||
| 		let (uuid, login, password, email, tokens) = FromResponse::from_response(resp)?; | ||||
| 		Ok(Self::new(uuid, login, password, email, tokens)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -1,36 +1,30 @@ | ||||
| mod v0; | ||||
| mod v1; | ||||
| 
 | ||||
| use { | ||||
| 	hyper::{ | ||||
| 		StatusCode, | ||||
| 		Request, | ||||
| 		header::HeaderValue, | ||||
| 		body::{ | ||||
| 			Incoming, | ||||
| 		}, | ||||
| 		body::Incoming, | ||||
| 	}, | ||||
| 	serde_json::{ | ||||
| 		Value as Json, | ||||
| 		json, | ||||
| 	}, | ||||
| 	skytable::pool::ConnectionMgrTcp, | ||||
| 	bb8::Pool, | ||||
| 	std::sync::Arc, | ||||
| 	crate::DBPool, | ||||
| }; | ||||
| 
 | ||||
| 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>>; | ||||
| 
 | ||||
| 
 | ||||
| pub async fn endpoint(req: Request<Incoming>, pool: DBPool) -> (String, StatusCode, HeaderValue) { | ||||
| 	let uri: &str = req.uri().path().as_ref(); | ||||
| 	let res: Json = match &uri[4..uri.len()] { | ||||
| 		"/test" => json!({"error": false, "msg": "test"}), | ||||
| 		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"}) | ||||
| 	}; | ||||
| 	
 | ||||
| 	let restype: HeaderValue = "application/json".parse().unwrap(); | ||||
| 	(res.to_string(), StatusCode::IM_A_TEAPOT, restype) | ||||
| 	(res.to_string(), StatusCode::OK, restype) | ||||
| } | ||||
							
								
								
									
										75
									
								
								src/url/api/v0.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/url/api/v0.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}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										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
	