shared/application/session/
jwt_service.rs1use crate::error::{AuthError, CoreError, Result, TokenErrorType};
2
3use crate::domain::model::{Claims, IssuedTokens, RefreshToken, TokenType};
4use crate::intern::session::SessionService;
5
6impl SessionService {
7 #[tracing::instrument(name = "auth.consume_refresh_token", skip(self, refresh_token))]
8 pub async fn consume_refresh_token(&self, refresh_token: &str) -> Result<String> {
9 let claims: Claims = self.crypto.jwt()?.decode(refresh_token)?;
10
11 if claims.token_type != TokenType::RefreshToken {
12 return Err(CoreError::Unauthenticated(AuthError::TokenInvalid {
13 token_type: TokenErrorType::RefreshToken,
14 }));
15 }
16
17 match self.jwt_repository.find_and_consume(&claims).await {
18 Ok(_) => Ok(claims.sub),
19 Err(CoreError::Unauthenticated { .. }) => {
20 self.jwt_repository.revoke(&claims.sub).await?;
23 Err(CoreError::Unauthenticated(AuthError::TokenReplay {
24 token_type: TokenErrorType::RefreshToken,
25 }))
26 }
27 Err(e) => Err(e), }
29 }
30
31 #[tracing::instrument(
32 name = "auth.issue_jwt", skip(self), fields(user.id = user_id)
33 )]
34 pub async fn issue_jwt(
35 &self,
36 user_id: &str,
37 full_permissions: Vec<String>,
38 ) -> Result<IssuedTokens> {
39 let jwt_config = self.configuration.auth.jwt()?;
40 let rbac_config = &self.configuration.auth.rbac;
41 let jwt = self.crypto.jwt()?;
42
43 let (access_claims, refresh_claims) = Claims::new(user_id, &rbac_config.default_role)
44 .with_issuer(&jwt_config.issuer)
45 .with_audience(&jwt_config.audience)
46 .with_permissions(full_permissions)
47 .into_token_pair(jwt_config);
48 let jti = refresh_claims.jti;
49
50 let refresh_token = RefreshToken::new(&jti.to_string())
51 .with_user_id(user_id)
52 .with_expire_at(jwt_config.refresh_token_expires_in);
53 self.jwt_repository.insert(refresh_token).await?;
54
55 let access = jwt.encode(access_claims)?;
56 let refresh = jwt.encode(refresh_claims)?;
57 Ok(IssuedTokens::default()
58 .with_access_token(&access)
59 .with_refresh_token(&refresh)
60 .with_jti(jti))
61 }
62
63 #[tracing::instrument(name = "auth.invalidate_jwt", skip(self, refresh_token))]
64 pub async fn invalidate_jwt(&self, refresh_token: &str) -> Result<()> {
65 let claims: Claims = self.crypto.jwt()?.decode(refresh_token)?;
66
67 if claims.token_type != TokenType::RefreshToken {
68 return Err(CoreError::from(AuthError::TokenInvalid {
69 token_type: TokenErrorType::RefreshToken,
70 }));
71 }
72
73 self.jwt_repository.invalidate(claims.jti).await?;
74 Ok(())
75 }
76
77 #[tracing::instrument(name = "auth.logout_all", skip(self), fields(user.id = user_id))]
83 pub async fn logout_all(&self, user_id: &str) -> Result<()> {
84 self.jwt_repository.revoke(user_id).await?;
85 self.session_repository.revoke(user_id).await?;
86 Ok(())
87 }
88
89 #[tracing::instrument(name = "auth.find_jwt_by_jti", skip(self, jti))]
90 pub async fn find_jwt_by_jti(&self, jti: &str) -> Result<RefreshToken> {
91 self.jwt_repository.find_by_jti(jti).await
92 }
93}