Mercurial
view auth/auth_store.h @ 279:b3b547563ec7
Add Google connector service and agent wiki
Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:22:36 -0700 |
| parents | 04fee26ecce0 |
| children |
line wrap: on
line source
#ifndef ZENBU_AUTH_STORE_H #define ZENBU_AUTH_STORE_H #include "dowa/dowa.h" #include "auth/auth_crypto.h" #define AUTH_STORE_USERNAME_MIN 3 #define AUTH_STORE_USERNAME_MAX 32 typedef struct Auth_Store Auth_Store; typedef enum { AUTH_STORE_ERROR = -1, AUTH_STORE_OK = 0, AUTH_STORE_NOT_FOUND = 1, AUTH_STORE_CONFLICT = 2, AUTH_STORE_EXPIRED = 3, AUTH_STORE_REVOKED = 4, AUTH_STORE_USER_DISABLED = 5, AUTH_STORE_STALE_PASSWORD = 6, AUTH_STORE_LAST_ADMIN = 7, AUTH_STORE_INVALID_ARG = 8, } Auth_Store_Result; typedef enum { AUTH_STORE_BOOTSTRAP_CREATED = 0, AUTH_STORE_BOOTSTRAP_ALREADY_PRESENT = 1, } Auth_Store_Bootstrap_Result; /* * Public user record — password_hash is never included here. * All fields are fixed-size; no arena pointer is needed. */ typedef struct { char id[37]; char username[AUTH_STORE_USERNAME_MAX + 1]; char normalized_username[AUTH_STORE_USERNAME_MAX + 1]; char role[8]; /* "admin" or "member" */ char status[9]; /* "active" or "disabled" */ boolean must_change_password; int64 password_changed_at; int64 created_at; int64 updated_at; } Auth_User_Record; /* * Authentication lookup record — includes the encoded password hash. * Callers must zero this struct after use; never log the hash field. */ typedef struct { Auth_User_Record user; char password_hash[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE]; } Auth_User_Auth_Record; /* * Session record — token/CSRF digests are not exposed here; the caller * already holds them before calling Create_Session or Find_Session. */ typedef struct { char user_id[37]; int64 created_at; int64 last_seen_at; int64 idle_expires_at; int64 absolute_expires_at; int64 password_changed_at_snapshot; } Auth_Session_Record; typedef struct { char id[37]; int64 created_at; int64 last_seen_at; int64 expires_at; } Auth_Guest_Identity_Record; /* --- Store lifecycle --- */ Auth_Store *Auth_Store_Create(const char *database_path); void Auth_Store_Destroy(Auth_Store *p_store); /* --- Username utilities --- */ /* * Trim ASCII spaces, lowercase, validate character set and length. * Writes the normalized form to 'normalized' (capacity must include NUL). * Returns TRUE on success; FALSE if invalid or buffer too small. */ boolean Auth_Store_Normalize_Username( const char *username, char *normalized, size_t capacity); /* * Validate a pre-normalized username (lowercase, no leading/trailing spaces). * Returns TRUE iff length and character set are within policy. */ boolean Auth_Store_Validate_Username(const char *normalized_username); /* --- User management --- */ Auth_Store_Result Auth_Store_Create_User( Auth_Store *p_store, const char *username, const char *encoded_hash, const char *role, boolean must_change_password, char output_id[37]); /* Create a user and its audit record in one transaction. */ Auth_Store_Result Auth_Store_Create_User_Audited( Auth_Store *p_store, const char *username, const char *encoded_hash, const char *role, boolean must_change_password, const char *actor_user_id, char output_id[37]); /* * Idempotent: creates an admin user only when no admin exists at all. * p_bootstrap_result receives CREATED or ALREADY_PRESENT. * output_id receives the UUID (new or existing) and may be NULL. */ Auth_Store_Result Auth_Store_Bootstrap_Admin( Auth_Store *p_store, const char *username, const char *encoded_hash, Auth_Store_Bootstrap_Result *p_bootstrap_result, char output_id[37]); /* Includes password_hash for authentication — zero record after use. */ Auth_Store_Result Auth_Store_Find_User_By_Username( Auth_Store *p_store, const char *username, Auth_User_Auth_Record *p_record); Auth_Store_Result Auth_Store_Get_User( Auth_Store *p_store, const char *user_id, Auth_User_Record *p_record); /* * List all users into a Dowa arena-backed array. * After a successful call, use Dowa_Array_Length(*pp_records) for count. */ Auth_Store_Result Auth_Store_List_Users( Auth_Store *p_store, Auth_User_Record **pp_records, Dowa_Arena *p_arena); /* * new_status must be "active" or "disabled". * Fails with AUTH_STORE_LAST_ADMIN if disabling the last active admin. */ Auth_Store_Result Auth_Store_Update_User_Status( Auth_Store *p_store, const char *user_id, const char *new_status, const char *actor_user_id); /* Atomically enable a user and write audit; revoked sessions stay revoked. */ Auth_Store_Result Auth_Store_Enable_User( Auth_Store *p_store, const char *user_id, const char *actor_user_id); /* Atomically enforce last-admin policy, disable, revoke, and write audit. */ Auth_Store_Result Auth_Store_Disable_User_And_Revoke_Sessions( Auth_Store *p_store, const char *user_id, const char *actor_user_id); /* * new_role must be "admin" or "member". * Fails with AUTH_STORE_LAST_ADMIN if demoting the last active admin. */ Auth_Store_Result Auth_Store_Update_User_Role( Auth_Store *p_store, const char *user_id, const char *new_role, const char *actor_user_id); /* Atomically enforce last-admin policy, update role, revoke, and audit. */ Auth_Store_Result Auth_Store_Update_Role_And_Revoke_Sessions( Auth_Store *p_store, const char *user_id, const char *new_role, const char *actor_user_id); Auth_Store_Result Auth_Store_Set_Must_Change_Password( Auth_Store *p_store, const char *user_id, boolean value, const char *actor_user_id); /* * Updates the encoded password hash; clears must_change_password. * If revoke_other_sessions is TRUE, all sessions except keep_token_digest * (which may be NULL) are revoked atomically in the same transaction. */ Auth_Store_Result Auth_Store_Update_Password( Auth_Store *p_store, const char *user_id, const char *new_encoded_hash, boolean revoke_other_sessions, const char *keep_token_digest); /* --- Session management --- */ /* * Creates a new session. Fails if the user is disabled. * current_unix is the caller-supplied Unix timestamp. */ Auth_Store_Result Auth_Store_Create_Session( Auth_Store *p_store, const char *user_id, const char *token_digest, const char *csrf_digest, int64 idle_ttl_secs, int64 absolute_ttl_secs, int64 current_unix, Auth_Session_Record *p_record); /* * CAS session create: only creates a session if the user's current * password_hash still exactly matches expected_password_hash. * Returns AUTH_STORE_STALE_PASSWORD when an administrator changed it. * The caller must cleanse expected_password_hash after this call. */ Auth_Store_Result Auth_Store_Create_Session_CAS( Auth_Store *p_store, const char *user_id, const char *expected_password_hash, const char *token_digest, const char *csrf_digest, int64 idle_ttl_secs, int64 absolute_ttl_secs, int64 current_unix, Auth_Session_Record *p_record); /* * Resolves a session by token_digest. * Returns: * OK — session is valid; p_session and p_user are populated. * NOT_FOUND — no such session. * REVOKED — session was explicitly revoked. * EXPIRED — idle or absolute expiry exceeded. * USER_DISABLED — session owner has been disabled. * STALE_PASSWORD — password changed after session was created. */ Auth_Store_Result Auth_Store_Find_Session( Auth_Store *p_store, const char *token_digest, int64 current_unix, Auth_Session_Record *p_session, Auth_User_Record *p_user); /* Extends the idle expiry; does nothing if the session is revoked. */ Auth_Store_Result Auth_Store_Touch_Session( Auth_Store *p_store, const char *token_digest, int64 current_unix, int64 idle_ttl_secs); Auth_Store_Result Auth_Store_Revoke_Session( Auth_Store *p_store, const char *token_digest); /* * Revokes all sessions for user_id. * If except_token_digest is non-NULL, that session is preserved. */ Auth_Store_Result Auth_Store_Revoke_All_Sessions( Auth_Store *p_store, const char *user_id, const char *except_token_digest); /* Atomically revoke sessions and write one audit record. */ Auth_Store_Result Auth_Store_Revoke_All_Sessions_Audited( Auth_Store *p_store, const char *user_id, const char *except_token_digest, const char *actor_user_id); /* * Atomically create a new session and revoke the old one in a single * transaction. Use this after a password change to rotate the current * session without any window where neither or both sessions are valid. * * old_token_digest: the existing session to revoke (must not be NULL or ""). * If old_token_digest is already revoked the rotation still succeeds and * the new session is created. * All other parameters are the same as Auth_Store_Create_Session. */ Auth_Store_Result Auth_Store_Rotate_Session( Auth_Store *p_store, const char *user_id, const char *old_token_digest, const char *new_token_digest, const char *new_csrf_digest, int64 idle_ttl_secs, int64 absolute_ttl_secs, int64 current_unix, Auth_Session_Record *p_record); /* * Atomically compare-and-swap the password hash, clear forced change, * revoke every existing session, and create one replacement session. * On AUTH_STORE_STALE_PASSWORD no state is changed. The caller must cleanse * both password hashes after this call. */ Auth_Store_Result Auth_Store_Self_Change_Password( Auth_Store *p_store, const char *user_id, const char *old_encoded_hash, const char *new_encoded_hash, const char *new_token_digest, const char *new_csrf_digest, int64 idle_ttl_secs, int64 absolute_ttl_secs, int64 current_unix, Auth_Session_Record *p_record); /* --- Guest quota --- */ typedef enum { AUTH_STORE_GUEST_QUOTA_OK = 0, AUTH_STORE_GUEST_QUOTA_TURNS_EXHAUSTED = 1, AUTH_STORE_GUEST_QUOTA_TOKENS_EXHAUSTED = 2, AUTH_STORE_GUEST_QUOTA_ERROR = -1, } Auth_Store_Guest_Quota_Result; typedef struct { int64 turns_used; int64 output_tokens_used; int64 output_tokens_reserved; } Auth_Store_Guest_Usage; /* * Read current daily usage for a guest window. * window_start: UTC midnight Unix timestamp for the current day. * Returns OK with zeroed usage if no row exists yet, or ERROR. */ Auth_Store_Result Auth_Store_Guest_Get_Usage( Auth_Store *p_store, const char *guest_id, int64 window_start, Auth_Store_Guest_Usage *p_usage); /* * Atomically reserve quota for one inference turn. * On success: increments turns_used by 1 and output_tokens_reserved by * max_output_tokens in the usage row for window_start. * Invariant after every reservation: * output_tokens_used + output_tokens_reserved <= tokens_limit. * Returns TURNS_EXHAUSTED or TOKENS_EXHAUSTED when the limit would be exceeded. * reservation_expires: Unix timestamp after which the reservation may be * discarded by cleanup; the turn charge (turns_used) persists regardless. */ Auth_Store_Guest_Quota_Result Auth_Store_Guest_Reserve( Auth_Store *p_store, const char *guest_id, const char *request_id, int64 window_start, int64 max_output_tokens, int64 turns_limit, int64 tokens_limit, int64 reservation_expires); /* * Reconcile a completed turn with the actual provider output token count. * actual_output_tokens is capped to the reserved amount (fail closed; if * the provider reports more than reserved, the reserved amount is charged). * Adds capped tokens to output_tokens_used; subtracts reserved from * output_tokens_reserved; deletes the reservation row. * Idempotent by request_id: safe to call when already reconciled. */ Auth_Store_Result Auth_Store_Guest_Reconcile( Auth_Store *p_store, const char *request_id, int64 actual_output_tokens); /* * Release a reservation on failure or abort. * Decrements output_tokens_reserved by the reserved amount; does NOT * change turns_used (the turn charge is retained). * Deletes the reservation row. * Idempotent by request_id. */ Auth_Store_Result Auth_Store_Guest_Release( Auth_Store *p_store, const char *request_id); /* * Clear all outstanding reservations for a guest. * Used after a successful guest→user login transfer. * Decrements output_tokens_reserved for each affected usage window. * Does NOT change turns_used. */ Auth_Store_Result Auth_Store_Guest_Clear_Reservations( Auth_Store *p_store, const char *guest_id); /* * Atomically reap expired reservations: decrement output_tokens_reserved * in the matching guest_usage rows and delete expired reservation rows, all * in one transaction. Uses MAX(0,...) to avoid underflow. * Idempotent: safe to call repeatedly with the same or decreasing current_unix. * Called at store startup and before quota read/reserve operations. */ Auth_Store_Result Auth_Store_Guest_Reap_Expired( Auth_Store *p_store, int64 current_unix); /* --- Guest identity --- */ /* * Insert or refresh a guest identity. * ip_binding_digest must be an HMAC digest — never the raw IP address. * last_seen_at is updated to now; expires_at is refreshed. */ Auth_Store_Result Auth_Store_Upsert_Guest_Identity( Auth_Store *p_store, const char *guest_id, const char *ip_binding_digest, int64 expires_at, Auth_Guest_Identity_Record *p_record); Auth_Store_Result Auth_Store_Find_Guest_Identity( Auth_Store *p_store, const char *guest_id, int64 current_unix, Auth_Guest_Identity_Record *p_record); /* --- Audit log --- */ /* * Append one bounded audit entry. actor/target/detail may be NULL. * Never include passwords, hashes, tokens, or session digests. * Returns OK or ERROR. */ Auth_Store_Result Auth_Store_Insert_Audit_Log( Auth_Store *p_store, const char *actor_user_id, const char *action, const char *target_user_id, const char *detail); /* --- Admin operations --- */ /* * Atomically: update password hash, set must_change_password=1, * revoke all target sessions, write audit row. * Use for admin-initiated temporary-password reset only. * new_encoded_hash must be a valid encoded zenbu-scrypt hash. */ Auth_Store_Result Auth_Store_Admin_Reset_Password( Auth_Store *p_store, const char *user_id, const char *new_encoded_hash, const char *actor_user_id); #endif