Mercurial
view mrjunejune/test/admin_api_test.c @ 267:a78f5986011f
discover bundled production runtimes
Locate Bazel-packaged Copilot and Python binaries by stable suffix instead of hardcoding an external repository prefix.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Fri, 07 Aug 2026 12:58:55 -0700 |
| parents | 04fee26ecce0 |
| children |
line wrap: on
line source
/* * admin_api_test.c — unit tests for the admin API handlers. * * Tests run in-process against real store + crypto (no network). * A fresh SQLite database is created per group. */ #include "mrjunejune/admin_api.h" #include "mrjunejune/auth_api.h" #include "auth/auth_crypto.h" #include "auth/auth_store.h" #include "deita/deita.h" #include "dowa/dowa.h" #include "seobeo/seobeo.h" #include <assert.h> #include <stdarg.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <openssl/crypto.h> /* ------------------------------------------------------------------ */ /* Utilities */ /* ------------------------------------------------------------------ */ #define ASSERT(cond) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL [%s:%d]: %s\n", __FILE__, __LINE__, #cond); \ abort(); \ } \ } while (0) #define TEST(name) \ do { fprintf(stdout, " %-60s", name); fflush(stdout); } while (0) #define PASS() \ do { fprintf(stdout, "PASS\n"); } while (0) static const uint8 k_secret[64] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, }; static void make_temp_db(char *out, size_t capacity) { snprintf(out, capacity, "/tmp/admin_api_test_XXXXXX"); int fd = mkstemp(out); ASSERT(fd >= 0); close(fd); } static void init_auth(const char *db) { boolean ok = Auth_API_Init( db, k_secret, sizeof(k_secret), NULL, NULL, NULL, AUTH_API_SESSION_IDLE_TTL_DEFAULT, AUTH_API_SESSION_ABS_TTL_DEFAULT, AUTH_API_GUEST_TTL_DEFAULT, TRUE); /* dev_insecure_cookie */ ASSERT(ok); } static Seobeo_Request_Entry *make_request(Dowa_Arena *arena, ...) { Seobeo_Request_Entry *req = NULL; va_list ap; va_start(ap, arena); const char *key; while ((key = va_arg(ap, const char *)) != NULL) { char *k = (char *)key; const char *val = va_arg(ap, const char *); char *stored = (char *)val; if (strcmp(key, "Body") == 0) stored = Dowa_Arena_Copy(arena, val, strlen(val) + 1); Dowa_HashMap_Push_Arena(req, k, stored, arena); } va_end(ap); return req; } static const char *resp_status(Seobeo_Request_Entry *resp) { void *p = Dowa_HashMap_Get_Ptr(resp, "status"); return p ? ((Seobeo_Request_Entry *)p)->value : NULL; } static const char *resp_body(Seobeo_Request_Entry *resp) { void *p = Dowa_HashMap_Get_Ptr(resp, "body"); return p ? ((Seobeo_Request_Entry *)p)->value : NULL; } /* * Create a user and return the new user_id. * Hash a real password so the store accepts it. */ static boolean create_test_user( Auth_Store *store, const char *username, const char *password, const char *role, boolean must_change, char id_out[37]) { char hash[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE]; if (Auth_Crypto_Password_Hash(password, hash, sizeof(hash)) != AUTH_CRYPTO_OK) return FALSE; Auth_Store_Result r = Auth_Store_Create_User( store, username, hash, role, must_change, id_out); OPENSSL_cleanse(hash, sizeof(hash)); return r == AUTH_STORE_OK; } /* * Login as a user and return the session cookie value + CSRF token. * Returns TRUE on success. */ static boolean login_user( Dowa_Arena *arena, const char *username, const char *password, char session_cookie_out[], size_t cookie_cap, char csrf_out[], size_t csrf_cap) { /* 1. Get a guest session to obtain a CSRF token + guest cookie. */ Seobeo_Request_Entry *sess_req = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", NULL, NULL); Seobeo_Request_Entry *sess_resp = Auth_API_Test_Session_Handler(sess_req, arena); const char *sess_body = resp_body(sess_resp); ASSERT(sess_body); /* Extract csrfToken from session response. */ const char *csrf_start = strstr(sess_body, "\"csrfToken\":\""); ASSERT(csrf_start); csrf_start += strlen("\"csrfToken\":\""); const char *csrf_end = strchr(csrf_start, '"'); ASSERT(csrf_end); size_t csrf_len = (size_t)(csrf_end - csrf_start); ASSERT(csrf_len < csrf_cap); memcpy(csrf_out, csrf_start, csrf_len); csrf_out[csrf_len] = '\0'; /* Extract the guest cookie from Set-Cookie to send with login. */ char guest_cookie_val[512] = {0}; void *sc_kv = Dowa_HashMap_Get_Ptr(sess_resp, "Set-Cookie"); if (sc_kv) { const char *sc_hdr = ((Seobeo_Request_Entry *)sc_kv)->value; const char *gc_start = strstr(sc_hdr, "mjj_guest="); if (gc_start) { gc_start += strlen("mjj_guest="); const char *gc_end = strchr(gc_start, ';'); size_t gclen = gc_end ? (size_t)(gc_end - gc_start) : strlen(gc_start); if (gclen < sizeof(guest_cookie_val)) { memcpy(guest_cookie_val, gc_start, gclen); guest_cookie_val[gclen] = '\0'; } } } /* 2. Call login with the guest cookie so Resolve_Principal finds the same guest. */ char login_body[512]; snprintf(login_body, sizeof(login_body), "{\"username\":\"%s\",\"password\":\"%s\",\"csrfToken\":\"%s\"}", username, password, csrf_out); char cookie_hdr[512] = {0}; if (guest_cookie_val[0] != '\0') snprintf(cookie_hdr, sizeof(cookie_hdr), "mjj_guest=%s", guest_cookie_val); Seobeo_Request_Entry *login_req; if (cookie_hdr[0] != '\0') { login_req = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", "Origin", "http://localhost", "Cookie", cookie_hdr, "Body", login_body, NULL, NULL); } else { login_req = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", "Origin", "http://localhost", "Body", login_body, NULL, NULL); } Seobeo_Request_Entry *login_resp = Auth_API_Test_Login_Handler(login_req, arena); const char *st = resp_status(login_resp); if (!st || strcmp(st, "200") != 0) return FALSE; /* 3. Extract session cookie value. */ void *lsc_kv = Dowa_HashMap_Get_Ptr(login_resp, "Set-Cookie"); if (!lsc_kv) return FALSE; const char *lsc_hdr = ((Seobeo_Request_Entry *)lsc_kv)->value; const char *cookie_start = strstr(lsc_hdr, "mjj_session="); if (!cookie_start) return FALSE; cookie_start += strlen("mjj_session="); const char *cookie_end = strchr(cookie_start, ';'); size_t clen = cookie_end ? (size_t)(cookie_end - cookie_start) : strlen(cookie_start); ASSERT(clen < cookie_cap); memcpy(session_cookie_out, cookie_start, clen); session_cookie_out[clen] = '\0'; /* 4. Fetch a fresh CSRF from the authenticated session. */ char auth_cookie_hdr[512]; snprintf(auth_cookie_hdr, sizeof(auth_cookie_hdr), "mjj_session=%s", session_cookie_out); Seobeo_Request_Entry *csrfsess_req = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", "Cookie", auth_cookie_hdr, NULL, NULL); Seobeo_Request_Entry *csrfsess_resp = Auth_API_Test_Session_Handler(csrfsess_req, arena); const char *csrfsess_body = resp_body(csrfsess_resp); ASSERT(csrfsess_body); const char *cs2 = strstr(csrfsess_body, "\"csrfToken\":\""); ASSERT(cs2); cs2 += strlen("\"csrfToken\":\""); const char *ce2 = strchr(cs2, '"'); ASSERT(ce2); size_t cl2 = (size_t)(ce2 - cs2); ASSERT(cl2 < csrf_cap); memcpy(csrf_out, cs2, cl2); csrf_out[cl2] = '\0'; return TRUE; } /* Build a request with session cookie + CSRF header + body. */ static Seobeo_Request_Entry *make_admin_req( Dowa_Arena *arena, const char *method, const char *session_cookie, const char *csrf_token, const char *body, const char *id_param) { /* Allocate cookie_hdr from arena so the pointer stays valid after return. */ char *cookie_hdr = Dowa_Arena_Allocate(arena, 528); ASSERT(cookie_hdr); snprintf(cookie_hdr, 528, "mjj_session=%s", session_cookie); Seobeo_Request_Entry *req = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", "Origin", "http://localhost", "Cookie", cookie_hdr, "X-CSRF-Token", (char *)csrf_token, "Body", body ? (char *)body : "", NULL, NULL); if (id_param) Dowa_HashMap_Push_Arena(req, ":id", (char *)id_param, arena); return req; (void)method; } /* ------------------------------------------------------------------ */ /* Test group: non-admin denial */ /* ------------------------------------------------------------------ */ static void test_non_admin_denial(void) { printf("\n[non-admin denial]\n"); char db[256]; make_temp_db(db, sizeof(db)); init_auth(db); Auth_Store *store = Auth_API_Get_Store(); char member_id[37]; ASSERT(create_test_user(store, "member1", "password123456", "member", FALSE, member_id)); Dowa_Arena *arena = Dowa_Arena_Create(128 * 1024); char scookie[512], csrf[512]; ASSERT(login_user(arena, "member1", "password123456", scookie, sizeof(scookie), csrf, sizeof(csrf))); TEST("member cannot list users (403)"); { Seobeo_Request_Entry *req = make_admin_req(arena, "GET", scookie, csrf, NULL, NULL); Seobeo_Request_Entry *resp = Admin_API_Test_List_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "403") == 0); } PASS(); TEST("member cannot create users (403)"); { Seobeo_Request_Entry *req = make_admin_req( arena, "POST", scookie, csrf, "{\"username\":\"hack\",\"temporaryPassword\":\"password123456\"}", NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Create_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "403") == 0); } PASS(); TEST("unauthenticated list → 401"); { Seobeo_Request_Entry *req = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", NULL, NULL); Seobeo_Request_Entry *resp = Admin_API_Test_List_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "401") == 0); /* No page content must leak */ const char *body = resp_body(resp); ASSERT(!body || strstr(body, "password") == NULL); } PASS(); TEST("unauthenticated page → redirect (not content)"); { Seobeo_Request_Entry *req = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", NULL, NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Page_Handler(req, arena); const char *st = resp_status(resp); /* Must be 302 redirect; body must be empty/no admin content */ ASSERT(st && (strcmp(st, "302") == 0 || strcmp(st, "401") == 0)); const char *body = resp_body(resp); ASSERT(!body || strstr(body, "<table") == NULL); } PASS(); Dowa_Arena_Free(arena); Auth_API_Destroy(); unlink(db); } /* ------------------------------------------------------------------ */ /* Test group: forced-password denial */ /* ------------------------------------------------------------------ */ static void test_forced_password_denial(void) { printf("\n[forced-password denial]\n"); char db[256]; make_temp_db(db, sizeof(db)); init_auth(db); Auth_Store *store = Auth_API_Get_Store(); char admin_id[37]; ASSERT(create_test_user(store, "fadmin", "password123456", "admin", TRUE, admin_id)); Dowa_Arena *arena = Dowa_Arena_Create(128 * 1024); char scookie[512], csrf[512]; ASSERT(login_user(arena, "fadmin", "password123456", scookie, sizeof(scookie), csrf, sizeof(csrf))); TEST("admin with must_change_password blocked from list (403)"); { Seobeo_Request_Entry *req = make_admin_req(arena, "GET", scookie, csrf, NULL, NULL); Seobeo_Request_Entry *resp = Admin_API_Test_List_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "403") == 0); } PASS(); TEST("admin with must_change_password blocked from create (403)"); { Seobeo_Request_Entry *req = make_admin_req( arena, "POST", scookie, csrf, "{\"username\":\"newu\",\"temporaryPassword\":\"password123456\"}", NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Create_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "403") == 0); } PASS(); Dowa_Arena_Free(arena); Auth_API_Destroy(); unlink(db); } /* ------------------------------------------------------------------ */ /* Test group: create user */ /* ------------------------------------------------------------------ */ static void test_create_user(void) { printf("\n[create user]\n"); char db[256]; make_temp_db(db, sizeof(db)); init_auth(db); Auth_Store *store = Auth_API_Get_Store(); char admin_id[37]; ASSERT(create_test_user(store, "admin1", "password123456", "admin", FALSE, admin_id)); Dowa_Arena *arena = Dowa_Arena_Create(128 * 1024); char scookie[512], csrf[512]; ASSERT(login_user(arena, "admin1", "password123456", scookie, sizeof(scookie), csrf, sizeof(csrf))); TEST("create user succeeds → 201, mustChangePassword=true"); { Seobeo_Request_Entry *req = make_admin_req( arena, "POST", scookie, csrf, "{\"username\":\"newuser\",\"temporaryPassword\":\"temppass123456\"}", NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Create_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "201") == 0); const char *body = resp_body(resp); ASSERT(body && strstr(body, "\"mustChangePassword\":true") != NULL); /* No hash/digest in response */ ASSERT(strstr(body, "hash") == NULL); ASSERT(strstr(body, "password_hash") == NULL); ASSERT(strstr(body, "digest") == NULL); ASSERT(strstr(body, "session") == NULL); } PASS(); TEST("duplicate username → 409"); { Seobeo_Request_Entry *req = make_admin_req( arena, "POST", scookie, csrf, "{\"username\":\"newuser\",\"temporaryPassword\":\"temppass123456\"}", NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Create_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "409") == 0); } PASS(); TEST("short password → 400 policy error"); { Seobeo_Request_Entry *req = make_admin_req( arena, "POST", scookie, csrf, "{\"username\":\"shortpw\",\"temporaryPassword\":\"tooshort\"}", NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Create_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "400") == 0); const char *body = resp_body(resp); ASSERT(body && strstr(body, "password_policy") != NULL); } PASS(); TEST("invalid role → 400"); { Seobeo_Request_Entry *req = make_admin_req( arena, "POST", scookie, csrf, "{\"username\":\"badrole\",\"temporaryPassword\":\"temppass123456\",\"role\":\"superuser\"}", NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Create_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "400") == 0); } PASS(); TEST("CSRF rejection → 403"); { char bad_cookie_hdr[512]; snprintf(bad_cookie_hdr, sizeof(bad_cookie_hdr), "mjj_session=%s", scookie); Seobeo_Request_Entry *req = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", "Origin", "http://localhost", "Cookie", bad_cookie_hdr, "X-CSRF-Token", "BADCSRF", "Body", "{\"username\":\"x\",\"temporaryPassword\":\"temppass123456\"}", NULL, NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Create_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "403") == 0); } PASS(); TEST("origin rejection → 403"); { char cookie_hdr[512]; snprintf(cookie_hdr, sizeof(cookie_hdr), "mjj_session=%s", scookie); Seobeo_Request_Entry *req = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", "Origin", "http://evil.com", "Cookie", cookie_hdr, "X-CSRF-Token", csrf, "Body", "{\"username\":\"y\",\"temporaryPassword\":\"temppass123456\"}", NULL, NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Create_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "403") == 0); } PASS(); TEST("audit insertion failure rolls back user creation"); { Deita_Connection *connection = Deita_Connection_Create( DEITA_DATABASE_TYPE_SQLITE3, db); ASSERT(connection); ASSERT(Deita_Query_Execute_Update( connection, "CREATE TRIGGER fail_admin_api_audit" " BEFORE INSERT ON admin_audit_log" " BEGIN SELECT RAISE(ABORT, 'forced audit failure'); END") >= 0); Deita_Connection_Close(connection); Seobeo_Request_Entry *req = make_admin_req( arena, "POST", scookie, csrf, "{\"username\":\"auditfail\"," "\"temporaryPassword\":\"temppass123456\"}", NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Create_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "500") == 0); Auth_User_Auth_Record record; memset(&record, 0, sizeof(record)); ASSERT(Auth_Store_Find_User_By_Username( store, "auditfail", &record) == AUTH_STORE_NOT_FOUND); connection = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, db); ASSERT(connection); ASSERT(Deita_Query_Execute_Update( connection, "DROP TRIGGER fail_admin_api_audit") >= 0); Deita_Connection_Close(connection); } PASS(); Dowa_Arena_Free(arena); Auth_API_Destroy(); unlink(db); } /* ------------------------------------------------------------------ */ /* Test group: enable/disable */ /* ------------------------------------------------------------------ */ static void test_enable_disable(void) { printf("\n[enable/disable]\n"); char db[256]; make_temp_db(db, sizeof(db)); init_auth(db); Auth_Store *store = Auth_API_Get_Store(); char admin_id[37], user_id[37]; ASSERT(create_test_user(store, "admin1", "password123456", "admin", FALSE, admin_id)); ASSERT(create_test_user(store, "user1", "password123456", "member", FALSE, user_id)); Dowa_Arena *arena = Dowa_Arena_Create(128 * 1024); char scookie[512], csrf[512]; ASSERT(login_user(arena, "admin1", "password123456", scookie, sizeof(scookie), csrf, sizeof(csrf))); const char *user_token_digest = "1111111111111111111111111111111111111111111111111111111111111111"; const char *user_csrf_digest = "2222222222222222222222222222222222222222222222222222222222222222"; int64 session_now = (int64)time(NULL); Auth_Session_Record user_session; ASSERT(Auth_Store_Create_Session( store, user_id, user_token_digest, user_csrf_digest, 3600, 86400, session_now, &user_session) == AUTH_STORE_OK); TEST("disable user → 200, status=disabled"); { Seobeo_Request_Entry *req = make_admin_req( arena, "PATCH", scookie, csrf, "{\"op\":\"disable\"}", user_id); Seobeo_Request_Entry *resp = Admin_API_Test_Update_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "200") == 0); const char *body = resp_body(resp); ASSERT(body && strstr(body, "\"status\":\"disabled\"") != NULL); Auth_Session_Record found_session; Auth_User_Record found_user; ASSERT(Auth_Store_Find_Session( store, user_token_digest, session_now + 1, &found_session, &found_user) == AUTH_STORE_REVOKED); } PASS(); TEST("enable user → 200, status=active"); { Seobeo_Request_Entry *req = make_admin_req( arena, "PATCH", scookie, csrf, "{\"op\":\"enable\"}", user_id); Seobeo_Request_Entry *resp = Admin_API_Test_Update_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "200") == 0); const char *body = resp_body(resp); ASSERT(body && strstr(body, "\"status\":\"active\"") != NULL); Auth_Session_Record found_session; Auth_User_Record found_user; ASSERT(Auth_Store_Find_Session( store, user_token_digest, session_now + 2, &found_session, &found_user) == AUTH_STORE_REVOKED); } PASS(); TEST("disable last active admin → 409 last_admin"); { /* admin1 is the only admin */ Seobeo_Request_Entry *req = make_admin_req( arena, "PATCH", scookie, csrf, "{\"op\":\"disable\"}", admin_id); Seobeo_Request_Entry *resp = Admin_API_Test_Update_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "409") == 0); const char *body = resp_body(resp); ASSERT(body && strstr(body, "last_admin") != NULL); } PASS(); TEST("response body contains no secrets"); { Seobeo_Request_Entry *req = make_admin_req( arena, "PATCH", scookie, csrf, "{\"op\":\"enable\"}", user_id); Seobeo_Request_Entry *resp = Admin_API_Test_Update_Handler(req, arena); const char *body = resp_body(resp); ASSERT(body && strstr(body, "hash") == NULL); ASSERT(body && strstr(body, "digest") == NULL); ASSERT(body && strstr(body, "session") == NULL); } PASS(); Dowa_Arena_Free(arena); Auth_API_Destroy(); unlink(db); } /* ------------------------------------------------------------------ */ /* Test group: role update */ /* ------------------------------------------------------------------ */ static void test_role_update(void) { printf("\n[role update]\n"); char db[256]; make_temp_db(db, sizeof(db)); init_auth(db); Auth_Store *store = Auth_API_Get_Store(); char admin_id[37], user_id[37]; ASSERT(create_test_user(store, "admin1", "password123456", "admin", FALSE, admin_id)); ASSERT(create_test_user(store, "user1", "password123456", "member", FALSE, user_id)); Dowa_Arena *arena = Dowa_Arena_Create(128 * 1024); char scookie[512], csrf[512]; ASSERT(login_user(arena, "admin1", "password123456", scookie, sizeof(scookie), csrf, sizeof(csrf))); TEST("promote member to admin → 200"); { Seobeo_Request_Entry *req = make_admin_req( arena, "PATCH", scookie, csrf, "{\"op\":\"set_role\",\"role\":\"admin\"}", user_id); Seobeo_Request_Entry *resp = Admin_API_Test_Update_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "200") == 0); ASSERT(strstr(resp_body(resp), "\"role\":\"admin\"") != NULL); } PASS(); TEST("demote admin to member → 200 (2 admins → safe)"); { Seobeo_Request_Entry *req = make_admin_req( arena, "PATCH", scookie, csrf, "{\"op\":\"set_role\",\"role\":\"member\"}", user_id); Seobeo_Request_Entry *resp = Admin_API_Test_Update_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "200") == 0); } PASS(); TEST("demote last admin → 409"); { Seobeo_Request_Entry *req = make_admin_req( arena, "PATCH", scookie, csrf, "{\"op\":\"set_role\",\"role\":\"member\"}", admin_id); Seobeo_Request_Entry *resp = Admin_API_Test_Update_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "409") == 0); } PASS(); TEST("invalid role value → 400"); { Seobeo_Request_Entry *req = make_admin_req( arena, "PATCH", scookie, csrf, "{\"op\":\"set_role\",\"role\":\"superuser\"}", user_id); Seobeo_Request_Entry *resp = Admin_API_Test_Update_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "400") == 0); } PASS(); Dowa_Arena_Free(arena); Auth_API_Destroy(); unlink(db); } /* ------------------------------------------------------------------ */ /* Test group: temp password reset */ /* ------------------------------------------------------------------ */ static void test_temp_reset(void) { printf("\n[temp password reset]\n"); char db[256]; make_temp_db(db, sizeof(db)); init_auth(db); Auth_Store *store = Auth_API_Get_Store(); char admin_id[37], user_id[37]; ASSERT(create_test_user(store, "admin1", "password123456", "admin", FALSE, admin_id)); ASSERT(create_test_user(store, "user1", "password123456", "member", FALSE, user_id)); Dowa_Arena *arena = Dowa_Arena_Create(128 * 1024); char scookie[512], csrf[512]; ASSERT(login_user(arena, "admin1", "password123456", scookie, sizeof(scookie), csrf, sizeof(csrf))); TEST("temp reset sets mustChangePassword=true in response"); { Seobeo_Request_Entry *req = make_admin_req( arena, "PATCH", scookie, csrf, "{\"op\":\"temp_reset\",\"temporaryPassword\":\"newtemp123456\"}", user_id); Seobeo_Request_Entry *resp = Admin_API_Test_Update_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "200") == 0); const char *body = resp_body(resp); ASSERT(body && strstr(body, "\"mustChangePassword\":true") != NULL); /* No raw password, hash, or digest in response. */ ASSERT(strstr(body, "hash") == NULL); ASSERT(strstr(body, "digest") == NULL); ASSERT(strstr(body, "newtemp123456") == NULL); } PASS(); TEST("temp reset short password → 400"); { Seobeo_Request_Entry *req = make_admin_req( arena, "PATCH", scookie, csrf, "{\"op\":\"temp_reset\",\"temporaryPassword\":\"short\"}", user_id); Seobeo_Request_Entry *resp = Admin_API_Test_Update_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "400") == 0); } PASS(); TEST("user can login after reset with new temp password"); { /* Login as user1 with the new temp password. */ char u_cookie[512], u_csrf[512]; boolean ok = login_user(arena, "user1", "newtemp123456", u_cookie, sizeof(u_cookie), u_csrf, sizeof(u_csrf)); ASSERT(ok); /* Confirm must_change_password is set. */ char cookie_hdr[512]; snprintf(cookie_hdr, sizeof(cookie_hdr), "mjj_session=%s", u_cookie); Seobeo_Request_Entry *sreq = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", "Cookie", cookie_hdr, NULL, NULL); Seobeo_Request_Entry *sresp = Auth_API_Test_Session_Handler(sreq, arena); const char *sbody = resp_body(sresp); ASSERT(sbody && strstr(sbody, "\"mustChangePassword\":true") != NULL); } PASS(); Dowa_Arena_Free(arena); Auth_API_Destroy(); unlink(db); } /* ------------------------------------------------------------------ */ /* Test group: session revocation */ /* ------------------------------------------------------------------ */ static void test_session_revocation(void) { printf("\n[session revocation]\n"); char db[256]; make_temp_db(db, sizeof(db)); init_auth(db); Auth_Store *store = Auth_API_Get_Store(); char admin_id[37], user_id[37]; ASSERT(create_test_user(store, "admin1", "password123456", "admin", FALSE, admin_id)); ASSERT(create_test_user(store, "user1", "password123456", "member", FALSE, user_id)); Dowa_Arena *arena = Dowa_Arena_Create(128 * 1024); char admin_cookie[512], admin_csrf[512]; ASSERT(login_user(arena, "admin1", "password123456", admin_cookie, sizeof(admin_cookie), admin_csrf, sizeof(admin_csrf))); /* Login user1 to create a session. */ char u_cookie[512], u_csrf[512]; ASSERT(login_user(arena, "user1", "password123456", u_cookie, sizeof(u_cookie), u_csrf, sizeof(u_csrf))); TEST("DELETE /api/admin/users/:id/sessions → 200"); { Seobeo_Request_Entry *req = make_admin_req( arena, "DELETE", admin_cookie, admin_csrf, NULL, user_id); Seobeo_Request_Entry *resp = Admin_API_Test_Revoke_Sessions_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "200") == 0); } PASS(); TEST("user session is invalid after revocation"); { char cookie_hdr[512]; snprintf(cookie_hdr, sizeof(cookie_hdr), "mjj_session=%s", u_cookie); Seobeo_Request_Entry *sreq = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", "Cookie", cookie_hdr, NULL, NULL); Seobeo_Request_Entry *sresp = Auth_API_Test_Session_Handler(sreq, arena); const char *sbody = resp_body(sresp); /* Should fall back to guest after session revoked */ ASSERT(sbody && strstr(sbody, "\"kind\":\"user\"") == NULL); } PASS(); TEST("revoke sessions for non-existent user → 404"); { Seobeo_Request_Entry *req = make_admin_req( arena, "DELETE", admin_cookie, admin_csrf, NULL, "00000000-0000-0000-0000-000000000000"); Seobeo_Request_Entry *resp = Admin_API_Test_Revoke_Sessions_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "404") == 0); } PASS(); TEST("CSRF required for DELETE → 403 without CSRF"); { char cookie_hdr[512]; snprintf(cookie_hdr, sizeof(cookie_hdr), "mjj_session=%s", admin_cookie); Seobeo_Request_Entry *req = make_request( arena, "Host", "localhost", "Remote-Addr", "127.0.0.1", "Origin", "http://localhost", "Cookie", cookie_hdr, ":id", user_id, NULL, NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Revoke_Sessions_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "403") == 0); } PASS(); Dowa_Arena_Free(arena); Auth_API_Destroy(); unlink(db); } /* ------------------------------------------------------------------ */ /* Test group: no secrets in responses */ /* ------------------------------------------------------------------ */ static void test_no_secret_fields(void) { printf("\n[no secret fields in JSON/page]\n"); char db[256]; make_temp_db(db, sizeof(db)); init_auth(db); Auth_Store *store = Auth_API_Get_Store(); char admin_id[37]; ASSERT(create_test_user(store, "admin1", "password123456", "admin", FALSE, admin_id)); ASSERT(create_test_user(store, "user2", "password123456", "member", FALSE, admin_id)); Dowa_Arena *arena = Dowa_Arena_Create(128 * 1024); char scookie[512], csrf[512]; ASSERT(login_user(arena, "admin1", "password123456", scookie, sizeof(scookie), csrf, sizeof(csrf))); TEST("list response has no password_hash, session, digest, or guest fields"); { Seobeo_Request_Entry *req = make_admin_req(arena, "GET", scookie, csrf, NULL, NULL); Seobeo_Request_Entry *resp = Admin_API_Test_List_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "200") == 0); const char *body = resp_body(resp); ASSERT(body); ASSERT(strstr(body, "password_hash") == NULL); ASSERT(strstr(body, "passwordHash") == NULL); ASSERT(strstr(body, "token_digest") == NULL); ASSERT(strstr(body, "csrf_digest") == NULL); ASSERT(strstr(body, "guest_id") == NULL); ASSERT(strstr(body, "ip_binding") == NULL); ASSERT(strstr(body, "session") == NULL); } PASS(); TEST("audit log for create contains no credentials"); { /* Create a user, then verify audit log doesn't have hash/password. */ Seobeo_Request_Entry *req = make_admin_req( arena, "POST", scookie, csrf, "{\"username\":\"audituser\",\"temporaryPassword\":\"auditpass123456\"}", NULL); Seobeo_Request_Entry *resp = Admin_API_Test_Create_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "201") == 0); const char *body = resp_body(resp); ASSERT(body && strstr(body, "auditpass123456") == NULL); ASSERT(body && strstr(body, "hash") == NULL); } PASS(); Dowa_Arena_Free(arena); Auth_API_Destroy(); unlink(db); } /* ------------------------------------------------------------------ */ /* Test group: list pagination */ /* ------------------------------------------------------------------ */ static void test_list_pagination(void) { printf("\n[list pagination]\n"); char db[256]; make_temp_db(db, sizeof(db)); init_auth(db); Auth_Store *store = Auth_API_Get_Store(); char admin_id[37]; ASSERT(create_test_user(store, "admin1", "password123456", "admin", FALSE, admin_id)); /* Create 5 more users. */ for (int i = 0; i < 5; i++) { char uname[32], uid[37]; snprintf(uname, sizeof(uname), "user%d", i); ASSERT(create_test_user(store, uname, "password123456", "member", FALSE, uid)); } Dowa_Arena *arena = Dowa_Arena_Create(128 * 1024); char scookie[512], csrf[512]; ASSERT(login_user(arena, "admin1", "password123456", scookie, sizeof(scookie), csrf, sizeof(csrf))); TEST("list all users includes total count"); { Seobeo_Request_Entry *req = make_admin_req(arena, "GET", scookie, csrf, NULL, NULL); Seobeo_Request_Entry *resp = Admin_API_Test_List_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "200") == 0); const char *body = resp_body(resp); ASSERT(body && strstr(body, "\"total\":6") != NULL); ASSERT(body && strstr(body, "\"users\":[") != NULL); } PASS(); TEST("huge page number returns an empty page without overflow"); { Seobeo_Request_Entry *req = make_admin_req(arena, "GET", scookie, csrf, NULL, NULL); Dowa_HashMap_Push_Arena( req, "Query-page", "9223372036854775807", arena); Dowa_HashMap_Push_Arena(req, "Query-limit", "100", arena); Seobeo_Request_Entry *resp = Admin_API_Test_List_Handler(req, arena); ASSERT(strcmp(resp_status(resp), "200") == 0); const char *body = resp_body(resp); ASSERT(body && strstr(body, "\"users\":[]") != NULL); } PASS(); Dowa_Arena_Free(arena); Auth_API_Destroy(); unlink(db); } /* ------------------------------------------------------------------ */ /* main */ /* ------------------------------------------------------------------ */ int main(void) { printf("=== admin_api_test ===\n"); test_non_admin_denial(); test_forced_password_denial(); test_create_user(); test_enable_disable(); test_role_update(); test_temp_reset(); test_session_revocation(); test_no_secret_fields(); test_list_pagination(); printf("\n=== ALL TESTS PASSED ===\n"); return 0; }