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",
"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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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<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 {
/// 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(())

View File

@ -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<data::TokenListRe
let list: Vec<(String, i64)> = sqlx::query_as!(
AuthToken,
r#"SELECT * FROM AuthTokens WHERE account = $1;"#,
token.account
token.account.id()
)
.fetch_all(pool)
.await?

View File

@ -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)
);
"#
)

View File

@ -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,

View File

@ -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<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 {
pub async fn new(
pool: &PgPool,
@ -17,7 +30,7 @@ impl Project {
name: String,
description: String,
) -> Result<Self> {
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);"#,

View File

@ -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<Self> {
pub async fn new(pool: &PgPool, account_id: &ID, lifetime: chrono::Duration) -> Result<Self> {
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<Self> {
pub async fn new(pool: &PgPool, account_id: &ID, lifetime: chrono::Duration) -> Result<Self> {
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<Option<Self>> {
pub async fn from_id(pool: &PgPool, account_id: &ID) -> Result<Option<Self>> {
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?;