239 lines
6.5 KiB
Rust
239 lines
6.5 KiB
Rust
use std::error::Error;
|
|
|
|
use axum::{
|
|
Json,
|
|
extract::{Query, State},
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
use sqlx::{PgPool, query, query_as, query_scalar, types::Uuid};
|
|
use tower_sessions::Session;
|
|
|
|
use crate::error::AppError;
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
pub struct User {
|
|
id: Uuid,
|
|
steamid: String,
|
|
name: String,
|
|
avatar: String,
|
|
}
|
|
|
|
impl User {
|
|
fn new<T>(steamid: T, name: T, avatar: T) -> User
|
|
where
|
|
String: From<T>,
|
|
{
|
|
User {
|
|
id: Uuid::nil(),
|
|
steamid: String::from(steamid),
|
|
name: String::from(name),
|
|
avatar: String::from(avatar),
|
|
}
|
|
}
|
|
|
|
async fn create(&mut self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
|
|
let uuid: Uuid = query_scalar!(
|
|
"INSERT INTO users (steamid, name, avatar) VALUES ($1, $2, $3) RETURNING id",
|
|
self.steamid,
|
|
self.name,
|
|
self.avatar
|
|
)
|
|
.fetch_one(pool)
|
|
.await?;
|
|
self.id = uuid;
|
|
Ok(())
|
|
}
|
|
|
|
async fn delete(&self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
|
|
query!("DELETE FROM users WHERE id=$1", self.id)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn update(&self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
|
|
query!(
|
|
"UPDATE users SET steamid=$1, name=$2, avatar=$3 WHERE id=$4",
|
|
self.steamid,
|
|
self.name,
|
|
self.avatar,
|
|
self.id
|
|
)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn fetch_id(pool: &PgPool, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
|
|
Ok(query_as!(User, "SELECT * FROM users WHERE id=$1", uuid)
|
|
.fetch_one(pool)
|
|
.await?)
|
|
}
|
|
|
|
async fn steamid_exists(pool: &PgPool, steamid: &String) -> Result<bool, Box<dyn Error>> {
|
|
match query!("SELECT id FROM users WHERE steamid=$1", steamid)
|
|
.fetch_optional(pool)
|
|
.await?
|
|
{
|
|
Some(_) => Ok(true),
|
|
None => Ok(false),
|
|
}
|
|
}
|
|
|
|
async fn from_steamid(pool: &PgPool, steamid: String) -> Result<Self, Box<dyn Error>> {
|
|
if User::steamid_exists(pool, &steamid).await? {
|
|
let user = query_as!(User, "SELECT * FROM users WHERE steamid=$1", steamid)
|
|
.fetch_one(pool)
|
|
.await?;
|
|
return Ok(user);
|
|
}
|
|
|
|
let raw_user = Self::fetch_steam(&steamid).await?;
|
|
let mut user = User::new(
|
|
raw_user["steamid"].to_string().trim_matches('"'),
|
|
raw_user["personaname"].to_string().trim_matches('"'),
|
|
raw_user["avatarfull"].to_string().trim_matches('"'),
|
|
);
|
|
user.create(pool).await?;
|
|
Ok(user)
|
|
}
|
|
|
|
async fn fetch_steam(steamid: &String) -> Result<Value, Box<dyn Error>> {
|
|
let response = reqwest::get(format!("https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=ABBFE30D7BDC1FD582DB1A2CEBD563B4&steamids={steamid}"))
|
|
.await?.bytes().await?;
|
|
let mut json: serde_json::Value = serde_json::from_slice(&response)?;
|
|
Ok(json["response"]["players"][0].take())
|
|
}
|
|
|
|
async fn update_from_steam(&mut self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
|
|
let raw_user = Self::fetch_steam(&self.steamid).await?;
|
|
self.name = raw_user["personaname"].to_string().trim_matches('"').into();
|
|
self.avatar = raw_user["avatarfull"].to_string().trim_matches('"').into();
|
|
self.update(pool).await
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
pub struct OpenId {
|
|
#[serde(rename = "openid.ns")]
|
|
ns: String,
|
|
|
|
#[serde(rename = "openid.mode")]
|
|
mode: String,
|
|
|
|
#[serde(rename = "openid.op_endpoint")]
|
|
op_endpoint: String,
|
|
|
|
#[serde(rename = "openid.claimed_id")]
|
|
claimed_id: String,
|
|
|
|
#[serde(rename = "openid.identity")]
|
|
identity: String,
|
|
|
|
#[serde(rename = "openid.return_to")]
|
|
return_to: String,
|
|
|
|
#[serde(rename = "openid.response_nonce")]
|
|
response_nonce: String,
|
|
|
|
#[serde(rename = "openid.assoc_handle")]
|
|
assoc_handle: String,
|
|
|
|
#[serde(rename = "openid.signed")]
|
|
signed: String,
|
|
|
|
#[serde(rename = "openid.sig")]
|
|
sig: String,
|
|
}
|
|
|
|
impl OpenId {
|
|
pub async fn is_valid(&mut self) -> Result<bool, Box<dyn Error>> {
|
|
self.mode = "check_authentication".into();
|
|
let url_encoded = serde_urlencoded::to_string(self)?;
|
|
|
|
let response = reqwest::get(format!(
|
|
"https://steamcommunity.com/openid/login?{url_encoded}"
|
|
))
|
|
.await?
|
|
.text()
|
|
.await?;
|
|
|
|
Ok(response.contains("is_valid:true"))
|
|
}
|
|
}
|
|
|
|
#[axum::debug_handler]
|
|
pub async fn login(
|
|
State(pool): State<PgPool>,
|
|
Query(mut info): Query<OpenId>,
|
|
session: Session,
|
|
) -> Result<(), AppError> {
|
|
if !info.is_valid().await? {
|
|
return Err(AppError::OpenIdError(
|
|
"Some query parameters are erronous or lacking.".into(),
|
|
));
|
|
}
|
|
let steamid = info
|
|
.identity
|
|
.replace("https://steamcommunity.com/openid/id/", "");
|
|
let user = User::from_steamid(&pool, steamid).await?;
|
|
session.insert("uuid", user.id).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn logout(session: Session) -> Result<(), AppError> {
|
|
session.delete().await?;
|
|
Ok(())
|
|
}
|
|
|
|
#[axum::debug_handler]
|
|
pub async fn me(State(pool): State<PgPool>, session: Session) -> Result<Json<User>, AppError> {
|
|
if let Some(uuid) = session.get("uuid").await? {
|
|
let user = User::fetch_id(&pool, uuid).await?;
|
|
Ok(Json(user))
|
|
} else {
|
|
Err(AppError::Unauthorised)
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct Settings {
|
|
username: String,
|
|
avatar: String,
|
|
}
|
|
|
|
#[axum::debug_handler]
|
|
pub async fn update(
|
|
State(pool): State<PgPool>,
|
|
session: Session,
|
|
data: Json<Settings>,
|
|
) -> Result<(), AppError> {
|
|
if let Some(uuid) = session.get("uuid").await? {
|
|
let mut user = User::fetch_id(&pool, uuid).await?;
|
|
|
|
if !data.username.is_empty() {
|
|
user.name = data.username.clone();
|
|
}
|
|
if !data.avatar.is_empty() {
|
|
user.avatar = data.avatar.clone();
|
|
}
|
|
|
|
user.update(&pool).await?;
|
|
|
|
Ok(())
|
|
} else {
|
|
Err(AppError::Unauthorised)
|
|
}
|
|
}
|
|
|
|
pub async fn reload(State(pool): State<PgPool>, session: Session) -> Result<(), AppError> {
|
|
if let Some(uuid) = session.get("uuid").await? {
|
|
let mut user = User::fetch_id(&pool, uuid).await?;
|
|
user.update_from_steam(&pool).await?;
|
|
Ok(())
|
|
} else {
|
|
Err(AppError::Unauthorised)
|
|
}
|
|
}
|