//
// Copyright (c) 2025 rustmailer.com (https://rustmailer.com)
//
// This file is part of the Bichon Email Archiving Project
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
use crate::{
decrypt, encrypt,
modules::{
database::{
async_find_impl, delete_impl, insert_impl, list_all_impl, manager::DB_MANAGER,
update_impl, upsert_impl,
},
error::{code::ErrorCode, BichonResult},
oauth2::entity::OAuth2,
},
raise_error, utc_now,
};
use native_db::*;
use native_model::{native_model, Model};
use poem_openapi::Object;
use serde::{Deserialize, Serialize};
pub const EXTERNAL_OAUTH_APP_ID: u64 = 0;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[native_model(id = 7, version = 1)]
#[native_db]
pub struct OAuth2AccessToken {
/// The ID of the account associated with this access token.
#[primary_key]
pub account_id: u64,
/// The id of the OAuth2 configuration associated with this access token.
#[secondary_key]
pub oauth2_id: u64,
/// The OAuth2 access token used to authenticate requests to the provider.
pub access_token: Option,
/// The OAuth2 refresh token used to obtain new access tokens.
pub refresh_token: Option,
/// The timestamp when the token record was created, in milliseconds since the Unix epoch.
pub created_at: i64,
/// The timestamp when the token record was last updated, in milliseconds since the Unix epoch.
pub updated_at: i64,
}
impl OAuth2AccessToken {
pub fn create(
account_id: u64,
oauth2_id: u64,
access_token: String,
refresh_token: String,
) -> BichonResult {
Ok(Self {
account_id,
oauth2_id,
access_token: Some(encrypt!(&access_token)?),
refresh_token: Some(encrypt!(&refresh_token)?),
created_at: utc_now!(),
updated_at: utc_now!(),
})
}
pub async fn upsert_external_oauth_token(
account_id: u64,
request: ExternalOAuth2Request,
) -> BichonResult<()> {
let now = utc_now!();
request.validate().await?;
let current = Self::get(account_id).await?;
match current {
Some(mut current) => {
// Update existing record
if let Some(oauth2_id) = request.oauth2_id {
current.oauth2_id = oauth2_id;
}
if let Some(access_token) = request.access_token {
current.access_token = Some(encrypt!(&access_token)?);
}
if let Some(refresh_token) = request.refresh_token {
current.refresh_token = Some(encrypt!(&refresh_token)?);
}
current.updated_at = now;
upsert_impl(DB_MANAGER.meta_db(), current).await?;
}
None => {
// Insert new record
let entity = Self {
account_id,
oauth2_id: request.oauth2_id.unwrap_or(EXTERNAL_OAUTH_APP_ID),
access_token: request
.access_token
.as_ref()
.map(|token| encrypt!(token))
.transpose()?,
refresh_token: request
.refresh_token
.as_ref()
.map(|token| encrypt!(token))
.transpose()?,
created_at: now,
updated_at: now,
};
insert_impl(DB_MANAGER.meta_db(), entity).await?;
}
}
Ok(())
}
// This function may be called multiple times for one account, so we use upsert.
pub async fn save_or_update(&self) -> BichonResult<()> {
upsert_impl(DB_MANAGER.meta_db(), self.clone()).await
}
pub async fn get(account_id: u64) -> BichonResult