use super::{DatabaseError, OAuthAccessTokenId, OAuthClientAuthorizationId, OAuthClientId, UserId}; use crate::models::pats::Scopes; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sha2::Digest; #[derive(Deserialize, Serialize, Clone, Debug)] pub struct OAuthAccessToken { pub id: OAuthAccessTokenId, pub authorization_id: OAuthClientAuthorizationId, pub token_hash: String, pub scopes: Scopes, pub created: DateTime, pub expires: DateTime, pub last_used: Option>, // Stored separately inside oauth_client_authorizations table pub client_id: OAuthClientId, pub user_id: UserId, } impl OAuthAccessToken { pub async fn get( token_hash: String, exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, ) -> Result, DatabaseError> { let value = sqlx::query!( " SELECT tokens.id, tokens.authorization_id, tokens.token_hash, tokens.scopes, tokens.created, tokens.expires, tokens.last_used, auths.client_id, auths.user_id FROM oauth_access_tokens tokens JOIN oauth_client_authorizations auths ON tokens.authorization_id = auths.id WHERE tokens.token_hash = $1 ", token_hash ) .fetch_optional(exec) .await?; Ok(value.map(|r| OAuthAccessToken { id: OAuthAccessTokenId(r.id), authorization_id: OAuthClientAuthorizationId(r.authorization_id), token_hash: r.token_hash, scopes: Scopes::from_postgres(r.scopes), created: r.created, expires: r.expires, last_used: r.last_used, client_id: OAuthClientId(r.client_id), user_id: UserId(r.user_id), })) } /// Inserts and returns the time until the token expires pub async fn insert( &self, exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, ) -> Result { let r = sqlx::query!( " INSERT INTO oauth_access_tokens ( id, authorization_id, token_hash, scopes, last_used ) VALUES ( $1, $2, $3, $4, $5 ) RETURNING created, expires ", self.id.0, self.authorization_id.0, self.token_hash, self.scopes.to_postgres(), Option::>::None ) .fetch_one(exec) .await?; let (created, expires) = (r.created, r.expires); let time_until_expiration = expires - created; Ok(time_until_expiration) } pub fn hash_token(token: &str) -> String { format!("{:x}", sha2::Sha512::digest(token.as_bytes())) } }