chore: moved routes under dedicated mod
This commit is contained in:
parent
2cf80d2eaf
commit
430342ac74
4 changed files with 179 additions and 160 deletions
171
src/main.rs
171
src/main.rs
|
@ -1,18 +1,19 @@
|
|||
mod proto;
|
||||
mod entities;
|
||||
mod routes;
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use chrono::{DateTime, Utc, Duration};
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::Parser;
|
||||
use axum::{Router, routing::{get, post}, response::IntoResponse, Json, http::StatusCode, extract::{State, Query}};
|
||||
use proto::{Error, AuthenticateRequest, AuthenticateResponse, JoinRequest, JoinResponse, ValidateRequest, RefreshRequest, RefreshResponse, RegisterRequest};
|
||||
use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, DatabaseConnection, Database, ActiveValue::NotSet, Set};
|
||||
use axum::{Router, routing::{get, post}, response::IntoResponse, Json, http::StatusCode, extract::State};
|
||||
use sea_orm::{EntityTrait, DatabaseConnection, Database, ActiveValue::NotSet, Set};
|
||||
use tokio::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
use tracing::{info, warn};
|
||||
use tracing::info;
|
||||
|
||||
use crate::proto::Property;
|
||||
use crate::routes::auth::{authenticate, validate, refresh};
|
||||
use crate::routes::session::{join, has_joined};
|
||||
|
||||
/// Reimplementation of legacy auth server for minecraft
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
|
@ -31,7 +32,7 @@ struct ConfigArgs {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct JoinAttempt {
|
||||
pub struct JoinAttempt {
|
||||
server: String,
|
||||
time: DateTime<Utc>,
|
||||
}
|
||||
|
@ -43,7 +44,7 @@ impl JoinAttempt {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
pub struct AppState {
|
||||
store: Arc<Mutex<HashMap<Uuid, JoinAttempt>>>,
|
||||
db: DatabaseConnection,
|
||||
cfg: ConfigArgs,
|
||||
|
@ -82,7 +83,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
|
||||
async fn fallback_route() -> impl IntoResponse {
|
||||
let error = Error {
|
||||
let error = proto::Error {
|
||||
error: "No valid route specified".into(),
|
||||
errorMessage: "No route requested or invalid route specified".into(),
|
||||
cause: "Routing".into(),
|
||||
|
@ -91,157 +92,7 @@ async fn fallback_route() -> impl IntoResponse {
|
|||
(StatusCode::OK, Json(error))
|
||||
}
|
||||
|
||||
async fn validate(State(state): State<AppState>, Json(payload): Json<ValidateRequest>) -> StatusCode {
|
||||
let token = entities::token::Entity::find().filter(
|
||||
entities::token::Column::AccessToken.eq(payload.accessToken)
|
||||
).one(&state.db).await.unwrap();
|
||||
|
||||
if let Some(_t) = token {
|
||||
StatusCode::NO_CONTENT
|
||||
} else {
|
||||
StatusCode::UNAUTHORIZED
|
||||
}
|
||||
}
|
||||
|
||||
async fn refresh(State(state): State<AppState>, Json(payload): Json<RefreshRequest>) -> Result<Json<RefreshResponse>, StatusCode> {
|
||||
let token = entities::token::Entity::find().filter(
|
||||
entities::token::Column::AccessToken.eq(payload.accessToken.clone())
|
||||
).one(&state.db).await.unwrap();
|
||||
|
||||
if let Some(t) = token {
|
||||
// TODO if user requests profile, fetch it and include it
|
||||
let user = entities::user::Entity::find_by_id(t.user_id).one(&state.db).await.unwrap().unwrap();
|
||||
entities::token::Entity::delete(
|
||||
entities::token::ActiveModel{
|
||||
id: NotSet,
|
||||
access_token: Set(payload.accessToken.clone()),
|
||||
created_at: NotSet,
|
||||
user_id: NotSet,
|
||||
}
|
||||
).exec(&state.db).await.unwrap();
|
||||
let new_access_token = Uuid::new_v4(); // TODO same as with authenticate
|
||||
entities::token::Entity::insert(
|
||||
entities::token::ActiveModel{
|
||||
id: NotSet,
|
||||
access_token: Set(payload.accessToken.clone()),
|
||||
created_at: Set(Utc::now()),
|
||||
user_id: Set(user.id),
|
||||
}
|
||||
).exec(&state.db).await.unwrap();
|
||||
Ok(Json(
|
||||
RefreshResponse {
|
||||
accessToken: new_access_token.to_string(),
|
||||
clientToken: "idc".into(),
|
||||
selectedProfile: proto::Profile { id: user.uuid, name: user.name },
|
||||
user: None,
|
||||
}
|
||||
))
|
||||
} else {
|
||||
Err(StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
|
||||
async fn authenticate(State(state): State<AppState>, Json(payload): Json<AuthenticateRequest>) -> Result<Json<AuthenticateResponse>, StatusCode> {
|
||||
let user = entities::user::Entity::find().filter(
|
||||
entities::user::Column::Name.eq(payload.username)
|
||||
).one(&state.db).await.unwrap();
|
||||
|
||||
if let Some(u) = user {
|
||||
if payload.password == u.password {
|
||||
// make new token
|
||||
let access_token = Uuid::new_v4().to_string(); // TODO maybe use a JWT?
|
||||
entities::token::Entity::insert(entities::token::ActiveModel {
|
||||
id: NotSet,
|
||||
user_id: Set(u.id),
|
||||
access_token: Set(access_token.clone()),
|
||||
created_at: Set(Utc::now()),
|
||||
}).exec(&state.db).await.unwrap();
|
||||
let profile = proto::Profile {
|
||||
name: u.name.clone(),
|
||||
id: u.uuid,
|
||||
};
|
||||
Ok(Json(
|
||||
AuthenticateResponse {
|
||||
accessToken: access_token,
|
||||
user: proto::User { id: u.uuid, username: u.name, properties: Some(vec![]) },
|
||||
clientToken: Uuid::new_v4().to_string(),
|
||||
availableProfiles: vec![profile.clone()],
|
||||
selectedProfile: profile,
|
||||
}
|
||||
))
|
||||
} else {
|
||||
Err(StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
} else {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
async fn join(State(state): State<AppState>, Json(payload): Json<JoinRequest>) -> StatusCode {
|
||||
let user = entities::user::Entity::find().filter(
|
||||
entities::user::Column::Uuid.eq(payload.selectedProfile.id)
|
||||
).one(&state.db).await.unwrap().unwrap();
|
||||
|
||||
let tokens = entities::token::Entity::find().filter(
|
||||
entities::token::Column::UserId.eq(user.id)
|
||||
).all(&state.db).await.unwrap();
|
||||
|
||||
if tokens.iter().any(|x| x.access_token == payload.accessToken) {
|
||||
state.store.lock().await.insert(payload.selectedProfile.id, JoinAttempt::new(payload.serverId.clone()));
|
||||
info!(target: "JOIN", "user {} has joined server {}", payload.selectedProfile.name, payload.serverId);
|
||||
StatusCode::OK
|
||||
} else {
|
||||
warn!(target: "JOIN", "user {} attempted to join server {} without a valid token", payload.selectedProfile.name, payload.serverId);
|
||||
StatusCode::UNAUTHORIZED
|
||||
}
|
||||
}
|
||||
|
||||
async fn has_joined(State(state): State<AppState>, Query(query): Query<HashMap<String, String>>) -> Result<Json<JoinResponse>, StatusCode> {
|
||||
let username = query.get("username").unwrap().clone();
|
||||
let server_id = query.get("serverId").unwrap();
|
||||
|
||||
let user = entities::user::Entity::find().filter(
|
||||
entities::user::Column::Name.eq(username.clone())
|
||||
).one(&state.db).await.unwrap();
|
||||
|
||||
match user {
|
||||
Some(user) => {
|
||||
match state.store.lock().await.get(&user.uuid) {
|
||||
Some(join) => {
|
||||
if Utc::now() - join.time < Duration::seconds(state.cfg.time_window as i64)
|
||||
&& join.server.to_lowercase() == server_id.to_lowercase() {
|
||||
let response = JoinResponse {
|
||||
id: user.uuid,
|
||||
name: username.clone(),
|
||||
properties: vec![
|
||||
Property {
|
||||
name: "textures".into(),
|
||||
value: "".into(),
|
||||
signature: Some("".into()),
|
||||
}
|
||||
],
|
||||
};
|
||||
info!(target: "hasJOINED", "server found user -> {:?}", response);
|
||||
Ok(Json(response))
|
||||
} else {
|
||||
warn!(target: "hasJOINED", "server found user but join was late or for another server");
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
},
|
||||
None => {
|
||||
warn!(target: "hasJOINED", "server didn't find user");
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
},
|
||||
}
|
||||
},
|
||||
None => {
|
||||
warn!(target: "hasJOINED", "invalid UUID");
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn register_tmp(State(state): State<AppState>, Json(payload): Json<RegisterRequest>) -> StatusCode {
|
||||
async fn register_tmp(State(state): State<AppState>, Json(payload): Json<proto::RegisterRequest>) -> StatusCode {
|
||||
let user = entities::user::ActiveModel {
|
||||
id: NotSet,
|
||||
name: Set(payload.user),
|
||||
|
|
93
src/routes/auth.rs
Normal file
93
src/routes/auth.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use axum::{extract::State, Json, http::StatusCode};
|
||||
use chrono::Utc;
|
||||
use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, ActiveValue::NotSet, Set};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{entities, AppState, proto};
|
||||
|
||||
|
||||
pub async fn validate(State(state): State<AppState>, Json(payload): Json<proto::ValidateRequest>) -> StatusCode {
|
||||
let token = entities::token::Entity::find().filter(
|
||||
entities::token::Column::AccessToken.eq(payload.accessToken)
|
||||
).one(&state.db).await.unwrap();
|
||||
|
||||
if let Some(_t) = token {
|
||||
StatusCode::NO_CONTENT
|
||||
} else {
|
||||
StatusCode::UNAUTHORIZED
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn refresh(State(state): State<AppState>, Json(payload): Json<proto::RefreshRequest>) -> Result<Json<proto::RefreshResponse>, StatusCode> {
|
||||
let token = entities::token::Entity::find().filter(
|
||||
entities::token::Column::AccessToken.eq(payload.accessToken.clone())
|
||||
).one(&state.db).await.unwrap();
|
||||
|
||||
if let Some(t) = token {
|
||||
// TODO if user requests profile, fetch it and include it
|
||||
let user = entities::user::Entity::find_by_id(t.user_id).one(&state.db).await.unwrap().unwrap();
|
||||
entities::token::Entity::delete(
|
||||
entities::token::ActiveModel{
|
||||
id: NotSet,
|
||||
access_token: Set(payload.accessToken.clone()),
|
||||
created_at: NotSet,
|
||||
user_id: NotSet,
|
||||
}
|
||||
).exec(&state.db).await.unwrap();
|
||||
let new_access_token = Uuid::new_v4(); // TODO same as with authenticate
|
||||
entities::token::Entity::insert(
|
||||
entities::token::ActiveModel{
|
||||
id: NotSet,
|
||||
access_token: Set(payload.accessToken.clone()),
|
||||
created_at: Set(Utc::now()),
|
||||
user_id: Set(user.id),
|
||||
}
|
||||
).exec(&state.db).await.unwrap();
|
||||
Ok(Json(
|
||||
proto::RefreshResponse {
|
||||
accessToken: new_access_token.to_string(),
|
||||
clientToken: "idc".into(),
|
||||
selectedProfile: proto::Profile { id: user.uuid, name: user.name },
|
||||
user: None,
|
||||
}
|
||||
))
|
||||
} else {
|
||||
Err(StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn authenticate(State(state): State<AppState>, Json(payload): Json<proto::AuthenticateRequest>) -> Result<Json<proto::AuthenticateResponse>, StatusCode> {
|
||||
let user = entities::user::Entity::find().filter(
|
||||
entities::user::Column::Name.eq(payload.username)
|
||||
).one(&state.db).await.unwrap();
|
||||
|
||||
if let Some(u) = user {
|
||||
if payload.password == u.password {
|
||||
// make new token
|
||||
let access_token = Uuid::new_v4().to_string(); // TODO maybe use a JWT?
|
||||
entities::token::Entity::insert(entities::token::ActiveModel {
|
||||
id: NotSet,
|
||||
user_id: Set(u.id),
|
||||
access_token: Set(access_token.clone()),
|
||||
created_at: Set(Utc::now()),
|
||||
}).exec(&state.db).await.unwrap();
|
||||
let profile = proto::Profile {
|
||||
name: u.name.clone(),
|
||||
id: u.uuid,
|
||||
};
|
||||
Ok(Json(
|
||||
proto::AuthenticateResponse {
|
||||
accessToken: access_token,
|
||||
user: proto::User { id: u.uuid, username: u.name, properties: Some(vec![]) },
|
||||
clientToken: Uuid::new_v4().to_string(),
|
||||
availableProfiles: vec![profile.clone()],
|
||||
selectedProfile: profile,
|
||||
}
|
||||
))
|
||||
} else {
|
||||
Err(StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
} else {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
2
src/routes/mod.rs
Normal file
2
src/routes/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod auth;
|
||||
pub mod session;
|
73
src/routes/session.rs
Normal file
73
src/routes/session.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use axum::{extract::{State, Query}, http::StatusCode, Json};
|
||||
use chrono::{Duration, Utc};
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::{AppState, proto, JoinAttempt, entities};
|
||||
|
||||
|
||||
pub async fn join(State(state): State<AppState>, Json(payload): Json<proto::JoinRequest>) -> StatusCode {
|
||||
let user = entities::user::Entity::find().filter(
|
||||
entities::user::Column::Uuid.eq(payload.selectedProfile.id)
|
||||
).one(&state.db).await.unwrap().unwrap();
|
||||
|
||||
let tokens = entities::token::Entity::find().filter(
|
||||
entities::token::Column::UserId.eq(user.id)
|
||||
).all(&state.db).await.unwrap();
|
||||
|
||||
if tokens.iter().any(|x| x.access_token == payload.accessToken) {
|
||||
state.store.lock().await.insert(payload.selectedProfile.id, JoinAttempt::new(payload.serverId.clone()));
|
||||
info!(target: "JOIN", "user {} has joined server {}", payload.selectedProfile.name, payload.serverId);
|
||||
StatusCode::OK
|
||||
} else {
|
||||
warn!(target: "JOIN", "user {} attempted to join server {} without a valid token", payload.selectedProfile.name, payload.serverId);
|
||||
StatusCode::UNAUTHORIZED
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn has_joined(State(state): State<AppState>, Query(query): Query<HashMap<String, String>>) -> Result<Json<proto::JoinResponse>, StatusCode> {
|
||||
let username = query.get("username").unwrap().clone();
|
||||
let server_id = query.get("serverId").unwrap();
|
||||
|
||||
let user = entities::user::Entity::find().filter(
|
||||
entities::user::Column::Name.eq(username.clone())
|
||||
).one(&state.db).await.unwrap();
|
||||
|
||||
match user {
|
||||
Some(user) => {
|
||||
match state.store.lock().await.get(&user.uuid) {
|
||||
Some(join) => {
|
||||
if Utc::now() - join.time < Duration::seconds(state.cfg.time_window as i64)
|
||||
&& join.server.to_lowercase() == server_id.to_lowercase() {
|
||||
let response = proto::JoinResponse {
|
||||
id: user.uuid,
|
||||
name: username.clone(),
|
||||
properties: vec![
|
||||
proto::Property {
|
||||
name: "textures".into(),
|
||||
value: "".into(),
|
||||
signature: Some("".into()),
|
||||
}
|
||||
],
|
||||
};
|
||||
info!(target: "hasJOINED", "server found user -> {:?}", response);
|
||||
Ok(Json(response))
|
||||
} else {
|
||||
warn!(target: "hasJOINED", "server found user but join was late or for another server");
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
},
|
||||
None => {
|
||||
warn!(target: "hasJOINED", "server didn't find user");
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
},
|
||||
}
|
||||
},
|
||||
None => {
|
||||
warn!(target: "hasJOINED", "invalid UUID");
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
},
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue