Enable descriptive HTTP errors to be returned from DB layer

For now, we only use this when redeeming an invite code.

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Nathan Sobo 2022-05-19 11:55:55 -06:00
parent d1b7a249b4
commit 3d7e912c6b
5 changed files with 45 additions and 7 deletions

1
Cargo.lock generated
View File

@ -851,6 +851,7 @@ dependencies = [
"envy",
"futures",
"gpui",
"hyper",
"language",
"lazy_static",
"lipsum",

View File

@ -25,6 +25,7 @@ base64 = "0.13"
clap = { version = "3.1", features = ["derive"], optional = true }
envy = "0.4.2"
futures = "0.3"
hyper = "0.14"
lazy_static = "1.4"
lipsum = { version = "0.8", optional = true }
nanoid = "0.4"

View File

@ -91,7 +91,8 @@ fn hash_access_token(token: &str) -> Result<String> {
None,
params,
&SaltString::generate(thread_rng()),
)?
)
.map_err(anyhow::Error::new)?
.to_string())
}
@ -105,6 +106,6 @@ pub fn encrypt_access_token(access_token: &str, public_key: String) -> Result<St
}
pub fn verify_access_token(token: &str, hash: &str) -> Result<bool> {
let hash = PasswordHash::new(hash)?;
let hash = PasswordHash::new(hash).map_err(anyhow::Error::new)?;
Ok(Scrypt.verify_password(token.as_bytes(), &hash).is_ok())
}

View File

@ -1,6 +1,7 @@
use crate::Result;
use crate::{Error, Result};
use anyhow::{anyhow, Context};
use async_trait::async_trait;
use axum::http::StatusCode;
use futures::StreamExt;
use nanoid::nanoid;
use serde::Serialize;
@ -237,7 +238,7 @@ impl Db for PostgresDb {
.fetch_optional(&self.pool)
.await?;
if let Some((code, count)) = result {
Ok(Some((code, count.try_into()?)))
Ok(Some((code, count.try_into().map_err(anyhow::Error::new)?)))
} else {
Ok(None)
}
@ -246,7 +247,7 @@ impl Db for PostgresDb {
async fn redeem_invite_code(&self, code: &str, login: &str) -> Result<UserId> {
let mut tx = self.pool.begin().await?;
let inviter_id: UserId = sqlx::query_scalar(
let inviter_id: Option<UserId> = sqlx::query_scalar(
"
UPDATE users
SET invite_count = invite_count - 1
@ -258,8 +259,30 @@ impl Db for PostgresDb {
)
.bind(code)
.fetch_optional(&mut tx)
.await?
.ok_or_else(|| anyhow!("invite code not found"))?;
.await?;
let inviter_id = match inviter_id {
Some(inviter_id) => inviter_id,
None => {
if sqlx::query_scalar::<_, i32>("SELECT 1 FROM users WHERE invite_code = $1")
.bind(code)
.fetch_optional(&mut tx)
.await?
.is_some()
{
Err(Error::Http(
StatusCode::UNAUTHORIZED,
"no invites remaining".to_string(),
))?
} else {
Err(Error::Http(
StatusCode::NOT_FOUND,
"invite code not found".to_string(),
))?
}
}
};
let invitee_id = sqlx::query_scalar(
"
INSERT INTO users

View File

@ -88,6 +88,18 @@ impl From<sqlx::Error> for Error {
}
}
impl From<axum::Error> for Error {
fn from(error: axum::Error) -> Self {
Self::Internal(error.into())
}
}
impl From<hyper::Error> for Error {
fn from(error: hyper::Error) -> Self {
Self::Internal(error.into())
}
}
impl IntoResponse for Error {
fn into_response(self) -> axum::response::Response {
match self {