2026-06-07 09:52:57 +02:00
|
|
|
use rocket::FromForm;
|
|
|
|
|
use rocket::{get, post, response::Redirect, http::{Status, ContentType}, State, form::Form};
|
|
|
|
|
use std::fs;
|
|
|
|
|
use rand::prelude::*;
|
|
|
|
|
use sqlx::Row;
|
|
|
|
|
use chrono::{Utc, Duration};
|
2026-06-07 18:30:43 +02:00
|
|
|
use crate::json_api::oauth::generate_token_v3::verify_nintendo_password;
|
2026-06-07 09:52:57 +02:00
|
|
|
use crate::Pool;
|
|
|
|
|
|
|
|
|
|
#[derive(FromForm)]
|
|
|
|
|
pub struct AuthorizeQueryParams<'r> {
|
|
|
|
|
pub response_type: &'r str,
|
|
|
|
|
pub client_id: &'r str,
|
|
|
|
|
pub redirect_uri: &'r str,
|
|
|
|
|
pub state: Option<&'r str>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(FromForm)]
|
|
|
|
|
pub struct AuthorizeFormSubmit<'r> {
|
|
|
|
|
pub username: &'r str,
|
|
|
|
|
pub password: &'r str,
|
|
|
|
|
pub client_id: &'r str,
|
|
|
|
|
pub redirect_uri: &'r str,
|
|
|
|
|
pub state: Option<&'r str>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/api/v2/oauth2/authorize?<params..>")]
|
|
|
|
|
pub async fn authorize_page(params: AuthorizeQueryParams<'_>) -> Result<(ContentType, String), Status> {
|
|
|
|
|
if params.response_type != "code" {
|
|
|
|
|
return Err(Status::BadRequest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let html_content = fs::read_to_string("res/login.html")
|
|
|
|
|
.map_err(|_| Status::InternalServerError)?;
|
|
|
|
|
|
|
|
|
|
let rendered = html_content
|
|
|
|
|
.replace("{{client_id}}", params.client_id)
|
|
|
|
|
.replace("{{redirect_uri}}", params.redirect_uri)
|
|
|
|
|
.replace("{{state}}", params.state.unwrap_or(""));
|
|
|
|
|
|
|
|
|
|
Ok((ContentType::HTML, rendered))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[post("/api/v2/oauth2/authorize/submit", data = "<form_data>")]
|
|
|
|
|
pub async fn authorize_submit(
|
|
|
|
|
pool: &State<Pool>,
|
|
|
|
|
form_data: Form<AuthorizeFormSubmit<'_>>
|
|
|
|
|
) -> Result<Redirect, (Status, &'static str)> {
|
|
|
|
|
|
|
|
|
|
let user_row = match sqlx::query("SELECT pid, password FROM users WHERE username = $1")
|
|
|
|
|
.bind(form_data.username)
|
|
|
|
|
.fetch_optional(pool.inner())
|
|
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
Ok(Some(row)) => row,
|
|
|
|
|
_ => return Err((Status::Unauthorized, "invalid_credentials")),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let pid: i32 = user_row.get("pid");
|
|
|
|
|
let db_bcrypt_hash: String = user_row.get("password");
|
|
|
|
|
|
|
|
|
|
let mut random_bytes = [0u8; 32];
|
|
|
|
|
rand::rng().fill_bytes(&mut random_bytes);
|
|
|
|
|
let secure_auth_code = hex::encode(random_bytes);
|
|
|
|
|
let expires_at = Utc::now().naive_utc() + Duration::minutes(5);
|
|
|
|
|
|
|
|
|
|
if let Err(db_err) = sqlx::query(
|
|
|
|
|
"INSERT INTO oauth_auth_codes (code, pid, redirect_uri, expires_at) VALUES ($1, $2, $3, $4)"
|
|
|
|
|
)
|
|
|
|
|
.bind(&secure_auth_code)
|
|
|
|
|
.bind(pid)
|
|
|
|
|
.bind(form_data.redirect_uri)
|
|
|
|
|
.bind(expires_at)
|
|
|
|
|
.execute(pool.inner())
|
|
|
|
|
.await {
|
|
|
|
|
eprintln!("failed to save auth code: {:?}", db_err);
|
|
|
|
|
return Err((Status::InternalServerError, "server error saving session"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !verify_nintendo_password(pid, form_data.password, &db_bcrypt_hash) {
|
|
|
|
|
return Err((Status::Unauthorized, "invalid_credentials"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut target_redirect = format!("{}?code={}", form_data.redirect_uri, secure_auth_code);
|
|
|
|
|
if let Some(state) = form_data.state {
|
|
|
|
|
target_redirect.push_str(&format!("&state={}", state));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Redirect::to(target_redirect))
|
|
|
|
|
}
|