Merge branch 'feat/website-api' into 'main'
Add Pretendo API See merge request perditum/account-rs!3
This commit is contained in:
commit
c874781101
8 changed files with 230 additions and 4 deletions
|
|
@ -12,7 +12,7 @@ stages:
|
|||
- initialize-submodules
|
||||
- build
|
||||
- push
|
||||
|
||||
- test # for SAST + Dependency Scanning
|
||||
|
||||
build:
|
||||
stage: build
|
||||
|
|
@ -31,3 +31,22 @@ push:
|
|||
only:
|
||||
- main
|
||||
|
||||
sast:
|
||||
stage: test
|
||||
allow_failure: true
|
||||
script: ['echo "Running SAST scan"']
|
||||
artifacts:
|
||||
reports:
|
||||
sast: gl-sast-report.json
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
dependency_scanning:
|
||||
stage: test
|
||||
allow_failure: true
|
||||
script: ['echo "Running dep scan"']
|
||||
artifacts:
|
||||
reports:
|
||||
dependency_scanning: gl-dependency-scanning-report.json
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
|
|
|||
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2570,6 +2570,7 @@ dependencies = [
|
|||
"rocket_codegen",
|
||||
"rocket_http",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"state",
|
||||
"tempfile",
|
||||
"time",
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ mod data_wrapper;
|
|||
mod grpc;
|
||||
mod graphql;
|
||||
mod email;
|
||||
mod papi;
|
||||
|
||||
type Pool = sqlx::Pool<Postgres>;
|
||||
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ pub async fn create_account(database: &State<Pool>, data: Xml<AccountCreationDat
|
|||
address
|
||||
},
|
||||
mii: Mii{
|
||||
name,
|
||||
data,
|
||||
..
|
||||
},
|
||||
|
|
@ -376,8 +377,8 @@ fn build_own_profile(user: User) -> Ds<Xml<GetOwnProfileData>> {
|
|||
&(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(),
|
||||
|
|
|
|||
91
src/papi/login.rs
Normal file
91
src/papi/login.rs
Normal file
|
|
@ -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<String>,
|
||||
password: Option<String>,
|
||||
refresh_token: Option<String>,
|
||||
}
|
||||
|
||||
#[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 = "<form_data>")]
|
||||
pub async fn login(pool: &State<Pool>, form_data: Json<LoginRequest>) -> Result<Json<LoginResponse>, Option<Errors<'static>>> {
|
||||
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,
|
||||
}))
|
||||
}
|
||||
4
src/papi/mod.rs
Normal file
4
src/papi/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#[deprecated(note="please use the upcoming api instead of this")]
|
||||
pub mod user;
|
||||
#[deprecated(note="please use the upcoming api instead of this")]
|
||||
pub mod login;
|
||||
107
src/papi/user.rs
Normal file
107
src/papi/user.rs
Normal file
|
|
@ -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<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct StripeInfo {
|
||||
tier_name: Option<String>,
|
||||
tier_level: Option<i32>,
|
||||
}
|
||||
|
||||
#[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<false>) -> Json<UserInfoResponse> {
|
||||
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://cdn.spfn.cc/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,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue