Compare commits

...

4 Commits

Author SHA1 Message Date
8ba0f02b98 Bump version 2023-11-12 10:00:46 +00:00
d36438c6ce Bump package lock. Add attachment serializer 2023-11-12 09:39:45 +00:00
e0c60e2ce3 Decode attachments correctly when patching a reminder 2023-11-11 15:05:35 +00:00
jude
e7160215b0 Defer offset response 2023-11-11 13:36:40 +00:00
7 changed files with 485 additions and 485 deletions

861
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "reminder-rs" name = "reminder-rs"
version = "1.6.47" version = "1.6.50"
authors = ["Jude Southworth <judesouthworth@pm.me>"] authors = ["Jude Southworth <judesouthworth@pm.me>"]
edition = "2021" edition = "2021"
license = "AGPL-3.0 only" license = "AGPL-3.0 only"

View File

@ -114,6 +114,8 @@ pub async fn offset(
#[description = "Number of minutes to offset by"] minutes: Option<isize>, #[description = "Number of minutes to offset by"] minutes: Option<isize>,
#[description = "Number of seconds to offset by"] seconds: Option<isize>, #[description = "Number of seconds to offset by"] seconds: Option<isize>,
) -> Result<(), Error> { ) -> Result<(), Error> {
ctx.defer().await?;
let combined_time = hours.map_or(0, |h| h * HOUR as isize) let combined_time = hours.map_or(0, |h| h * HOUR as isize)
+ minutes.map_or(0, |m| m * MINUTE as isize) + minutes.map_or(0, |m| m * MINUTE as isize)
+ seconds.map_or(0, |s| s); + seconds.map_or(0, |s| s);

View File

@ -1,6 +1,6 @@
[package] [package]
name = "reminder_web" name = "reminder_web"
version = "0.1.2" version = "0.1.4"
authors = ["jellywx <judesouthworth@pm.me>"] authors = ["jellywx <judesouthworth@pm.me>"]
edition = "2018" edition = "2018"

View File

@ -33,11 +33,9 @@ impl<'r> FromRequest<'r> for Transaction<'r> {
match request.guard::<&State<Pool<Database>>>().await { match request.guard::<&State<Pool<Database>>>().await {
Outcome::Success(pool) => match pool.begin().await { Outcome::Success(pool) => match pool.begin().await {
Ok(transaction) => Outcome::Success(Transaction(transaction)), Ok(transaction) => Outcome::Success(Transaction(transaction)),
Err(e) => { Err(e) => Outcome::Error((Status::InternalServerError, TransactionError::Error(e))),
Outcome::Failure((Status::InternalServerError, TransactionError::Error(e)))
}
}, },
Outcome::Failure(e) => Outcome::Failure((e.0, TransactionError::Missing)), Outcome::Error(e) => Outcome::Error((e.0, TransactionError::Missing)),
Outcome::Forward(f) => Outcome::Forward(f), Outcome::Forward(f) => Outcome::Forward(f),
} }
} }

View File

@ -4,7 +4,7 @@ use chrono::{naive::NaiveDateTime, Utc};
use rand::{rngs::OsRng, seq::IteratorRandom}; use rand::{rngs::OsRng, seq::IteratorRandom};
use rocket::{http::CookieJar, response::Redirect, serde::json::json}; use rocket::{http::CookieJar, response::Redirect, serde::json::json};
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serenity::{ use serenity::{
client::Context, client::Context,
http::Http, http::Http,
@ -51,12 +51,27 @@ fn interval_default() -> Unset<Option<u32>> {
None None
} }
fn deserialize_optional_field<'de, T, D>(deserializer: D) -> Result<Option<Option<T>>, D::Error> #[derive(sqlx::Type)]
where #[sqlx(transparent)]
D: Deserializer<'de>, struct Attachment(Vec<u8>);
T: Deserialize<'de>,
{ impl<'de> Deserialize<'de> for Attachment {
Ok(Some(Option::deserialize(deserializer)?)) fn deserialize<D>(deserializer: D) -> Result<Attachment, D::Error>
where
D: Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
Ok(Attachment(base64::decode(string).map_err(de::Error::custom)?))
}
}
impl Serialize for Attachment {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(&base64::encode(&self.0))
}
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -67,7 +82,7 @@ pub struct ReminderTemplate {
guild_id: u32, guild_id: u32,
#[serde(default = "template_name_default")] #[serde(default = "template_name_default")]
name: String, name: String,
attachment: Option<Vec<u8>>, attachment: Option<Attachment>,
attachment_name: Option<String>, attachment_name: Option<String>,
avatar: Option<String>, avatar: Option<String>,
content: String, content: String,
@ -92,7 +107,7 @@ pub struct ReminderTemplate {
pub struct ReminderTemplateCsv { pub struct ReminderTemplateCsv {
#[serde(default = "template_name_default")] #[serde(default = "template_name_default")]
name: String, name: String,
attachment: Option<Vec<u8>>, attachment: Option<Attachment>,
attachment_name: Option<String>, attachment_name: Option<String>,
avatar: Option<String>, avatar: Option<String>,
content: String, content: String,
@ -127,8 +142,7 @@ pub struct EmbedField {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Reminder { pub struct Reminder {
#[serde(with = "base64s")] attachment: Option<Attachment>,
attachment: Option<Vec<u8>>,
attachment_name: Option<String>, attachment_name: Option<String>,
avatar: Option<String>, avatar: Option<String>,
#[serde(with = "string")] #[serde(with = "string")]
@ -161,8 +175,7 @@ pub struct Reminder {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ReminderCsv { pub struct ReminderCsv {
#[serde(with = "base64s")] attachment: Option<Attachment>,
attachment: Option<Vec<u8>>,
attachment_name: Option<String>, attachment_name: Option<String>,
avatar: Option<String>, avatar: Option<String>,
channel: String, channel: String,
@ -195,7 +208,7 @@ pub struct PatchReminder {
uid: String, uid: String,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_optional_field")] #[serde(deserialize_with = "deserialize_optional_field")]
attachment: Unset<Option<String>>, attachment: Unset<Option<Attachment>>,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_optional_field")] #[serde(deserialize_with = "deserialize_optional_field")]
attachment_name: Unset<Option<String>>, attachment_name: Unset<Option<String>>,
@ -291,6 +304,14 @@ pub fn generate_uid() -> String {
.join("") .join("")
} }
fn deserialize_optional_field<'de, T, D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
Ok(Some(Option::deserialize(deserializer)?))
}
// https://github.com/serde-rs/json/issues/329#issuecomment-305608405 // https://github.com/serde-rs/json/issues/329#issuecomment-305608405
mod string { mod string {
use std::{fmt::Display, str::FromStr}; use std::{fmt::Display, str::FromStr};
@ -315,29 +336,6 @@ mod string {
} }
} }
mod base64s {
use serde::{de, Deserialize, Deserializer, Serializer};
pub fn serialize<S>(value: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(opt) = value {
serializer.collect_str(&base64::encode(opt))
} else {
serializer.serialize_none()
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
let string = Option::<String>::deserialize(deserializer)?;
Some(string.map(|b| base64::decode(b).map_err(de::Error::custom))).flatten().transpose()
}
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct DeleteReminder { pub struct DeleteReminder {
uid: String, uid: String,

View File

@ -31,22 +31,20 @@ pub async fn discord_login(
// store the pkce secret to verify the authorization later // store the pkce secret to verify the authorization later
cookies.add_private( cookies.add_private(
Cookie::build("verify", pkce_verifier.secret().to_string()) Cookie::build(("verify", pkce_verifier.secret().to_string()))
.http_only(true) .http_only(true)
.path("/login") .path("/login")
.same_site(SameSite::Lax) .same_site(SameSite::Lax)
.expires(Expiration::Session) .expires(Expiration::Session),
.finish(),
); );
// store the csrf token to verify no interference // store the csrf token to verify no interference
cookies.add_private( cookies.add_private(
Cookie::build("csrf", csrf_token.secret().to_string()) Cookie::build(("csrf", csrf_token.secret().to_string()))
.http_only(true) .http_only(true)
.path("/login") .path("/login")
.same_site(SameSite::Lax) .same_site(SameSite::Lax)
.expires(Expiration::Session) .expires(Expiration::Session),
.finish(),
); );
Redirect::to(auth_url.to_string()) Redirect::to(auth_url.to_string())
@ -54,9 +52,9 @@ pub async fn discord_login(
#[get("/discord/logout")] #[get("/discord/logout")]
pub async fn discord_logout(cookies: &CookieJar<'_>) -> Redirect { pub async fn discord_logout(cookies: &CookieJar<'_>) -> Redirect {
cookies.remove_private(Cookie::named("username")); cookies.remove_private(Cookie::from("username"));
cookies.remove_private(Cookie::named("userid")); cookies.remove_private(Cookie::from("userid"));
cookies.remove_private(Cookie::named("access_token")); cookies.remove_private(Cookie::from("access_token"));
Redirect::to(uri!(routes::index)) Redirect::to(uri!(routes::index))
} }
@ -80,17 +78,16 @@ pub async fn discord_callback(
.request_async(async_http_client) .request_async(async_http_client)
.await; .await;
cookies.remove_private(Cookie::named("verify")); cookies.remove_private(Cookie::from("verify"));
cookies.remove_private(Cookie::named("csrf")); cookies.remove_private(Cookie::from("csrf"));
match token_result { match token_result {
Ok(token) => { Ok(token) => {
cookies.add_private( cookies.add_private(
Cookie::build("access_token", token.access_token().secret().to_string()) Cookie::build(("access_token", token.access_token().secret().to_string()))
.secure(true) .secure(true)
.http_only(true) .http_only(true)
.path("/dashboard") .path("/dashboard"),
.finish(),
); );
let request_res = reqwest_client let request_res = reqwest_client