Complete private authentication, server integrations and activity reporting
Add Fabric/Forge version builds and Paper integration, preserve permanent player identities across renames, harden session authorization, and surface privacy-conscious launcher/server activity in the panel.
This commit is contained in:
commit
a1f19e86c6
74 files changed
+2108
-95
No files matched your search
@@ -38,6 +38,8 @@ pub fn validate_password(password: &str) -> AppResult<()> {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
#[serde(default)]
|
||||
pub version: i64,
|
||||
pub sub: i64,
|
||||
pub name: String,
|
||||
pub role: String,
|
||||
@@ -56,6 +58,7 @@ impl Keys {
|
||||
|
||||
pub fn issue(&self, user: &UserRow) -> AppResult<String> {
|
||||
let claims = Claims {
|
||||
version: user.auth_version,
|
||||
sub: user.id,
|
||||
name: user.username.clone(),
|
||||
role: user.role.clone(),
|
||||
@@ -72,6 +75,8 @@ impl Keys {
|
||||
|
||||
#[derive(Debug, Clone, sqlx::FromRow, Serialize)]
|
||||
pub struct UserRow {
|
||||
#[serde(skip)]
|
||||
pub auth_version: i64,
|
||||
pub id: i64,
|
||||
pub username: String,
|
||||
#[serde(skip)]
|
||||
@@ -113,8 +118,7 @@ pub async fn public_user(state: &AppState, user: &UserRow) -> AppResult<PublicUs
|
||||
})
|
||||
}
|
||||
|
||||
/// Insert an account. Every account gets its offline-mode UUID, so offline
|
||||
/// and panel-authenticated servers identify players the same way.
|
||||
/// Allocate identity once. Names can change; the account UUID never does.
|
||||
pub async fn create_user(
|
||||
state: &AppState,
|
||||
username: &str,
|
||||
@@ -133,11 +137,13 @@ pub async fn create_user(
|
||||
.bind(role)
|
||||
.bind(status)
|
||||
.bind(crate::db::now())
|
||||
.bind(scopenet_shared::offline_uuid(username))
|
||||
.bind(uuid::Uuid::new_v4().to_string())
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
sqlx::Error::Database(d) if d.message().contains("UNIQUE") => AppError::conflict("that username is taken"),
|
||||
sqlx::Error::Database(d) if d.message().contains("UNIQUE") || d.message().contains("username reserved") => {
|
||||
AppError::conflict("that username is taken or reserved")
|
||||
}
|
||||
e => e.into(),
|
||||
})
|
||||
}
|
||||
@@ -161,7 +167,7 @@ async fn resolve(parts: &Parts, state: &AppState) -> AppResult<Option<UserRow>>
|
||||
};
|
||||
let user: Option<UserRow> = sqlx::query_as("SELECT * FROM users WHERE id = ?").bind(claims.sub).fetch_optional(&state.db).await?;
|
||||
match user {
|
||||
Some(u) if u.status == "active" => Ok(Some(u)),
|
||||
Some(u) if u.status == "active" && u.auth_version == claims.version => Ok(Some(u)),
|
||||
Some(u) if u.status == "pending" => Err(AppError::forbidden("your account is waiting for approval")),
|
||||
_ => Err(AppError::unauthorized("account disabled or removed")),
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user