🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-09-25 2:09:01.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-09-25 2:09:01.0 +01:00:00
commit
a7544b43b22dbd2baff1c1a736b766acac5519aa [patch]
tree
e39f625596101c8235d00460c2d1d39aaa542b17
parent
99888d42837d6baef5751155633a3044b571c822
download
a7544b43b22dbd2baff1c1a736b766acac5519aa.tar.gz

Move permissions to its own module



Diff

 chartered-db/src/crates.rs                                   | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
 chartered-db/src/lib.rs                                      |   7 ++++---
 chartered-db/src/organisations.rs                            |  50 +++++++++++++++++++++++++++++++-------------------
 chartered-db/src/permissions.rs                              |  33 +++++++++++++++++++++++++++++++++
 chartered-db/src/users.rs                                    |  71 ++++-------------------------------------------------------------------
 chartered-web/src/endpoints/web_api/crates/members.rs        |  11 ++++-------
 chartered-web/src/endpoints/web_api/organisations/info.rs    |  12 +++++-------
 chartered-web/src/endpoints/web_api/organisations/members.rs |   6 ++----
 8 files changed, 155 insertions(+), 156 deletions(-)

diff --git a/chartered-db/src/crates.rs b/chartered-db/src/crates.rs
index 0cad66b..5840386 100644
--- a/chartered-db/src/crates.rs
+++ a/chartered-db/src/crates.rs
@@ -1,17 +1,47 @@
use crate::organisations::Organisation;
use crate::users::{User, UserCratePermission};

use super::{
    coalesce,
    schema::{crate_versions, crates, organisations, users},
    users::UserCratePermissionValue as Permissions,
    organisations::Organisation,
    permissions::UserPermission,
    schema::{crate_versions, crates, organisations, user_crate_permissions, users},
    users::User,
    BitwiseExpressionMethods, ConnectionPool, Error, Result,
};
use diesel::{insert_into, prelude::*, Associations, Identifiable, Queryable};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::Arc};

#[derive(Identifiable, Queryable, Associations, Default, PartialEq, Eq, Hash, Debug)]
#[belongs_to(User)]
#[belongs_to(Crate)]
pub struct UserCratePermission {
    pub id: i32,
    pub user_id: i32,
    pub crate_id: i32,
    pub permissions: UserPermission,
}

impl UserCratePermission {
    pub async fn find(
        conn: ConnectionPool,
        given_user_id: i32,
        given_crate_id: i32,
    ) -> Result<Option<UserCratePermission>> {
        use crate::schema::user_crate_permissions::dsl::{crate_id, user_id};

        tokio::task::spawn_blocking(move || {
            let conn = conn.get()?;

            Ok(crate::schema::user_crate_permissions::table
                .filter(user_id.eq(given_user_id))
                .filter(crate_id.eq(given_crate_id))
                .get_result(&conn)
                .optional()?)
        })
        .await?
    }
}

#[derive(Identifiable, Queryable, Associations, PartialEq, Eq, Hash, Debug)]
#[belongs_to(Organisation)]
pub struct Crate {
@@ -77,8 +107,8 @@
                .filter(org_name.eq(given_org_name))
                .filter(
                    select_permissions!()
                        .bitwise_and(Permissions::VISIBLE.bits())
                        .eq(Permissions::VISIBLE.bits()),
                        .bitwise_and(UserPermission::VISIBLE.bits())
                        .eq(UserPermission::VISIBLE.bits()),
                )
                .inner_join(crate_versions::table)
                .select((crates::all_columns, crate_versions::all_columns))
@@ -99,8 +129,8 @@
            let crates = crate_with_permissions!(requesting_user_id)
                .filter(
                    select_permissions!()
                        .bitwise_and(Permissions::VISIBLE.bits())
                        .eq(Permissions::VISIBLE.bits()),
                        .bitwise_and(UserPermission::VISIBLE.bits())
                        .eq(UserPermission::VISIBLE.bits()),
                )
                .inner_join(organisations::table)
                .inner_join(crate_versions::table)
@@ -135,17 +165,17 @@
                .filter(org_name.eq(given_org_name))
                .filter(crate_name.eq(given_crate_name))
                .select((crate::schema::crates::all_columns, select_permissions!()))
                .first::<(Crate, Permissions)>(&conn)
                .first::<(Crate, UserPermission)>(&conn)
                .optional()?
                .ok_or(Error::MissingCrate)?;

            if permissions.contains(Permissions::VISIBLE) {
            if permissions.contains(UserPermission::VISIBLE) {
                Ok(CrateWithPermissions {
                    crate_,
                    permissions,
                })
            } else {
                Err(Error::MissingCratePermission(Permissions::VISIBLE))
                Err(Error::MissingCratePermission(UserPermission::VISIBLE))
            }
        })
        .await?
@@ -172,13 +202,13 @@
                        .on(organisation_id.eq(id).and(user_id.eq(requesting_user_id))),
                )
                .select((id, permissions))
                .first::<(i32, Permissions)>(&conn)?;
                .first::<(i32, UserPermission)>(&conn)?;

            #[allow(clippy::if_not_else)]
            if !perms.contains(Permissions::VISIBLE) {
                Err(Error::MissingCratePermission(Permissions::VISIBLE))
            } else if !perms.contains(Permissions::CREATE_CRATE) {
                Err(Error::MissingCratePermission(Permissions::CREATE_CRATE))
            if !perms.contains(UserPermission::VISIBLE) {
                Err(Error::MissingCratePermission(UserPermission::VISIBLE))
            } else if !perms.contains(UserPermission::CREATE_CRATE) {
                Err(Error::MissingCratePermission(UserPermission::CREATE_CRATE))
            } else {
                use crate::schema::crates::dsl::{crates, name, organisation_id};

@@ -204,7 +234,7 @@
#[derive(Debug)]
pub struct CrateWithPermissions {
    pub crate_: Crate,
    pub permissions: Permissions,
    pub permissions: UserPermission,
}

impl CrateWithPermissions {
@@ -249,7 +279,7 @@
            Ok(UserCratePermission::belonging_to(&self.crate_)
                .filter(
                    permissions
                        .bitwise_and(crate::users::UserCratePermissionValue::MANAGE_USERS.bits())
                        .bitwise_and(UserPermission::MANAGE_USERS.bits())
                        .ne(0),
                )
                .inner_join(crate::schema::users::dsl::users)
@@ -262,20 +292,17 @@
    pub async fn members(
        self: Arc<Self>,
        conn: ConnectionPool,
    ) -> Result<Vec<(crate::users::User, crate::users::UserCratePermissionValue)>> {
        if !self.permissions.contains(Permissions::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(Permissions::MANAGE_USERS));
    ) -> Result<Vec<(crate::users::User, UserPermission)>> {
        if !self.permissions.contains(UserPermission::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS));
        }

        tokio::task::spawn_blocking(move || {
            let conn = conn.get()?;

            Ok(UserCratePermission::belonging_to(&self.crate_)
                .inner_join(crate::schema::users::dsl::users)
                .select((
                    crate::schema::users::all_columns,
                    crate::schema::user_crate_permissions::permissions,
                ))
                .inner_join(users::dsl::users)
                .select((users::all_columns, user_crate_permissions::permissions))
                .load(&conn)?)
        })
        .await?
@@ -285,10 +312,10 @@
        self: Arc<Self>,
        conn: ConnectionPool,
        given_user_id: i32,
        given_permissions: crate::users::UserCratePermissionValue,
        given_permissions: UserPermission,
    ) -> Result<usize> {
        if !self.permissions.contains(Permissions::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(Permissions::MANAGE_USERS));
        if !self.permissions.contains(UserPermission::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS));
        }

        tokio::task::spawn_blocking(move || {
@@ -313,10 +340,10 @@
        self: Arc<Self>,
        conn: ConnectionPool,
        given_user_id: i32,
        given_permissions: crate::users::UserCratePermissionValue,
        given_permissions: UserPermission,
    ) -> Result<usize> {
        if !self.permissions.contains(Permissions::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(Permissions::MANAGE_USERS));
        if !self.permissions.contains(UserPermission::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS));
        }

        tokio::task::spawn_blocking(move || {
@@ -342,8 +369,8 @@
        conn: ConnectionPool,
        given_user_id: i32,
    ) -> Result<()> {
        if !self.permissions.contains(Permissions::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(Permissions::MANAGE_USERS));
        if !self.permissions.contains(UserPermission::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS));
        }

        tokio::task::spawn_blocking(move || {
@@ -376,16 +403,20 @@
        given: chartered_types::cargo::CrateVersion<'static>,
        metadata: chartered_types::cargo::CrateVersionMetadata,
    ) -> Result<()> {
        use crate::schema::crate_versions::dsl::{
            checksum, crate_id, crate_versions, dependencies, features, filesystem_object, links,
            size, user_id, version,
        };
        use crate::schema::crates::dsl::{
            crates, description, documentation, homepage, id, name, readme, repository,
        use crate::schema::{
            crate_versions::dsl::{
                checksum, crate_id, crate_versions, dependencies, features, filesystem_object,
                links, size, user_id, version,
            },
            crates::dsl::{
                crates, description, documentation, homepage, id, name, readme, repository,
            },
        };

        if !self.permissions.contains(Permissions::PUBLISH_VERSION) {
            return Err(Error::MissingCratePermission(Permissions::PUBLISH_VERSION));
        if !self.permissions.contains(UserPermission::PUBLISH_VERSION) {
            return Err(Error::MissingCratePermission(
                UserPermission::PUBLISH_VERSION,
            ));
        }

        tokio::task::spawn_blocking(move || {
@@ -441,8 +472,8 @@
    ) -> Result<()> {
        use crate::schema::crate_versions::dsl::{crate_id, crate_versions, version, yanked};

        if !self.permissions.contains(Permissions::YANK_VERSION) {
            return Err(Error::MissingCratePermission(Permissions::YANK_VERSION));
        if !self.permissions.contains(UserPermission::YANK_VERSION) {
            return Err(Error::MissingCratePermission(UserPermission::YANK_VERSION));
        }

        tokio::task::spawn_blocking(move || {
diff --git a/chartered-db/src/lib.rs b/chartered-db/src/lib.rs
index c66eb8a..bc4c9af 100644
--- a/chartered-db/src/lib.rs
+++ a/chartered-db/src/lib.rs
@@ -33,6 +33,7 @@

pub mod crates;
pub mod organisations;
pub mod permissions;
pub mod schema;
pub mod users;
pub mod uuid;
@@ -68,9 +69,9 @@
    /// Key parse failure: `{0}`

    KeyParse(#[from] thrussh_keys::Error),
    /// You don't have the {0:?} permission for this crate

    MissingCratePermission(crate::users::UserCratePermissionValue),
    MissingCratePermission(crate::permissions::UserPermission),
    /// You don't have the {0:?} permission for this organisation

    MissingOrganisationPermission(crate::users::UserCratePermissionValue),
    MissingOrganisationPermission(crate::permissions::UserPermission),
    /// The requested crate does not exist

    MissingCrate,
    /// The requested organisation does not exist

@@ -85,7 +86,7 @@
        match self {
            Self::MissingCrate => http::StatusCode::NOT_FOUND,
            Self::MissingCratePermission(v) | Self::MissingOrganisationPermission(v)
                if v.contains(crate::users::UserCratePermissionValue::VISIBLE) =>
                if v.contains(crate::permissions::UserPermission::VISIBLE) =>
            {
                http::StatusCode::NOT_FOUND
            }
diff --git a/chartered-db/src/organisations.rs b/chartered-db/src/organisations.rs
index 5a0e735..3ffad25 100644
--- a/chartered-db/src/organisations.rs
+++ a/chartered-db/src/organisations.rs
@@ -1,8 +1,4 @@
use crate::{
    crates::Crate,
    users::{User, UserCratePermissionValue as Permission},
    Error,
};
use crate::{crates::Crate, permissions::UserPermission, users::User, Error};

use super::{
    schema::{organisations, user_organisation_permissions, users},
@@ -46,12 +42,13 @@
                    user_organisation_permissions::dsl::permissions.nullable(),
                    organisations::all_columns,
                ))
                .get_result::<(Option<Permission>, _)>(&conn)
                .get_result::<(Option<UserPermission>, _)>(&conn)
                .optional()?
                .ok_or(Error::MissingOrganisation)?;

            let permissions =
                permissions.ok_or(Error::MissingOrganisationPermission(Permission::VISIBLE))?;
            let permissions = permissions.ok_or(Error::MissingOrganisationPermission(
                UserPermission::VISIBLE,
            ))?;

            Ok(OrganisationWithPermissions {
                organisation,
@@ -64,18 +61,20 @@

pub struct OrganisationWithPermissions {
    organisation: Organisation,
    permissions: Permission,
    permissions: UserPermission,
}

impl OrganisationWithPermissions {
    #[must_use]
    pub fn permissions(&self) -> Permission {
    pub fn permissions(&self) -> UserPermission {
        self.permissions
    }

    pub async fn crates(self: Arc<Self>, conn: ConnectionPool) -> Result<Vec<Crate>> {
        if !self.permissions.contains(Permission::VISIBLE) {
            return Err(Error::MissingOrganisationPermission(Permission::VISIBLE));
        if !self.permissions.contains(UserPermission::VISIBLE) {
            return Err(Error::MissingOrganisationPermission(
                UserPermission::VISIBLE,
            ));
        }

        tokio::task::spawn_blocking(move || {
@@ -87,9 +86,14 @@
        .await?
    }

    pub async fn members(self: Arc<Self>, conn: ConnectionPool) -> Result<Vec<(User, Permission)>> {
        if !self.permissions.contains(Permission::VISIBLE) {
            return Err(Error::MissingOrganisationPermission(Permission::VISIBLE));
    pub async fn members(
        self: Arc<Self>,
        conn: ConnectionPool,
    ) -> Result<Vec<(User, UserPermission)>> {
        if !self.permissions.contains(UserPermission::VISIBLE) {
            return Err(Error::MissingOrganisationPermission(
                UserPermission::VISIBLE,
            ));
        }

        tokio::task::spawn_blocking(move || {
@@ -113,10 +117,10 @@
        self: Arc<Self>,
        conn: ConnectionPool,
        given_user_id: i32,
        given_permissions: crate::users::UserCratePermissionValue,
        given_permissions: UserPermission,
    ) -> Result<usize> {
        if !self.permissions.contains(Permission::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(Permission::MANAGE_USERS));
        if !self.permissions.contains(UserPermission::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS));
        }

        tokio::task::spawn_blocking(move || {
@@ -141,10 +145,10 @@
        self: Arc<Self>,
        conn: ConnectionPool,
        given_user_id: i32,
        given_permissions: crate::users::UserCratePermissionValue,
        given_permissions: UserPermission,
    ) -> Result<usize> {
        if !self.permissions.contains(Permission::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(Permission::MANAGE_USERS));
        if !self.permissions.contains(UserPermission::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS));
        }

        tokio::task::spawn_blocking(move || {
@@ -170,8 +174,8 @@
        conn: ConnectionPool,
        given_user_id: i32,
    ) -> Result<()> {
        if !self.permissions.contains(Permission::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(Permission::MANAGE_USERS));
        if !self.permissions.contains(UserPermission::MANAGE_USERS) {
            return Err(Error::MissingCratePermission(UserPermission::MANAGE_USERS));
        }

        tokio::task::spawn_blocking(move || {
diff --git a/chartered-db/src/permissions.rs b/chartered-db/src/permissions.rs
new file mode 100644
index 0000000..0b3e282 100644
--- /dev/null
+++ a/chartered-db/src/permissions.rs
@@ -1,0 +1,33 @@
use bitflags::bitflags;
use option_set::{option_set, OptionSet};

option_set! {
    #[derive(FromSqlRow, AsExpression)]
    pub struct UserPermission: Identity + i32 {
        const VISIBLE         = 0b0000_0000_0000_0000_0000_0000_0000_0001;
        const PUBLISH_VERSION = 0b0000_0000_0000_0000_0000_0000_0000_0010;
        const YANK_VERSION    = 0b0000_0000_0000_0000_0000_0000_0000_0100;
        const MANAGE_USERS    = 0b0000_0000_0000_0000_0000_0000_0000_1000;
        const CREATE_CRATE    = 0b0000_0000_0000_0000_0000_0000_0001_0000;
    }
}

impl UserPermission {
    #[must_use]
    pub fn names() -> &'static [&'static str] {
        Self::NAMES
    }
}

impl<B: diesel::backend::Backend> diesel::deserialize::FromSql<diesel::sql_types::Integer, B>
    for UserPermission
where
    i32: diesel::deserialize::FromSql<diesel::sql_types::Integer, B>,
{
    fn from_sql(
        bytes: Option<&B::RawValue>,
    ) -> std::result::Result<UserPermission, Box<dyn std::error::Error + Send + Sync>> {
        let val = i32::from_sql(bytes)?;
        Ok(UserPermission::from_bits_truncate(val))
    }
}
diff --git a/chartered-db/src/users.rs b/chartered-db/src/users.rs
index 00d6011..69e16d3 100644
--- a/chartered-db/src/users.rs
+++ a/chartered-db/src/users.rs
@@ -1,11 +1,11 @@
use super::{
    crates::UserCratePermission,
    permissions::UserPermission,
    schema::{user_crate_permissions, user_sessions, user_ssh_keys, users},
    uuid::SqlUuid,
    ConnectionPool, Result,
};
use bitflags::bitflags;
use diesel::{insert_into, prelude::*, Associations, Identifiable, Queryable};
use option_set::{option_set, OptionSet};
use rand::{thread_rng, Rng};
use std::sync::Arc;
use thrussh_keys::PublicKeyBase64;
@@ -192,7 +192,7 @@
    pub async fn accessible_crates(
        self: Arc<Self>,
        conn: ConnectionPool,
    ) -> Result<Vec<(UserCratePermissionValue, crate::crates::Crate)>> {
    ) -> Result<Vec<(UserPermission, crate::crates::Crate)>> {
        use crate::schema::crates;

        tokio::task::spawn_blocking(move || {
@@ -210,7 +210,7 @@
        self: Arc<Self>,
        conn: ConnectionPool,
        crate_id: i32,
    ) -> Result<UserCratePermissionValue> {
    ) -> Result<UserPermission> {
        Ok(UserCratePermission::find(conn, self.id, crate_id)
            .await?
            .unwrap_or_default()
@@ -267,69 +267,6 @@
            Ok(crate::schema::user_sessions::table
                .filter(session_key.eq(generated_session_key))
                .get_result(&conn)?)
        })
        .await?
    }
}

option_set! {
    #[derive(FromSqlRow, AsExpression)]
    pub struct UserCratePermissionValue: Identity + i32 {
        const VISIBLE         = 0b0000_0000_0000_0000_0000_0000_0000_0001;
        const PUBLISH_VERSION = 0b0000_0000_0000_0000_0000_0000_0000_0010;
        const YANK_VERSION    = 0b0000_0000_0000_0000_0000_0000_0000_0100;
        const MANAGE_USERS    = 0b0000_0000_0000_0000_0000_0000_0000_1000;
        const CREATE_CRATE    = 0b0000_0000_0000_0000_0000_0000_0001_0000;
    }
}

impl UserCratePermissionValue {
    #[must_use]
    pub fn names() -> &'static [&'static str] {
        Self::NAMES
    }
}

impl<B: diesel::backend::Backend> diesel::deserialize::FromSql<diesel::sql_types::Integer, B>
    for UserCratePermissionValue
where
    i32: diesel::deserialize::FromSql<diesel::sql_types::Integer, B>,
{
    fn from_sql(
        bytes: Option<&B::RawValue>,
    ) -> std::result::Result<UserCratePermissionValue, Box<dyn std::error::Error + Send + Sync>>
    {
        let val = i32::from_sql(bytes)?;
        Ok(UserCratePermissionValue::from_bits_truncate(val))
    }
}

#[derive(Identifiable, Queryable, Associations, Default, PartialEq, Eq, Hash, Debug)]
#[belongs_to(User)]
#[belongs_to(super::crates::Crate)]
pub struct UserCratePermission {
    pub id: i32,
    pub user_id: i32,
    pub crate_id: i32,
    pub permissions: UserCratePermissionValue,
}

impl UserCratePermission {
    pub async fn find(
        conn: ConnectionPool,
        given_user_id: i32,
        given_crate_id: i32,
    ) -> Result<Option<UserCratePermission>> {
        use crate::schema::user_crate_permissions::dsl::{crate_id, user_id};

        tokio::task::spawn_blocking(move || {
            let conn = conn.get()?;

            Ok(crate::schema::user_crate_permissions::table
                .filter(user_id.eq(given_user_id))
                .filter(crate_id.eq(given_crate_id))
                .get_result(&conn)
                .optional()?)
        })
        .await?
    }
diff --git a/chartered-web/src/endpoints/web_api/crates/members.rs b/chartered-web/src/endpoints/web_api/crates/members.rs
index 4059533..c9a186f 100644
--- a/chartered-web/src/endpoints/web_api/crates/members.rs
+++ a/chartered-web/src/endpoints/web_api/crates/members.rs
@@ -1,9 +1,6 @@
use axum::{extract, Json};
use chartered_db::{
    crates::Crate,
    users::{User, UserCratePermissionValue as Permission},
    uuid::Uuid,
    ConnectionPool,
    crates::Crate, permissions::UserPermission, users::User, uuid::Uuid, ConnectionPool,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
@@ -21,7 +18,7 @@
pub struct GetResponseMember {
    uuid: Uuid,
    username: String,
    permissions: Permission,
    permissions: UserPermission,
}

pub async fn handle_get(
@@ -44,7 +41,7 @@
        .collect();

    Ok(Json(GetResponse {
        allowed_permissions: Permission::names(),
        allowed_permissions: UserPermission::names(),
        members,
    }))
}
@@ -52,7 +49,7 @@
#[derive(Deserialize)]
pub struct PutOrPatchRequest {
    user_uuid: chartered_db::uuid::Uuid,
    permissions: Permission,
    permissions: UserPermission,
}

pub async fn handle_patch(
diff --git a/chartered-web/src/endpoints/web_api/organisations/info.rs b/chartered-web/src/endpoints/web_api/organisations/info.rs
index 052228f..26527a3 100644
--- a/chartered-web/src/endpoints/web_api/organisations/info.rs
+++ a/chartered-web/src/endpoints/web_api/organisations/info.rs
@@ -1,8 +1,6 @@
use axum::{extract, Json};
use chartered_db::{
    organisations::Organisation,
    users::{User, UserCratePermissionValue as Permission},
    ConnectionPool,
    organisations::Organisation, permissions::UserPermission, users::User, ConnectionPool,
};
use serde::Serialize;
use std::sync::Arc;
@@ -34,7 +32,7 @@

    let can_manage_users = organisation
        .permissions()
        .contains(Permission::MANAGE_USERS);
        .contains(UserPermission::MANAGE_USERS);

    let (crates, users) = tokio::try_join!(
        organisation.clone().crates(db.clone()),
@@ -42,7 +40,7 @@
    )?;

    Ok(Json(Response {
        possible_permissions: can_manage_users.then(Permission::all),
        possible_permissions: can_manage_users.then(UserPermission::all),
        crates: crates
            .into_iter()
            .map(|v| ResponseCrate {
@@ -63,7 +61,7 @@

#[derive(Serialize)]
pub struct Response {
    possible_permissions: Option<Permission>,
    possible_permissions: Option<UserPermission>,
    crates: Vec<ResponseCrate>,
    members: Vec<ResponseUser>,
}
@@ -78,5 +76,5 @@
pub struct ResponseUser {
    uuid: String,
    username: String,
    permissions: Option<Permission>,
    permissions: Option<UserPermission>,
}
diff --git a/chartered-web/src/endpoints/web_api/organisations/members.rs b/chartered-web/src/endpoints/web_api/organisations/members.rs
index f80f673..2262a17 100644
--- a/chartered-web/src/endpoints/web_api/organisations/members.rs
+++ a/chartered-web/src/endpoints/web_api/organisations/members.rs
@@ -1,8 +1,6 @@
use axum::{extract, Json};
use chartered_db::{
    organisations::Organisation,
    users::{User, UserCratePermissionValue as Permission},
    ConnectionPool,
    organisations::Organisation, permissions::UserPermission, users::User, ConnectionPool,
};
use serde::Deserialize;
use std::sync::Arc;
@@ -13,7 +11,7 @@
#[derive(Deserialize)]
pub struct PutOrPatchRequest {
    user_uuid: chartered_db::uuid::Uuid,
    permissions: Permission,
    permissions: UserPermission,
}

pub async fn handle_patch(