From 9e6572fa282a18fecfb31a2c35c17c0e8c23e371 Mon Sep 17 00:00:00 2001 From: pennae Date: Mon, 25 Jul 2022 02:26:35 +0200 Subject: remove dependency on chrono prompted by a cargo audit run. time works just as well and is better maintained. web-push still uses chrono, but from the looks of things it won't be affected. --- Cargo.lock | 5 +++-- Cargo.toml | 4 ++-- src/api/auth/account.rs | 12 ++++++------ src/api/auth/device.rs | 17 +++++++++-------- src/api/auth/invite.rs | 4 ++-- src/api/auth/oauth.rs | 12 ++++++------ src/api/mod.rs | 22 +--------------------- src/db/mod.rs | 8 ++++---- src/lib.rs | 2 +- src/types.rs | 16 ++++++++-------- tests/integration.rs | 6 ++++-- 11 files changed, 46 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b2788b..a0445bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1448,7 +1448,6 @@ version = "0.1.0" dependencies = [ "anyhow", "base64", - "chrono", "dotenv", "either", "futures", @@ -1469,6 +1468,7 @@ dependencies = [ "sha2 0.10.2", "sqlx", "subtle", + "time 0.3.11", "url", "validator", "web-push", @@ -2501,7 +2501,6 @@ dependencies = [ "bitflags", "byteorder", "bytes", - "chrono", "crc", "crossbeam-queue", "dirs", @@ -2534,6 +2533,7 @@ dependencies = [ "sqlx-rt", "stringprep", "thiserror", + "time 0.3.11", "tokio-stream", "url", "whoami", @@ -2693,6 +2693,7 @@ dependencies = [ "itoa", "libc", "num_threads", + "serde", "time-macros", ] diff --git a/Cargo.toml b/Cargo.toml index 9a67b16..239e1a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" [dependencies] anyhow = "1.0" base64 = "0.13" -chrono = "0.4.19" dotenv = "0.15" either = "1.7" futures = "0.3.21" @@ -25,8 +24,9 @@ scrypt = { version = "0.10", features = [ "simple" ] } serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" sha2 = "0.10.2" -sqlx = { version = "0.6.0", features = [ "runtime-tokio-native-tls", "postgres", "chrono", "json", "offline" ] } +sqlx = { version = "0.6.0", features = [ "runtime-tokio-native-tls", "postgres", "time", "json", "offline" ] } subtle = "2.4.1" +time = { version = "0.3.11", features = [ "serde" ] } url = "2.2.2" validator = { version = "0.15", features = [ "derive" ] } web-push = "0.9.2" diff --git a/src/api/auth/account.rs b/src/api/auth/account.rs index 8f3ac55..d96229d 100644 --- a/src/api/auth/account.rs +++ b/src/api/auth/account.rs @@ -1,13 +1,13 @@ use std::sync::Arc; use anyhow::Result; -use chrono::{DateTime, Utc}; use password_hash::SaltString; use rand::{thread_rng, Rng}; use rocket::request::FromRequest; use rocket::State; use rocket::{serde::json::Json, Request}; use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; use validator::Validate; use crate::api::{Empty, EMPTY}; @@ -19,7 +19,7 @@ use crate::types::{AccountResetID, HawkKey}; use crate::utils::DeferAction; use crate::Config; use crate::{ - api::{auth, serialize_dt}, + api::auth, auth::{AuthSource, Authenticated}, crypto::{AuthPW, KeyBundle, KeyFetchReq, SessionCredentials}, types::{KeyFetchID, OauthToken, SecretKey, User, UserID, VerifyHash}, @@ -56,8 +56,8 @@ pub(crate) struct CreateResp { sessionToken: SessionToken, #[serde(skip_serializing_if = "Option::is_none")] keyFetchToken: Option, - #[serde(serialize_with = "serialize_dt")] - authAt: DateTime, + #[serde(with = "time::serde::timestamp")] + authAt: OffsetDateTime, // MISSING verificationMethod } @@ -165,8 +165,8 @@ pub(crate) struct LoginResp { // NOTE this is the *account* verified status, not the session status. // the spec doesn't say. verified: bool, - #[serde(serialize_with = "serialize_dt")] - authAt: DateTime, + #[serde(with = "time::serde::timestamp")] + authAt: OffsetDateTime, // MISSING metricsEnabled } diff --git a/src/api/auth/device.rs b/src/api/auth/device.rs index 44fbd2a..5201073 100644 --- a/src/api/auth/device.rs +++ b/src/api/auth/device.rs @@ -1,11 +1,11 @@ use std::time::Duration; use std::{collections::HashMap, sync::Arc}; -use chrono::{DateTime, Utc}; use futures::future::join_all; use rocket::{serde::json::Json, State}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use time::OffsetDateTime; use crate::api::auth::{WithSession, WithVerifiedFxaLogin, WithVerifiedSession}; use crate::api::{Empty, EMPTY}; @@ -13,7 +13,7 @@ use crate::db::DbConn; use crate::push::PushClient; use crate::utils::DeferAction; use crate::{ - api::{auth, serialize_dt_opt}, + api::auth, auth::Authenticated, db::Db, types::{ @@ -39,7 +39,8 @@ fn map_error(e: sqlx::Error) -> auth::Error { pub(crate) struct Info { isCurrentDevice: bool, id: DeviceID, - lastAccessTime: i64, + #[serde(with = "time::serde::timestamp")] + lastAccessTime: OffsetDateTime, name: String, r#type: String, pushCallback: Option, @@ -62,7 +63,7 @@ fn device_to_json(current: Option<&DeviceID>, dev: Device) -> Info { Info { isCurrentDevice: Some(&dev.device_id) == current, id: dev.device_id, - lastAccessTime: dev.last_active.timestamp(), + lastAccessTime: dev.last_active, name: dev.name, r#type: dev.type_, pushCallback: pcb, @@ -356,11 +357,11 @@ pub(crate) struct AttachedClient { isCurrentSession: bool, deviceType: Option, name: Option, - #[serde(serialize_with = "serialize_dt_opt")] - createdTime: Option>, + #[serde(with = "time::serde::timestamp::option")] + createdTime: Option, // MISSING createdTimeFormatted - #[serde(serialize_with = "serialize_dt_opt")] - lastAccessTime: Option>, + #[serde(with = "time::serde::timestamp::option")] + lastAccessTime: Option, // MISSING lastAccessTimeFormatted // MISSING approximateLastAccessTime // MISSING approximateLastAccessTimeFormatted diff --git a/src/api/auth/invite.rs b/src/api/auth/invite.rs index e70c3d6..ecd39f9 100644 --- a/src/api/auth/invite.rs +++ b/src/api/auth/invite.rs @@ -1,7 +1,7 @@ use base64::URL_SAFE_NO_PAD; -use chrono::{Duration, Utc}; use rocket::{http::uri::Reference, serde::json::Json, State}; use serde::{Deserialize, Serialize}; +use time::{Duration, OffsetDateTime}; use crate::{api::auth, auth::Authenticated, crypto::random_bytes, db::DbConn, Config}; @@ -13,7 +13,7 @@ pub(crate) async fn generate_invite_link( ttl: Duration, ) -> anyhow::Result> { let code = base64::encode_config(&random_bytes::<32>(), URL_SAFE_NO_PAD); - db.add_invite_code(&code, Utc::now() + ttl).await?; + db.add_invite_code(&code, OffsetDateTime::now_utc() + ttl).await?; Reference::parse_owned(format!("{}/#/register/{}", cfg.location, code)) .map_err(|e| anyhow!("url building failed at {e}")) } diff --git a/src/api/auth/oauth.rs b/src/api/auth/oauth.rs index 384d4b4..25d6150 100644 --- a/src/api/auth/oauth.rs +++ b/src/api/auth/oauth.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; -use chrono::{DateTime, Duration, Local, Utc}; use rocket::serde::json::Json; use serde::{Deserialize, Serialize}; use serde_json::Value; use sha2::Digest; use subtle::ConstantTimeEq; +use time::{Duration, OffsetDateTime}; use crate::api::auth::WithVerifiedFxaLogin; use crate::api::{Empty, EMPTY}; @@ -13,7 +13,7 @@ use crate::crypto::SessionToken; use crate::db::DbConn; use crate::types::oauth::{Scope, ScopeSet}; use crate::{ - api::{auth, serialize_dt}, + api::auth, auth::Authenticated, crypto::SessionCredentials, types::{ @@ -283,8 +283,8 @@ pub(crate) struct TokenResp { scope: ScopeSet, token_type: TokenType, expires_in: u32, - #[serde(serialize_with = "serialize_dt")] - auth_at: DateTime, + #[serde(with = "time::serde::timestamp")] + auth_at: OffsetDateTime, #[serde(skip_serializing_if = "Option::is_none")] keys_jwe: Option, } @@ -328,7 +328,7 @@ pub(crate) async fn token_unauthenticated( async fn token_impl( db: &DbConn, user_id: Option, - auth_at: Option>, + auth_at: Option, req: TokenReq, parent_refresh: Option, parent_session: Option, @@ -385,7 +385,7 @@ async fn token_impl( scope: scope.clone(), parent_refresh, parent_session, - expires_at: (Local::now() + Duration::seconds(ttl.into())).into(), + expires_at: OffsetDateTime::now_utc() + Duration::seconds(ttl.into()), }, ) .await?; diff --git a/src/api/mod.rs b/src/api/mod.rs index 1831659..d5997dc 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,30 +1,10 @@ -use chrono::{DateTime, TimeZone}; use rocket::serde::json::Json; -use serde::{Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; pub(crate) mod auth; pub(crate) mod oauth; pub(crate) mod profile; -pub fn serialize_dt(dt: &DateTime, ser: S) -> Result -where - S: Serializer, - TZ: TimeZone, -{ - ser.serialize_i64(dt.timestamp()) -} - -pub fn serialize_dt_opt(dt: &Option>, ser: S) -> Result -where - S: Serializer, - TZ: TimeZone, -{ - match dt { - Some(dt) => serialize_dt(dt, ser), - None => ser.serialize_unit(), - } -} - #[derive(Clone, Copy, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Empty {} diff --git a/src/db/mod.rs b/src/db/mod.rs index 6cea895..d9a114e 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,7 +1,6 @@ use std::{error::Error, mem::replace, sync::Arc}; use anyhow::Result; -use chrono::{DateTime, Duration, Utc}; use password_hash::SaltString; use rocket::{ fairing::{self, Fairing}, @@ -12,6 +11,7 @@ use rocket::{ }; use serde_json::Value; use sqlx::{query, query_as, query_scalar, PgPool, Postgres, Transaction}; +use time::{Duration, OffsetDateTime}; use crate::{ crypto::WrappedKeyBundle, @@ -198,7 +198,7 @@ impl DbConn { key: HawkKey, verified: bool, verify_code: Option<&str>, - ) -> sqlx::Result> { + ) -> sqlx::Result { query_scalar!( r#"insert into user_session (session_id, user_id, req_hmac_key, device_id, verified, verify_code) @@ -313,7 +313,7 @@ impl DbConn { payload: &Value, ttl: u32, ) -> sqlx::Result { - let expires = Utc::now() + Duration::seconds(ttl as i64); + let expires = OffsetDateTime::now_utc() + Duration::seconds(ttl as i64); query!( r#"insert into device_commands (device_id, command, payload, expires, sender) values ($1, $2, $3, $4, $5) @@ -989,7 +989,7 @@ impl DbConn { // // - pub async fn add_invite_code(&self, code: &str, expires: DateTime) -> sqlx::Result<()> { + pub async fn add_invite_code(&self, code: &str, expires: OffsetDateTime) -> sqlx::Result<()> { query!(r#"insert into invite_codes (code, expires_at) values ($1, $2)"#, code, expires,) .execute(&mut self.get().await?.tx) .await?; diff --git a/src/lib.rs b/src/lib.rs index 29f3e08..d783c6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ use std::{ }; use anyhow::Context; -use chrono::Duration; use db::Db; use futures::Future; use lettre::message::Mailbox; @@ -24,6 +23,7 @@ use rocket::{ Request, State, }; use serde_json::{json, Value}; +use time::Duration; use utils::DeferredActions; use crate::api::auth::invite::generate_invite_link; diff --git a/src/types.rs b/src/types.rs index 7699483..64b35a1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,3 @@ -use chrono::{DateTime, Utc}; use password_hash::{rand_core::OsRng, Output, SaltString}; use rand::RngCore; use serde::{Deserialize, Serialize}; @@ -14,6 +13,7 @@ use std::{ ops::Deref, str::FromStr, }; +use time::OffsetDateTime; use self::oauth::ScopeSet; @@ -271,7 +271,7 @@ pub(crate) struct UserSession { pub(crate) uid: UserID, pub(crate) req_hmac_key: HawkKey, pub(crate) device_id: Option, - pub(crate) created_at: DateTime, + pub(crate) created_at: OffsetDateTime, pub(crate) verified: bool, pub(crate) verify_code: Option, } @@ -282,7 +282,7 @@ pub(crate) struct DeviceCommand { pub(crate) command: String, pub(crate) payload: Value, #[allow(dead_code)] - pub(crate) expires: DateTime, + pub(crate) expires: OffsetDateTime, // NOTE this is a device ID, but we don't link it to the actual sender device // because removing a device would also remove its queued commands. this mirrors // what fxa does. @@ -332,7 +332,7 @@ impl Deref for DeviceCommands { pub(crate) struct Device { pub(crate) device_id: DeviceID, // taken from session, otherwise UNIX_EPOCH - pub(crate) last_active: DateTime, + pub(crate) last_active: OffsetDateTime, pub(crate) name: String, pub(crate) type_: String, pub(crate) push: Option, @@ -367,7 +367,7 @@ pub(crate) struct OauthAccessToken { pub(crate) scope: ScopeSet, pub(crate) parent_refresh: Option, pub(crate) parent_session: Option, - pub(crate) expires_at: DateTime, + pub(crate) expires_at: OffsetDateTime, } #[derive(Debug)] @@ -386,7 +386,7 @@ pub(crate) struct OauthAuthorization { pub(crate) access_type: OauthAccessType, pub(crate) code_challenge: String, pub(crate) keys_jwe: Option, - pub(crate) auth_at: DateTime, + pub(crate) auth_at: OffsetDateTime, } #[derive(Debug)] @@ -418,8 +418,8 @@ pub(crate) struct AttachedClient { pub(crate) refresh_token_id: Option, pub(crate) device_type: Option, pub(crate) name: Option, - pub(crate) created_time: Option>, - pub(crate) last_access_time: Option>, + pub(crate) created_time: Option, + pub(crate) last_access_time: Option, pub(crate) scope: Option, } diff --git a/tests/integration.rs b/tests/integration.rs index 1866702..99c7a4f 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2,7 +2,6 @@ use std::env; use anyhow::Context; use base64::URL_SAFE_NO_PAD; -use chrono::{Duration, Utc}; use futures::channel::oneshot::channel; use minor_skulk::{build, db::Db}; use password_hash::rand_core::OsRng; @@ -11,6 +10,7 @@ use rocket::{ fairing::AdHoc, tokio::{process::Command, spawn}, }; +use time::{Duration, OffsetDateTime}; #[macro_use] extern crate rocket; @@ -54,7 +54,9 @@ async fn run_pytest( let mut code = [0; 32]; OsRng.fill_bytes(&mut code); let code = base64::encode_config(code, URL_SAFE_NO_PAD); - tx.add_invite_code(&code, Utc::now() + Duration::minutes(5)).await.unwrap(); + tx.add_invite_code(&code, OffsetDateTime::now_utc() + Duration::minutes(5)) + .await + .unwrap(); tx.commit().await.unwrap(); env::set_var("INVITE_CODE", code); } -- cgit v1.2.3