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
30impl Outcome for Issued {
31    fn plan(&self, plan: &mut Plan<'_>) -> Result<()> {
32        self.token.plan(plan)
33    }
34
35    fn committed(&mut self, stamp: Stamp) {
36        self.token.committed(stamp);
37    }
38
39    fn reply(self) -> Result<Reply> {
40        Ok(Reply::Json(sofie::serde_json::json!({
41            "id": self.token.id().uuid().to_string(),
42            "token": self.secret.expose(),
43        })))
44    }
45}
46
47/// `scopes` narrow what `account` is granted in a `Permitted` area `A`.
48pub async fn issue<A: Area>(
49    read: &Read,
50    account: AccountId,
51    scopes: &[&str],
52    expires_at: Instant,
53) -> Result<Issued> {
54    for scope in scopes {
55        refuse_unless_permission("scopes", scope)?;
56    }
57    if held(read, account, A::NAME).await?.is_none() {
58        return Err(Error::Rule(format!(
59            "a token for area `{}` needs its account to be a member there",
60            A::NAME
61        )));
62    }
63    let secret = BearerToken::generate()?;
64    let token = AccessToken {
65        meta: Meta::new()?,
66        account,
67        area: A::NAME.to_owned(),
68        digest: secret.digest(),
69        scopes: scopes.join(" "),
70        expires_at,
71        last_used_at: None,
72    };
73    Ok(Issued { token, secret })
74}
75
76pub async fn revoke(read: &Read, account: AccountId, token: AccessTokenId) -> Result<AccessToken> {
77    let mut held = AccessToken::query()
78        .where_(AccessToken::ID.eq(token))
79        .where_(AccessToken::ACCOUNT.eq(account))
80        .first(read)
81        .await?
82        .ok_or(Error::NotFound)?;
83    held.delete();
84    Ok(held)
85}
86
87/// Revoked tokens are left out, expired ones kept until revoked.
88pub async fn list(read: &Read, account: AccountId) -> Result<Vec<AccessToken>> {
89    AccessToken::query()
90        .where_(AccessToken::ACCOUNT.eq(account))
91        .latest()
92        .all(read)
93        .await
94}