Skip to content

Commit 133eef7

Browse files
committed
initial implementaiton of login with discord, getting your own user info and websocket with auth
1 parent 44cfcbb commit 133eef7

20 files changed

Lines changed: 1865 additions & 203 deletions

Cargo.lock

Lines changed: 1208 additions & 128 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ base64="0.13"
99
darkredis = "0.7"
1010
flexi_logger = { version = "0.15", default-features = false, features = ["colors", "specfile", "ziplogs"] }
1111
futures-util = { version = "0.3", default-features = false }
12-
hyper = { version = "0.13" }
12+
form_urlencoded="1.0"
13+
hyper = { version = "0.13"}
14+
hyper-tls = "0.4"
1315
log = "0.4"
16+
rand="0.8"
17+
serde = { version = "1.0", features = ["derive"] }
18+
serde_json = "1"
19+
serde_urlencoded="0.7"
20+
sqlx = { version = "0.4", default-features = false, features = ["postgres", "json", "runtime-tokio-rustls", "macros"] }
1421
sha-1 = "0.9"
1522
tokio = { version = "0.2", features = ["full", "sync", "time"] }
1623
tokio-tungstenite = "0.11.0"
1724
toml = "0.5"
18-
serde = { version = "1.0", features = ["derive"] }
19-
serde_json = "1"
25+
twilight-model = "0.2"
2026
uuid = { version = "0.8", features = ["serde", "v4"], default_features = false }

src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ use std::fs;
66
pub struct ApiConfig {
77
pub redis: String,
88
pub port: u16,
9+
pub application_id: u64,
10+
pub client_secret: String,
11+
pub redirect_uri: String,
12+
pub domain: String,
13+
pub secure: bool
914
}
1015

1116
impl ApiConfig {

src/error.rs

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use hyper::StatusCode;
22
use std::fmt;
33
use tokio::sync::oneshot::error::RecvError;
4+
use std::fmt::Formatter;
5+
use std::borrow::Cow;
46

57
#[derive(Debug)]
68
pub enum StartupError {
@@ -29,14 +31,107 @@ pub enum CommunicationError {
2931

3032
#[derive(Debug)]
3133
pub enum ServerError {
32-
Hyper(hyper::http::Error),
34+
HyperHttp(hyper::http::Error),
3335
Communication(CommunicationError),
36+
Hyper(hyper::Error),
37+
DiscordError(String),
38+
Database(DatabaseError),
3439
}
3540

3641
#[derive(Debug)]
3742
pub enum BadRequestError {
3843
UpgradeOnly,
3944
MissingWsKey,
45+
NoAccessCode,
46+
}
47+
48+
#[derive(Debug)]
49+
pub enum DatabaseError {
50+
Sqlx(sqlx::Error),
51+
Deserializing(serde_json::Error),
52+
Serializing(serde_json::Error),
53+
Darkredis(darkredis::Error),
54+
}
55+
56+
#[derive(Debug)]
57+
pub enum WSMessageError {
58+
Tungstenite(tokio_tungstenite::tungstenite::Error),
59+
Database(DatabaseError),
60+
Communication(CommunicationError),
61+
CorruptMessage(serde_json::Error),
62+
NotAuthorized,
63+
BadAuthorization,
64+
AlreadyAuthorized,
65+
ClosedGracefully,
66+
}
67+
68+
69+
impl fmt::Display for WSMessageError {
70+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
71+
match self {
72+
WSMessageError::Tungstenite(e) => write!(f, "Tunstenite failure: {}", e),
73+
WSMessageError::Database(e) => write!(f, "Failed to fetch database information: {}", e),
74+
WSMessageError::Communication(e) => write!(f, "Failed to communicate with GearBot: {}", e),
75+
WSMessageError::CorruptMessage(e) => write!(f, "Corrupt message recieved: {}", e),
76+
WSMessageError::NotAuthorized => write!(f, "Someone didn't identify themselves"),
77+
WSMessageError::BadAuthorization => write!(f, "Someone gave us an invalid token to try and identify"),
78+
WSMessageError::AlreadyAuthorized => write!(f, "Someone double identified"),
79+
WSMessageError::ClosedGracefully => write!(f, "Connection closed by client")
80+
}
81+
}
82+
}
83+
84+
const CORRUPT_MESSAGE: &str = "Corrupt message recieved";
85+
const NOT_AUTHORIZED: &str = "You failed to identify yourself first, access denied!";
86+
const BAD_AUTHORIZATION: &str = "You failed to identify yourself first, access denied!";
87+
const ALREADY_AUTHORIZED: &str = "You can not identify twice!";
88+
const TUNGSTENITE: &str = "Unable to process message";
89+
90+
impl WSMessageError {
91+
pub fn closes_socket(&self) -> bool {
92+
match self {
93+
WSMessageError::Tungstenite(_) |
94+
WSMessageError::CorruptMessage(_) |
95+
WSMessageError::NotAuthorized |
96+
WSMessageError::AlreadyAuthorized |
97+
WSMessageError::BadAuthorization => true,
98+
_ => false
99+
}
100+
}
101+
102+
pub fn get_close_message(&self) -> &'static str {
103+
match self {
104+
WSMessageError::CorruptMessage(_) => CORRUPT_MESSAGE,
105+
WSMessageError::NotAuthorized => NOT_AUTHORIZED,
106+
WSMessageError::BadAuthorization => BAD_AUTHORIZATION,
107+
WSMessageError::AlreadyAuthorized => ALREADY_AUTHORIZED,
108+
WSMessageError::Tungstenite(_) => TUNGSTENITE,
109+
_ => unreachable!()
110+
}
111+
}
112+
}
113+
114+
impl fmt::Display for DatabaseError {
115+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
116+
match self {
117+
DatabaseError::Sqlx(e) => write!(f, "Database failure: {:?}", e),
118+
DatabaseError::Deserializing(e) => write!(f, "Failed to deserialize: {}", e),
119+
DatabaseError::Serializing(e) => write!(f, "Failed to seralize: {}", e),
120+
DatabaseError::Darkredis(e) => write!(f, "Redis failure: {}", e),
121+
}
122+
}
123+
}
124+
125+
impl From<darkredis::Error> for DatabaseError {
126+
fn from(e: darkredis::Error) -> Self {
127+
DatabaseError::Darkredis(e)
128+
}
129+
}
130+
131+
impl From<sqlx::Error> for DatabaseError {
132+
fn from(e: sqlx::Error) -> Self {
133+
DatabaseError::Sqlx(e)
134+
}
40135
}
41136

42137
impl RequestError {
@@ -75,8 +170,11 @@ impl fmt::Display for StartupError {
75170
impl fmt::Display for ServerError {
76171
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77172
match self {
78-
ServerError::Hyper(e) => write!(f, "Error assembling hyper response: {}", e),
173+
ServerError::HyperHttp(e) => write!(f, "Error assembling hyper response: {}", e),
79174
ServerError::Communication(e) => write!(f, "Error communicating with GearBot: {}", e),
175+
ServerError::Hyper(e) => write!(f, "Error making a request to the discord api: {}", e),
176+
ServerError::Database(e) => write!(f, "Database error occured: {}", e),
177+
ServerError::DiscordError(e) => write!(f, "Error making a request to discord: {}", e)
80178
}
81179
}
82180
}
@@ -107,7 +205,7 @@ impl fmt::Display for CommunicationError {
107205

108206
impl From<hyper::http::Error> for RequestError {
109207
fn from(e: hyper::http::Error) -> Self {
110-
RequestError::Server(ServerError::Hyper(e))
208+
RequestError::Server(ServerError::HyperHttp(e))
111209
}
112210
}
113211

@@ -140,3 +238,27 @@ impl From<BadRequestError> for RequestError {
140238
RequestError::BadRequest(e)
141239
}
142240
}
241+
242+
impl From<hyper::Error> for RequestError {
243+
fn from(e: hyper::Error) -> Self {
244+
RequestError::Server(ServerError::Hyper(e))
245+
}
246+
}
247+
248+
impl From<DatabaseError> for RequestError {
249+
fn from(e: DatabaseError) -> Self {
250+
RequestError::Server(ServerError::Database(e))
251+
}
252+
}
253+
254+
impl From<DatabaseError> for WSMessageError {
255+
fn from(e: DatabaseError) -> Self {
256+
WSMessageError::Database(e)
257+
}
258+
}
259+
260+
impl From<CommunicationError> for WSMessageError {
261+
fn from(e: CommunicationError) -> Self {
262+
WSMessageError::Communication(e)
263+
}
264+
}

src/main.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
use crate::config::ApiConfig;
22
use crate::error::{RequestError, StartupError};
33
use crate::redis::redis_link::RedisLink;
4-
use crate::routes::{hello_world, not_found, team_info, ws};
4+
use crate::routes::{hello_world, not_found, team_info, ws, discord::{login, auth, user_info}};
55
use flexi_logger::{colored_opt_format, Age, Cleanup, Criterion, Duplicate, Logger, Naming};
66
use hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
77
use hyper::service::{make_service_fn, service_fn};
8-
use hyper::{Body, Method, Request, Response, Server};
8+
use hyper::{Body, Method, Request, Response, Server, Client};
99
use log::{error, info};
1010
use std::convert::Infallible;
1111
use std::env;
1212
use std::net::SocketAddr;
1313
use std::sync::Arc;
14+
use twilight_model::id::ApplicationId;
15+
use hyper_tls::HttpsConnector;
16+
use hyper_tls::native_tls::TlsConnector;
17+
use hyper::client::HttpConnector;
18+
1419

1520
mod config;
1621
mod error;
1722
mod redis;
1823
mod routes;
24+
mod models;
25+
mod util;
1926

2027
pub struct ApiContext {
2128
pub config: ApiConfig,
2229
pub redis_link: RedisLink,
30+
pub client: Client<HttpsConnector<HttpConnector>>,
2331
}
2432

2533
#[tokio::main]
@@ -47,7 +55,9 @@ async fn main() -> Result<(), StartupError> {
4755
info!("Redis connection established");
4856

4957
let port = config.port;
50-
let api_context = Arc::new(ApiContext { config, redis_link });
58+
let https = HttpsConnector::new();
59+
let client = Client::builder().build::<_, hyper::Body>(https);
60+
let api_context = Arc::new(ApiContext { config, redis_link, client });
5161
let addr = SocketAddr::from(([127, 0, 0, 1], port));
5262
let make_svc = make_service_fn(|_conn| {
5363
let context = api_context.clone();
@@ -86,10 +96,14 @@ async fn handle_request(
8696
.skip_while(|p| *p == "api")
8797
.collect::<Vec<&str>>();
8898
let method = Method::from(request.method());
99+
let query = path_and_query.query();
89100
let response = match (&method, parts.as_slice()) {
90101
(&Method::GET, ["hello"]) => hello_world().await,
91102
(&Method::GET, ["team_info"]) => team_info(context).await,
92103
(&Method::GET, ["ws"]) => ws(context, request).await,
104+
(&Method::GET, ["discord", "login"]) => login(context).await,
105+
(&Method::GET, ["discord", "auth"]) => auth(context, query).await,
106+
(&Method::GET, ["discord", "user"]) => user_info(context, request).await,
93107
_ => not_found(),
94108
};
95109

src/models/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod token_response;
2+
pub use token_response::TokenResponse;
3+
4+
mod user_guilds;
5+
pub use user_guilds::UserGuild;

src/models/token_response.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use serde::Deserialize;
2+
3+
#[derive(Deserialize, Debug)]
4+
pub struct TokenResponse {
5+
pub access_token: String,
6+
pub token_type: String,
7+
pub expires_in: u64,
8+
pub refresh_token: String,
9+
pub scope: String
10+
}

src/models/user_guilds.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use twilight_model::id::GuildId;
2+
use twilight_model::guild::Permissions;
3+
use serde::{Deserialize, Serialize};
4+
5+
#[derive(Debug, Deserialize, Serialize)]
6+
pub struct UserGuild {
7+
pub id: GuildId,
8+
pub name: String,
9+
pub icon: Option<String>,
10+
pub owner: bool,
11+
pub permissions: Permissions,
12+
pub features: Vec<String>
13+
}

src/redis/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use serde::{Deserialize, Serialize};
22
use uuid::Uuid;
3+
use twilight_model::user::UserFlags;
34

45
pub mod redis_link;
56

@@ -12,6 +13,7 @@ pub struct GearBotRequest {
1213
#[derive(Debug, Serialize, Deserialize)]
1314
pub enum Request {
1415
TeamInfo,
16+
UserInfo(u64)
1517
}
1618

1719
#[derive(Debug, Deserialize, Clone)]
@@ -24,6 +26,7 @@ pub struct Reply {
2426
pub enum ReplyData {
2527
Blank, //
2628
TeamInfo(TeamInfo),
29+
UserInfo(Option<UserInfo>)
2730
}
2831

2932
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -51,6 +54,20 @@ pub struct TeamSocials {
5154
pub website: Option<String>,
5255
}
5356

57+
#[derive(Debug, Serialize, Deserialize, Clone)]
58+
pub struct UserInfo {
59+
pub name: String,
60+
pub discriminator: String,
61+
#[serde(default, skip_serializing_if = "is_default")]
62+
pub avatar: Option<String>,
63+
#[serde(default, skip_serializing_if = "is_default")]
64+
pub bot_user: bool,
65+
#[serde(default, skip_serializing_if = "is_default")]
66+
pub system_user: bool,
67+
#[serde(default, skip_serializing_if = "is_default")]
68+
pub public_flags: Option<UserFlags>,
69+
}
70+
5471
fn is_default<T: Default + PartialEq>(t: &T) -> bool {
5572
t == &T::default()
5673
}

0 commit comments

Comments
 (0)