diff --git a/.sqlx/query-699dbb92baf5954e5457dbd2dfded021c2436e57112259a8b0e208e4aa7628ae.json b/.sqlx/query-50c529b57bcd8d46a5160f07fe11ce40e1db91fab254f3604e5b08587fe7caf2.json similarity index 82% rename from .sqlx/query-699dbb92baf5954e5457dbd2dfded021c2436e57112259a8b0e208e4aa7628ae.json rename to .sqlx/query-50c529b57bcd8d46a5160f07fe11ce40e1db91fab254f3604e5b08587fe7caf2.json index 216b469..7d0e292 100644 --- a/.sqlx/query-699dbb92baf5954e5457dbd2dfded021c2436e57112259a8b0e208e4aa7628ae.json +++ b/.sqlx/query-50c529b57bcd8d46a5160f07fe11ce40e1db91fab254f3604e5b08587fe7caf2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n CREATE TABLE IF NOT EXISTS Accounts (\n id SERIAL8 NOT NULL,\n\t username VARCHAR(32) NOT NULL,\n email TEXT NOT NULL,\n\t salt VARCHAR(22) NOT NULL,\n\t password VARCHAR(96) NOT NULL,\n joined TIMESTAMP NOT NULL,\n verified BOOLEAN NOT NULL,\n follows BIGINT[],\n followers BIGINT[],\n permissions BIGINT NOT NULL,\n\t PRIMARY KEY(id)\n );\n ", + "query": "\n CREATE TABLE IF NOT EXISTS Accounts (\n id SERIAL8 NOT NULL,\n\t username VARCHAR(32) NOT NULL,\n email TEXT NOT NULL,\n\t salt VARCHAR(22) NOT NULL,\n\t password VARCHAR(96) NOT NULL,\n joined TIMESTAMP NOT NULL,\n verified BOOLEAN NOT NULL,\n follows BIGINT[],\n followers BIGINT[],\n flags INT2 NOT NULL,\n\t PRIMARY KEY(id)\n );\n ", "describe": { "columns": [], "parameters": { @@ -8,5 +8,5 @@ }, "nullable": [] }, - "hash": "699dbb92baf5954e5457dbd2dfded021c2436e57112259a8b0e208e4aa7628ae" + "hash": "50c529b57bcd8d46a5160f07fe11ce40e1db91fab254f3604e5b08587fe7caf2" } diff --git a/.sqlx/query-d7aa893f2cd4154c72883bf50a031bdd1083c402c2409388f522767bbcdc2806.json b/.sqlx/query-53b07fddec8a07a8e272d7a72e6efbc62529183723e51d2cb2dfcad6ea23a39c.json similarity index 55% rename from .sqlx/query-d7aa893f2cd4154c72883bf50a031bdd1083c402c2409388f522767bbcdc2806.json rename to .sqlx/query-53b07fddec8a07a8e272d7a72e6efbc62529183723e51d2cb2dfcad6ea23a39c.json index 9a96940..9f3dc04 100644 --- a/.sqlx/query-d7aa893f2cd4154c72883bf50a031bdd1083c402c2409388f522767bbcdc2806.json +++ b/.sqlx/query-53b07fddec8a07a8e272d7a72e6efbc62529183723e51d2cb2dfcad6ea23a39c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n CREATE TABLE IF NOT EXISTS VerificationTokens (\n token VARCHAR(32) NOT NULL,\n account SERIAL8 NOT NULL,\n expire TIMESTAMP NOT NULL\n );\n ", + "query": "\n CREATE TABLE IF NOT EXISTS VerificationTokens (\n token VARCHAR(32) NOT NULL,\n account BIGINT NOT NULL,\n expire TIMESTAMP NOT NULL,\n PRIMARY KEY(token)\n );\n ", "describe": { "columns": [], "parameters": { @@ -8,5 +8,5 @@ }, "nullable": [] }, - "hash": "d7aa893f2cd4154c72883bf50a031bdd1083c402c2409388f522767bbcdc2806" + "hash": "53b07fddec8a07a8e272d7a72e6efbc62529183723e51d2cb2dfcad6ea23a39c" } diff --git a/.sqlx/query-f77dc2bcb3826a98bebee9bbcb3117953f781bd3f3f2afe6fd0b266da541df8c.json b/.sqlx/query-f2fb0bafc777a9e5dea6c7b8c53dbb15035ea96515ec04e43761425479ae1e6c.json similarity index 56% rename from .sqlx/query-f77dc2bcb3826a98bebee9bbcb3117953f781bd3f3f2afe6fd0b266da541df8c.json rename to .sqlx/query-f2fb0bafc777a9e5dea6c7b8c53dbb15035ea96515ec04e43761425479ae1e6c.json index a9226d3..b1feb54 100644 --- a/.sqlx/query-f77dc2bcb3826a98bebee9bbcb3117953f781bd3f3f2afe6fd0b266da541df8c.json +++ b/.sqlx/query-f2fb0bafc777a9e5dea6c7b8c53dbb15035ea96515ec04e43761425479ae1e6c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n CREATE TABLE IF NOT EXISTS AuthTokens (\n token VARCHAR(32) NOT NULL,\n account SERIAL8 NOT NULL,\n expire TIMESTAMP NOT NULL\n );\n ", + "query": "\n CREATE TABLE IF NOT EXISTS AuthTokens (\n token VARCHAR(32) NOT NULL,\n account BIGINT NOT NULL,\n expire TIMESTAMP NOT NULL,\n PRIMARY KEY(token)\n );\n ", "describe": { "columns": [], "parameters": { @@ -8,5 +8,5 @@ }, "nullable": [] }, - "hash": "f77dc2bcb3826a98bebee9bbcb3117953f781bd3f3f2afe6fd0b266da541df8c" + "hash": "f2fb0bafc777a9e5dea6c7b8c53dbb15035ea96515ec04e43761425479ae1e6c" } diff --git a/src/accounts.rs b/src/accounts.rs index af0aac1..bf7a352 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -5,9 +5,15 @@ use pbkdf2::{ }; use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono}; +#[derive(Debug, Clone, Copy)] +pub struct ID { + id: u64, + flags: u16, +} + #[derive(Debug)] pub struct Account { - pub id: i64, + pub id: ID, pub username: String, pub email: String, pub salt: String, @@ -19,6 +25,36 @@ pub struct Account { pub permissions: i64, } +impl From for ID { + fn from(value: i64) -> Self { + Self { + id: (value as u64 & 0x0000FFFFFFFFFFFF), + flags: (value as u64 & 0xFFFF000000000000) as u16, + } + } +} + +impl ID { + pub fn new(id: i64, flags: u16) -> Self { + Self { + id: id as u64, + flags, + } + } + + pub fn id(&self) -> i64 { + self.id as i64 + } + + pub fn flags(&self) -> u16 { + self.flags + } + + pub fn combined(&self) -> i64 { + (self.id | ((self.flags as u64) << 46)) as i64 + } +} + impl Account { /// This doesn't check if an account with that name is already existing! pub async fn new( @@ -143,11 +179,11 @@ impl Account { } pub async fn delete(&self, pool: &PgPool) -> Result<()> { - sqlx::query!(r#"DELETE FROM AuthTokens WHERE account = $1;"#, self.id) + sqlx::query!(r#"DELETE FROM AuthTokens WHERE account = $1;"#, self.id.id()) .execute(pool) .await?; - sqlx::query!(r#"DELETE FROM Accounts WHERE id = $1;"#, self.id) + sqlx::query!(r#"DELETE FROM Accounts WHERE id = $1;"#, self.id.id()) .execute(pool) .await?; Ok(()) diff --git a/src/api/account/handlers.rs b/src/api/account/handlers.rs index 6eefb40..446c36d 100644 --- a/src/api/account/handlers.rs +++ b/src/api/account/handlers.rs @@ -29,7 +29,7 @@ pub async fn register( if let Some(account) = Account::from_username(pool, &request.username).await? { // Check if the account that has taken the username is verified or has an open verification request. if account.verified - || VerificationToken::from_id(pool, account.id) + || VerificationToken::from_id(pool, &account.id) .await? .is_some() { @@ -44,7 +44,7 @@ pub async fn register( // The same stuff for the email if let Some(account) = Account::from_email(pool, &request.email).await? { if account.verified - || VerificationToken::from_id(pool, account.id) + || VerificationToken::from_id(pool, &account.id) .await? .is_some() { @@ -57,7 +57,7 @@ pub async fn register( let account = Account::new(pool, &request.username, &request.email, &request.password).await?; - let token = VerificationToken::new(pool, account.id, chrono::Duration::minutes(10)).await?; + let token = VerificationToken::new(pool, &account.id, chrono::Duration::minutes(10)).await?; let message = MessageBuilder::new() .from(("Nerdcult Account Management (noreply)", "account@nerdcult.net")) @@ -119,7 +119,7 @@ pub async fn authenticate( return Ok(data::AuthenticateResponse::NotVerified); } - let token = AuthToken::new(pool, account.id, chrono::Duration::days(7)).await?; + let token = AuthToken::new(pool, &account.id, chrono::Duration::days(7)).await?; Ok(data::AuthenticateResponse::Success( data::AuthenticateSuccess { token: token.token }, @@ -156,7 +156,7 @@ pub async fn delete( } } - match Account::from_id(pool, token.account).await? { + match Account::from_id(pool, token.account.id()).await? { Some(a) => a.delete(pool).await?, None => { return Err(anyhow::Error::msg( @@ -184,7 +184,7 @@ pub async fn token_delete( let delete_token = match AuthToken::check(pool, &request.token).await? { Some(t) => { - if t.account == token.account { + if t.account.id() == token.account.id() { t } else { return Ok(data::TokenDeleteResponse::TokenNotFound); @@ -211,7 +211,7 @@ pub async fn token_list(pool: &PgPool, auth: String) -> Result = sqlx::query_as!( AuthToken, r#"SELECT * FROM AuthTokens WHERE account = $1;"#, - token.account + token.account.id() ) .fetch_all(pool) .await? diff --git a/src/api/mod.rs b/src/api/mod.rs index 429a9e6..975ba1a 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -25,7 +25,7 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> { verified BOOLEAN NOT NULL, follows BIGINT[], followers BIGINT[], - permissions BIGINT NOT NULL, + flags INT2 NOT NULL, PRIMARY KEY(id) ); "# @@ -37,8 +37,9 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> { r#" CREATE TABLE IF NOT EXISTS AuthTokens ( token VARCHAR(32) NOT NULL, - account SERIAL8 NOT NULL, - expire TIMESTAMP NOT NULL + account BIGINT NOT NULL, + expire TIMESTAMP NOT NULL, + PRIMARY KEY(token) ); "# ) @@ -49,8 +50,9 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> { r#" CREATE TABLE IF NOT EXISTS VerificationTokens ( token VARCHAR(32) NOT NULL, - account SERIAL8 NOT NULL, - expire TIMESTAMP NOT NULL + account BIGINT NOT NULL, + expire TIMESTAMP NOT NULL, + PRIMARY KEY(token) ); "# ) diff --git a/src/api/project/handlers.rs b/src/api/project/handlers.rs index e93f8bc..d4d7e52 100644 --- a/src/api/project/handlers.rs +++ b/src/api/project/handlers.rs @@ -29,7 +29,7 @@ pub async fn create( return Ok(data::CreateResponse::Conflict); } - let project = Project::new(pool, token.account, request.name, request.description).await?; + let project = Project::new(pool, token.account.id(), request.name, request.description).await?; Ok(data::CreateResponse::Success(data::CreateSuccess { id: project.id, diff --git a/src/projects.rs b/src/projects.rs index f1ef948..9a2ad9b 100644 --- a/src/projects.rs +++ b/src/projects.rs @@ -1,3 +1,4 @@ +use crate::accounts::ID; use anyhow::{Error, Result}; use sqlx::postgres::PgPool; @@ -10,6 +11,18 @@ pub struct Project { pub members: Vec, } +pub enum ProjectMemberFlags { + Owner, +} + +impl From for u16 { + fn from(value: ProjectMemberFlags) -> Self { + match value { + ProjectMemberFlags::Owner => 0b0000_0000_0000_0001, + } + } +} + impl Project { pub async fn new( pool: &PgPool, @@ -17,7 +30,7 @@ impl Project { name: String, description: String, ) -> Result { - let members = vec![owner_id]; + let members = vec![ID::new(owner_id, ProjectMemberFlags::Owner.into()).combined()]; sqlx::query!( r#"INSERT INTO Projects (name, description, created, members) VALUES ($1, $2, $3, $4);"#, diff --git a/src/tokens.rs b/src/tokens.rs index e86e3cf..0c418fc 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -1,19 +1,16 @@ +use crate::accounts::ID; use anyhow::{Error, Result}; use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono}; #[derive(Debug)] pub struct AuthToken { pub token: String, - pub account: i64, + pub account: ID, pub expire: sqlx_chrono::NaiveDateTime, } impl AuthToken { - pub async fn new( - pool: &PgPool, - account_id: i64, - lifetime: chrono::Duration, - ) -> Result { + pub async fn new(pool: &PgPool, account_id: &ID, lifetime: chrono::Duration) -> Result { let expire = match sqlx_chrono::Utc::now() .naive_utc() .checked_add_signed(lifetime) @@ -28,7 +25,7 @@ impl AuthToken { let mut token = Self { token: uuid::Uuid::new_v4().simple().to_string(), - account: account_id, + account: account_id.clone(), expire, }; @@ -41,7 +38,7 @@ impl AuthToken { INSERT INTO AuthTokens (token, account, expire) VALUES ($1, $2, $3); "#, token.token, - token.account, + token.account.id(), token.expire, ) .execute(pool) @@ -84,16 +81,12 @@ impl AuthToken { #[derive(Debug)] pub struct VerificationToken { pub token: String, - pub account: i64, + pub account: ID, pub expire: sqlx_chrono::NaiveDateTime, } impl VerificationToken { - pub async fn new( - pool: &PgPool, - account_id: i64, - lifetime: chrono::Duration, - ) -> Result { + pub async fn new(pool: &PgPool, account_id: &ID, lifetime: chrono::Duration) -> Result { let expire = match sqlx_chrono::Utc::now() .naive_utc() .checked_add_signed(lifetime) @@ -108,7 +101,7 @@ impl VerificationToken { let mut token = Self { token: uuid::Uuid::new_v4().simple().to_string(), - account: account_id, + account: account_id.clone(), expire, }; @@ -124,7 +117,7 @@ impl VerificationToken { INSERT INTO VerificationTokens (token, account, expire) VALUES ($1, $2, $3); "#, token.token, - token.account, + token.account.id(), token.expire, ) .execute(pool) @@ -156,11 +149,11 @@ impl VerificationToken { } } - pub async fn from_id(pool: &PgPool, account_id: i64) -> Result> { + pub async fn from_id(pool: &PgPool, account_id: &ID) -> Result> { let query_result = sqlx::query_as!( Self, r#"SELECT * FROM VerificationTokens WHERE account = $1;"#, - account_id + account_id.id() ) .fetch_one(pool) .await; @@ -194,7 +187,7 @@ impl VerificationToken { sqlx::query!( r#"UPDATE Accounts SET verified=true WHERE id = $1;"#, - self.account + self.account.id() ) .execute(pool) .await?;