summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2025-01-04 02:31:25 +0100
committerJoel Klinghed <the_jk@spawned.biz>2025-01-04 02:31:25 +0100
commit0be7bb847f64367fbc64fbdea2d11684fbcdaa8f (patch)
tree4a22ceeadadc2767437614712114c2392402dc2a /server
parentd09ffb6ee8b872c69321b3a9d992f278224741dc (diff)
Support ldap in auth
Non-test auth is now using ldap for account syncing and authentication. Test auth is still using hardcoded users (user and other). But it is now also possible to login as "other".
Diffstat (limited to 'server')
-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