From 2168f6fec13d4a591046f34c61b79c207176e728 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Mon, 28 Apr 2025 11:26:32 +0200 Subject: [PATCH 1/6] feat(website): support the pretendo website API, PAPI for short --- Cargo.lock | 1 + Cargo.toml | 2 +- src/main.rs | 3 ++ src/nnid/people.rs | 6 ++- src/papi/login.rs | 91 ++++++++++++++++++++++++++++++++++++++ src/papi/mod.rs | 2 + src/papi/user.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 src/papi/login.rs create mode 100644 src/papi/mod.rs create mode 100644 src/papi/user.rs diff --git a/Cargo.lock b/Cargo.lock index 5cc2276..a8e1d7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2570,6 +2570,7 @@ dependencies = [ "rocket_codegen", "rocket_http", "serde", + "serde_json", "state", "tempfile", "time", diff --git a/Cargo.toml b/Cargo.toml index fb981ac..2a394a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ lto = true incremental = false [dependencies] -rocket = "0.5.1" +rocket = { version = "0.5.1", features = ["json"] } serde = { version = "1.0.218", features = ["derive"] } log = "0.4.26" quick-xml = { version = "0.37.2", features = ["serialize"] } diff --git a/src/main.rs b/src/main.rs index 450e206..904f82b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,7 @@ mod data_wrapper; mod grpc; mod graphql; mod email; +mod papi; type Pool = sqlx::Pool; @@ -167,6 +168,8 @@ async fn launch() -> _ { nnid::provider::get_nex_token, nnid::provider::get_service_token, nnid::mapped_ids::mapped_ids, + papi::login::login, + papi::user::get_user, //graphql graphql::graphiql, graphql::playground, diff --git a/src/nnid/people.rs b/src/nnid/people.rs index 9de450d..839ca5b 100644 --- a/src/nnid/people.rs +++ b/src/nnid/people.rs @@ -170,6 +170,7 @@ pub async fn create_account(database: &State, data: Xml, data: Xml Ds> { &(gxhash64(mii_data.as_bytes(), 1) & !(0x1000000000000000)) )), name: mii::MiiData::read(&mii_data) - .map(|v| v.name) - .unwrap_or_else(|| "INVALID".to_string()), + .map(|v| v.name) + .unwrap_or_else(|| "INVALID".to_string()), primary: YesNoVal(true), data: mii_data, status: "COMPLETED".to_string(), diff --git a/src/papi/login.rs b/src/papi/login.rs new file mode 100644 index 0000000..7e228e7 --- /dev/null +++ b/src/papi/login.rs @@ -0,0 +1,91 @@ +use rocket::{post, State}; +use rocket::form::Form; +use serde::Deserialize; +use serde::Serialize; +use crate::Pool; +use crate::account::account::{User, read_bearer_auth_token}; +use crate::nnid::oauth::generate_token::{create_token, token_type::AUTH_TOKEN, token_type::AUTH_REFRESH_TOKEN}; +use crate::error::{Error, Errors}; +use rocket::serde::json::Json; + +#[derive(Deserialize)] +pub struct LoginRequest { + grant_type: String, + username: Option, + password: Option, + refresh_token: Option, +} + +#[derive(Serialize)] +pub struct LoginResponse { + access_token: String, + token_type: String, + expires_in: i32, + refresh_token: String, +} + +const INVALID_GRANT_TYPE_ERROR: Errors<'static> = Errors { + error: &[Error { + code: "0100", + message: "Invalid grant type", + }] +}; + +const ACCOUNT_ID_OR_PASSWORD_ERRORS: Errors<'static> = Errors { + error: &[Error { + code: "0106", + message: "Invalid account ID or password", + }] +}; + +const INVALID_REFRESH_TOKEN_ERRORS: Errors<'static> = Errors { + error: &[Error { + code: "0107", + message: "Invalid or missing refresh token", + }] +}; + +#[post("/v1/login", data = "")] +pub async fn login(pool: &State, form_data: Json) -> Result, Option>> { + let pool = pool.inner(); + let grant_type = form_data.grant_type.as_str(); + + if grant_type != "password" && grant_type != "refresh_token" { + return Err(Some(INVALID_GRANT_TYPE_ERROR)); + } + + let user: User; + + if grant_type == "password" { + let username = form_data.username.as_ref().ok_or(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS))?; + let password = form_data.password.as_ref().ok_or(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS))?; + + user = User::get_by_username(username, pool) + .await + .ok_or(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS))?; + + if !user.verify_cleartext_password(password).is_some_and(|v| v) { + return Err(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS)); + } + } else { + let refresh_token = form_data.refresh_token.as_ref().ok_or(Some(INVALID_REFRESH_TOKEN_ERRORS))?; + + user = read_bearer_auth_token(pool, refresh_token) + .await + .ok_or(Some(INVALID_REFRESH_TOKEN_ERRORS))?; + } + + if user.account_level < 0 { + return Err(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS)); + } + + let access_token = create_token(pool, user.pid, AUTH_TOKEN, None).await; + let refresh_token = create_token(pool, user.pid, AUTH_REFRESH_TOKEN, None).await; + + Ok(Json(LoginResponse { + access_token, + token_type: "Bearer".to_string(), + expires_in: 3600, + refresh_token, + })) +} diff --git a/src/papi/mod.rs b/src/papi/mod.rs new file mode 100644 index 0000000..c4566ae --- /dev/null +++ b/src/papi/mod.rs @@ -0,0 +1,2 @@ +pub mod user; +pub mod login; \ No newline at end of file diff --git a/src/papi/user.rs b/src/papi/user.rs new file mode 100644 index 0000000..cae5862 --- /dev/null +++ b/src/papi/user.rs @@ -0,0 +1,107 @@ +use rocket::{get}; +use crate::account::account::{Auth}; +use rocket::serde::json::Json; + +#[derive(serde::Serialize)] +struct EmailInfo { + address: String, +} + +#[derive(serde::Serialize)] +struct TimezoneInfo { + name: String, +} + +#[derive(serde::Serialize)] +struct MiiInfo { + data: String, + name: String, + image_url: String, +} + +#[derive(serde::Serialize)] +struct FlagsInfo { + marketing: bool, +} + +#[derive(serde::Serialize)] +struct ConnectionsInfo { + discord: DiscordInfo, + stripe: StripeInfo, +} + +#[derive(serde::Serialize)] +struct DiscordInfo { + id: Option, +} + +#[derive(serde::Serialize)] +struct StripeInfo { + tier_name: Option, + tier_level: Option, +} + +#[derive(serde::Serialize)] +struct UserInfoResponse { + deleted: bool, + access_level: i32, + server_access_level: String, + pid: i32, + creation_date: chrono::NaiveDateTime, + updated: chrono::NaiveDateTime, + username: String, + birthdate: chrono::NaiveDate, + gender: String, + country: String, + email: EmailInfo, + timezone: TimezoneInfo, + mii: MiiInfo, + flags: FlagsInfo, + connections: ConnectionsInfo, +} + +#[get("/v1/user")] +pub async fn get_user(auth: Auth) -> Json { + let user = auth.0; + + Json(UserInfoResponse { + deleted: false, + access_level: user.account_level, + server_access_level: "test".to_string(), + pid: user.pid, + creation_date: user.creation_date, + updated: user.updated, + username: user.username.clone(), + birthdate: user.birthdate, + gender: user.gender.clone(), + country: user.country.clone(), + email: EmailInfo { + address: user.email.clone(), + }, + timezone: TimezoneInfo { + name: user.timezone.clone(), + }, + mii: MiiInfo { + data: user.mii_data.clone(), + name: { + let cleaned = user.mii_data.replace('\n', "").replace('\r', ""); + mii::MiiData::read(&cleaned) + .map(|v| v.name) + .unwrap_or_else(|| "INVALID".to_string()) + }, + image_url: format!("https://minio.spfn.cc:9000/act-rs/mii/{}/normal_face.png", user.pid), + }, + flags: FlagsInfo { + marketing: user.marketing_allowed, + }, + connections: ConnectionsInfo { + discord: DiscordInfo { + id: None, + }, + stripe: StripeInfo { + tier_name: None, + tier_level: None, + }, + }, + }) +} From c4cd1fd16a8e429a8dafbbc4f78e77605870ada4 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Mon, 28 Apr 2025 11:30:48 +0200 Subject: [PATCH 2/6] fix(mii): remove redundant mii_name --- src/nnid/people.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nnid/people.rs b/src/nnid/people.rs index 839ca5b..0eaef7e 100644 --- a/src/nnid/people.rs +++ b/src/nnid/people.rs @@ -219,7 +219,6 @@ pub async fn create_account(database: &State, data: Xml Date: Mon, 28 Apr 2025 11:54:18 +0200 Subject: [PATCH 3/6] attempt to run SAST --- .gitlab-ci.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3098a7f..e1dfe06 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,7 +12,7 @@ stages: - initialize-submodules - build - push - + - test # for SAST + Dependency Scanning build: stage: build @@ -31,3 +31,20 @@ push: only: - main +sast: + stage: test + allow_failure: true + artifacts: + reports: + sast: gl-sast-report.json + rules: + - if: $CI_COMMIT_BRANCH + +dependency_scanning: + stage: test + allow_failure: true + artifacts: + reports: + dependency_scanning: gl-dependency-scanning-report.json + rules: + - if: $CI_COMMIT_BRANCH From 9cc643814f558da322d03fbfa29467c922f8eedc Mon Sep 17 00:00:00 2001 From: andrea <1-ssdrive@users.noreply.git.perditum.com> Date: Mon, 28 Apr 2025 09:56:56 +0000 Subject: [PATCH 4/6] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e1dfe06..8ae1303 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,6 +34,7 @@ push: sast: stage: test allow_failure: true + script: ['echo "Running SAST scan"'] artifacts: reports: sast: gl-sast-report.json @@ -43,6 +44,7 @@ sast: dependency_scanning: stage: test allow_failure: true + script: ['echo "Running dep scan"'] artifacts: reports: dependency_scanning: gl-dependency-scanning-report.json From cdd7ddd6cc60c64e091e5764cf9e031fc43574a2 Mon Sep 17 00:00:00 2001 From: ssdrive Date: Tue, 29 Apr 2025 12:52:58 +0200 Subject: [PATCH 5/6] fix(papi-user): use normal CDN routes --- src/papi/user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/papi/user.rs b/src/papi/user.rs index cae5862..770f786 100644 --- a/src/papi/user.rs +++ b/src/papi/user.rs @@ -89,7 +89,7 @@ pub async fn get_user(auth: Auth) -> Json { .map(|v| v.name) .unwrap_or_else(|| "INVALID".to_string()) }, - image_url: format!("https://minio.spfn.cc:9000/act-rs/mii/{}/normal_face.png", user.pid), + image_url: format!("https://cdn.spfn.cc/mii/{}/normal_face.png", user.pid), }, flags: FlagsInfo { marketing: user.marketing_allowed, From 77cec0f6bc514173940754df33f684aa98d20e93 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Tue, 29 Apr 2025 13:53:28 +0200 Subject: [PATCH 6/6] mark PAPI as deprecated --- src/papi/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/papi/mod.rs b/src/papi/mod.rs index c4566ae..4bc9288 100644 --- a/src/papi/mod.rs +++ b/src/papi/mod.rs @@ -1,2 +1,4 @@ +#[deprecated(note="please use the upcoming api instead of this")] pub mod user; -pub mod login; \ No newline at end of file +#[deprecated(note="please use the upcoming api instead of this")] +pub mod login;