-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Redid Permissions and API Tokens. Frontend is angry
- Loading branch information
1 parent
644fe75
commit 0f612ff
Showing
35 changed files
with
1,228 additions
and
484 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
crates/core/src/database/user/auth_token/repository_scope.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
use derive_builder::Builder; | ||
use serde::{Deserialize, Serialize}; | ||
use sqlx::{ | ||
prelude::{FromRow, Type}, | ||
PgPool, | ||
}; | ||
use tracing::{debug, instrument, span}; | ||
use uuid::Uuid; | ||
|
||
use crate::{database::DateTime, user::permissions::RepositoryActionOptions}; | ||
|
||
use super::{create_token, hash_token}; | ||
/// 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)] | ||
pub struct AuthTokenRepositoryScope { | ||
pub id: i32, | ||
pub user_auth_token_id: i32, | ||
pub repository_id: Uuid, | ||
pub action: Vec<RepositoryActionOptions>, | ||
pub created_at: DateTime, | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq, Builder)] | ||
pub struct NewRepositoryToken { | ||
pub user_id: i32, | ||
pub source: String, | ||
pub repositories: Vec<(Uuid, Vec<RepositoryActionOptions>)>, | ||
pub expires_at: Option<DateTime>, | ||
} | ||
impl NewRepositoryToken { | ||
pub fn new( | ||
user_id: i32, | ||
source: String, | ||
repository: Uuid, | ||
actions: Vec<RepositoryActionOptions>, | ||
) -> Self { | ||
Self { | ||
user_id, | ||
source, | ||
repositories: vec![(repository, actions)], | ||
expires_at: None, | ||
} | ||
} | ||
pub fn add_repository( | ||
mut self, | ||
repository: Uuid, | ||
actions: Vec<RepositoryActionOptions>, | ||
) -> Self { | ||
self.repositories.push((repository, actions)); | ||
self | ||
} | ||
#[instrument] | ||
pub async fn insert(self, database: &PgPool) -> sqlx::Result<(i32, String)> { | ||
let token = create_token(database).await?; | ||
let hashed_token = hash_token(&token); | ||
let Self { | ||
user_id, | ||
source, | ||
repositories, | ||
expires_at, | ||
} = self; | ||
|
||
let token_id: i32 = sqlx::query_scalar( | ||
r#"INSERT INTO user_auth_tokens (user_id, token, source, expires_at) VALUES ($1, $2, $3, $4) RETURNING id"#, | ||
).bind(user_id) | ||
.bind(hashed_token) | ||
.bind(source) | ||
.bind(expires_at) | ||
.fetch_one(database).await?; | ||
let span = span!(tracing::Level::DEBUG, "inserting scopes"); | ||
let _guard = span.enter(); | ||
for (repository_id, actions) in repositories { | ||
debug!(?repository_id, ?actions, "Inserting scope"); | ||
NewRepositoryScope { | ||
token_id: token_id, | ||
repository: repository_id, | ||
actions, | ||
} | ||
.insert_no_return(database) | ||
.await?; | ||
} | ||
Ok((token_id, token)) | ||
} | ||
} | ||
#[derive(Debug)] | ||
pub struct NewRepositoryScope { | ||
pub token_id: i32, | ||
pub repository: Uuid, | ||
pub actions: Vec<RepositoryActionOptions>, | ||
} | ||
impl NewRepositoryScope { | ||
#[instrument] | ||
pub async fn insert_no_return(self, database: &PgPool) -> sqlx::Result<()> { | ||
let Self { | ||
token_id, | ||
repository, | ||
actions, | ||
} = self; | ||
sqlx::query( | ||
r#"INSERT INTO user_auth_token_repository_scopes (user_auth_token_id, repository, actions) VALUES ($1, $2, $3)"#, | ||
) | ||
.bind(token_id) | ||
.bind(repository) | ||
.bind(actions) | ||
.execute(database) | ||
.await?; | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use serde::{Deserialize, Serialize}; | ||
use sqlx::{prelude::FromRow, PgPool}; | ||
|
||
use crate::{database::DateTime, user::scopes::Scopes}; | ||
|
||
/// Table Name: user_auth_token_scopes | ||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromRow)] | ||
pub struct AuthTokenScope { | ||
pub id: i32, | ||
pub user_auth_token_id: i32, | ||
pub scope: Scopes, | ||
pub created_at: DateTime, | ||
} | ||
#[derive(Debug)] | ||
pub struct NewAuthTokenScope { | ||
pub user_auth_token_id: i32, | ||
pub scope: Scopes, | ||
} | ||
impl NewAuthTokenScope { | ||
pub async fn insert_no_return(&self, database: &PgPool) -> sqlx::Result<()> { | ||
sqlx::query( | ||
r#"INSERT INTO user_auth_token_scopes (user_auth_token_id, scope) VALUES ($1, $2)"#, | ||
) | ||
.bind(self.user_auth_token_id) | ||
.bind(self.scope) | ||
.execute(database) | ||
.await?; | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
use crate::utils::base64_utils; | ||
use rand::distributions::Alphanumeric; | ||
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 { | ||
let token = generate_token(); | ||
let exists: i64 = | ||
sqlx::query_scalar(r#"SELECT COUNT(id) FROM user_auth_tokens WHERE token = $1"#) | ||
.bind(&token) | ||
.fetch_one(database) | ||
.await?; | ||
if exists == 0 { | ||
break token; | ||
} | ||
}; | ||
Ok(token) | ||
} | ||
/// Generates a new token for the user | ||
pub fn generate_token() -> String { | ||
// TODO: Secure this | ||
thread_rng() | ||
.sample_iter(&Alphanumeric) | ||
.take(32) | ||
.map(char::from) | ||
.collect() | ||
} | ||
|
||
pub fn hash_token(token: &str) -> String { | ||
let mut hasher = Sha256::new(); | ||
hasher.update(token); | ||
base64_utils::encode(&hasher.finalize()) | ||
} |
Oops, something went wrong.