use axum::body::Bytes;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum_extra::extract::Query;
use hyper::HeaderMap;
use std::fs::create_dir_all;
use crate::server::{ADMIN, AUTHENTICATION, OrdinaryApiServerState, ROOT};
use ordinary_auth::{validate_account, validate_domain};
use base64::{Engine as B64Engine, engine::general_purpose::URL_SAFE_NO_PAD as b64};
use flexbuffers::VectorReader;
use ordinary_utils::get_bearer_token_as_bytes;
use serde::Deserialize;
use std::sync::Arc;
use utoipa::IntoParams;
#[allow(clippy::unnecessary_wraps)]
fn invite_claims_validator(account: &str, claims: &VectorReader<&[u8]>) -> anyhow::Result<bool> {
let claims_account = claims.idx(1).as_str();
if claims_account != account {
return Ok(false);
}
Ok(true)
}
#[derive(Deserialize, IntoParams)]
pub struct InviteParams {
d: String,
a: String,
#[serde(default)]
p: Vec<u8>,
}
#[utoipa::path(
get,
path = "/accounts/invite",
tags = [AUTHENTICATION],
params(Params),
responses(
(status = 401, description = "unauthorized for operation"),
(status = 409, description = "account or domain already registered"),
(status = 200, description = "invite token as bytes", body = [u8]),
),
security(
("access" = []),
),
)]
pub async fn invite(
State(state): State<Arc<OrdinaryApiServerState>>,
Query(InviteParams { d, a, p }): Query<InviteParams>,
headers: HeaderMap,
) -> impl IntoResponse {
let span = tracing::info_span!("api");
let span = span.in_scope(|| tracing::info_span!("accounts"));
let span = span.in_scope(|| tracing::info_span!("invite"));
span.in_scope(|| {
if !validate_domain(&state.app_domains, d.as_str())
&& !state.privileged_domains.contains(&d)
{
tracing::error!("invalid domain");
return StatusCode::BAD_REQUEST.into_response();
}
let invited_domain = d;
let inviter_account =
match crate::server::check_ordinary_auth(&state, &headers, 0, &invited_domain) {
Ok(account) => account,
Err(code) => return code.into_response(),
};
let domain_exists = state.apps_dir.join(&invited_domain).exists();
if domain_exists {
tracing::warn!(domain = %invited_domain, "exists");
return StatusCode::CONFLICT.into_response();
}
let Ok(invited_account) = validate_account(&a) else {
tracing::error!("invalid account");
return StatusCode::BAD_REQUEST.into_response();
};
match state.auth.account_exists(&invited_account) {
Ok(exists) => {
if exists {
tracing::warn!(account = %invited_account, "exists");
return StatusCode::CONFLICT.into_response();
}
}
Err(err) => {
tracing::error!(%err);
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
}
tracing::info!("inviting");
let Some(invite_config) = &state.auth.config.invite else {
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
};
if invite_config.claims.is_none() {
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
let mut builder = flexbuffers::Builder::new(&flexbuffers::BuilderOptions::SHARE_NONE);
let mut builder_vec = builder.start_vector();
builder_vec.push(());
builder_vec.push(invited_account.as_str());
builder_vec.push(invited_domain.as_str());
let mut perms_vec = builder_vec.start_vector();
for perm in &p {
perms_vec.push(*perm);
}
perms_vec.end_vector();
builder_vec.end_vector();
let inviter_domain = if inviter_account == "root" {
state.domain.clone()
} else {
invited_domain
};
match state.auth.api_invite_get(
&inviter_domain,
&inviter_account,
Some(Bytes::copy_from_slice(builder.view())),
) {
Ok(v) => (StatusCode::OK, v).into_response(),
Err(err) => {
tracing::error!(%err);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
})
}
#[utoipa::path(
post,
path = "/accounts/registration/start",
tag = AUTHENTICATION,
description = "
Extended [OPAQUE Key Exchange](https://datatracker.ietf.org/doc/rfc9807/) Registration Start
request body:
- 1.....`body[0]`: account name length
- <=255.`body[1..=body[0]]`: account name
- 32....`body[body[0] + 1..=body[0] + 32]`: client registration start for [OPAQUE Key Exchange](https://datatracker.ietf.org/doc/rfc9807/)
",
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "registration start error"),
(
status = 200,
body = [u8],
description = "
response body:
- 64 `body[0..=64]`: server registration start for [OPAQUE Key Exchange](https://datatracker.ietf.org/doc/rfc9807/)
",
),
),
security(
("invite" = []),
),
)]
pub async fn registration_start(
State(state): State<Arc<OrdinaryApiServerState>>,
headers: HeaderMap,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| {
let Ok(invite_token) = get_bearer_token_as_bytes(&headers) else {
return StatusCode::UNAUTHORIZED.into_response();
};
let Ok(checked_claims) = state.auth.invite_check(invite_token.as_ref()) else {
return StatusCode::UNAUTHORIZED.into_response();
};
let invited_domain = checked_claims.0.idx(2).as_str();
let domain_exists = state.apps_dir.join(invited_domain).exists();
if domain_exists {
tracing::warn!(domain = %invited_domain, "exists");
return StatusCode::CONFLICT.into_response();
}
match state.auth.registration_start(
body,
None,
Some(invite_claims_validator),
Some(checked_claims),
) {
Ok(v) => (StatusCode::OK, v).into_response(),
Err(err) => {
tracing::error!(%err);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
})
}
#[utoipa::path(
post,
path = "/accounts/registration/finish",
tag = AUTHENTICATION,
description = "
Extended [OPAQUE Key Exchange](https://datatracker.ietf.org/doc/rfc9807/) Registration Finish
request body:
- 1.....`body[0]`: account name length
- <=255.`body[1..=body[0]]`: account name
- 32....`body[body[0] + 1..=body[0] + 32]`: ephemeral client public `X25519` key (for encrypting MFA secret and recovery codes)
- 192...`body[body[0] + 1 + 32..]`: client registration finish for [OPAQUE Key Exchange](https://datatracker.ietf.org/doc/rfc9807/)
",
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "registration finish error"),
(
status = 200,
body = [u8],
description = "
TOTP MFA secret and recovery codes encrypted, together, using
`XChaCha20Poly1305` with an ephemeral `X25519` shared secret.
---
response body:
- 20 `body[0..<20]`: (encrypted) TOTP MFA secret
- 55 `body[20..<75]`: (encrypted) recovery codes (5 codes each 11 chars)
- 16 `body[75..<91]`: `Poly1305` MAC
- 24 `body[91..<115]`: extended 192 bit nonce
- 32 `body[115..<147]`: ephemeral server public `X25519` key
",
),
),
)]
pub async fn registration_finish(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| {
match state.auth.registration_finish(body, None) {
Ok((v, account_bytes, invite_claims)) => {
if let Some(invite_claims) = invite_claims {
let mut builder =
flexbuffers::Builder::new(&flexbuffers::BuilderOptions::SHARE_NONE);
let mut builder_vec = builder.start_vector();
let system_claims_gap = builder_vec.start_vector();
system_claims_gap.end_vector();
if let Ok(invite_claims_root) =
flexbuffers::Reader::get_root(&invite_claims[..])
{
let invite_claims_vec = invite_claims_root.as_vector();
for claim in &state.auth.config.access_token.claims {
if claim.idx == 1 {
let domain = invite_claims_vec.idx(2).as_str();
let app_dir = state.apps_dir.join(domain);
if let Err(err) = create_dir_all(app_dir) {
tracing::error!(%err);
}
builder_vec.push(domain);
} else if claim.idx == 2
&& let Err(err) = claim.kind.copy_to(
&invite_claims_vec.idx(3),
&mut builder_vec,
None,
)
{
tracing::error!(%err);
}
}
builder_vec.end_vector();
if let Err(err) = state.auth.set_claims(&account_bytes, builder.view()) {
tracing::error!(%err);
}
}
}
(StatusCode::OK, v)
}
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
}
})
}
#[utoipa::path(
post,
path = "/accounts/login/start",
tag = AUTHENTICATION,
description = "
Extended [OPAQUE Key Exchange](https://datatracker.ietf.org/doc/rfc9807/) Login Start
request body:
- 1.....`body[0]`: account name length
- <=255.`body[1..=body[0]]`: account name
- 96....`body[body[0] + 1..=body[0] + 32]`: client login start for [OPAQUE Key Exchange](https://datatracker.ietf.org/doc/rfc9807/)
",
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "login start error"),
(
status = 200,
body = [u8],
description = "
response body:
- 320 `body[0..=320]`: server login start for [OPAQUE Key Exchange](https://datatracker.ietf.org/doc/rfc9807/)
",
),
),
)]
pub async fn login_start(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.login_start(body) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
post,
path = "/accounts/login/finish",
tag = AUTHENTICATION,
description = "
Extended [OPAQUE Key Exchange](https://datatracker.ietf.org/doc/rfc9807/) Login Finish
`Ed25519` client verifying key and `SHA-256` hashed TOTP MFA code, encrypted, together,
using `XChaCha20Poly1305` with the `OPAQUE` session key, in order to bind
those components to the successful OPAQUE result.
request body:
- 1.....`body[0]`: account name length
- <=255.`body[1..=body[0]]`: account name
- 32....`body[body[0] + 1..=body[0] + 32]`: (encrypted) client `Ed25519` verifying key
- 32....`body[body[0] + 1 + 32..=body[0] + 64]`: (encrypted) `SHA-256` hash of the TOTP MFA code
- 16....`body[body[0] + 1 + 64..=body[0] + 80]`: `Ploy1305` MAC
- 24....`body[body[0] + 1 + 80..=body[0] + 104]`: extended 192 bit nonce
- 64...`body[body[0] + 1 + 104..=body[0] + 172]`: client login finish for [OPAQUE Key Exchange](https://datatracker.ietf.org/doc/rfc9807/)
",
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "login finish error"),
(
status = 200,
body = [u8],
description = "
encrypted using `XChaCha20Poly1305` with the `OPAQUE` session key, to ensure
the token can only be accessed by the client that successfully generated the
OPAQUE session key.
---
response body:
- n..`body[..-40]`: refresh token bytes
- 16.`body[n..<n + 16]`: `Ploy1305` MAC
- 24.`body[n + 16..<n + 40]`: extended 192 bit nonce
",
),
),
)]
pub async fn login_finish(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.login_finish(body, true) {
Ok((v, _, _)) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
get,
path = "/accounts/access",
tag = AUTHENTICATION,
description = "get an `access_token` with a `refresh_token`",
responses(
(status = 500, description = "access token generation error"),
(status = 401, description = "invalid refresh token"),
(
status = 200,
body = [u8],
description = "valid access token",
),
),
security(
("refresh" = []),
),
)]
pub async fn access_get(
State(state): State<Arc<OrdinaryApiServerState>>,
headers: HeaderMap,
) -> impl IntoResponse {
let Ok(refresh_token) = get_bearer_token_as_bytes(&headers) else {
return StatusCode::UNAUTHORIZED.into_response();
};
match state.auth.access_get(&refresh_token.into()) {
Ok(v) => (StatusCode::OK, v).into_response(),
Err(err) => {
tracing::error!(%err);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
}
pub async fn logout(
State(_state): State<Arc<OrdinaryApiServerState>>,
_body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| {
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
})
}
#[utoipa::path(
post,
path = "/accounts/password/reset/login/start",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "password reset login start error"),
(status = 200, description = "login start", body = [u8]),
),
)]
pub async fn password_reset_login_start(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.reset_password_login_start(body) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
post,
path = "/accounts/password/reset/login/finish",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "password reset login finish error"),
(status = 200, description = "encrypted password reset token", body = [u8]),
),
)]
pub async fn password_reset_login_finish(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.reset_password_login_finish(body) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
post,
path = "/accounts/password/reset/registration/start",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "password reset registration start error"),
(status = 200, description = "registration start", body = [u8]),
),
)]
pub async fn password_reset_registration_start(
State(state): State<Arc<OrdinaryApiServerState>>,
headers: HeaderMap,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| {
let mut token: Vec<u8> = vec![];
if let Some(val) = headers.get("authorization")
&& let Ok(str_val) = val.to_str()
&& let Some(b64_token) = str_val.strip_prefix("Bearer ")
&& let Ok(t) = b64.decode(b64_token)
{
token = t;
}
if token.is_empty() {
return (StatusCode::UNAUTHORIZED, Bytes::new());
}
match state.auth.reset_password_registration_start(body, &token) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
}
})
}
#[utoipa::path(
post,
path = "/accounts/password/reset/registration/finish",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "password reset registration finish error"),
(status = 200, description = "success"),
),
)]
pub async fn password_reset_registration_finish(
State(state): State<Arc<OrdinaryApiServerState>>,
headers: HeaderMap,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| {
let mut token: Vec<u8> = vec![];
if let Some(val) = headers.get("authorization")
&& let Ok(str_val) = val.to_str()
&& let Some(b64_token) = str_val.strip_prefix("Bearer ")
&& let Ok(t) = b64.decode(b64_token)
{
token = t;
}
if token.is_empty() {
return StatusCode::UNAUTHORIZED;
}
match state.auth.reset_password_registration_finish(body, &token) {
Ok(()) => StatusCode::OK,
Err(err) => {
tracing::error!(%err);
StatusCode::INTERNAL_SERVER_ERROR
}
}
})
}
#[utoipa::path(
post,
path = "/accounts/password/forgot/start",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "password forgot start error"),
(status = 200, description = "registration start", body = [u8]),
),
)]
pub async fn password_forgot_start(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.forgot_password_start(body) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
post,
path = "/accounts/password/forgot/finish",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "password forgot finish error"),
(status = 200, description = "success"),
),
)]
pub async fn password_forgot_finish(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.forgot_password_finish(body) {
Ok(()) => StatusCode::OK,
Err(err) => {
tracing::error!(%err);
StatusCode::INTERNAL_SERVER_ERROR
}
})
}
#[utoipa::path(
post,
path = "/accounts/mfa/totp/reset/start",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "MFA TOTP reset start error"),
(status = 200, description = "login start", body = [u8]),
),
)]
pub async fn mfa_reset_totp_start(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.reset_totp_mfa_start(body) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
post,
path = "/accounts/mfa/totp/reset/finish",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "MFA TOTP reset finish error"),
(status = 200, description = "encrypted MFA secret", body = [u8]),
),
)]
pub async fn mfa_reset_totp_finish(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.reset_totp_mfa_finish(body) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
post,
path = "/accounts/mfa/totp/lost/start",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "MFA TOTP lost start error"),
(status = 200, description = "login start", body = [u8]),
),
)]
pub async fn mfa_lost_totp_start(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.lost_totp_mfa_start(body) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
post,
path = "/accounts/mfa/totp/lost/finish",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "MFA TOTP lost finish error"),
(status = 200, description = "encrypted MFA TOTP secret", body = [u8]),
),
)]
pub async fn mfa_lost_totp_finish(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.lost_totp_mfa_finish(body) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
post,
path = "/accounts/recovery-codes/reset/start",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "recovery codes reset start error"),
(status = 200, description = "login start", body = [u8]),
),
)]
pub async fn recovery_reset_codes_start(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.reset_recovery_codes_start(body) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
post,
path = "/accounts/recovery-codes/reset/finish",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "recovery codes reset finish error"),
(status = 200, description = "encrypted recovery codes", body = [u8]),
),
)]
pub async fn recovery_reset_codes_finish(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| match state.auth.reset_recovery_codes_finish(body) {
Ok(v) => (StatusCode::OK, v),
Err(err) => {
tracing::error!(%err);
(StatusCode::INTERNAL_SERVER_ERROR, Bytes::new())
}
})
}
#[utoipa::path(
post,
path = "/accounts/delete/start",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "delete start error"),
(status = 200, description = "login start", body = [u8]),
),
)]
pub async fn account_delete_start(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| {
if body.len() > 4 {
let account_len = body[0] as usize;
let raw_account = &body[1..=account_len];
if let Ok(raw_account_str) = std::str::from_utf8(raw_account)
&& raw_account_str == "root"
{
tracing::error!("cannot delete root user");
return (StatusCode::FORBIDDEN, "cannot delete root user").into_response();
}
}
match state.auth.delete_account_start(body) {
Ok(v) => (StatusCode::OK, v).into_response(),
Err(err) => {
tracing::error!(%err);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
})
}
#[utoipa::path(
post,
path = "/accounts/delete/finish",
tags = [AUTHENTICATION],
request_body(
content = [u8],
content_type = "application/octet-stream",
),
responses(
(status = 500, description = "delete finish error"),
(status = 200, description = "success", body = [u8]),
),
)]
pub async fn account_delete_finish(
State(state): State<Arc<OrdinaryApiServerState>>,
body: Bytes,
) -> impl IntoResponse {
let span = tracing::info_span!("auth");
span.in_scope(|| {
if body.len() > 4 {
let account_len = body[0] as usize;
let raw_account = &body[1..=account_len];
if let Ok(raw_account_str) = std::str::from_utf8(raw_account)
&& raw_account_str == "root"
{
tracing::error!("cannot delete root user");
return (StatusCode::FORBIDDEN, "cannot delete root user").into_response();
}
}
match state.auth.delete_account_finish(body) {
Ok(()) => StatusCode::OK.into_response(),
Err(err) => {
tracing::error!(%err);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
})
}
#[derive(Deserialize, IntoParams)]
pub struct Params {
d: String,
}
#[utoipa::path(
get,
path = "/accounts",
tags = [ADMIN, ROOT],
params(Params),
request_body(content = [u8], content_type = "application/octet-stream"),
responses(
(status = 401, description = "unauthorized for operation"),
(status = 200, description = "accounts list as flexbuffer bytes", body = [u8]),
),
security(
("access" = []),
),
)]
pub async fn list(
State(state): State<Arc<OrdinaryApiServerState>>,
Query(Params { d }): Query<Params>,
headers: HeaderMap,
_body: Bytes,
) -> impl IntoResponse {
let domain = d;
let span = tracing::info_span!("admin");
let span = span.in_scope(|| tracing::info_span!("accounts"));
let span = span.in_scope(|| tracing::info_span!("list"));
span.in_scope(|| {
tracing::info!("listing");
let _account = match crate::server::check_ordinary_auth(&state, &headers, 1, &domain) {
Ok(account) => account,
Err(code) => return code.into_response(),
};
(StatusCode::OK, Bytes::new()).into_response()
})
}
#[utoipa::path(
delete,
path = "/accounts",
tags = [ADMIN, ROOT],
params(Params),
request_body(content = [u8], content_type = "application/octet-stream"),
responses(
(status = 401, description = "unauthorized for operation"),
(status = 200, description = "successfully deleted account"),
),
security(
("access" = []),
),
)]
pub async fn delete(
State(state): State<Arc<OrdinaryApiServerState>>,
Query(Params { d }): Query<Params>,
headers: HeaderMap,
_body: Bytes,
) -> impl IntoResponse {
let domain = d;
let span = tracing::info_span!("admin");
let span = span.in_scope(|| tracing::info_span!("accounts"));
let span = span.in_scope(|| tracing::info_span!("delete"));
span.in_scope(|| {
tracing::info!("deleting");
let _account = match crate::server::check_ordinary_auth(&state, &headers, 0, &domain) {
Ok(account) => account,
Err(code) => return code.into_response(),
};
StatusCode::OK.into_response()
})
}