This commit is contained in:
parent
c47a3fb5fc
commit
f32e69be99
4 changed files with 219 additions and 110 deletions
|
|
@ -117,7 +117,10 @@ pub fn generate_password(pid: i32, cleartext_password: &str) -> Option<String> {
|
||||||
bcrypt::hash(password, 10).ok()
|
bcrypt::hash(password, 10).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_basic_auth_token(connection: &Pool, token: &str) -> Option<User> {
|
pub async fn read_basic_auth_token(
|
||||||
|
connection: &Pool,
|
||||||
|
token: &str,
|
||||||
|
) -> Option<(User, Option<[u8; 32]>)> {
|
||||||
let data = match BASE64_STANDARD.decode(&token) {
|
let data = match BASE64_STANDARD.decode(&token) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -161,13 +164,16 @@ pub async fn read_basic_auth_token(connection: &Pool, token: &str) -> Option<Use
|
||||||
let password_valid = user.verify_cleartext_password(&login_password);
|
let password_valid = user.verify_cleartext_password(&login_password);
|
||||||
|
|
||||||
if password_valid == Some(true) {
|
if password_valid == Some(true) {
|
||||||
Some(user)
|
Some((user, None))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_bearer_auth_token(connection: &Pool, token: &str) -> Option<User> {
|
pub async fn read_bearer_auth_token(
|
||||||
|
connection: &Pool,
|
||||||
|
token: &str,
|
||||||
|
) -> Option<(User, Option<[u8; 32]>)> {
|
||||||
let data = TokenData::decode(token)?;
|
let data = TokenData::decode(token)?;
|
||||||
|
|
||||||
let token_info = sqlx::query!(
|
let token_info = sqlx::query!(
|
||||||
|
|
@ -189,11 +195,11 @@ pub async fn read_bearer_auth_token(connection: &Pool, token: &str) -> Option<Us
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
Some(user)
|
Some((user, token_info.cert_hash.and_then(|v| v.try_into().ok())))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_nex_password() -> String {
|
pub fn generate_nex_password() -> String {
|
||||||
let mut rng = rand::rng();
|
let mut rng = rand::rng();
|
||||||
let mut output = String::with_capacity(16);
|
let mut output = String::with_capacity(16);
|
||||||
|
|
||||||
while output.len() < 16 {
|
while output.len() < 16 {
|
||||||
|
|
@ -214,6 +220,7 @@ pub fn generate_nex_password() -> String {
|
||||||
|
|
||||||
pub struct Auth<const FORCE_BEARER_AUTH: bool = true, const USE_CERT: bool = FORCE_BEARER_AUTH>(
|
pub struct Auth<const FORCE_BEARER_AUTH: bool = true, const USE_CERT: bool = FORCE_BEARER_AUTH>(
|
||||||
pub User,
|
pub User,
|
||||||
|
pub Option<[u8; 32]>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl<const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> AsRef<User>
|
impl<const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> AsRef<User>
|
||||||
|
|
@ -278,6 +285,7 @@ pub async fn link_certificate_to_pid(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: make this more consistent by requiring a cert to be asociated to the token if USE_CERT is set
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r>
|
impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r>
|
||||||
for Auth<FORCE_BEARER_AUTH, USE_CERT>
|
for Auth<FORCE_BEARER_AUTH, USE_CERT>
|
||||||
|
|
@ -297,13 +305,13 @@ impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r>
|
||||||
|
|
||||||
let (auth_type, token) = request_try!(auth.split_once(' ').ok_or(INVALID_TOKEN_ERRORS));
|
let (auth_type, token) = request_try!(auth.split_once(' ').ok_or(INVALID_TOKEN_ERRORS));
|
||||||
|
|
||||||
let user = match auth_type {
|
let data = match auth_type {
|
||||||
"Basic" if !FORCE_BEARER_AUTH => read_basic_auth_token(pool, token).await,
|
"Basic" if !FORCE_BEARER_AUTH => read_basic_auth_token(pool, token).await,
|
||||||
"Bearer" => read_bearer_auth_token(pool, token).await,
|
"Bearer" => read_bearer_auth_token(pool, token).await,
|
||||||
_ => return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS)),
|
_ => return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(user) = user else {
|
let Some((user, cert)) = data else {
|
||||||
return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS));
|
return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -316,7 +324,7 @@ impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r>
|
||||||
// ..user
|
// ..user
|
||||||
// };
|
// };
|
||||||
|
|
||||||
Outcome::Success(Self(user))
|
Outcome::Success(Self(user, cert))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
use crate::Pool;
|
use crate::Pool;
|
||||||
use serde::Serialize;
|
|
||||||
use rocket::FromForm;
|
|
||||||
use sha2::{Sha256, Digest};
|
|
||||||
use bytemuck::bytes_of;
|
|
||||||
use rocket::{post, State, form::Form, http::Status, serde::json::Json};
|
|
||||||
use sqlx::Row;
|
|
||||||
use std::env;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use crate::nnid::oauth::generate_token::create_token;
|
use crate::nnid::oauth::generate_token::create_token;
|
||||||
use crate::nnid::oauth::generate_token::token_type::AUTH_TOKEN;
|
use crate::nnid::oauth::generate_token::token_type::AUTH_TOKEN;
|
||||||
|
use bytemuck::bytes_of;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use rocket::FromForm;
|
||||||
|
use rocket::{State, form::Form, http::Status, post, serde::json::Json};
|
||||||
|
use serde::Serialize;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use sqlx::Row;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
pub static CLIENT_SECRET: Lazy<String> = Lazy::new(|| {
|
pub static CLIENT_SECRET: Lazy<String> =
|
||||||
env::var("OAUTH_CLIENT_SECRET").expect("OAUTH_CLIENT_SECRET not set")
|
Lazy::new(|| env::var("OAUTH_CLIENT_SECRET").expect("OAUTH_CLIENT_SECRET not set"));
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct OAuthTokenResponse {
|
pub struct OAuthTokenResponse {
|
||||||
|
|
@ -52,17 +51,18 @@ pub struct OAuthErrorResponse {
|
||||||
|
|
||||||
#[post("/api/v2/oauth2/generate_token", data = "<form_data>")]
|
#[post("/api/v2/oauth2/generate_token", data = "<form_data>")]
|
||||||
pub async fn generate_token(
|
pub async fn generate_token(
|
||||||
pool: &State<Pool>,
|
pool: &State<Pool>,
|
||||||
form_data: Form<TokenRequest<'_>>
|
form_data: Form<TokenRequest<'_>>,
|
||||||
) -> Result<Json<OAuthTokenResponse>, (Status, Json<OAuthErrorResponse>)> {
|
) -> Result<Json<OAuthTokenResponse>, (Status, Json<OAuthErrorResponse>)> {
|
||||||
|
|
||||||
match form_data.client_id {
|
match form_data.client_id {
|
||||||
Some("account") | Some("splatnet") => {
|
Some("account") | Some("splatnet") => {
|
||||||
let cl_secret: String = CLIENT_SECRET.clone();
|
let cl_secret: String = CLIENT_SECRET.clone();
|
||||||
if form_data.client_secret != Some(&cl_secret) {
|
if form_data.client_secret != Some(&cl_secret) {
|
||||||
return Err((
|
return Err((
|
||||||
Status::Unauthorized,
|
Status::Unauthorized,
|
||||||
Json(OAuthErrorResponse { error: "invalid_client".to_string() })
|
Json(OAuthErrorResponse {
|
||||||
|
error: "invalid_client".to_string(),
|
||||||
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +72,9 @@ pub async fn generate_token(
|
||||||
_ => {
|
_ => {
|
||||||
return Err((
|
return Err((
|
||||||
Status::BadRequest,
|
Status::BadRequest,
|
||||||
Json(OAuthErrorResponse { error: "invalid_client".to_string() })
|
Json(OAuthErrorResponse {
|
||||||
|
error: "invalid_client".to_string(),
|
||||||
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,23 +84,31 @@ pub async fn generate_token(
|
||||||
"password" => {
|
"password" => {
|
||||||
let username = form_data.username.ok_or((
|
let username = form_data.username.ok_or((
|
||||||
Status::BadRequest,
|
Status::BadRequest,
|
||||||
Json(OAuthErrorResponse { error: "invalid_request".to_string() })
|
Json(OAuthErrorResponse {
|
||||||
|
error: "invalid_request".to_string(),
|
||||||
|
}),
|
||||||
))?;
|
))?;
|
||||||
let password = form_data.password.ok_or((
|
let password = form_data.password.ok_or((
|
||||||
Status::BadRequest,
|
Status::BadRequest,
|
||||||
Json(OAuthErrorResponse { error: "invalid_request".to_string() })
|
Json(OAuthErrorResponse {
|
||||||
|
error: "invalid_request".to_string(),
|
||||||
|
}),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
let user_row = match sqlx::query("SELECT pid, password FROM users WHERE username = $1")
|
let user_row = match sqlx::query("SELECT pid, password FROM users WHERE username = $1")
|
||||||
.bind(username)
|
.bind(username)
|
||||||
.fetch_optional(pool.inner())
|
.fetch_optional(pool.inner())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(Some(row)) => row,
|
Ok(Some(row)) => row,
|
||||||
_ => return Err((
|
_ => {
|
||||||
Status::BadRequest,
|
return Err((
|
||||||
Json(OAuthErrorResponse { error: "invalid_grant".to_string() })
|
Status::BadRequest,
|
||||||
)),
|
Json(OAuthErrorResponse {
|
||||||
|
error: "invalid_grant".to_string(),
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let db_pid: i32 = user_row.get("pid");
|
let db_pid: i32 = user_row.get("pid");
|
||||||
|
|
@ -107,31 +117,39 @@ pub async fn generate_token(
|
||||||
if !verify_nintendo_password(db_pid, password, &db_bcrypt_hash) {
|
if !verify_nintendo_password(db_pid, password, &db_bcrypt_hash) {
|
||||||
return Err((
|
return Err((
|
||||||
Status::Unauthorized,
|
Status::Unauthorized,
|
||||||
Json(OAuthErrorResponse { error: "invalid_grant".to_string() })
|
Json(OAuthErrorResponse {
|
||||||
|
error: "invalid_grant".to_string(),
|
||||||
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
db_pid
|
db_pid
|
||||||
},
|
}
|
||||||
|
|
||||||
"authorization_code" => {
|
"authorization_code" => {
|
||||||
let incoming_code = form_data.code.ok_or((
|
let incoming_code = form_data.code.ok_or((
|
||||||
Status::BadRequest,
|
Status::BadRequest,
|
||||||
Json(OAuthErrorResponse { error: "invalid_request".to_string() })
|
Json(OAuthErrorResponse {
|
||||||
|
error: "invalid_request".to_string(),
|
||||||
|
}),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
let code_row = match sqlx::query(
|
let code_row = match sqlx::query(
|
||||||
"SELECT pid, expires_at, used FROM oauth_auth_codes WHERE code = $1"
|
"SELECT pid, expires_at, used FROM oauth_auth_codes WHERE code = $1",
|
||||||
)
|
)
|
||||||
.bind(incoming_code)
|
.bind(incoming_code)
|
||||||
.fetch_optional(pool.inner())
|
.fetch_optional(pool.inner())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(Some(row)) => row,
|
Ok(Some(row)) => row,
|
||||||
_ => return Err((
|
_ => {
|
||||||
Status::BadRequest,
|
return Err((
|
||||||
Json(OAuthErrorResponse { error: "invalid_grant".to_string() })
|
Status::BadRequest,
|
||||||
)),
|
Json(OAuthErrorResponse {
|
||||||
|
error: "invalid_grant".to_string(),
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_used: bool = code_row.get("used");
|
let is_used: bool = code_row.get("used");
|
||||||
|
|
@ -140,8 +158,10 @@ pub async fn generate_token(
|
||||||
|
|
||||||
if is_used || expires_at < Utc::now().naive_utc() {
|
if is_used || expires_at < Utc::now().naive_utc() {
|
||||||
return Err((
|
return Err((
|
||||||
Status::BadRequest,
|
Status::BadRequest,
|
||||||
Json(OAuthErrorResponse { error: "invalid_grant".to_string() })
|
Json(OAuthErrorResponse {
|
||||||
|
error: "invalid_grant".to_string(),
|
||||||
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,15 +171,19 @@ pub async fn generate_token(
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
target_pid
|
target_pid
|
||||||
},
|
}
|
||||||
|
|
||||||
_ => return Err((
|
_ => {
|
||||||
Status::BadRequest,
|
return Err((
|
||||||
Json(OAuthErrorResponse { error: "unsupported_grant_type".to_string() })
|
Status::BadRequest,
|
||||||
))
|
Json(OAuthErrorResponse {
|
||||||
|
error: "unsupported_grant_type".to_string(),
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let token = create_token(pool.inner(), pid, AUTH_TOKEN, None).await;
|
let token = create_token(pool.inner(), pid, AUTH_TOKEN, None, None).await;
|
||||||
|
|
||||||
Ok(Json(OAuthTokenResponse {
|
Ok(Json(OAuthTokenResponse {
|
||||||
access_token: token,
|
access_token: token,
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,43 @@
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
use rocket::{post, FromForm, State};
|
use crate::Pool;
|
||||||
use rocket::form::Form;
|
|
||||||
use serde::{Serialize};
|
|
||||||
use crate::account::account::{Auth, DeviceCert, User, link_certificate_to_pid};
|
use crate::account::account::{Auth, DeviceCert, User, link_certificate_to_pid};
|
||||||
use crate::error::{Error, Errors};
|
use crate::error::{Error, Errors};
|
||||||
use crate::nnid::agreements::{CFIP, EVIL_AGREEMENT_THING};
|
use crate::nnid::agreements::{CFIP, EVIL_AGREEMENT_THING};
|
||||||
use crate::nnid::oauth::generate_token::token_type::{AUTH_REFRESH_TOKEN, AUTH_TOKEN};
|
|
||||||
use crate::nnid::oauth::TokenData;
|
use crate::nnid::oauth::TokenData;
|
||||||
use crate::Pool;
|
use crate::nnid::oauth::generate_token::token_type::{AUTH_REFRESH_TOKEN, AUTH_TOKEN};
|
||||||
use crate::xml::Xml;
|
use crate::xml::Xml;
|
||||||
|
use rocket::form::Form;
|
||||||
|
use rocket::{FromForm, State, post};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
pub mod token_type{
|
pub mod token_type {
|
||||||
pub const AUTH_REFRESH_TOKEN: i32 = 1;
|
pub const AUTH_REFRESH_TOKEN: i32 = 1;
|
||||||
pub const AUTH_TOKEN: i32 = 0;
|
pub const AUTH_TOKEN: i32 = 0;
|
||||||
pub const NEX_TOKEN: i32 = 2;
|
pub const NEX_TOKEN: i32 = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ACCOUNT_ID_OR_PASSWORD_ERRORS: Errors = Errors{
|
const ACCOUNT_ID_OR_PASSWORD_ERRORS: Errors = Errors {
|
||||||
error: &[
|
error: &[Error {
|
||||||
Error{
|
code: "0106",
|
||||||
code: "0106",
|
message: "Invalid account ID or password",
|
||||||
message: "Invalid account ID or password"
|
}],
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACCOUNT_BANNED_ERRORS: Errors = Errors{
|
const ACCOUNT_BANNED_ERRORS: Errors = Errors {
|
||||||
error: &[
|
error: &[Error {
|
||||||
Error{
|
code: "0108",
|
||||||
code: "0108",
|
message: "Account banned from server",
|
||||||
message: "Account banned from server"
|
}],
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const REREAD_EULA_EXTRABANNED_ERRORS: Errors = Errors {
|
||||||
const REREAD_EULA_EXTRABANNED_ERRORS: Errors = Errors{
|
error: &[Error {
|
||||||
error: &[
|
code: "0109",
|
||||||
Error{
|
message: "REREAD THE EULA LOL",
|
||||||
code: "0109",
|
}],
|
||||||
message: "REREAD THE EULA LOL"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
pub struct TokenRequestData<'a>{
|
pub struct TokenRequestData<'a> {
|
||||||
grant_type: &'a str,
|
grant_type: &'a str,
|
||||||
user_id: &'a str,
|
user_id: &'a str,
|
||||||
password: &'a str,
|
password: &'a str,
|
||||||
|
|
@ -55,17 +48,27 @@ pub struct TokenRequestData<'a>{
|
||||||
pub struct TokenReturnData {
|
pub struct TokenReturnData {
|
||||||
token: String,
|
token: String,
|
||||||
refresh_token: String,
|
refresh_token: String,
|
||||||
expires_in: i32
|
expires_in: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_token(pool: &Pool, pid: i32, token_type: i32, title_id: Option<&str>) -> String{
|
pub async fn create_token(
|
||||||
|
pool: &Pool,
|
||||||
|
pid: i32,
|
||||||
|
token_type: i32,
|
||||||
|
title_id: Option<&str>,
|
||||||
|
cert_hash: Option<&[u8]>,
|
||||||
|
) -> String {
|
||||||
let data = sqlx::query!(
|
let data = sqlx::query!(
|
||||||
"insert into tokens (token_type, pid, title_id)
|
"insert into tokens (token_type, pid, title_id, cert_hash)
|
||||||
values ($1, $2, $3) returning token_id, random",
|
values ($1, $2, $3, $4) returning token_id, random",
|
||||||
token_type, pid, title_id
|
token_type,
|
||||||
)
|
pid,
|
||||||
.fetch_one(pool)
|
title_id,
|
||||||
.await.unwrap();
|
cert_hash
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let token_id = data.token_id;
|
let token_id = data.token_id;
|
||||||
let random = data.random;
|
let random = data.random;
|
||||||
|
|
@ -73,61 +76,79 @@ pub async fn create_token(pool: &Pool, pid: i32, token_type: i32, title_id: Opti
|
||||||
let token = TokenData {
|
let token = TokenData {
|
||||||
token_id,
|
token_id,
|
||||||
random,
|
random,
|
||||||
pid
|
pid,
|
||||||
};
|
};
|
||||||
|
|
||||||
token.encode().to_string()
|
token.encode().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl TokenReturnData {
|
impl TokenReturnData {
|
||||||
async fn new(pid: i32, pool: &Pool) -> Self{
|
async fn new(pid: i32, pool: &Pool) -> Self {
|
||||||
let token = create_token(pool, pid, AUTH_TOKEN, None).await;
|
let token = create_token(pool, pid, AUTH_TOKEN, None, None).await;
|
||||||
|
|
||||||
let refresh_token = create_token(pool, pid, AUTH_REFRESH_TOKEN, None).await;
|
let refresh_token = create_token(pool, pid, AUTH_REFRESH_TOKEN, None, None).await;
|
||||||
|
|
||||||
Self{
|
Self {
|
||||||
token,
|
token,
|
||||||
refresh_token,
|
refresh_token,
|
||||||
expires_in: 3600
|
expires_in: 3600,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn new_with_cert(pid: i32, cert: Option<&[u8]>, pool: &Pool) -> Self {
|
||||||
|
let token = create_token(pool, pid, AUTH_TOKEN, None, None).await;
|
||||||
|
|
||||||
|
let refresh_token = create_token(pool, pid, AUTH_REFRESH_TOKEN, None, None).await;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
token,
|
||||||
|
refresh_token,
|
||||||
|
expires_in: 3600,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename="OAuth20")]
|
#[serde(rename = "OAuth20")]
|
||||||
pub struct TokenRequestReturnData{
|
pub struct TokenRequestReturnData {
|
||||||
access_token: TokenReturnData
|
access_token: TokenReturnData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/v1/api/oauth20/access_token/generate", data="<data>")]
|
#[post("/v1/api/oauth20/access_token/generate", data = "<data>")]
|
||||||
pub async fn generate_token(pool: &State<Pool>, data: Form<TokenRequestData<'_>>, ip: CFIP, cert: DeviceCert) -> Result<Xml<TokenRequestReturnData>, Option<Errors<'static>>>{
|
pub async fn generate_token(
|
||||||
|
pool: &State<Pool>,
|
||||||
|
data: Form<TokenRequestData<'_>>,
|
||||||
|
ip: CFIP,
|
||||||
|
cert: DeviceCert,
|
||||||
|
) -> Result<Xml<TokenRequestReturnData>, Option<Errors<'static>>> {
|
||||||
let pool = pool.inner();
|
let pool = pool.inner();
|
||||||
|
|
||||||
let user = User::get_by_username(data.user_id, pool).await
|
let user = User::get_by_username(data.user_id, pool)
|
||||||
|
.await
|
||||||
.ok_or(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS))?;
|
.ok_or(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS))?;
|
||||||
|
|
||||||
if !user.verify_hashed_password(&data.password).is_some_and(|v| v){
|
if !user
|
||||||
|
.verify_hashed_password(&data.password)
|
||||||
|
.is_some_and(|v| v)
|
||||||
|
{
|
||||||
return Err(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS));
|
return Err(Some(ACCOUNT_ID_OR_PASSWORD_ERRORS));
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.account_level < 0{
|
if user.account_level < 0 {
|
||||||
if user.account_level == -2 {
|
if user.account_level == -2 {
|
||||||
return Err(Some(REREAD_EULA_EXTRABANNED_ERRORS));
|
return Err(Some(REREAD_EULA_EXTRABANNED_ERRORS));
|
||||||
}
|
}
|
||||||
if user.account_level == -3{
|
if user.account_level == -3 {
|
||||||
EVIL_AGREEMENT_THING.write().await.insert(ip.0);
|
EVIL_AGREEMENT_THING.write().await.insert(ip.0);
|
||||||
|
|
||||||
return Err(Some(REREAD_EULA_EXTRABANNED_ERRORS));
|
return Err(Some(REREAD_EULA_EXTRABANNED_ERRORS));
|
||||||
}
|
}
|
||||||
return Err(Some(ACCOUNT_BANNED_ERRORS));
|
return Err(Some(ACCOUNT_BANNED_ERRORS));
|
||||||
}
|
}
|
||||||
|
|
||||||
link_certificate_to_pid(&pool, &cert.0, user.pid).await?;
|
link_certificate_to_pid(&pool, &cert.0, user.pid).await?;
|
||||||
|
|
||||||
let access_token = TokenReturnData::new(user.pid, pool).await;
|
let access_token =
|
||||||
|
TokenReturnData::new_with_cert(user.pid, Some(&cert.0.hash()[..]), pool).await;
|
||||||
|
|
||||||
Ok(Xml(TokenRequestReturnData{
|
Ok(Xml(TokenRequestReturnData { access_token }))
|
||||||
access_token
|
}
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,14 @@ use crate::error::{Error, Errors};
|
||||||
use crate::nnid::oauth::generate_token::create_token;
|
use crate::nnid::oauth::generate_token::create_token;
|
||||||
use crate::nnid::oauth::generate_token::token_type::NEX_TOKEN;
|
use crate::nnid::oauth::generate_token::token_type::NEX_TOKEN;
|
||||||
use crate::xml::Xml;
|
use crate::xml::Xml;
|
||||||
|
use log::{info, warn};
|
||||||
use nex_account::grpc::Pid;
|
use nex_account::grpc::Pid;
|
||||||
use reqwest::header::SERVER;
|
use reqwest::header::SERVER;
|
||||||
use rocket::{State, get};
|
use rocket::http::Status;
|
||||||
|
use rocket::request::{FromRequest, Outcome, Request};
|
||||||
|
use rocket::{State, async_trait, get};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use sqlx::query;
|
||||||
use sqlx::types::ipnetwork::IpNetwork::V4;
|
use sqlx::types::ipnetwork::IpNetwork::V4;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
|
|
@ -55,10 +59,52 @@ pub struct ServiceToken {
|
||||||
token: String,
|
token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn store_or_check_serial(pool: &Pool, serial: &str, cert_hash: [u8; 32]) -> bool {
|
||||||
|
let Ok(res) = query!(
|
||||||
|
"select serial from certificates where hash = $1",
|
||||||
|
&cert_hash[..]
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
else {
|
||||||
|
warn!(
|
||||||
|
"user tried to access a route which is locked behind a console asociated token without a console token"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(stored_serial) = res.serial else {
|
||||||
|
query!(
|
||||||
|
"update certificates set serial = $1 where hash = $2",
|
||||||
|
serial,
|
||||||
|
&cert_hash[..]
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
serial == stored_serial
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Serial(String);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for Serial {
|
||||||
|
type Error = Errors<'static>;
|
||||||
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
|
let Some(header) = request.headers().get("X-Nintendo-Serial-Number").next() else {
|
||||||
|
warn!("serial number wasnt sent on request which expected a serial number");
|
||||||
|
return Outcome::Error((Status::BadRequest, SERVER_ERROR));
|
||||||
|
};
|
||||||
|
|
||||||
|
Outcome::Success(Self(header.to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/v1/api/provider/service_token/@me")]
|
#[get("/v1/api/provider/service_token/@me")]
|
||||||
pub async fn get_service_token(
|
pub async fn get_service_token(
|
||||||
pool: &State<Pool>,
|
pool: &State<Pool>,
|
||||||
auth: Auth<true, false>,
|
auth: Auth<true, false>,
|
||||||
|
serial: Serial,
|
||||||
) -> Result<Xml<ServiceToken>, Option<Errors<'static>>> {
|
) -> Result<Xml<ServiceToken>, Option<Errors<'static>>> {
|
||||||
// just gonna put this here as a side note for the future:
|
// just gonna put this here as a side note for the future:
|
||||||
// we could also be using key derivation to derive the nex token as if it were a key
|
// we could also be using key derivation to derive the nex token as if it were a key
|
||||||
|
|
@ -69,7 +115,17 @@ pub async fn get_service_token(
|
||||||
|
|
||||||
let pool = pool.inner();
|
let pool = pool.inner();
|
||||||
|
|
||||||
let token = create_token(pool, auth.pid, NEX_TOKEN, None).await;
|
let Some(cert_hash) = auth.1 else {
|
||||||
|
info!("attempt to generate service token using non wii u/certificate token");
|
||||||
|
return Err(Some(SERVER_ERROR));
|
||||||
|
};
|
||||||
|
|
||||||
|
if !store_or_check_serial(pool, &serial.0, cert_hash).await {
|
||||||
|
info!("serial mismatched with certificate");
|
||||||
|
return Err(Some(SERVER_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = create_token(pool, auth.pid, NEX_TOKEN, None, Some(&cert_hash[..])).await;
|
||||||
|
|
||||||
Ok(Xml(ServiceToken { token }))
|
Ok(Xml(ServiceToken { token }))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue