feat(backend): changed account ids to 48bit integers to leave 16 bits for flags

This commit is contained in:
antifallobst 2023-08-23 20:11:58 +02:00
parent 642024126a
commit 12aa9d3146
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
9 changed files with 86 additions and 42 deletions

View File

@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "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": { "describe": {
"columns": [], "columns": [],
"parameters": { "parameters": {
@ -8,5 +8,5 @@
}, },
"nullable": [] "nullable": []
}, },
"hash": "699dbb92baf5954e5457dbd2dfded021c2436e57112259a8b0e208e4aa7628ae" "hash": "50c529b57bcd8d46a5160f07fe11ce40e1db91fab254f3604e5b08587fe7caf2"
} }

View File

@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "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": { "describe": {
"columns": [], "columns": [],
"parameters": { "parameters": {
@ -8,5 +8,5 @@
}, },
"nullable": [] "nullable": []
}, },
"hash": "d7aa893f2cd4154c72883bf50a031bdd1083c402c2409388f522767bbcdc2806" "hash": "53b07fddec8a07a8e272d7a72e6efbc62529183723e51d2cb2dfcad6ea23a39c"
} }

View File

@ -1,6 +1,6 @@
{ {
"db_name": "PostgreSQL", "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": { "describe": {
"columns": [], "columns": [],
"parameters": { "parameters": {
@ -8,5 +8,5 @@
}, },
"nullable": [] "nullable": []
}, },
"hash": "f77dc2bcb3826a98bebee9bbcb3117953f781bd3f3f2afe6fd0b266da541df8c" "hash": "f2fb0bafc777a9e5dea6c7b8c53dbb15035ea96515ec04e43761425479ae1e6c"
} }

View File

@ -5,9 +5,15 @@ use pbkdf2::{
}; };
use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono}; use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono};
#[derive(Debug, Clone, Copy)]
pub struct ID {
id: u64,
flags: u16,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Account { pub struct Account {
pub id: i64, pub id: ID,
pub username: String, pub username: String,
pub email: String, pub email: String,
pub salt: String, pub salt: String,
@ -19,6 +25,36 @@ pub struct Account {
pub permissions: i64, pub permissions: i64,
} }
impl From<i64> 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 { impl Account {
/// This doesn't check if an account with that name is already existing! /// This doesn't check if an account with that name is already existing!
pub async fn new( pub async fn new(
@ -143,11 +179,11 @@ impl Account {
} }
pub async fn delete(&self, pool: &PgPool) -> Result<()> { 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) .execute(pool)
.await?; .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) .execute(pool)
.await?; .await?;
Ok(()) Ok(())

View File

@ -29,7 +29,7 @@ pub async fn register(
if let Some(account) = Account::from_username(pool, &request.username).await? { 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. // Check if the account that has taken the username is verified or has an open verification request.
if account.verified if account.verified
|| VerificationToken::from_id(pool, account.id) || VerificationToken::from_id(pool, &account.id)
.await? .await?
.is_some() .is_some()
{ {
@ -44,7 +44,7 @@ pub async fn register(
// The same stuff for the email // The same stuff for the email
if let Some(account) = Account::from_email(pool, &request.email).await? { if let Some(account) = Account::from_email(pool, &request.email).await? {
if account.verified if account.verified
|| VerificationToken::from_id(pool, account.id) || VerificationToken::from_id(pool, &account.id)
.await? .await?
.is_some() .is_some()
{ {
@ -57,7 +57,7 @@ pub async fn register(
let account = Account::new(pool, &request.username, &request.email, &request.password).await?; 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() let message = MessageBuilder::new()
.from(("Nerdcult Account Management (noreply)", "account@nerdcult.net")) .from(("Nerdcult Account Management (noreply)", "account@nerdcult.net"))
@ -119,7 +119,7 @@ pub async fn authenticate(
return Ok(data::AuthenticateResponse::NotVerified); 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( Ok(data::AuthenticateResponse::Success(
data::AuthenticateSuccess { token: token.token }, 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?, Some(a) => a.delete(pool).await?,
None => { None => {
return Err(anyhow::Error::msg( return Err(anyhow::Error::msg(
@ -184,7 +184,7 @@ pub async fn token_delete(
let delete_token = match AuthToken::check(pool, &request.token).await? { let delete_token = match AuthToken::check(pool, &request.token).await? {
Some(t) => { Some(t) => {
if t.account == token.account { if t.account.id() == token.account.id() {
t t
} else { } else {
return Ok(data::TokenDeleteResponse::TokenNotFound); return Ok(data::TokenDeleteResponse::TokenNotFound);
@ -211,7 +211,7 @@ pub async fn token_list(pool: &PgPool, auth: String) -> Result<data::TokenListRe
let list: Vec<(String, i64)> = sqlx::query_as!( let list: Vec<(String, i64)> = sqlx::query_as!(
AuthToken, AuthToken,
r#"SELECT * FROM AuthTokens WHERE account = $1;"#, r#"SELECT * FROM AuthTokens WHERE account = $1;"#,
token.account token.account.id()
) )
.fetch_all(pool) .fetch_all(pool)
.await? .await?

View File

@ -25,7 +25,7 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> {
verified BOOLEAN NOT NULL, verified BOOLEAN NOT NULL,
follows BIGINT[], follows BIGINT[],
followers BIGINT[], followers BIGINT[],
permissions BIGINT NOT NULL, flags INT2 NOT NULL,
PRIMARY KEY(id) PRIMARY KEY(id)
); );
"# "#
@ -37,8 +37,9 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> {
r#" r#"
CREATE TABLE IF NOT EXISTS AuthTokens ( CREATE TABLE IF NOT EXISTS AuthTokens (
token VARCHAR(32) NOT NULL, token VARCHAR(32) NOT NULL,
account SERIAL8 NOT NULL, account BIGINT NOT NULL,
expire TIMESTAMP NOT NULL expire TIMESTAMP NOT NULL,
PRIMARY KEY(token)
); );
"# "#
) )
@ -49,8 +50,9 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> {
r#" r#"
CREATE TABLE IF NOT EXISTS VerificationTokens ( CREATE TABLE IF NOT EXISTS VerificationTokens (
token VARCHAR(32) NOT NULL, token VARCHAR(32) NOT NULL,
account SERIAL8 NOT NULL, account BIGINT NOT NULL,
expire TIMESTAMP NOT NULL expire TIMESTAMP NOT NULL,
PRIMARY KEY(token)
); );
"# "#
) )

View File

@ -29,7 +29,7 @@ pub async fn create(
return Ok(data::CreateResponse::Conflict); 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 { Ok(data::CreateResponse::Success(data::CreateSuccess {
id: project.id, id: project.id,

View File

@ -1,3 +1,4 @@
use crate::accounts::ID;
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use sqlx::postgres::PgPool; use sqlx::postgres::PgPool;
@ -10,6 +11,18 @@ pub struct Project {
pub members: Vec<i64>, pub members: Vec<i64>,
} }
pub enum ProjectMemberFlags {
Owner,
}
impl From<ProjectMemberFlags> for u16 {
fn from(value: ProjectMemberFlags) -> Self {
match value {
ProjectMemberFlags::Owner => 0b0000_0000_0000_0001,
}
}
}
impl Project { impl Project {
pub async fn new( pub async fn new(
pool: &PgPool, pool: &PgPool,
@ -17,7 +30,7 @@ impl Project {
name: String, name: String,
description: String, description: String,
) -> Result<Self> { ) -> Result<Self> {
let members = vec![owner_id]; let members = vec![ID::new(owner_id, ProjectMemberFlags::Owner.into()).combined()];
sqlx::query!( sqlx::query!(
r#"INSERT INTO Projects (name, description, created, members) VALUES ($1, $2, $3, $4);"#, r#"INSERT INTO Projects (name, description, created, members) VALUES ($1, $2, $3, $4);"#,

View File

@ -1,19 +1,16 @@
use crate::accounts::ID;
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono}; use sqlx::{postgres::PgPool, types::chrono as sqlx_chrono};
#[derive(Debug)] #[derive(Debug)]
pub struct AuthToken { pub struct AuthToken {
pub token: String, pub token: String,
pub account: i64, pub account: ID,
pub expire: sqlx_chrono::NaiveDateTime, pub expire: sqlx_chrono::NaiveDateTime,
} }
impl AuthToken { impl AuthToken {
pub async fn new( pub async fn new(pool: &PgPool, account_id: &ID, lifetime: chrono::Duration) -> Result<Self> {
pool: &PgPool,
account_id: i64,
lifetime: chrono::Duration,
) -> Result<Self> {
let expire = match sqlx_chrono::Utc::now() let expire = match sqlx_chrono::Utc::now()
.naive_utc() .naive_utc()
.checked_add_signed(lifetime) .checked_add_signed(lifetime)
@ -28,7 +25,7 @@ impl AuthToken {
let mut token = Self { let mut token = Self {
token: uuid::Uuid::new_v4().simple().to_string(), token: uuid::Uuid::new_v4().simple().to_string(),
account: account_id, account: account_id.clone(),
expire, expire,
}; };
@ -41,7 +38,7 @@ impl AuthToken {
INSERT INTO AuthTokens (token, account, expire) VALUES ($1, $2, $3); INSERT INTO AuthTokens (token, account, expire) VALUES ($1, $2, $3);
"#, "#,
token.token, token.token,
token.account, token.account.id(),
token.expire, token.expire,
) )
.execute(pool) .execute(pool)
@ -84,16 +81,12 @@ impl AuthToken {
#[derive(Debug)] #[derive(Debug)]
pub struct VerificationToken { pub struct VerificationToken {
pub token: String, pub token: String,
pub account: i64, pub account: ID,
pub expire: sqlx_chrono::NaiveDateTime, pub expire: sqlx_chrono::NaiveDateTime,
} }
impl VerificationToken { impl VerificationToken {
pub async fn new( pub async fn new(pool: &PgPool, account_id: &ID, lifetime: chrono::Duration) -> Result<Self> {
pool: &PgPool,
account_id: i64,
lifetime: chrono::Duration,
) -> Result<Self> {
let expire = match sqlx_chrono::Utc::now() let expire = match sqlx_chrono::Utc::now()
.naive_utc() .naive_utc()
.checked_add_signed(lifetime) .checked_add_signed(lifetime)
@ -108,7 +101,7 @@ impl VerificationToken {
let mut token = Self { let mut token = Self {
token: uuid::Uuid::new_v4().simple().to_string(), token: uuid::Uuid::new_v4().simple().to_string(),
account: account_id, account: account_id.clone(),
expire, expire,
}; };
@ -124,7 +117,7 @@ impl VerificationToken {
INSERT INTO VerificationTokens (token, account, expire) VALUES ($1, $2, $3); INSERT INTO VerificationTokens (token, account, expire) VALUES ($1, $2, $3);
"#, "#,
token.token, token.token,
token.account, token.account.id(),
token.expire, token.expire,
) )
.execute(pool) .execute(pool)
@ -156,11 +149,11 @@ impl VerificationToken {
} }
} }
pub async fn from_id(pool: &PgPool, account_id: i64) -> Result<Option<Self>> { pub async fn from_id(pool: &PgPool, account_id: &ID) -> Result<Option<Self>> {
let query_result = sqlx::query_as!( let query_result = sqlx::query_as!(
Self, Self,
r#"SELECT * FROM VerificationTokens WHERE account = $1;"#, r#"SELECT * FROM VerificationTokens WHERE account = $1;"#,
account_id account_id.id()
) )
.fetch_one(pool) .fetch_one(pool)
.await; .await;
@ -194,7 +187,7 @@ impl VerificationToken {
sqlx::query!( sqlx::query!(
r#"UPDATE Accounts SET verified=true WHERE id = $1;"#, r#"UPDATE Accounts SET verified=true WHERE id = $1;"#,
self.account self.account.id()
) )
.execute(pool) .execute(pool)
.await?; .await?;