#![allow(dead_code)]
use serde::Serialize;
use utoipa::openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme};
use utoipa::openapi::{ComponentsBuilder, OpenApi, OpenApiBuilder};
use utoipa::ToSchema;
#[derive(Serialize, ToSchema)]
pub(crate) struct RegisterRequest {
email: String,
password: String,
display_name: Option<String>,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct LoginRequest {
email: String,
password: String,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct SteamLoginRequest {
ticket_hex: String,
identity: String,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct RefreshRequest {
refresh_token: String,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct LogoutRequest {
everywhere: Option<bool>,
refresh_token: Option<String>,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct TokenPair {
token_type: String,
access_token: String,
access_expires_at: i64,
refresh_token: String,
refresh_expires_at: i64,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct LinkedIdentity {
provider: String,
subject: String,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct Account {
id: i64,
email: Option<String>,
email_verified: bool,
display_name: Option<String>,
roles: Vec<String>,
identities: Vec<LinkedIdentity>,
created_at: i64,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct AuthSession {
account: Account,
tokens: TokenPair,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct UpdateAccountRequest {
display_name: Option<String>,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct ChangePasswordRequest {
current_password: String,
new_password: String,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct VerifyEmailRequest {
token: String,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct ForgotPasswordRequest {
email: String,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct ResetPasswordRequest {
token: String,
new_password: String,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct Ack {}
#[derive(Serialize, ToSchema)]
pub(crate) struct BanInfo {
banned_at: i64,
until: Option<i64>,
reason: Option<String>,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct AdminUser {
account: Account,
ban: Option<BanInfo>,
last_seen_at: Option<i64>,
active_sessions: u32,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct AdminUserPage {
items: Vec<AdminUser>,
next_cursor: Option<String>,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct BanRequest {
reason: Option<String>,
until: Option<i64>,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct AuditEntry {
id: i64,
actor: Option<i64>,
action: String,
target_type: Option<String>,
target_id: Option<String>,
ip: Option<String>,
request_id: Option<String>,
#[schema(value_type = Option<Object>)]
data: Option<serde_json::Value>,
created_at: i64,
}
#[derive(Serialize, ToSchema)]
pub(crate) struct AuditPage {
items: Vec<AuditEntry>,
next_cursor: Option<String>,
}
pub(crate) fn document() -> OpenApi {
let scheme = SecurityScheme::Http(
HttpBuilder::new().scheme(HttpAuthScheme::Bearer).description(Some("An access token from login, registration or refresh")).build(),
);
OpenApiBuilder::new().components(Some(ComponentsBuilder::new().security_scheme("bearer", scheme).build())).build()
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use net_backend_protocol::admin as p_admin;
use net_backend_protocol::auth as p;
use net_backend_protocol::{Ack as PAck, UnixMillis, UserId};
use serde_json::Value;
use utoipa::openapi::schema::Schema;
use utoipa::openapi::RefOr;
use utoipa::PartialSchema;
use super::*;
fn properties<T: PartialSchema>() -> BTreeSet<String> {
match T::schema() {
RefOr::T(Schema::Object(object)) => object.properties.keys().cloned().collect(),
_ => BTreeSet::new(),
}
}
fn keys(value: impl serde::Serialize) -> BTreeSet<String> {
match serde_json::to_value(value) {
Ok(Value::Object(map)) => map.keys().cloned().collect(),
_ => BTreeSet::new(),
}
}
#[test]
fn mirrors_match_the_protocol() {
let t = UnixMillis(1);
let account = p::Account::new(UserId(1), t).with_email("a@example.com", true).with_display_name("A");
let pair = p::TokenPair::new(p::AccessToken::new("a"), t, p::RefreshToken::new("r"), t);
assert_eq!(properties::<RegisterRequest>(), keys(p::RegisterRequest::new("a@b", "p").with_display_name("x")));
assert_eq!(properties::<LoginRequest>(), keys(p::LoginRequest::new("a@b", "p")));
assert_eq!(properties::<SteamLoginRequest>(), keys(p::SteamLoginRequest::new("00", "g")));
assert_eq!(properties::<RefreshRequest>(), keys(p::RefreshRequest::new("r")));
assert_eq!(properties::<LogoutRequest>(), keys(p::LogoutRequest::everywhere().with_refresh_token("r")));
assert_eq!(properties::<TokenPair>(), keys(&pair));
assert_eq!(properties::<Account>(), keys(&account));
assert_eq!(properties::<AuthSession>(), keys(p::AuthSession::new(account.clone(), pair)));
assert_eq!(properties::<LinkedIdentity>(), keys(p::LinkedIdentity::new("steam", "1")));
assert_eq!(properties::<UpdateAccountRequest>(), keys(p::UpdateAccountRequest::new().with_display_name("x")));
assert_eq!(properties::<ChangePasswordRequest>(), keys(p::ChangePasswordRequest::new("a", "b")));
assert_eq!(properties::<VerifyEmailRequest>(), keys(p::VerifyEmailRequest::new("t")));
assert_eq!(properties::<ForgotPasswordRequest>(), keys(p::ForgotPasswordRequest::new("a@b")));
assert_eq!(properties::<ResetPasswordRequest>(), keys(p::ResetPasswordRequest::new("t", "p")));
assert_eq!(properties::<Ack>(), keys(PAck::new()));
let ban = p_admin::BanInfo::new(t).with_until(t).with_reason("r");
assert_eq!(properties::<BanInfo>(), keys(&ban));
assert_eq!(properties::<AdminUser>(), keys(p_admin::AdminUser::new(account).with_ban(ban).with_last_seen_at(t)));
assert_eq!(properties::<BanRequest>(), keys(p_admin::BanRequest::new().with_reason("r").with_until(t)));
let entry = p_admin::AuditEntry::new(1, "x", t)
.with_actor(UserId(1))
.with_target("user", "1")
.with_ip("1.2.3.4")
.with_request_id("r")
.with_data(serde_json::json!({}));
assert_eq!(properties::<AuditEntry>(), keys(entry));
assert_eq!(properties::<AdminUserPage>(), keys(net_backend_protocol::Page::new(Vec::<u8>::new(), Some(net_backend_protocol::Cursor::new("c")))));
}
}