braincell die from grng
This commit is contained in:
commit
eb13664f84
6 changed files with 3355 additions and 0 deletions
3102
Cargo.lock
generated
Normal file
3102
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "rdiscord"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dotenv = "0.15.0"
|
||||||
|
postgres = "0.19.8"
|
||||||
|
rand = "0.8.5"
|
||||||
|
rand_chacha = "0.3.1"
|
||||||
|
rand_core = "0.6.4"
|
||||||
|
reqwest = {version = "0.12.5", features = ["json"]}
|
||||||
|
rocket = {version="0.5.1",features=["json"]}
|
||||||
|
rocket_contrib = "0.4.11"
|
||||||
|
serde = "1.0.206"
|
||||||
|
urlencoding = "2.1.3"
|
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 190 KiB |
106
src/discord.rs
Normal file
106
src/discord.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
extern crate reqwest;
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
pub mod discord {
|
||||||
|
static API: &str = "https://discord.com/api/v10";
|
||||||
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
|
use std::error::Error;
|
||||||
|
use urlencoding::encode;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct TokenResponse {
|
||||||
|
pub access_token: String,
|
||||||
|
pub token_type: String,
|
||||||
|
pub expires_in: usize,
|
||||||
|
pub refresh_token: String,
|
||||||
|
pub scope: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
pub struct AvatarDecorationData {
|
||||||
|
asset: String,
|
||||||
|
sku_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct User {
|
||||||
|
pub id: String,
|
||||||
|
pub username: String,
|
||||||
|
pub discriminator: String,
|
||||||
|
pub global_name: Option<String>,
|
||||||
|
pub avatar: Option<String>,
|
||||||
|
pub bot: Option<bool>,
|
||||||
|
pub system: Option<bool>,
|
||||||
|
pub mfa_enabled: Option<bool>,
|
||||||
|
pub banner: Option<String>,
|
||||||
|
pub accent_color: Option<usize>,
|
||||||
|
pub locale: Option<String>,
|
||||||
|
pub verified: Option<bool>,
|
||||||
|
pub email: Option<String>,
|
||||||
|
pub flags: Option<usize>,
|
||||||
|
pub premium_type: Option<usize>,
|
||||||
|
pub public_flags: Option<usize>,
|
||||||
|
pub avatar_decoration_data: Option<AvatarDecorationData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Client {
|
||||||
|
redirect_uri: String,
|
||||||
|
client_secret: String,
|
||||||
|
client_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub fn new(redirect_uri: String, client_id: String, client_secret: String) -> Self {
|
||||||
|
Client {
|
||||||
|
redirect_uri,
|
||||||
|
client_id,
|
||||||
|
client_secret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_authorize_url(&self, scope: &str, response_type: &str) -> String {
|
||||||
|
let AUTHORIZE_ENDPOINT = "https://discord.com/oauth2/authorize";
|
||||||
|
format!(
|
||||||
|
"{}?client_id={}&response_type={}&redirect_uri={}&scope={}",
|
||||||
|
AUTHORIZE_ENDPOINT,
|
||||||
|
self.client_id,
|
||||||
|
response_type,
|
||||||
|
&encode(&self.redirect_uri),
|
||||||
|
scope
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user(
|
||||||
|
&self,
|
||||||
|
token_response: &TokenResponse,
|
||||||
|
) -> Result<User, Box<dyn Error>> {
|
||||||
|
let http_client = reqwest::Client::new();
|
||||||
|
let user_request = http_client
|
||||||
|
.get(format!("{}/{}", API, "users/@me"))
|
||||||
|
.bearer_auth(&token_response.access_token)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
let res: User = user_request.json().await?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn exchange_code(&self, code: &String) -> Result<TokenResponse, Box<dyn Error>> {
|
||||||
|
let http_client = reqwest::Client::new();
|
||||||
|
let exchange_request = http_client
|
||||||
|
.post(format!("{}/{}", API, "/oauth2/token"))
|
||||||
|
.header("content-type", "application/x-www-form-urlencoded")
|
||||||
|
.basic_auth(&self.client_id, Some(&self.client_secret))
|
||||||
|
.form(&[
|
||||||
|
("grant_type", "authorization_code"),
|
||||||
|
("code", code),
|
||||||
|
("redirect_uri", &self.redirect_uri),
|
||||||
|
])
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let res: TokenResponse = exchange_request.json().await?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/main.rs
Normal file
75
src/main.rs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
extern crate reqwest;
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
mod discord;
|
||||||
|
mod session;
|
||||||
|
|
||||||
|
use discord::discord::Client;
|
||||||
|
use dotenv::dotenv;
|
||||||
|
use rocket::fs::NamedFile;
|
||||||
|
use rocket::response::Redirect;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use rocket::tokio::sync::Mutex;
|
||||||
|
use rocket::State;
|
||||||
|
use rocket::{get, launch, routes};
|
||||||
|
|
||||||
|
use self::session::session::{Session, SessionManager};
|
||||||
|
|
||||||
|
type SharedSessionManager = Mutex<SessionManager>;
|
||||||
|
|
||||||
|
#[get("/callback?<code>")]
|
||||||
|
async fn index(
|
||||||
|
shared_session_manager: &State<SharedSessionManager>,
|
||||||
|
code: String,
|
||||||
|
client: &State<Client>,
|
||||||
|
) -> Json<Session> {
|
||||||
|
let token_exchange = client.exchange_code(&code).await.unwrap();
|
||||||
|
let user = client.get_user(&token_exchange).await.unwrap();
|
||||||
|
let mut session_manager = shared_session_manager.lock().await;
|
||||||
|
let session = session_manager
|
||||||
|
.generate_session(user, &token_exchange.access_token)
|
||||||
|
.await;
|
||||||
|
Json(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/favicon.ico")]
|
||||||
|
async fn get_favicon() -> Option<NamedFile> {
|
||||||
|
NamedFile::open("./favicon.ico").await.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/session?<token>")]
|
||||||
|
async fn return_sess(
|
||||||
|
shared_session_manager: &State<SharedSessionManager>,
|
||||||
|
token: String,
|
||||||
|
) -> Json<Session> {
|
||||||
|
Json(
|
||||||
|
shared_session_manager
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.get_session(&token)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn redirect_auth(client: &State<Client>) -> Redirect {
|
||||||
|
Redirect::to(client.generate_authorize_url("identify", "code"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
fn launch() -> _ {
|
||||||
|
dotenv().ok();
|
||||||
|
let REDIRECT_URI = std::env::var("REDIRECT_URI").expect("yall forgot REDIRECT_URI");
|
||||||
|
let CLIENT_ID = std::env::var("CLIENT_ID").expect("yall forgot CLIENT_ID");
|
||||||
|
let CLIENT_SECRET = std::env::var("CLIENT_SECRET").expect("yall forgot CLIENT_SECRET");
|
||||||
|
|
||||||
|
let client: Client = Client::new(REDIRECT_URI, CLIENT_ID, CLIENT_SECRET);
|
||||||
|
let session_manager: SessionManager = SessionManager::new();
|
||||||
|
let shared_session_manager: SharedSessionManager = SharedSessionManager::new(session_manager);
|
||||||
|
rocket::build()
|
||||||
|
.manage(client)
|
||||||
|
.manage(shared_session_manager)
|
||||||
|
.mount("/", routes![index, redirect_auth, return_sess, get_favicon])
|
||||||
|
}
|
56
src/session.rs
Normal file
56
src/session.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
pub mod session {
|
||||||
|
use std::error::Error;
|
||||||
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::discord::discord::User;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Session {
|
||||||
|
pub access_token: String,
|
||||||
|
pub user: User,
|
||||||
|
pub session_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SessionManager {
|
||||||
|
pub sessions: Vec<Session>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionManager {
|
||||||
|
pub async fn generate_random_string(&mut self) -> String {
|
||||||
|
rand::thread_rng()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(32)
|
||||||
|
.map(char::from)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
sessions: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_session(&mut self, session: &String) -> Result<Session, Box<dyn Error>> {
|
||||||
|
let found_sessions: Vec<Session> = self
|
||||||
|
.sessions
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|s| s.session_token.eq(session))
|
||||||
|
.collect();
|
||||||
|
Ok(found_sessions.get(0).unwrap().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn generate_session(&mut self, user: User, access_token: &str) -> Session {
|
||||||
|
self.sessions.retain(|s| s.user.id != user.id);
|
||||||
|
let token = self.generate_random_string().await;
|
||||||
|
let session = Session {
|
||||||
|
access_token: access_token.to_string(),
|
||||||
|
user,
|
||||||
|
session_token: token,
|
||||||
|
};
|
||||||
|
self.sessions.push(session.clone());
|
||||||
|
session
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue