Skip to content

Commit

Permalink
Started User Profile Page
Browse files Browse the repository at this point in the history
  • Loading branch information
wyatt-herkamp committed Aug 28, 2024
1 parent f097892 commit 7f4eeb6
Show file tree
Hide file tree
Showing 45 changed files with 1,837 additions and 237 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ derive_builder.workspace = true
derive_more.workspace = true
digestible.workspace = true
url.workspace = true
nr-macros.workspace = true
[lints]
workspace = true
61 changes: 58 additions & 3 deletions crates/core/src/database/user/auth_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use uuid::Uuid;

use crate::{
database::DateTime,
user::{permissions::RepositoryActions, scopes::Scopes},
user::{permissions::RepositoryActions, scopes::NRScope},
};

use super::ReferencesUser;
Expand All @@ -16,12 +16,15 @@ pub use repository_scope::*;
pub use scope::*;
pub use utils::*;
/// Table Name: user_auth_tokens
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromRow)]
#[derive(Debug, Clone, PartialEq, Eq, FromRow)]
pub struct AuthToken {
pub id: i32,
pub user_id: i32,
pub name: Option<String>,
pub description: Option<String>,
pub token: String,
pub active: bool,
pub source: String,
pub expires_at: Option<DateTime>,
pub created_at: DateTime,
}
Expand Down Expand Up @@ -50,7 +53,7 @@ impl AuthToken {
.await?;
Ok(token)
}
pub async fn has_scope(&self, scope: Scopes, database: &PgPool) -> sqlx::Result<bool> {
pub async fn has_scope(&self, scope: NRScope, database: &PgPool) -> sqlx::Result<bool> {
let can_read: i64 = sqlx::query_scalar(
r#"SELECT COUNT(id) FROM user_auth_token_scopes WHERE user_auth_token_id = $1 AND scope = $2"#,
)
Expand Down Expand Up @@ -93,3 +96,55 @@ impl AuthToken {
Ok(actions.contains(&repository_action))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct NewAuthToken {
pub user_id: i32,
pub name: Option<String>,
pub description: Option<String>,
pub source: String,
pub scopes: Vec<NRScope>,
pub repositories: Vec<(Uuid, Vec<RepositoryActions>)>,
}
impl NewAuthToken {
pub async fn insert(self, database: &PgPool) -> sqlx::Result<(i32, String)> {
let (token, hashed_token) = create_token(database).await?;
let Self {
user_id,
name,
description,
source,
scopes,
repositories,
} = self;

let token_id: i32 = sqlx::query_scalar(
r#"INSERT INTO user_auth_tokens (user_id, name, description, token, source) VALUES ($1, $2, $3, $4, $5) RETURNING id"#,
)
.bind(user_id)
.bind(name)
.bind(description)
.bind(hashed_token)
.bind(source)
.fetch_one(database)
.await?;

for scope in scopes {
let scope = NewAuthTokenScope {
user_auth_token_id: token_id,
scope,
};
scope.insert_no_return(database).await?;
}

for (repository, actions) in repositories {
let repository_scope = NewRepositoryScope {
token_id,
repository,
actions,
};
repository_scope.insert_no_return(database).await?;
}

Ok((token_id, token))
}
}
28 changes: 22 additions & 6 deletions crates/core/src/database/user/auth_token/repository_scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,48 @@ use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use sqlx::{prelude::FromRow, PgPool};
use tracing::{debug, instrument, span};
use utoipa::ToSchema;
use uuid::Uuid;

use crate::{database::DateTime, user::permissions::RepositoryActions};

use super::{create_token, hash_token};
/// Table Name: user_auth_token_repository_scopes
/// Represents the actions that can be taken on a repository
///
/// Repository Scopes can be overridden by having a scope for all repositories
///
/// RepositoryActions::Read has Scopes::ReadRepository meaning they can read all repositories that the user has access to
/// RepositoryActions::Write has Scopes::WriteRepository meaning they can write to all repositories that the user has access to
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromRow)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromRow, ToSchema)]
pub struct AuthTokenRepositoryScope {
pub id: i32,
pub user_auth_token_id: i32,
pub repository_id: Uuid,
pub action: Vec<RepositoryActions>,
pub actions: Vec<RepositoryActions>,
pub created_at: DateTime,
}
impl AuthTokenRepositoryScope {
pub async fn get_by_token_id(
user_auth_token_id: i32,
database: &PgPool,
) -> sqlx::Result<Vec<Self>> {
let scopes = sqlx::query_as(
r#"SELECT * FROM user_auth_token_repository_scopes WHERE user_auth_token_id = $1"#,
)
.bind(user_auth_token_id)
.fetch_all(database)
.await?;
Ok(scopes)
}
}

#[derive(Debug, Clone, PartialEq, Eq, Builder)]
pub struct NewRepositoryToken {
pub user_id: i32,
pub source: String,

pub repositories: Vec<(Uuid, Vec<RepositoryActions>)>,
pub expires_at: Option<DateTime>,
}
Expand All @@ -48,10 +65,9 @@ impl NewRepositoryToken {
self.repositories.push((repository, actions));
self
}
#[instrument]
#[instrument(name = "NewRepositoryToken::insert")]
pub async fn insert(self, database: &PgPool) -> sqlx::Result<(i32, String)> {
let token = create_token(database).await?;
let hashed_token = hash_token(&token);
let (token, hashed_token) = create_token(database).await?;
let Self {
user_id,
source,
Expand Down Expand Up @@ -88,7 +104,7 @@ pub struct NewRepositoryScope {
pub actions: Vec<RepositoryActions>,
}
impl NewRepositoryScope {
#[instrument]
#[instrument(name = "NewRepositoryScope::insert")]
pub async fn insert_no_return(self, database: &PgPool) -> sqlx::Result<()> {
let Self {
token_id,
Expand Down
22 changes: 18 additions & 4 deletions crates/core/src/database/user/auth_token/scope.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
use serde::{Deserialize, Serialize};
use sqlx::{prelude::FromRow, PgPool};
use utoipa::ToSchema;

use crate::{database::DateTime, user::scopes::Scopes};
use crate::{database::DateTime, user::scopes::NRScope};

/// Table Name: user_auth_token_scopes
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromRow)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromRow, ToSchema)]
pub struct AuthTokenScope {
pub id: i32,
pub user_auth_token_id: i32,
pub scope: Scopes,
pub scope: NRScope,
pub created_at: DateTime,
}
impl AuthTokenScope {
pub async fn get_by_token_id(
user_auth_token_id: i32,
database: &PgPool,
) -> sqlx::Result<Vec<Self>> {
let scopes =
sqlx::query_as(r#"SELECT * FROM user_auth_token_scopes WHERE user_auth_token_id = $1"#)
.bind(user_auth_token_id)
.fetch_all(database)
.await?;
Ok(scopes)
}
}
#[derive(Debug)]
pub struct NewAuthTokenScope {
pub user_auth_token_id: i32,
pub scope: Scopes,
pub scope: NRScope,
}
impl NewAuthTokenScope {
pub async fn insert_no_return(&self, database: &PgPool) -> sqlx::Result<()> {
Expand Down
15 changes: 9 additions & 6 deletions crates/core/src/database/user/auth_token/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ use rand::{thread_rng, Rng};
use sha2::{Digest, Sha256};
use sqlx::PgPool;
/// Creates a new token checking if it already exists
pub async fn create_token(database: &PgPool) -> Result<String, sqlx::Error> {
let token = loop {
///
/// Returns a tuple with the token and the hashed token
pub async fn create_token(database: &PgPool) -> Result<(String, String), sqlx::Error> {
let (token, hashed) = loop {
let token = generate_token();
let hashed_token = hash_token(&token);
let exists: i64 =
sqlx::query_scalar(r#"SELECT COUNT(id) FROM user_auth_tokens WHERE token = $1"#)
.bind(&token)
.bind(&hashed_token)
.fetch_one(database)
.await?;
if exists == 0 {
break token;
break (token, hashed_token);
}
};
Ok(token)
Ok((token, hashed))
}
/// Generates a new token for the user
pub fn generate_token() -> String {
Expand All @@ -27,7 +30,7 @@ pub fn generate_token() -> String {
.map(char::from)
.collect()
}

/// Hashes the token using SHA256 and encodes it in base64
pub fn hash_token(token: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(token);
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use sqlx::prelude::Type;
use thiserror::Error;
use tracing::instrument;
pub mod scopes;
pub mod token;
use crate::utils::validations;

pub mod permissions;
Expand Down
10 changes: 5 additions & 5 deletions crates/core/src/user/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::database::user::{
permissions::{NewUserRepositoryPermissions, UserRepositoryPermissions},
};

use super::scopes::Scopes;
use super::scopes::NRScope;
/// User permissions
///
/// Default permissions are allowed to read and write to repositories but nothing else
Expand Down Expand Up @@ -115,12 +115,12 @@ impl RepositoryActions {
vec![Self::Read, Self::Write, Self::Edit]
}
}
impl From<RepositoryActions> for Scopes {
impl From<RepositoryActions> for NRScope {
fn from(action: RepositoryActions) -> Self {
match action {
RepositoryActions::Read => Scopes::ReadRepository,
RepositoryActions::Write => Scopes::WriteRepository,
RepositoryActions::Edit => Scopes::EditRepository,
RepositoryActions::Read => NRScope::ReadRepository,
RepositoryActions::Write => NRScope::WriteRepository,
RepositoryActions::Edit => NRScope::EditRepository,
}
}
}
Expand Down
48 changes: 44 additions & 4 deletions crates/core/src/user/scopes.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,50 @@
use serde::{Deserialize, Serialize};
use derive_more::derive::From;
use nr_macros::Scopes;
use serde::Serialize;
use sqlx::prelude::Type;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Type, Serialize, Deserialize)]
use strum::EnumIter;
use thiserror::Error;
use utoipa::ToSchema;
#[derive(Debug, Error, From)]
#[error("Invalid Scope: {0}")]
pub struct InvalidScope(pub String);
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Type, ToSchema, EnumIter, Scopes)]
#[sqlx(type_name = "TEXT")]
pub enum Scopes {
pub enum NRScope {
/// Can read all repositories the user has access to
#[scope(title = "Read Repository", parent = "Repository")]
ReadRepository,
/// Can write to all repositories the user has access to
#[scope(title = "Write Repository", parent = "Repository")]
WriteRepository,
/// Can edit all repositories the user has access to
#[scope(title = "Edit Repository", parent = "Repository")]
EditRepository,
#[scope(title = "Update Repository", parent = "User")]
/// Update your password
UpdatePassword,
}
#[derive(Debug, Serialize, PartialEq, Eq, Hash, ToSchema)]
pub struct ScopeDescription {
pub key: NRScope,
pub description: &'static str,
pub name: &'static str,
pub parent: Option<&'static str>,
pub requires_user_manager: bool,
pub requires_admin: bool,
pub requires_system: bool,
}

impl Default for ScopeDescription {
fn default() -> Self {
Self {
key: NRScope::ReadRepository,
description: "",
name: "",
parent: None,
requires_user_manager: false,
requires_admin: false,
requires_system: false,
}
}
}
Loading

0 comments on commit 7f4eeb6

Please sign in to comment.