openauth-plugins 0.0.4

Official OpenAuth plugin modules.
Documentation
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;

use openauth_core::db::{DbRecord, DbValue};
use openauth_core::error::OpenAuthError;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdminUser {
    pub id: String,
    pub name: String,
    pub email: String,
    pub email_verified: bool,
    pub image: Option<String>,
    pub created_at: OffsetDateTime,
    pub updated_at: OffsetDateTime,
    pub role: Option<String>,
    pub banned: bool,
    pub ban_reason: Option<String>,
    pub ban_expires: Option<OffsetDateTime>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdminSession {
    pub id: String,
    pub user_id: String,
    pub expires_at: OffsetDateTime,
    pub token: String,
    pub ip_address: Option<String>,
    pub user_agent: Option<String>,
    pub created_at: OffsetDateTime,
    pub updated_at: OffsetDateTime,
    pub impersonated_by: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserIdBody {
    pub user_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetRoleBody {
    pub user_id: String,
    pub role: RoleInput,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum RoleInput {
    One(String),
    Many(Vec<String>),
}

impl RoleInput {
    pub fn roles(&self) -> Vec<String> {
        match self {
            Self::One(role) => vec![role.clone()],
            Self::Many(roles) => roles.clone(),
        }
    }

    pub fn joined(&self) -> String {
        self.roles().join(",")
    }
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateUserBody {
    pub email: String,
    pub password: Option<String>,
    pub name: String,
    pub role: Option<RoleInput>,
    #[serde(default)]
    pub data: serde_json::Map<String, serde_json::Value>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateUserBody {
    pub user_id: String,
    pub data: serde_json::Map<String, serde_json::Value>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BanUserBody {
    pub user_id: String,
    pub ban_reason: Option<String>,
    pub ban_expires_in: Option<i64>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RevokeSessionBody {
    pub session_token: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetPasswordBody {
    pub user_id: String,
    pub new_password: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HasPermissionBody {
    pub user_id: Option<String>,
    pub role: Option<String>,
    #[serde(default, alias = "permission")]
    pub permissions: crate::admin::PermissionMap,
}

pub(crate) fn admin_user_from_record(record: DbRecord) -> Result<AdminUser, OpenAuthError> {
    Ok(AdminUser {
        id: string(&record, "id")?.to_owned(),
        name: string(&record, "name")?.to_owned(),
        email: string(&record, "email")?.to_owned(),
        email_verified: bool_field(&record, "email_verified")?,
        image: optional_string(&record, "image")?,
        created_at: timestamp(&record, "created_at")?,
        updated_at: timestamp(&record, "updated_at")?,
        role: optional_string(&record, "role")?,
        banned: optional_bool(&record, "banned")?.unwrap_or(false),
        ban_reason: optional_string(&record, "ban_reason")?,
        ban_expires: optional_timestamp(&record, "ban_expires")?,
    })
}

pub(crate) fn admin_session_from_record(record: DbRecord) -> Result<AdminSession, OpenAuthError> {
    Ok(AdminSession {
        id: string(&record, "id")?.to_owned(),
        user_id: string(&record, "user_id")?.to_owned(),
        expires_at: timestamp(&record, "expires_at")?,
        token: string(&record, "token")?.to_owned(),
        ip_address: optional_string(&record, "ip_address")?,
        user_agent: optional_string(&record, "user_agent")?,
        created_at: timestamp(&record, "created_at")?,
        updated_at: timestamp(&record, "updated_at")?,
        impersonated_by: optional_string(&record, "impersonated_by")?,
    })
}

fn string<'a>(record: &'a DbRecord, field: &str) -> Result<&'a str, OpenAuthError> {
    match record.get(field) {
        Some(DbValue::String(value)) => Ok(value),
        _ => Err(OpenAuthError::Adapter(format!(
            "missing string field `{field}`"
        ))),
    }
}

fn bool_field(record: &DbRecord, field: &str) -> Result<bool, OpenAuthError> {
    match record.get(field) {
        Some(DbValue::Boolean(value)) => Ok(*value),
        _ => Err(OpenAuthError::Adapter(format!(
            "missing boolean field `{field}`"
        ))),
    }
}

fn timestamp(record: &DbRecord, field: &str) -> Result<OffsetDateTime, OpenAuthError> {
    match record.get(field) {
        Some(DbValue::Timestamp(value)) => Ok(*value),
        _ => Err(OpenAuthError::Adapter(format!(
            "missing timestamp field `{field}`"
        ))),
    }
}

fn optional_string(record: &DbRecord, field: &str) -> Result<Option<String>, OpenAuthError> {
    match record.get(field) {
        Some(DbValue::String(value)) => Ok(Some(value.clone())),
        Some(DbValue::Null) | None => Ok(None),
        _ => Err(OpenAuthError::Adapter(format!(
            "invalid string field `{field}`"
        ))),
    }
}

fn optional_bool(record: &DbRecord, field: &str) -> Result<Option<bool>, OpenAuthError> {
    match record.get(field) {
        Some(DbValue::Boolean(value)) => Ok(Some(*value)),
        Some(DbValue::Null) | None => Ok(None),
        _ => Err(OpenAuthError::Adapter(format!(
            "invalid boolean field `{field}`"
        ))),
    }
}

fn optional_timestamp(
    record: &DbRecord,
    field: &str,
) -> Result<Option<OffsetDateTime>, OpenAuthError> {
    match record.get(field) {
        Some(DbValue::Timestamp(value)) => Ok(Some(*value)),
        Some(DbValue::Null) | None => Ok(None),
        _ => Err(OpenAuthError::Adapter(format!(
            "invalid timestamp field `{field}`"
        ))),
    }
}