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}; use crate::json_api::oauth::generate_token::verify_nintendo_password; 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?")] 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 = "")] pub async fn authorize_submit( pool: &State, form_data: Form> ) -> Result { 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)) }