Skip to main content

sofie_identity/tokens/
factory.rs

1use sofie::auth::BearerToken;
2use sofie::core::Instant;
3use sofie::db::{Outcome, Plan, Reply, Stamp};
4use sofie::http::Area;
5use sofie::prelude::*;
6
7use super::{AccessToken, AccessTokenId};
8use crate::AccountId;
9use crate::membership::held;
10use crate::role_grant::factory::refuse_unless_permission;
11
12/// Committed as its token; its reply holds the token's text, which is never shown again.
13pub struct Issued {
14    token: AccessToken,
15    secret: BearerToken,
16}
17
18impl Issued {
19    #[must_use]
20    pub fn token(&self) -> &AccessToken {
21        &self.token
22    }
23
24    #[must_use]
25    pub fn secret(&self) -> &BearerToken {
26        &self.secret
27    }
28
29    #[cfg(feature = "token-screens")]
30    pub(crate) fn revealed(mut self, sealing: &sofie::http::Sealing) -> Result<Self> {
31        let sealed = sealing.seal(super::REVEALING, self.secret.expose().as_bytes())?;
32        self.token.revealing = Some(sealed);
33        Ok(self)
34    }
35}
36
37impl Outcome for Issued {
38    fn plan(&self, plan: &mut Plan<'_>) -> Result<()> {
39        self.token.plan(plan)
40    }
41
42    fn committed(&mut self, stamp: Stamp) {
43        self.token.committed(stamp);
44    }
45
46    fn reply(self) -> Result<Reply> {
47        Ok(Reply::Json(sofie::serde_json::json!({
48            "id": self.token.id().uuid().to_string(),
49            "token": self.secret.expose(),
50        })))
51    }
52}
53
54/// `scopes` narrow what `account` is granted in a `Permitted` area `A`.
55pub async fn issue<A: Area>(
56    read: &Read,
57    account: AccountId,
58    scopes: &[&str],
59    expires_at: Instant,
60) -> Result<Issued> {
61    for scope in scopes {
62        refuse_unless_permission("scopes", scope)?;
63    }
64    if held(read, account, A::NAME).await?.is_none() {
65        return Err(Error::Rule(format!(
66            "a token for area `{}` needs its account to be a member there",
67            A::NAME
68        )));
69    }
70    let secret = BearerToken::generate()?;
71    let token = AccessToken {
72        meta: Meta::new()?,
73        account,
74        area: A::NAME.to_owned(),
75        digest: secret.digest(),
76        scopes: scopes.join(" "),
77        expires_at,
78        last_used_at: None,
79        revealing: None,
80    };
81    Ok(Issued { token, secret })
82}
83
84pub async fn revoke(read: &Read, account: AccountId, token: AccessTokenId) -> Result<AccessToken> {
85    let mut held = AccessToken::query()
86        .where_(AccessToken::ID.eq(token))
87        .where_(AccessToken::ACCOUNT.eq(account))
88        .first(read)
89        .await?
90        .ok_or(Error::NotFound)?;
91    held.delete();
92    Ok(held)
93}
94
95/// Revoked tokens are left out, expired ones kept until revoked.
96pub async fn list(read: &Read, account: AccountId) -> Result<Vec<AccessToken>> {
97    AccessToken::query()
98        .where_(AccessToken::ACCOUNT.eq(account))
99        .latest()
100        .all(read)
101        .await
102}