Skip to content

Commit

Permalink
add(authbeam api): /profile/:id/labels
Browse files Browse the repository at this point in the history
add(authbeam): ability to create user labels
  • Loading branch information
trisuaso committed Dec 10, 2024
1 parent ea204c0 commit 2d68c97
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 24 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/authbeam/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "authbeam"
version = "1.2.1"
version = "1.3.0"
edition = "2021"
description = "Authentication manager"
authors = ["trisuaso", "swmff"]
Expand Down
1 change: 1 addition & 0 deletions crates/authbeam/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub fn routes(database: Database) -> Router {
post(profile::update_metdata_request),
)
.route("/profile/:id/badges", post(profile::update_badges_request))
.route("/profile/:id/labels", post(profile::update_labels_request))
.route("/profile/:id/banner", get(profile::banner_request))
.route("/profile/:id/avatar", get(profile::avatar_request))
.route("/profile/:id", delete(profile::delete_request))
Expand Down
131 changes: 123 additions & 8 deletions crates/authbeam/src/api/profile.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::database::Database;
use crate::model::{
DatabaseError, NotificationCreate, Permission, SetProfileBadges, SetProfileGroup,
SetProfileMetadata, SetProfilePassword, SetProfileTier, SetProfileUsername, TokenContext,
TokenPermission,
SetProfileLabels, SetProfileMetadata, SetProfilePassword, SetProfileTier, SetProfileUsername,
TokenContext, TokenPermission,
};
use databeam::DefaultReturn;

Expand Down Expand Up @@ -249,11 +249,8 @@ pub async fn get_request(
}
};

// edit profile
auth_user.salt = String::new();
auth_user.password = String::new();
auth_user.tokens = Vec::new();
auth_user.ips = Vec::new();
// clean profile
auth_user.clean();

// return
Json(DefaultReturn {
Expand Down Expand Up @@ -1226,7 +1223,7 @@ pub async fn update_metdata_request(
}
}

/// Update a user's metadata
/// Update a user's badges
pub async fn update_badges_request(
jar: CookieJar,
Path(id): Path<String>,
Expand Down Expand Up @@ -1344,6 +1341,124 @@ pub async fn update_badges_request(
}
}

/// Update a user's labels
pub async fn update_labels_request(
jar: CookieJar,
Path(id): Path<String>,
State(database): State<Database>,
Json(props): Json<SetProfileLabels>,
) -> impl IntoResponse {
// get user from token
let auth_user = match jar.get("__Secure-Token") {
Some(c) => {
let token = c.value_trimmed().to_string();

match database.get_profile_by_unhashed(token.clone()).await {
Ok(ua) => {
// check token permission
if !ua
.token_context_from_token(&token)
.can_do(TokenPermission::Moderator)
{
return Json(DefaultReturn {
success: false,
message: DatabaseError::NotAllowed.to_string(),
payload: (),
});
}

// return
ua
}
Err(e) => {
return Json(DefaultReturn {
success: false,
message: e.to_string(),
payload: (),
});
}
}
}
None => {
return Json(DefaultReturn {
success: false,
message: DatabaseError::NotAllowed.to_string(),
payload: (),
});
}
};

// check permission
let group = match database.get_group_by_id(auth_user.group).await {
Ok(g) => g,
Err(e) => {
return Json(DefaultReturn {
success: false,
message: e.to_string(),
payload: (),
})
}
};

if !group.permissions.contains(&Permission::Helper) {
// we must have the "Helper" permission to edit other users' badges
return Json(DefaultReturn {
success: false,
message: DatabaseError::NotAllowed.to_string(),
payload: (),
});
}

// get other user
let other_user = match database.get_profile(id.clone()).await {
Ok(ua) => ua,
Err(e) => {
return Json(DefaultReturn {
success: false,
message: e.to_string(),
payload: (),
});
}
};

// check permission
let other_group = match database.get_group_by_id(other_user.group).await {
Ok(g) => g,
Err(e) => {
return Json(DefaultReturn {
success: false,
message: e.to_string(),
payload: (),
})
}
};

if other_group.permissions.contains(&Permission::Helper)
&& !group.permissions.contains(&Permission::Manager)
{
// we cannot manage other helpers without manager
return Json(DefaultReturn {
success: false,
message: DatabaseError::NotAllowed.to_string(),
payload: (),
});
}

// return
match database.update_profile_labels(id, props.labels).await {
Ok(_) => Json(DefaultReturn {
success: true,
message: "Acceptable".to_string(),
payload: (),
}),
Err(e) => Json(DefaultReturn {
success: false,
message: e.to_string(),
payload: (),
}),
}
}

/// Delete another user
pub async fn delete_request(
jar: CookieJar,
Expand Down
126 changes: 120 additions & 6 deletions crates/authbeam/src/database.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::str::Split;

use crate::model::{
Expand Down Expand Up @@ -1046,7 +1047,48 @@ impl Database {
Ok(_) => {
self.base
.cachedb
.remove(format!("rbeam.auth.profile:{}", id))
.remove(format!("rbeam.auth.profile:{}", ua.username))
.await;

self.base
.cachedb
.remove(format!("rbeam.auth.profile:{}", ua.id))
.await;

Ok(())
}
Err(_) => Err(DatabaseError::Other),
}
}

/// Update a [`Profile`]'s labels by its `id`
pub async fn update_profile_labels(&self, id: String, labels: Vec<String>) -> Result<()> {
// make sure user exists
let ua = match self.get_profile(id.clone()).await {
Ok(ua) => ua,
Err(e) => return Err(e),
};

// update user
let query: &str = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
"UPDATE \"xprofiles\" SET \"labels\" = ? WHERE \"id\" = ?"
} else {
"UPDATE \"xprofiles\" SET (\"labels\") = ($1) WHERE \"id\" = $2"
};

let c = &self.base.db.client;
let labels = &serde_json::to_string(&labels).unwrap();

match sqlquery(query)
.bind::<&String>(labels)
.bind::<&String>(&id)
.execute(c)
.await
{
Ok(_) => {
self.base
.cachedb
.remove(format!("rbeam.auth.profile:{}", ua.username))
.await;

self.base
Expand Down Expand Up @@ -4264,6 +4306,16 @@ impl Database {

// labels

/// Get a [`UserLabel`] from a database result
pub async fn gimme_label(&self, res: HashMap<String, String>) -> Result<UserLabel> {
Ok(UserLabel {
id: res.get("id").unwrap().to_string(),
name: res.get("name").unwrap().to_string(),
timestamp: res.get("timestamp").unwrap().parse::<u128>().unwrap(),
creator: res.get("author").unwrap().to_string(),
})
}

/// Get an existing label
///
/// ## Arguments:
Expand Down Expand Up @@ -4304,11 +4356,9 @@ impl Database {
};

// return
let label = UserLabel {
id: res.get("id").unwrap().to_string(),
name: res.get("name").unwrap().to_string(),
timestamp: res.get("timestamp").unwrap().parse::<u128>().unwrap(),
creator: res.get("author").unwrap().to_string(),
let label = match self.gimme_label(res).await {
Ok(l) => l,
Err(e) => return Err(e),
};

// store in cache
Expand All @@ -4323,4 +4373,68 @@ impl Database {
// return
Ok(label)
}

/// Create a new user label
///
/// # Arguments
/// * `name` - the name of the label
/// * `author` - the ID of the user creating the label
pub async fn create_label(&self, name: String, author: String) -> Result<UserLabel> {
// check author permissions
let author = match self.get_profile(author.clone()).await {
Ok(ua) => ua,
Err(e) => return Err(e),
};

let group = match self.get_group_by_id(author.group).await {
Ok(g) => g,
Err(e) => return Err(e),
};

if !group.permissions.contains(&Permission::Helper) {
return Err(DatabaseError::NotAllowed);
}

// check name length
if name.len() < 2 {
return Err(DatabaseError::Other);
}

if name.len() > 32 {
return Err(DatabaseError::Other);
}

// ...
let label = UserLabel {
id: utility::random_id(),
name,
timestamp: utility::unix_epoch_timestamp(),
creator: author.id,
};

// create page
let query: String = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql")
{
"INSERT INTO \"xlabels\" VALUES (?, ?, ?, ?)"
} else {
"INSERT INTO \"xlabels\" VALEUS ($1, $2, $3, $4)"
}
.to_string();

let c = &self.base.db.client;
match sqlquery(&query)
.bind::<&String>(&label.id)
.bind::<&String>(&label.name)
.bind::<&String>(&label.timestamp.to_string())
.bind::<&String>(&label.creator)
.execute(c)
.await
{
Ok(_) => Ok(label),
Err(_) => Err(DatabaseError::Other),
}
}

// user labels **CANNOT** be deleted because that could possible affect THOUSANDS of users
// user labels should instead be versioned (if possible)
}
5 changes: 5 additions & 0 deletions crates/authbeam/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,11 @@ pub struct SetProfileBadges {
pub badges: Vec<(String, String, String)>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct SetProfileLabels {
pub labels: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct SetProfileGroup {
pub group: i32,
Expand Down
2 changes: 1 addition & 1 deletion crates/rainbeam-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rainbeam-core"
version = "1.18.5"
version = "1.19.0"
edition = "2021"
authors = ["trisuaso", "swmff"]
description = "Rainbeam backend core"
Expand Down
1 change: 0 additions & 1 deletion crates/rainbeam-core/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1247,7 +1247,6 @@ impl Database {

if !props.media.is_empty() {
if !props.media.starts_with("https://") && !props.media.starts_with("--CARP") {
dbg!(1);
return Err(DatabaseError::Other);
}
}
Expand Down
Loading

0 comments on commit 2d68c97

Please sign in to comment.