• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

divviup / divviup-api / 26155766475

20 May 2026 10:07AM UTC coverage: 68.351% (+9.0%) from 59.364%
26155766475

push

github

web-flow
Bump async-trait from 0.1.79 to 0.1.89 (#2272)

Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.79 to 0.1.89.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.79...0.1.89)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-version: 0.1.89
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

4341 of 6351 relevant lines covered (68.35%)

70.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

48.84
/src/handler/session_store.rs
1
use crate::{config::SessionSecrets, entity::session, Db};
2
use async_trait::async_trait;
3
use sea_orm::{
4
    sea_query::{any, OnConflict},
5
    ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter,
6
};
7
use std::collections::HashMap;
8
use time::{Duration, OffsetDateTime};
9
use tower_sessions::{
10
    cookie::{Key, SameSite},
11
    service::SignedCookie,
12
    session::{Id as TowerSessionId, Record},
13
    session_store as tower_store, Expiry, SessionManagerLayer,
14
};
15

16
/// Cookie name used by the session middleware.
17
pub const SESSION_COOKIE_NAME: &str = "divviup.sid";
18

19
/// Database-backed session store for [`tower-sessions`].
20
///
21
/// Sessions are stored in the `sessions` table. Expired sessions are
22
/// cleaned up by the [`SessionCleanup`](crate::queue::SessionCleanup)
23
/// queue job rather than by the store itself.
24
#[derive(Debug, Clone)]
25
pub struct TowerSessionStore {
26
    db: Db,
27
}
28

29
impl TowerSessionStore {
30
    pub fn new(db: Db) -> Self {
277✔
31
        Self { db }
277✔
32
    }
277✔
33
}
34

35
/// Build the Axum-side [`SessionManagerLayer`].
36
///
37
/// `tower-sessions` 0.15 accepts only a single signing key; there is no
38
/// `with_older_secrets` equivalent. Older secrets in the config are
39
/// parsed but ignored — see <https://github.com/divviup/divviup-api/issues/2252>.
40
pub fn axum_session_layer(
277✔
41
    db: Db,
277✔
42
    secrets: &SessionSecrets,
277✔
43
) -> SessionManagerLayer<TowerSessionStore, SignedCookie> {
277✔
44
    if !secrets.older.is_empty() {
277✔
45
        tracing::warn!(
×
46
            count = secrets.older.len(),
×
47
            "SESSION_SECRETS contains older keys that are ignored — \
48
             session cookie rotation is not yet supported, see \
49
             https://github.com/divviup/divviup-api/issues/2252"
50
        );
51
    }
277✔
52

53
    // `cookie::Key::from` panics on keys shorter than 64 bytes. We only
54
    // guarantee 32, so derive a longer key from the configured secret.
55
    let key = Key::derive_from(&secrets.current);
277✔
56
    SessionManagerLayer::new(TowerSessionStore::new(db))
277✔
57
        .with_name(SESSION_COOKIE_NAME)
277✔
58
        .with_secure(true)
277✔
59
        .with_http_only(true)
277✔
60
        .with_same_site(SameSite::Lax)
277✔
61
        .with_path("/")
277✔
62
        .with_signed(key)
277✔
63
        .with_expiry(Expiry::OnInactivity(Duration::days(1)))
277✔
64
}
277✔
65

66
#[async_trait]
67
impl tower_store::SessionStore for TowerSessionStore {
68
    async fn save(&self, record: &Record) -> tower_store::Result<()> {
1✔
69
        let model = session::Model {
70
            id: record.id.to_string(),
71
            expiry: Some(record.expiry_date),
72
            data: serde_json::to_value(&record.data)
73
                .map_err(|e| tower_store::Error::Encode(e.to_string()))?,
×
74
        };
75

76
        session::Entity::insert(model.into_active_model())
77
            .on_conflict(
78
                OnConflict::column(session::Column::Id)
79
                    .update_columns([session::Column::Data, session::Column::Expiry])
80
                    .clone(),
81
            )
82
            .exec(&self.db)
83
            .await
84
            .map_err(|e| tower_store::Error::Backend(e.to_string()))?;
×
85

86
        Ok(())
87
    }
1✔
88

89
    async fn load(&self, session_id: &TowerSessionId) -> tower_store::Result<Option<Record>> {
×
90
        let model = session::Entity::find_by_id(session_id.to_string())
91
            .filter(any![
92
                session::Column::Expiry.is_null(),
93
                session::Column::Expiry.gt(OffsetDateTime::now_utc())
94
            ])
95
            .one(&self.db)
96
            .await
97
            .map_err(|e| tower_store::Error::Backend(e.to_string()))?;
×
98

99
        model
100
            .map(|m| {
×
101
                let data: HashMap<String, serde_json::Value> = serde_json::from_value(m.data)
×
102
                    .map_err(|e| tower_store::Error::Decode(e.to_string()))?;
×
103
                let id: TowerSessionId = m.id.parse().map_err(|e: base64::DecodeSliceError| {
×
104
                    tower_store::Error::Decode(e.to_string())
×
105
                })?;
×
106
                Ok(Record {
107
                    id,
×
108
                    data,
×
109
                    expiry_date: m.expiry.unwrap_or_else(|| {
×
110
                        // The DB allows null expiry, but tower-sessions requires a
111
                        // value. Use a far-future sentinel.
112
                        OffsetDateTime::now_utc() + time::Duration::weeks(52)
×
113
                    }),
×
114
                })
115
            })
×
116
            .transpose()
117
    }
×
118

119
    async fn delete(&self, session_id: &TowerSessionId) -> tower_store::Result<()> {
×
120
        session::Entity::delete_by_id(session_id.to_string())
121
            .exec(&self.db)
122
            .await
123
            .map_err(|e| tower_store::Error::Backend(e.to_string()))?;
×
124
        Ok(())
125
    }
×
126
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc