permissions: homepage, sql + crate, add/remove/view for user
This commit is contained in:
parent
8dbeffddc9
commit
440f42ed2e
26 changed files with 880 additions and 1 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -823,6 +823,7 @@ dependencies = [
|
|||
"jwt",
|
||||
"minify-html",
|
||||
"openid",
|
||||
"permissions",
|
||||
"refresh_tokens",
|
||||
"rocket",
|
||||
"rocket_cors",
|
||||
|
|
@ -2190,6 +2191,17 @@ version = "2.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "permissions"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"database",
|
||||
"id",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.5.6"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
drop table if exists permissions;
|
||||
drop index if exists permissions_unique_user_role;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
create table if not exists permissions
|
||||
(
|
||||
user TEXT not null references users (id),
|
||||
role TEXT not null references roles (name),
|
||||
created_at TEXT not null default CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- unique user & role
|
||||
create unique index if not exists permissions_unique_user_role on permissions (user, role);
|
||||
2
crates/database/queries/permissions/add.sql
Normal file
2
crates/database/queries/permissions/add.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
insert into permissions(user, role)
|
||||
values (?, ?)
|
||||
5
crates/database/queries/permissions/delete.sql
Normal file
5
crates/database/queries/permissions/delete.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
delete
|
||||
from permissions
|
||||
|
||||
where user is ?
|
||||
and role is ?
|
||||
5
crates/database/queries/permissions/get_all.sql
Normal file
5
crates/database/queries/permissions/get_all.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
select user,
|
||||
role,
|
||||
created_at as "created_at: DateTime<Utc>"
|
||||
|
||||
from permissions
|
||||
6
crates/database/queries/permissions/get_all_role.sql
Normal file
6
crates/database/queries/permissions/get_all_role.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
select user,
|
||||
role,
|
||||
created_at as "created_at: DateTime<Utc>"
|
||||
|
||||
from permissions
|
||||
where role is (?)
|
||||
6
crates/database/queries/permissions/get_all_user.sql
Normal file
6
crates/database/queries/permissions/get_all_user.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
select user,
|
||||
role,
|
||||
created_at as "created_at: DateTime<Utc>"
|
||||
|
||||
from permissions
|
||||
where user is (?)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
select user,
|
||||
role,
|
||||
created_at as "created_at: DateTime<Utc>"
|
||||
|
||||
from permissions
|
||||
where user is (?)
|
||||
and role is (?)
|
||||
|
|
@ -306,6 +306,36 @@
|
|||
},
|
||||
"query": "insert into roles (name, label)\nvalues (?, ?)\n"
|
||||
},
|
||||
"46caa546db24d2c1e8192f9e699202be5129c74a5569b2dc7bf95761fe09a6a3": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "user",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at: DateTime<Utc>",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "select user,\n role,\n created_at as \"created_at: DateTime<Utc>\"\n\nfrom permissions\nwhere role is (?)\n"
|
||||
},
|
||||
"520fe30e21f6b6c4d9a47c457675eebd144cf020e9230d154e9e4d0c8d6e01ca": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
|
@ -622,6 +652,16 @@
|
|||
},
|
||||
"query": "update roles\n\nset label = ?\n\nwhere name is ?"
|
||||
},
|
||||
"6fa3c48b9e93fb9ec6807f7547a8f999fc55a6fb8ad4abe7af89ec52e0d10a0e": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
}
|
||||
},
|
||||
"query": "delete\nfrom permissions\n\nwhere user is ?\n and role is ?"
|
||||
},
|
||||
"6ff12f357d884a50035d708577a7f3109a07a1ca193cb3082d13687af65c6de0": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
|
@ -728,6 +768,36 @@
|
|||
},
|
||||
"query": "select id,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n is_admin as \"is_admin: bool\",\n username,\n name,\n email,\n password,\n password_recover,\n paper_key,\n is_archived as \"is_archived: bool\",\n timezone,\n totp_secret,\n totp_backup\nfrom users\n\nwhere id is (?)\n"
|
||||
},
|
||||
"73bdbde04fca37f2411e7a9e1b6dbccc0dd4d12ebcb933024d9867f07bba3eb8": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "user",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at: DateTime<Utc>",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
}
|
||||
},
|
||||
"query": "select user,\n role,\n created_at as \"created_at: DateTime<Utc>\"\n\nfrom permissions\nwhere user is (?)\n and role is (?)\n"
|
||||
},
|
||||
"7b7f2430b2a719b3d5ce504c0a9302731b3ff82da99ba7771c2728d88aee642a": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
|
@ -768,6 +838,36 @@
|
|||
},
|
||||
"query": "update users\n\nset name = ?\n\nwhere id is ?"
|
||||
},
|
||||
"8c37375b0694df02b7f1b6678e4e2c3ffbc590c0f305ff5a8f44350fba3eaec7": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "user",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at: DateTime<Utc>",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "select user,\n role,\n created_at as \"created_at: DateTime<Utc>\"\n\nfrom permissions\nwhere user is (?)\n"
|
||||
},
|
||||
"93b15a942a6c7db595990f00e14fde26d6d36b8c8de9935179d41f6c7c755978": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
|
@ -1088,6 +1188,36 @@
|
|||
},
|
||||
"query": "insert into authorization_codes (code, app, user, expires_at)\nvalues (?, ?, ?, datetime(?, 'unixepoch'))\n"
|
||||
},
|
||||
"a9e910eedc27c495262571520627363290640b3af7d177a024cad06220a770f0": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "user",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at: DateTime<Utc>",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
}
|
||||
},
|
||||
"query": "select user,\n role,\n created_at as \"created_at: DateTime<Utc>\"\n\nfrom permissions\n"
|
||||
},
|
||||
"aae93a39c5a9f46235b5ef871b45ba76d7efa1677bfe8291a62b8cbf9cd9e0d5": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
|
@ -1212,6 +1342,16 @@
|
|||
},
|
||||
"query": "update refresh_tokens\n\nset revoked_at = CURRENT_TIMESTAMP\n\nwhere token is ?"
|
||||
},
|
||||
"c724c273f9d99bde48c29d7a0e65198a1ddd775cd1bda10f6e4a8acfbca64b72": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
}
|
||||
},
|
||||
"query": "insert into permissions(user, role)\nvalues (?, ?)\n"
|
||||
},
|
||||
"ca7d100a9440fb7854a27f9aafd91ce94d1df9fa1ccd65b549be92d16741f9d2": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
mod apps;
|
||||
mod authorization_codes;
|
||||
mod keys;
|
||||
mod permissions;
|
||||
mod refresh_tokens;
|
||||
mod roles;
|
||||
mod settings;
|
||||
|
|
@ -10,6 +11,7 @@ mod users;
|
|||
pub use apps::Apps;
|
||||
pub use authorization_codes::AuthorizationCodes;
|
||||
pub use keys::Keys;
|
||||
pub use permissions::Permissions;
|
||||
pub use refresh_tokens::RefreshTokens;
|
||||
pub use roles::Roles;
|
||||
pub use settings::Settings;
|
||||
|
|
|
|||
82
crates/database/src/tables/permissions.rs
Normal file
82
crates/database/src/tables/permissions.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
use crate::error::{handle_error, Error};
|
||||
use sqlx::sqlite::SqliteQueryResult;
|
||||
use sqlx::types::chrono::{DateTime, Utc};
|
||||
use sqlx::{FromRow, SqliteExecutor};
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub struct Permissions {
|
||||
pub user: String,
|
||||
pub role: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Permissions {
|
||||
pub async fn get_all(
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
user: Option<&str>,
|
||||
role: Option<&str>,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
match (user, role) {
|
||||
(Some(user), Some(role)) => {
|
||||
// Get all for user and role
|
||||
sqlx::query_file_as!(
|
||||
Self,
|
||||
"queries/permissions/get_all_user_role.sql",
|
||||
user,
|
||||
role
|
||||
)
|
||||
.fetch_all(conn)
|
||||
.await
|
||||
.map_err(handle_error)
|
||||
}
|
||||
(Some(user), None) => {
|
||||
// Get all for user
|
||||
sqlx::query_file_as!(Self, "queries/permissions/get_all_user.sql", user)
|
||||
.fetch_all(conn)
|
||||
.await
|
||||
.map_err(handle_error)
|
||||
}
|
||||
(None, Some(role)) => {
|
||||
// Get all for role
|
||||
sqlx::query_file_as!(Self, "queries/permissions/get_all_role.sql", role)
|
||||
.fetch_all(conn)
|
||||
.await
|
||||
.map_err(handle_error)
|
||||
}
|
||||
(None, None) => {
|
||||
// Get all permissions
|
||||
sqlx::query_file_as!(Self, "queries/permissions/get_all.sql")
|
||||
.fetch_all(conn)
|
||||
.await
|
||||
.map_err(handle_error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add(
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
user: &str,
|
||||
role: &str,
|
||||
) -> Result<Option<()>, Error> {
|
||||
let query: SqliteQueryResult = sqlx::query_file!("queries/permissions/add.sql", user, role)
|
||||
.execute(conn)
|
||||
.await
|
||||
.map_err(handle_error)?;
|
||||
|
||||
Ok((query.rows_affected() == 1).then_some(()))
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
user: &str,
|
||||
role: &str,
|
||||
) -> Result<Option<()>, Error> {
|
||||
let query: SqliteQueryResult =
|
||||
sqlx::query_file!("queries/permissions/delete.sql", user, role)
|
||||
.execute(conn)
|
||||
.await
|
||||
.map_err(handle_error)?;
|
||||
|
||||
Ok((query.rows_affected() == 1).then_some(()))
|
||||
}
|
||||
}
|
||||
|
|
@ -32,3 +32,4 @@ refresh_tokens = { path = "../refresh_tokens" }
|
|||
email = { path = "../email" }
|
||||
totp = { path = "../totp" }
|
||||
roles = { path = "../roles" }
|
||||
permissions = { path = "../permissions" }
|
||||
|
|
|
|||
|
|
@ -99,6 +99,12 @@ impl From<roles::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<permissions::Error> for Error {
|
||||
fn from(e: permissions::Error) -> Self {
|
||||
Error::internal_server_error(e)
|
||||
}
|
||||
}
|
||||
|
||||
// std Types
|
||||
|
||||
impl From<String> for Error {
|
||||
|
|
|
|||
|
|
@ -43,13 +43,17 @@ impl Icon {
|
|||
"paperclip", Paperclip, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-paperclip" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M15 7l-6.5 6.5a1.5 1.5 0 0 0 3 3l6.5 -6.5a3 3 0 0 0 -6 -6l-6.5 6.5a4.5 4.5 0 0 0 9 9l6.5 -6.5"></path></svg>"#,
|
||||
"paperclip-large", PaperclipLarge, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-lg icon-tabler icon-tabler-paperclip" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M15 7l-6.5 6.5a1.5 1.5 0 0 0 3 3l6.5 -6.5a3 3 0 0 0 -6 -6l-6.5 6.5a4.5 4.5 0 0 0 9 9l6.5 -6.5"></path></svg>"#,
|
||||
"users", Users, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-users" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M9 7m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"></path><path d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path><path d="M21 21v-2a4 4 0 0 0 -3 -3.85"></path></svg>"#,
|
||||
"users-large", UsersLarge, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-lg icon-tabler icon-tabler-users" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M9 7m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"></path><path d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path><path d="M21 21v-2a4 4 0 0 0 -3 -3.85"></path></svg>"#,
|
||||
"mail", Mail, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-mail" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z"></path><path d="M3 7l9 6l9 -6"></path></svg>"#,
|
||||
"password", Password, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-password" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M12 10v4"></path><path d="M10 13l4 -2"></path><path d="M10 11l4 2"></path><path d="M5 10v4"></path><path d="M3 13l4 -2"></path><path d="M3 11l4 2"></path><path d="M19 10v4"></path><path d="M17 13l4 -2"></path><path d="M17 11l4 2"></path></svg>"#,
|
||||
"2fa-large", TwoFaLarge, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-lg icon-tabler icon-tabler-2fa" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 16h-4l3.47 -4.66a2 2 0 1 0 -3.47 -1.54"></path><path d="M10 16v-8h4"></path><path d="M10 12l3 0"></path><path d="M17 16v-6a2 2 0 0 1 4 0v6"></path><path d="M17 13l4 0"></path></svg>"#,
|
||||
"check", Check, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-check" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 12l5 5l10 -10"></path></svg>"#,
|
||||
"x", X, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M18 6l-12 12"></path><path d="M6 6l12 12"></path></svg>"#,
|
||||
"progress", Progress, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-progress" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 20.777a8.942 8.942 0 0 1 -2.48 -.969"></path><path d="M14 3.223a9.003 9.003 0 0 1 0 17.554"></path><path d="M4.579 17.093a8.961 8.961 0 0 1 -1.227 -2.592"></path><path d="M3.124 10.5c.16 -.95 .468 -1.85 .9 -2.675l.169 -.305"></path><path d="M6.907 4.579a8.954 8.954 0 0 1 3.093 -1.356"></path></svg>"#,
|
||||
"users-group", UsersGroup, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-users-group" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1"></path><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M17 10h2a2 2 0 0 1 2 2v1"></path><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M3 13v-1a2 2 0 0 1 2 -2h2"></path></svg>"#
|
||||
"users-group", UsersGroup, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-users-group" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1"></path><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M17 10h2a2 2 0 0 1 2 2v1"></path><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M3 13v-1a2 2 0 0 1 2 -2h2"></path></svg>"#,
|
||||
"users-group-large", UsersGroupLarge, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-lg icon-tabler icon-tabler-users-group" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1"></path><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M17 10h2a2 2 0 0 1 2 2v1"></path><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M3 13v-1a2 2 0 0 1 2 -2h2"></path></svg>"#,
|
||||
"adjustments", Adjustments, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-adjustments" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 10a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M6 4v4"></path><path d="M6 12v8"></path><path d="M10 16a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M12 4v10"></path><path d="M12 18v2"></path><path d="M16 7a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M18 4v1"></path><path d="M18 9v11"></path></svg>"#,
|
||||
"device-floppy", DeviceFloppy, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-device-floppy" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2"></path><path d="M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path><path d="M14 4l0 4l-6 0l0 -4"></path></svg>"#
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +77,7 @@ pub fn icons_to_templates(tera: &mut Tera) {
|
|||
Icon::Paperclip,
|
||||
Icon::PaperclipLarge,
|
||||
Icon::Users,
|
||||
Icon::UsersLarge,
|
||||
Icon::Mail,
|
||||
Icon::Password,
|
||||
Icon::TwoFaLarge,
|
||||
|
|
@ -80,6 +85,9 @@ pub fn icons_to_templates(tera: &mut Tera) {
|
|||
Icon::X,
|
||||
Icon::Progress,
|
||||
Icon::UsersGroup,
|
||||
Icon::UsersGroupLarge,
|
||||
Icon::Adjustments,
|
||||
Icon::DeviceFloppy,
|
||||
];
|
||||
|
||||
// For each icon, it will output: ("icons/name", "<svg>...</svg>")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ pub enum AdminMenu {
|
|||
Apps,
|
||||
Users,
|
||||
Roles,
|
||||
Permissions,
|
||||
Settings,
|
||||
}
|
||||
|
||||
|
|
@ -21,6 +22,7 @@ impl AdminMenu {
|
|||
AdminMenu::Users => "users",
|
||||
AdminMenu::Roles => "roles",
|
||||
AdminMenu::Settings => "settings",
|
||||
AdminMenu::Permissions => "permissions",
|
||||
}
|
||||
}
|
||||
pub fn list() -> Vec<MainItem> {
|
||||
|
|
@ -60,6 +62,13 @@ impl AdminMenu {
|
|||
icon: Icon::UsersGroup.svg,
|
||||
sub: None,
|
||||
},
|
||||
MainItem {
|
||||
id: AdminMenu::Permissions.id(),
|
||||
label: "Permissions",
|
||||
link: uri!(routes::admin::permissions::admin_permissions_home).to_string(),
|
||||
icon: Icon::Adjustments.svg,
|
||||
sub: None,
|
||||
},
|
||||
MainItem {
|
||||
id: AdminMenu::Settings.id(),
|
||||
label: "Server settings",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ pub enum Page {
|
|||
AdminRolesList(AdminRolesList),
|
||||
AdminRolesNew(AdminRolesNew),
|
||||
AdminRolesView(AdminRolesView),
|
||||
AdminPermissionsHome(AdminPermissionsHome),
|
||||
AdminPermissionsUsers(AdminPermissionsUsers),
|
||||
AdminPermissionsForUser(AdminPermissionsForUser),
|
||||
}
|
||||
|
||||
impl Page {
|
||||
|
|
@ -66,6 +69,9 @@ impl Page {
|
|||
Page::AdminRolesList(_) => "pages/admin/roles/list",
|
||||
Page::AdminRolesNew(_) => "pages/admin/roles/new",
|
||||
Page::AdminRolesView(_) => "pages/admin/roles/view",
|
||||
Page::AdminPermissionsHome(_) => "pages/admin/permissions/home",
|
||||
Page::AdminPermissionsUsers(_) => "pages/admin/permissions/users",
|
||||
Page::AdminPermissionsForUser(_) => "pages/admin/permissions/for-user",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,6 +103,9 @@ impl Page {
|
|||
Page::AdminRolesList(_) => "Roles",
|
||||
Page::AdminRolesNew(_) => "New role",
|
||||
Page::AdminRolesView(_) => "Role info",
|
||||
Page::AdminPermissionsHome(_) => "Permissions",
|
||||
Page::AdminPermissionsUsers(_) => "Users permissions",
|
||||
Page::AdminPermissionsForUser(_) => "Permissions for user",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,6 +139,9 @@ impl Page {
|
|||
Page::AdminRolesList(_) => Some(AdminMenu::Roles.into()),
|
||||
Page::AdminRolesNew(_) => Some(AdminMenu::Roles.into()),
|
||||
Page::AdminRolesView(_) => Some(AdminMenu::Roles.into()),
|
||||
Page::AdminPermissionsHome(_) => Some(AdminMenu::Permissions.into()),
|
||||
Page::AdminPermissionsUsers(_) => Some(AdminMenu::Permissions.into()),
|
||||
Page::AdminPermissionsForUser(_) => Some(AdminMenu::Permissions.into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,6 +173,9 @@ impl Page {
|
|||
Page::AdminRolesList(list) => Box::new(list),
|
||||
Page::AdminRolesNew(new) => Box::new(new),
|
||||
Page::AdminRolesView(view) => Box::new(view),
|
||||
Page::AdminPermissionsHome(home) => Box::new(home),
|
||||
Page::AdminPermissionsUsers(users) => Box::new(users),
|
||||
Page::AdminPermissionsForUser(user) => Box::new(user),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use self::apps::*;
|
||||
use self::permissions::*;
|
||||
use self::roles::*;
|
||||
use self::settings::*;
|
||||
use self::users::*;
|
||||
|
|
@ -7,6 +8,7 @@ use rocket::{routes, Route};
|
|||
|
||||
pub mod apps;
|
||||
pub mod dashboard;
|
||||
pub mod permissions;
|
||||
pub mod roles;
|
||||
pub mod settings;
|
||||
pub mod users;
|
||||
|
|
@ -43,10 +45,15 @@ pub fn routes() -> Vec<Route> {
|
|||
admin_roles_view,
|
||||
admin_roles_archive,
|
||||
admin_roles_info_update,
|
||||
admin_permissions_home,
|
||||
admin_permissions_users,
|
||||
admin_permissions_for_user,
|
||||
admin_permissions_for_user_form,
|
||||
]
|
||||
}
|
||||
|
||||
pub mod content {
|
||||
use super::RolePermission;
|
||||
use apps::App;
|
||||
use jwt::JwtClaims;
|
||||
use rocket::serde::Serialize;
|
||||
|
|
@ -155,4 +162,28 @@ pub mod content {
|
|||
pub role: Role,
|
||||
pub jwt_duration: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone)]
|
||||
pub struct AdminPermissionsHome {
|
||||
pub user: JwtClaims,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone)]
|
||||
pub struct AdminPermissionsUsers {
|
||||
pub user: JwtClaims,
|
||||
pub users: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone)]
|
||||
pub struct AdminPermissionsForUser {
|
||||
pub user: JwtClaims,
|
||||
pub local: User,
|
||||
pub roles_permissions: Vec<RolePermission>,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
193
crates/ezidam/src/routes/admin/permissions.rs
Normal file
193
crates/ezidam/src/routes/admin/permissions.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
use crate::routes::prelude::*;
|
||||
use crate::tokens::JWT_DURATION_MINUTES;
|
||||
use chrono_humanize::Humanize;
|
||||
use permissions::Permission;
|
||||
use rocket::form::validate::Contains;
|
||||
use rocket::serde::Serialize;
|
||||
use rocket::{get, post};
|
||||
use rocket_db_pools::sqlx::types::chrono::{DateTime, Utc};
|
||||
use roles::Role;
|
||||
use std::str::FromStr;
|
||||
use users::User;
|
||||
|
||||
#[get("/admin/permissions")]
|
||||
pub async fn admin_permissions_home(admin: JwtAdmin) -> Result<Page> {
|
||||
Ok(Page::AdminPermissionsHome(
|
||||
super::content::AdminPermissionsHome { user: admin.0 },
|
||||
))
|
||||
}
|
||||
|
||||
#[get("/admin/permissions/users")]
|
||||
pub async fn admin_permissions_users(
|
||||
mut db: Connection<Database>,
|
||||
admin: JwtAdmin,
|
||||
flash: Option<FlashMessage<'_>>,
|
||||
) -> Result<Template> {
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
let users = User::get_all(&mut transaction)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|user| !user.is_archived())
|
||||
.collect();
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
let page = Page::AdminPermissionsUsers(super::content::AdminPermissionsUsers {
|
||||
user: admin.0,
|
||||
users,
|
||||
});
|
||||
|
||||
Ok(flash
|
||||
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||
.unwrap_or_else(|| page.into()))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PermissionTiming {
|
||||
created_at: DateTime<Utc>,
|
||||
relative: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RolePermission {
|
||||
role: Role,
|
||||
permission: Option<PermissionTiming>,
|
||||
}
|
||||
|
||||
#[get("/admin/permissions/users/<id>")]
|
||||
pub async fn admin_permissions_for_user(
|
||||
mut db: Connection<Database>,
|
||||
admin: JwtAdmin,
|
||||
id: RocketUserID,
|
||||
flash: Option<FlashMessage<'_>>,
|
||||
) -> Result<Template> {
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
// Get user
|
||||
let user = User::get_by_id(&mut transaction, &id.0)
|
||||
.await?
|
||||
.ok_or_else(|| Error::not_found("Failed to find user"))?;
|
||||
|
||||
if user.is_archived() {
|
||||
return Err(Error::forbidden("User is archived"));
|
||||
}
|
||||
|
||||
// Get roles
|
||||
let roles = Role::get_all(&mut transaction)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|role| !role.is_archived())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Get permissions for user
|
||||
let permissions = Permission::get_all(&mut transaction, Some(user.id()), None).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
// For every role, attempt to get existing permission
|
||||
let roles_permissions = roles
|
||||
.iter()
|
||||
.map(|role| RolePermission {
|
||||
role: role.clone(),
|
||||
permission: permissions
|
||||
.iter()
|
||||
.find(|perm| perm.role() == role.name())
|
||||
.map(|perm| PermissionTiming {
|
||||
created_at: perm.created_at(),
|
||||
relative: perm.created_at().humanize(),
|
||||
}),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let page = Page::AdminPermissionsForUser(super::content::AdminPermissionsForUser {
|
||||
user: admin.0,
|
||||
local: user,
|
||||
roles_permissions,
|
||||
});
|
||||
|
||||
Ok(flash
|
||||
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||
.unwrap_or_else(|| page.into()))
|
||||
}
|
||||
|
||||
#[derive(Debug, FromForm)]
|
||||
pub struct PermissionsForUserForm<'r> {
|
||||
pub roles: Vec<&'r str>,
|
||||
}
|
||||
|
||||
#[post("/admin/permissions/users/<id>", data = "<form>")]
|
||||
pub async fn admin_permissions_for_user_form(
|
||||
_admin: JwtAdmin,
|
||||
mut db: Connection<Database>,
|
||||
id: RocketUserID,
|
||||
form: Form<PermissionsForUserForm<'_>>,
|
||||
) -> Result<Flash<Redirect>> {
|
||||
// Parse ids
|
||||
let roles_to_use = form
|
||||
.roles
|
||||
.iter()
|
||||
.map(|role| RoleID::from_str(role))
|
||||
.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(|_| Error::bad_request(format!("Invalid role detected")))?;
|
||||
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
// Get user
|
||||
let user = User::get_by_id(&mut transaction, &id.0)
|
||||
.await?
|
||||
.ok_or_else(|| Error::not_found("Failed to find user"))?;
|
||||
|
||||
if user.is_archived() {
|
||||
return Err(Error::forbidden("User is archived"));
|
||||
}
|
||||
|
||||
// Get roles
|
||||
let roles = Role::get_all(&mut transaction)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|role| !role.is_archived())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Get permissions for user
|
||||
let permissions = Permission::get_all(&mut transaction, Some(user.id()), None).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
// For every role, check to add or remove permissions
|
||||
for role in &roles {
|
||||
if roles_to_use.contains(role.name()) {
|
||||
// Intent is to add new permission
|
||||
|
||||
if permissions.iter().all(|perm| perm.role() != role.name()) {
|
||||
// If the permission does not exist, add it
|
||||
Permission::add(&mut transaction, user.id(), role.name()).await?;
|
||||
}
|
||||
} else {
|
||||
// Intent is to delete permission
|
||||
|
||||
if permissions.iter().any(|perm| perm.role() == role.name()) {
|
||||
// If the permission exists, delete it
|
||||
Permission::delete(&mut transaction, user.id(), role.name()).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(Flash::new(
|
||||
Redirect::to(uri!(admin_permissions_users)),
|
||||
FlashKind::Success,
|
||||
format!(
|
||||
"Permissions have been updated for {}.\
|
||||
<br>Changes can take up to {JWT_DURATION_MINUTES} minutes to appear.",
|
||||
user.username()
|
||||
),
|
||||
))
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
{% extends "shell" %}
|
||||
|
||||
{% import "utils/form" as form %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form id="permissions" action="" method="post">
|
||||
|
||||
{% set save = "Save" %}
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Roles for {{ local.username }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-auto ms-auto">
|
||||
<div class="btn-list">
|
||||
<button type="submit" class="btn btn-primary d-none d-sm-inline-block">
|
||||
{% include "icons/device-floppy" %}
|
||||
{{ save }}
|
||||
</button>
|
||||
|
||||
<button type="submit" class="btn btn-primary d-sm-none btn-icon" aria-label="{{ save }}">
|
||||
{% include "icons/device-floppy" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
|
||||
{% if flash %}
|
||||
<div class="alert alert-{{flash.0}}" role="alert">
|
||||
<h4 class="alert-title">{{ flash.1 }}</h4>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Roles</h3>
|
||||
</div>
|
||||
<div class="list-group list-group-flush overflow-auto">
|
||||
|
||||
{% for item in roles_permissions %}
|
||||
|
||||
{# If permission exists, show active class #}
|
||||
{% if item.permission %}
|
||||
<div class="list-group-item active">
|
||||
{% else %}
|
||||
<div class="list-group-item">
|
||||
{% endif %}
|
||||
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<div class="col-auto">
|
||||
|
||||
{# If permission exists, tick the box #}
|
||||
|
||||
{% if item.permission %}
|
||||
<input
|
||||
name="roles"
|
||||
value="{{ item.role.name }}"
|
||||
id="select-{{ item.role.name }}"
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
checked
|
||||
>
|
||||
{% else %}
|
||||
<input
|
||||
name="roles"
|
||||
value="{{ item.role.name }}"
|
||||
id="select-{{ item.role.name }}"
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col text-truncate">
|
||||
|
||||
<!-- Role -->
|
||||
<label for="select-{{ item.role.name }}" class="text-body">
|
||||
{{ item.role.label }}
|
||||
</label>
|
||||
|
||||
{# If permission exists, show how long the user belongs to the role #}
|
||||
{% if item.permission %}
|
||||
<div class="d-block text-muted text-truncate mt-1">
|
||||
Since {{ item.permission.created_at | date(format="%F %T",
|
||||
timezone=user.zoneinfo | default(value="UTC")) }}
|
||||
<div class="mt-1">
|
||||
({{ item.permission.relative }})
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
{{ form::disable_button_delay_submit(form_id="permissions") }}
|
||||
|
||||
{% endblock content %}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
{% extends "shell" %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Permissions
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-cards">
|
||||
|
||||
<!-- Users -->
|
||||
<div class="col-sm-6">
|
||||
<div class="card card-md">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-uppercase font-weight-medium">Users</div>
|
||||
<div class="display-5 fw-bold my-3">
|
||||
{% include "icons/users-large" %}
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<a href="permissions/users" class="btn btn-primary w-100">Permissions for users</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Roles -->
|
||||
<div class="col-sm-6">
|
||||
<div class="card card-md">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-uppercase font-weight-medium">Roles</div>
|
||||
<div class="display-5 fw-bold my-3">
|
||||
{% include "icons/users-group-large" %}
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<a href="permissions/roles" class="btn btn-primary w-100">Permissions for roles</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
{% extends "shell" %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Permissions for users
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
|
||||
{% if flash %}
|
||||
<div class="alert alert-{{flash.0}}" role="alert">
|
||||
<h4 class="alert-title">{{ flash.1 | safe }}</h4>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Users</h3>
|
||||
</div>
|
||||
<div class="list-group list-group-flush overflow-auto">
|
||||
|
||||
{% for user_loop in users %}
|
||||
<div class="list-group-item">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
{{ user::avatar(username=user_loop.username, name=user_loop.name, size="sm", css="") }}
|
||||
</div>
|
||||
<div class="col text-truncate">
|
||||
<div class="text-body">
|
||||
{% if user_loop.name %}
|
||||
{{ user_loop.name }}
|
||||
{% else %}
|
||||
{{ user_loop.username }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="users/{{ user_loop.id }}" class="list-group-item-actions btn btn-outline-primary">Select</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
11
crates/permissions/Cargo.toml
Normal file
11
crates/permissions/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "permissions"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
database = { path = "../database" }
|
||||
id = { path = "../id" }
|
||||
thiserror = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
61
crates/permissions/src/database.rs
Normal file
61
crates/permissions/src/database.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use crate::error::Error;
|
||||
use crate::Permission;
|
||||
use database::sqlx::SqliteExecutor;
|
||||
use database::Error as DatabaseError;
|
||||
use database::Permissions as DatabasePermissions;
|
||||
use id::{RoleID, UserID};
|
||||
|
||||
impl From<DatabasePermissions> for Permission {
|
||||
fn from(db: DatabasePermissions) -> Self {
|
||||
Self {
|
||||
user: UserID(db.user),
|
||||
role: RoleID(db.role),
|
||||
created_at: db.created_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Permission {
|
||||
pub async fn get_all(
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
user: Option<&UserID>,
|
||||
role: Option<&RoleID>,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
Ok(DatabasePermissions::get_all(
|
||||
conn,
|
||||
user.map(|UserID(user)| user.as_str()),
|
||||
role.map(|RoleID(role)| role.as_str()),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Self::from)
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
pub async fn add(
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
user: &UserID,
|
||||
role: &RoleID,
|
||||
) -> Result<(), Error> {
|
||||
DatabasePermissions::add(conn, user.as_ref(), role.as_ref())
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
DatabaseError::UniqueConstraintPrimaryKey => {
|
||||
Error::Duplicate(user.to_string(), role.to_string())
|
||||
}
|
||||
_ => e.into(),
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
conn: impl SqliteExecutor<'_>,
|
||||
user: &UserID,
|
||||
role: &RoleID,
|
||||
) -> Result<(), Error> {
|
||||
DatabasePermissions::delete(conn, user.as_ref(), role.as_ref()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
11
crates/permissions/src/error.rs
Normal file
11
crates/permissions/src/error.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// error
|
||||
#[derive(thiserror::Error)]
|
||||
// the rest
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
#[error("Database: {0}")]
|
||||
Database(#[from] database::Error),
|
||||
|
||||
#[error("The permission user:\"{0}\" and role:\"{1}\" already exists.")]
|
||||
Duplicate(String, String),
|
||||
}
|
||||
28
crates/permissions/src/lib.rs
Normal file
28
crates/permissions/src/lib.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
mod database;
|
||||
mod error;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use id::{RoleID, UserID};
|
||||
use serde::Serialize;
|
||||
|
||||
// Exports
|
||||
pub use crate::error::Error;
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct Permission {
|
||||
user: UserID,
|
||||
role: RoleID,
|
||||
created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Permission {
|
||||
pub fn user(&self) -> &UserID {
|
||||
&self.user
|
||||
}
|
||||
pub fn role(&self) -> &RoleID {
|
||||
&self.role
|
||||
}
|
||||
pub fn created_at(&self) -> DateTime<Utc> {
|
||||
self.created_at
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue