summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--server/Cargo.lock169
-rw-r--r--server/Cargo.toml1
-rw-r--r--server/Rocket.toml3
-rw-r--r--server/migrations/1_initial_eyeballs.sql2
-rw-r--r--server/src/auth.rs229
-rw-r--r--server/src/main.rs24
6 files changed, 387 insertions, 41 deletions
diff --git a/server/Cargo.lock b/server/Cargo.lock
index 622a32b..6cad1d0 100644
--- a/server/Cargo.lock
+++ b/server/Cargo.lock
@@ -282,6 +282,22 @@ dependencies = [
]
[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
name = "cpufeatures"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -498,6 +514,7 @@ name = "eyeballs"
version = "0.1.0"
dependencies = [
"futures",
+ "ldap3",
"rocket",
"rocket_db_pools",
"serde",
@@ -556,6 +573,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1079,6 +1111,40 @@ dependencies = [
]
[[package]]
+name = "lber"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2df7f9fd9f64cf8f59e1a4a0753fe7d575a5b38d3d7ac5758dcee9357d83ef0a"
+dependencies = [
+ "bytes",
+ "nom",
+]
+
+[[package]]
+name = "ldap3"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "166199a8207874a275144c8a94ff6eed5fcbf5c52303e4d9b4d53a0c7ac76554"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures",
+ "futures-util",
+ "lazy_static",
+ "lber",
+ "log",
+ "native-tls",
+ "nom",
+ "percent-encoding",
+ "thiserror 1.0.69",
+ "tokio",
+ "tokio-native-tls",
+ "tokio-stream",
+ "tokio-util",
+ "url",
+]
+
+[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1237,6 +1303,23 @@ dependencies = [
]
[[package]]
+name = "native-tls"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1341,6 +1424,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
+name = "openssl"
+version = "0.10.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.93",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1857,6 +1984,15 @@ dependencies = [
]
[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1879,6 +2015,29 @@ dependencies = [
]
[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2457,6 +2616,16 @@ dependencies = [
]
[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
name = "tokio-stream"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/server/Cargo.toml b/server/Cargo.toml
index d939013..10783ba 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2021"
[dependencies]
futures = "0.3.31"
+ldap3 = { version = "0.11.5", default-features = false, features = [ "native-tls", "tls", "tls-native", "tokio-native-tls" ] }
rocket = { version = "0.5.1", features = ["json", "secrets"] }
rocket_db_pools = { version = "0.2.0", features = ["sqlx_mysql"] }
serde = { version = "1.0", features = ["derive"] }
diff --git a/server/Rocket.toml b/server/Rocket.toml
index 00ead14..4f3137a 100644
--- a/server/Rocket.toml
+++ b/server/Rocket.toml
@@ -1,6 +1,9 @@
[default]
secret_key = "itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg="
session_max_age_days = 7
+ldap_url = "ldap://localhost:1389"
+ldap_users = "ou=users,dc=example,dc=org"
+ldap_filter = "(objectClass=posixAccount)"
[default.databases.eyeballs]
# root is needed for tests
diff --git a/server/migrations/1_initial_eyeballs.sql b/server/migrations/1_initial_eyeballs.sql
index aeb1470..2e5f771 100644
--- a/server/migrations/1_initial_eyeballs.sql
+++ b/server/migrations/1_initial_eyeballs.sql
@@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS users (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(256) NOT NULL UNIQUE,
name VARCHAR(1024) NOT NULL DEFAULT '',
- active BOOLEAN NOT NULL DEFAULT 1
+ dn VARCHAR(256) NULL
);
CREATE TABLE IF NOT EXISTS project_users (
diff --git a/server/src/auth.rs b/server/src/auth.rs
index db3a6a0..d74ec27 100644
--- a/server/src/auth.rs
+++ b/server/src/auth.rs
@@ -1,5 +1,6 @@
use core::net::IpAddr;
-use futures::future::TryFutureExt;
+use futures::{future::TryFutureExt, stream::TryStreamExt};
+use ldap3::{Ldap, LdapConnAsync};
use rocket::fairing::{self, AdHoc};
use rocket::form::Form;
use rocket::http::{Cookie, CookieJar, Status};
@@ -10,8 +11,10 @@ use rocket::serde::json::{self, Json};
use rocket::serde::{Deserialize, Serialize};
use rocket::{Build, Rocket, State};
use rocket_db_pools::{sqlx, Connection, Database};
+use std::borrow::Cow;
use std::collections::BTreeMap;
use std::sync::Mutex;
+use std::sync::OnceLock;
use std::time::Instant;
use time::Duration;
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
@@ -47,8 +50,11 @@ struct Login<'r> {
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
-struct AuthConfig {
+struct AuthConfig<'a> {
session_max_age_days: u32,
+ ldap_url: Cow<'a, str>,
+ ldap_users: Cow<'a, str>,
+ ldap_filter: Cow<'a, str>,
}
struct SessionsData {
@@ -60,6 +66,10 @@ struct Sessions {
data: Mutex<SessionsData>,
}
+struct LdapState {
+ ldap: OnceLock<ldap3::Ldap>,
+}
+
#[derive(Debug, Deserialize, Serialize)]
pub struct Session {
pub user_id: u64,
@@ -155,6 +165,27 @@ fn new_session(
}
}
+#[cfg(not(test))]
+async fn authenticate(ldap_state: &State<LdapState>, dn: &str, password: &str) -> bool {
+ let mut ldap = ldap_state.ldap.get().unwrap().clone();
+ let maybe_result = ldap.compare(dn, "userPassword", password.as_bytes()).await;
+ if let Ok(result) = maybe_result {
+ if let Ok(is_equal) = result.equal() {
+ return is_equal;
+ }
+ }
+ false
+}
+
+#[cfg(test)]
+async fn authenticate(_ldap_state: &State<LdapState>, dn: &str, password: &str) -> bool {
+ match dn {
+ "user" => password == "password",
+ "other" => password == "secret",
+ _ => false,
+ }
+}
+
#[utoipa::path(
responses(
(status = 200, description = "Login successful", body = api_model::StatusResponse,
@@ -168,35 +199,38 @@ fn new_session(
)]
#[post("/login", data = "<login>")]
async fn login(
- auth_config: &State<AuthConfig>,
+ auth_config: &State<AuthConfig<'_>>,
+ ldap_state: &State<LdapState>,
sessions: &State<Sessions>,
ipaddr: IpAddr,
cookies: &CookieJar<'_>,
mut db: Connection<Db>,
login: Form<Login<'_>>,
) -> Result<Json<api_model::StatusResponse>, Unauthorized<&'static str>> {
- if login.username == "user" && login.password == "password" {
- let user_id = sqlx::query!("SELECT id FROM users WHERE username=?", login.username)
+ let (user_id, maybe_dn) =
+ sqlx::query!("SELECT id,dn FROM users WHERE username=?", login.username)
.fetch_one(&mut **db)
- .map_ok(|r| r.id)
+ .map_ok(|r| (r.id, r.dn))
.map_err(|_| Unauthorized("Unknown username or password"))
- .await
- .unwrap();
+ .await?;
- let max_age = Duration::days(i64::from(auth_config.session_max_age_days));
- let session = new_session(sessions, user_id, ipaddr.to_string(), max_age);
+ if let Some(dn) = maybe_dn {
+ if authenticate(ldap_state, dn.as_str(), login.password).await {
+ let max_age = Duration::days(i64::from(auth_config.session_max_age_days));
+ let session = new_session(sessions, user_id, ipaddr.to_string(), max_age);
- let cookie = Cookie::build((SESSION_COOKIE, json::to_string(&session).unwrap()))
- .path("/api")
- .max_age(max_age)
- .http_only(true)
- .build();
+ let cookie = Cookie::build((SESSION_COOKIE, json::to_string(&session).unwrap()))
+ .path("/api")
+ .max_age(max_age)
+ .http_only(true)
+ .build();
- cookies.add_private(cookie);
- Ok(Json(STATUS_OK))
- } else {
- Err(Unauthorized("Unknown username or password"))
+ cookies.add_private(cookie);
+ return Ok(Json(STATUS_OK));
+ }
}
+
+ Err(Unauthorized("Unknown username or password"))
}
#[utoipa::path(
@@ -248,16 +282,150 @@ fn unauthorized() -> Json<api_model::StatusResponse> {
Json(STATUS_UNAUTHORIZED)
}
+async fn setup_ldap(
+ ldap_state: &LdapState,
+ config: &AuthConfig<'_>,
+) -> Result<Ldap, ldap3::LdapError> {
+ let (conn, ldap) = LdapConnAsync::new(&config.ldap_url).await?;
+ ldap3::drive!(conn);
+ let ret = ldap.clone();
+ ldap_state
+ .ldap
+ .set(ldap)
+ .expect("setup_ldap must only be called once");
+ Ok(ret)
+}
+
+#[derive(Debug)]
+#[allow(dead_code)]
+enum LdapOrSqlError {
+ LdapError(ldap3::LdapError),
+ SqlError(sqlx::Error),
+}
+
+async fn sync_ldap(
+ ldap_state: &LdapState,
+ config: &AuthConfig<'_>,
+ db: &Db,
+) -> Result<(), LdapOrSqlError> {
+ let mut ldap = setup_ldap(ldap_state, config)
+ .map_err(|e| LdapOrSqlError::LdapError(e))
+ .await?;
+ let (entries, _) = ldap
+ .search(
+ &config.ldap_users,
+ ldap3::Scope::OneLevel,
+ &config.ldap_filter,
+ vec!["uid"],
+ )
+ .map_err(|e| LdapOrSqlError::LdapError(e))
+ .await?
+ .success()
+ .map_err(|e| LdapOrSqlError::LdapError(e))?;
+
+ let mut tx = db.begin().await.unwrap();
+
+ // TODO: Insert/Update name as well as dn.
+
+ let db_users = sqlx::query!("SELECT id,username,dn FROM users ORDER BY username")
+ .fetch(&mut *tx)
+ .map_ok(|r| (r.id, r.username, r.dn))
+ .try_collect::<Vec<_>>()
+ .await
+ .unwrap();
+
+ let mut new_users: Vec<(String, String)> = Vec::new();
+ let mut updated_users: Vec<(u64, String)> = Vec::new();
+ let mut old_users: Vec<u64> = Vec::new();
+
+ let mut db_user = db_users.iter().peekable();
+
+ for entry in entries {
+ let se = ldap3::SearchEntry::construct(entry);
+ let uid = se.attrs.get("uid").unwrap().get(0).unwrap();
+ loop {
+ if let Some(du) = db_user.peek() {
+ if du.1 == *uid {
+ if du.2.as_ref().is_none_or(|x| *x != se.dn) {
+ updated_users.push((du.0, se.dn));
+ }
+ db_user.next();
+ break;
+ } else if du.1 < *uid {
+ old_users.push(du.0);
+ db_user.next();
+ continue;
+ }
+ }
+ new_users.push((uid.to_string(), se.dn));
+ break;
+ }
+ }
+
+ if !new_users.is_empty() {
+ let mut query_builder: sqlx::QueryBuilder<sqlx::MySql> =
+ sqlx::QueryBuilder::new("INSERT INTO users (username,dn) VALUES");
+
+ let mut first = true;
+ for pair in new_users {
+ if first {
+ first = false;
+ } else {
+ query_builder.push(",");
+ }
+ query_builder.push("(");
+ query_builder.push_bind(pair.0);
+ query_builder.push(",");
+ query_builder.push_bind(pair.1);
+ query_builder.push(")");
+ }
+
+ query_builder
+ .build()
+ .execute(&mut *tx)
+ .map_err(|e| LdapOrSqlError::SqlError(e))
+ .await?;
+ }
+
+ for pair in updated_users {
+ sqlx::query!("UPDATE users SET dn=? WHERE id=?", pair.1, pair.0)
+ .execute(&mut *tx)
+ .map_err(|e| LdapOrSqlError::SqlError(e))
+ .await?;
+ }
+
+ if !old_users.is_empty() {
+ let params = format!("?{}", ", ?".repeat(old_users.len() - 1));
+ let query_str = format!("UPDATE users SET dn=NULL WHERE id IN ({})", params);
+ let mut query = sqlx::query(&query_str);
+
+ for id in old_users {
+ query = query.bind(id);
+ }
+
+ query
+ .execute(&mut *tx)
+ .map_err(|e| LdapOrSqlError::SqlError(e))
+ .await?;
+ }
+
+ tx.commit().map_err(|e| LdapOrSqlError::SqlError(e)).await?;
+
+ Ok(())
+}
+
#[cfg(not(test))]
async fn run_import(rocket: Rocket<Build>) -> fairing::Result {
- match Db::fetch(&rocket) {
- // TODO: Replace with ldap
- Some(db) => match sqlx::query!("INSERT IGNORE INTO users (username) VALUES (?)", "user")
- .execute(&**db)
- .await
- {
- Ok(_) => Ok(rocket),
- Err(_) => Err(rocket),
+ match rocket.state::<AuthConfig>() {
+ Some(config) => match rocket.state::<LdapState>() {
+ Some(ldap) => match Db::fetch(&rocket) {
+ Some(db) => match sync_ldap(ldap, config, db).await {
+ Ok(_) => Ok(rocket),
+ Err(_) => Err(rocket),
+ },
+ None => Err(rocket),
+ },
+ None => Err(rocket),
},
None => Err(rocket),
}
@@ -267,8 +435,10 @@ async fn run_import(rocket: Rocket<Build>) -> fairing::Result {
async fn run_import(rocket: Rocket<Build>) -> fairing::Result {
match Db::fetch(&rocket) {
Some(db) => match sqlx::query!(
- "INSERT IGNORE INTO users (username) VALUES (?), (?)",
+ "INSERT IGNORE INTO users (username,dn) VALUES (?,?), (?,?)",
"user",
+ "user",
+ "other",
"other",
)
.execute(&**db)
@@ -292,6 +462,9 @@ pub fn stage(basepath: &str) -> AdHoc {
}),
})
.attach(AdHoc::config::<AuthConfig>())
+ .manage(LdapState {
+ ldap: OnceLock::new(),
+ })
.attach(AdHoc::try_on_ignite("Auth Import", run_import))
.mount(l_basepath.clone(), routes![login, logout, status])
.register(l_basepath, catchers![unauthorized])
diff --git a/server/src/main.rs b/server/src/main.rs
index 6f66866..596eb5b 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -135,7 +135,7 @@ async fn get_project(
projectid: u64,
) -> Result<Json<api_model::Project>, NotFound<&'static str>> {
let users = sqlx::query!(
- "SELECT id, username, name, active, default_role, maintainer FROM users JOIN project_users ON project_users.user=users.id WHERE project_users.project=?",
+ "SELECT id, username, name, dn, default_role, maintainer FROM users JOIN project_users ON project_users.user=users.id WHERE project_users.project=?",
projectid)
.fetch(&mut ***db)
.map_ok(|r| api_model::ProjectUserEntry {
@@ -143,7 +143,7 @@ async fn get_project(
id: r.id,
username: r.username,
name: r.name,
- active: r.active != 0,
+ active: r.dn.is_some(),
},
default_role: api_model::UserReviewRole::try_from(r.default_role).unwrap(),
maintainer: r.maintainer != 0,
@@ -440,7 +440,7 @@ async fn reviews(
let uw_offset = offset.unwrap_or(0);
let uw_limit = limit.unwrap_or(10);
let entries = sqlx::query!(
- "SELECT reviews.id AS id,title,state,progress,users.id AS user_id,users.username AS username,users.name AS name,users.active AS user_active FROM reviews JOIN users ON users.id=owner WHERE project=? ORDER BY id DESC LIMIT ? OFFSET ?",
+ "SELECT reviews.id AS id,title,state,progress,users.id AS user_id,users.username AS username,users.name AS name,users.dn AS user_dn FROM reviews JOIN users ON users.id=owner WHERE project=? ORDER BY id DESC LIMIT ? OFFSET ?",
projectid, uw_limit, uw_offset)
.fetch(&mut **db)
.map_ok(|r| api_model::ReviewEntry {
@@ -450,7 +450,7 @@ async fn reviews(
id: r.user_id,
username: r.username,
name: r.name,
- active: r.user_active != 0,
+ active: r.user_dn.is_some(),
},
state: api_model::ReviewState::try_from(r.state).unwrap(),
progress: r.progress,
@@ -497,7 +497,7 @@ async fn review(
let mut projectid = 0;
let mut review = sqlx::query!(
- "SELECT reviews.id AS id,project,title,description,state,progress,users.id AS user_id,users.username AS username,users.name AS name,users.active AS user_active FROM reviews JOIN users ON users.id=owner WHERE reviews.id=?",
+ "SELECT reviews.id AS id,project,title,description,state,progress,users.id AS user_id,users.username AS username,users.name AS name,users.dn AS user_dn FROM reviews JOIN users ON users.id=owner WHERE reviews.id=?",
reviewid)
.fetch_one(&mut **db)
.map_ok(|r| {
@@ -511,7 +511,7 @@ async fn review(
id: r.user_id,
username: r.username,
name: r.name,
- active: r.user_active != 0,
+ active: r.user_dn.is_some(),
},
users: Vec::new(),
state: api_model::ReviewState::try_from(r.state).unwrap(),
@@ -522,7 +522,7 @@ async fn review(
.await?;
let mut users = sqlx::query!(
- "SELECT id,username,name,active,project_users.default_role AS role FROM users JOIN project_users ON project_users.user=id WHERE project_users.project=? ORDER BY role,username,id",
+ "SELECT id,username,name,dn,project_users.default_role AS role FROM users JOIN project_users ON project_users.user=id WHERE project_users.project=? ORDER BY role,username,id",
projectid)
.fetch(&mut **db)
.map_ok(|r| api_model::ReviewUserEntry {
@@ -530,7 +530,7 @@ async fn review(
id: r.id,
username: r.username,
name: r.name,
- active: r.active != 0,
+ active: r.dn.is_some(),
},
role: api_model::UserReviewRole::try_from(r.role).unwrap(),
})
@@ -539,7 +539,7 @@ async fn review(
.unwrap();
let override_users = sqlx::query!(
- "SELECT id,username,name,active,review_users.role AS role FROM users JOIN review_users ON review_users.user=id WHERE review_users.review=? ORDER BY role,username,id",
+ "SELECT id,username,name,dn,review_users.role AS role FROM users JOIN review_users ON review_users.user=id WHERE review_users.review=? ORDER BY role,username,id",
reviewid)
.fetch(&mut **db)
.map_ok(|r| api_model::ReviewUserEntry {
@@ -547,7 +547,7 @@ async fn review(
id: r.id,
username: r.username,
name: r.name,
- active: r.active != 0,
+ active: r.dn.is_some(),
},
role: api_model::UserReviewRole::try_from(r.role).unwrap(),
})
@@ -589,7 +589,7 @@ async fn users(
let uw_offset = offset.unwrap_or(0);
let uw_limit = limit.unwrap_or(10);
let entries = sqlx::query!(
- "SELECT id,username,name,active FROM users ORDER BY username LIMIT ? OFFSET ?",
+ "SELECT id,username,name,dn FROM users ORDER BY username LIMIT ? OFFSET ?",
uw_limit,
uw_offset
)
@@ -598,7 +598,7 @@ async fn users(
id: r.id,
username: r.username,
name: r.name,
- active: r.active != 0,
+ active: r.dn.is_some(),
})
.try_collect::<Vec<_>>()
.await