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
|
- initialize-submodules
|
||||||
- build
|
- build
|
||||||
- push
|
- push
|
||||||
|
- test # for SAST + Dependency Scanning
|
||||||
|
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
|
|
@ -31,3 +31,22 @@ push:
|
||||||
only:
|
only:
|
||||||
- main
|
- 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_codegen",
|
||||||
"rocket_http",
|
"rocket_http",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"state",
|
"state",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"time",
|
"time",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ lto = true
|
||||||
incremental = false
|
incremental = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = "0.5.1"
|
rocket = { version = "0.5.1", features = ["json"] }
|
||||||
serde = { version = "1.0.218", features = ["derive"] }
|
serde = { version = "1.0.218", features = ["derive"] }
|
||||||
log = "0.4.26"
|
log = "0.4.26"
|
||||||
quick-xml = { version = "0.37.2", features = ["serialize"] }
|
quick-xml = { version = "0.37.2", features = ["serialize"] }
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ mod data_wrapper;
|
||||||
mod grpc;
|
mod grpc;
|
||||||
mod graphql;
|
mod graphql;
|
||||||
mod email;
|
mod email;
|
||||||
|
mod papi;
|
||||||
|
|
||||||
type Pool = sqlx::Pool<Postgres>;
|
type Pool = sqlx::Pool<Postgres>;
|
||||||
|
|
||||||
|
|
@ -167,6 +168,8 @@ async fn launch() -> _ {
|
||||||
nnid::provider::get_nex_token,
|
nnid::provider::get_nex_token,
|
||||||
nnid::provider::get_service_token,
|
nnid::provider::get_service_token,
|
||||||
nnid::mapped_ids::mapped_ids,
|
nnid::mapped_ids::mapped_ids,
|
||||||
|
papi::login::login,
|
||||||
|
papi::user::get_user,
|
||||||
//graphql
|
//graphql
|
||||||
graphql::graphiql,
|
graphql::graphiql,
|
||||||
graphql::playground,
|
graphql::playground,
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,7 @@ pub async fn create_account(database: &State<Pool>, data: Xml<AccountCreationDat
|
||||||
address
|
address
|
||||||
},
|
},
|
||||||
mii: Mii{
|
mii: Mii{
|
||||||
|
name,
|
||||||
data,
|
data,
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
|
|
@ -376,8 +377,8 @@ fn build_own_profile(user: User) -> Ds<Xml<GetOwnProfileData>> {
|
||||||
&(gxhash64(mii_data.as_bytes(), 1) & !(0x1000000000000000))
|
&(gxhash64(mii_data.as_bytes(), 1) & !(0x1000000000000000))
|
||||||
)),
|
)),
|
||||||
name: mii::MiiData::read(&mii_data)
|
name: mii::MiiData::read(&mii_data)
|
||||||
.map(|v| v.name)
|
.map(|v| v.name)
|
||||||
.unwrap_or_else(|| "INVALID".to_string()),
|
.unwrap_or_else(|| "INVALID".to_string()),
|
||||||
primary: YesNoVal(true),
|
primary: YesNoVal(true),
|
||||||
data: mii_data,
|
data: mii_data,
|
||||||
status: "COMPLETED".to_string(),
|
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