Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
 
root / src / virt / v-libvirt.c
Author[]Andy Green <andy@warmcat.com> 2026-08-02 07:21 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-08-02 07:36 UTC
Treeeb8030f66ec7db4b0e4953a70fdafec1d813e415   Raw Patch
 
oauth: rely on lws-login synth state
oauth: rely on lws-login synth state
diff --git a/assets/sai.js b/assets/sai.js index 69cf70b..0c81bb4 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -398,7 +398,8 @@ const SaiAuthState = { NOT_LOGGED_IN: 0, LOGGED_IN_NO_GRANT: 1, LOGGED_IN_GRANT_USER: 2, // < :2 - LOGGED_IN_GRANT_ADMIN: 3 // >= :2 + LOGGED_IN_GRANT_ADMIN: 3, // >= :2 + PENDING: 4 // backend login-status fetch in flight }; var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, auth_is_admin = 0, auth_grant_level = -1, auth_state = SaiAuthState.NOT_LOGGED_IN, exptimer, auth_user = "", @@ -3170,21 +3171,30 @@ function ws_open_sai() location.reload(); break; - case "com.warmcat.sai.auth_state": - console.log("Backend auth_state:", jso.auth_state); - if (jso.auth_state === 3) { - auth_state = SaiAuthState.LOGGED_IN_GRANT_ADMIN; - auth_is_admin = 1; - } else if (jso.auth_state === 2) { - auth_state = SaiAuthState.LOGGED_IN_GRANT_USER; - auth_is_admin = 0; - } else if (jso.auth_state === 1) { - auth_state = SaiAuthState.LOGGED_IN_NO_GRANT; - auth_is_admin = 0; - } else { - auth_state = SaiAuthState.NOT_LOGGED_IN; - auth_is_admin = 0; - } + case "com.warmcat.sai.auth_state": + console.log("Backend auth_state:", jso.auth_state); + /* + * 4 = PENDING: the backend hasn't resolved the login + * status yet (it fetches it from lws-login async at WS + * establish). Leave the UI as-is; a follow-up message + * will deliver the resolved state. + */ + if (jso.auth_state === 4) { + break; + } + if (jso.auth_state === 3) { + auth_state = SaiAuthState.LOGGED_IN_GRANT_ADMIN; + auth_is_admin = 1; + } else if (jso.auth_state === 2) { + auth_state = SaiAuthState.LOGGED_IN_GRANT_USER; + auth_is_admin = 0; + } else if (jso.auth_state === 1) { + auth_state = SaiAuthState.LOGGED_IN_NO_GRANT; + auth_is_admin = 0; + } else { + auth_state = SaiAuthState.NOT_LOGGED_IN; + auth_is_admin = 0; + } const statusContainer = document.getElementById('lws-login-status-container'); if (statusContainer) { statusContainer.classList.remove('grant-admin', 'grant-user', 'grant-none'); diff --git a/etc-sai-EXAMPLE/web/conf.d/unixskt b/etc-sai-EXAMPLE/web/conf.d/unixskt index 42500f2..e8f11d0 100644 --- a/etc-sai-EXAMPLE/web/conf.d/unixskt +++ b/etc-sai-EXAMPLE/web/conf.d/unixskt @@ -63,7 +63,28 @@ # "database": "/srv/sai/sai-master", - + # sai takes its login state from the lws-login + # interceptor rather than validating any JWT itself. + # At WS establish it performs an internal GET of + # auth-status-url on self-address (its own listener), + # forwarding the browser's Cookie, so lws-login (which + # holds the JWK and grant name) can decide admin or + # not. Both default to the example deployment below. + # + # self-address: "+"-prefixed lws client address of + # sai's own listener; "+" selects a unix socket, the + # rest is the filesystem path. + # + # "self-address": "+/var/run/sai", + # "auth-status-url": "/sai/.lws-login-status", + # + # For the fetch to reach lws-login, the /sai mount + # must have lws-login wired as its interceptor with a + # pmo service-name binding the grant name (eg lws-sai) + # -- that pmo is the single place where the grant name + # lives. See protocol_lws_login.md for the mount + # wiring. Without lws-login, the fetch 404s and sai + # treats the user as not having admin rights. # template HTML to use for this vhost. You'd normally # copy this to gitohashi-vhostname.html and modify it diff --git a/src/web/w-comms.c b/src/web/w-comms.c index 36d2497..fcb52e5 100644 --- a/src/web/w-comms.c +++ b/src/web/w-comms.c @@ -165,24 +165,27 @@ w_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, { const struct lws_protocol_vhost_options *pvo = (const struct lws_protocol_vhost_options *)in; - const char *jwk_path = "/etc/sai/web/auth.jwk"; - lws_strncpy(vhd->cookie_name, "auth_session", sizeof(vhd->cookie_name)); + /* + * Defaults match the example deployment: sai listens on + * the unix socket /var/run/sai and lws-login serves the + * cooked login status on /sai/.lws-login-status. Both + * are overridable per-vhost. + */ + lws_strncpy(vhd->self_address, "+/var/run/sai", + sizeof(vhd->self_address)); + lws_strncpy(vhd->auth_status_url, "/sai/.lws-login-status", + sizeof(vhd->auth_status_url)); while (pvo) { - if (!strcmp(pvo->name, "jwt-auth-jwk-path")) - jwk_path = pvo->value; - if (!strcmp(pvo->name, "cookie-name")) - lws_strncpy(vhd->cookie_name, pvo->value, sizeof(vhd->cookie_name)); + if (!strcmp(pvo->name, "self-address")) + lws_strncpy(vhd->self_address, pvo->value, + sizeof(vhd->self_address)); + if (!strcmp(pvo->name, "auth-status-url")) + lws_strncpy(vhd->auth_status_url, pvo->value, + sizeof(vhd->auth_status_url)); pvo = pvo->next; } - lws_strncpy(vhd->jwk_path, jwk_path, sizeof(vhd->jwk_path)); - if (!lws_jwk_load(&vhd->jwk, vhd->jwk_path, NULL, NULL)) { - vhd->has_jwk = 1; - lwsl_notice("Loaded JWT jwk from %s\n", vhd->jwk_path); - } else { - lwsl_err("FAILED to load JWT jwk from %s\n", vhd->jwk_path); - } } if (lws_pvo_get_str(in, "database", &vhd->sqlite3_path_lhs)) { @@ -570,61 +573,20 @@ http_resp: } #endif - pss->auth_state = SAI_AUTH_STATE_NOT_LOGGED_IN; - if (vhd->has_jwk) { -#if defined(LWS_WITH_JOSE) - const char *reason = "unknown"; - struct lws_jwt_auth *ja = lws_jwt_auth_create(wsi, &vhd->jwk, vhd->cookie_name, NULL, NULL, &reason); - if (ja) { - int grant = (int)lws_jwt_auth_query_grant(ja, "com.warmcat.sai"); - int grant_all = (int)lws_jwt_auth_query_grant(ja, "*"); - int max_grant = grant > grant_all ? grant : grant_all; - - /* - * Security: enforce JWT expiry at request - * time. lws's proactive SUL expiry timer - * invokes a callback we registered as - * NULL, so on its own it does nothing. - * A stolen cookie that has already - * expired would otherwise still - * authorize privileged operations for - * the life of this WS. Reject if the - * token's exp is in the past. - */ - uint64_t exp = lws_jwt_auth_get_exp(ja); - if (exp && exp < (uint64_t)lws_now_secs()) { - lwsl_wsi_err(wsi, "Rejecting WS: JWT expired (exp %llu, now %llu)", - (unsigned long long)exp, - (unsigned long long)lws_now_secs()); - } else { - if (max_grant >= 0) { - pss->auth_state = SAI_AUTH_STATE_LOGGED_IN_GRANT_ADMIN; - lwsl_wsi_notice(wsi, "Authorized WebSocket connection (admin/grant, max_grant: %d)", max_grant); - } else { - pss->auth_state = SAI_AUTH_STATE_LOGGED_IN_NO_GRANT; - lwsl_wsi_err(wsi, "JWT validation passed, but no grant found (grant: %d, grant_all: %d)", grant, grant_all); - } - } - - lws_jwt_auth_destroy(&ja); - } else { - if (reason && !strcmp(reason, "Cookie not found")) { - char cookies[512]; - int n = lws_hdr_copy(wsi, cookies, sizeof(cookies), WSI_TOKEN_HTTP_COOKIE); - if (n > 0) - lwsl_wsi_err(wsi, "JWT auth failed (cookie: '%s'): Cookie not found (available cookies: '%s') (using JWK from %s)", vhd->cookie_name, cookies, vhd->jwk_path); - else - lwsl_wsi_err(wsi, "JWT auth failed (cookie: '%s'): Cookie not found (no cookies sent by browser) (using JWK from %s)", vhd->cookie_name, vhd->jwk_path); - } else { - lwsl_wsi_err(wsi, "JWT auth failed (cookie: '%s'): %s (using JWK from %s)", vhd->cookie_name, reason ? reason : "unknown", vhd->jwk_path); - } - } -#endif - } else { - lwsl_wsi_err(wsi, "Cannot validate JWT because no JWK was loaded (expected at %s)", vhd->jwk_path); - } + /* + * sai takes its login state from lws-login rather than + * validating any JWT itself. Kick off an async internal + * GET of the cooked login status on our own vhost, + * forwarding the browser's Cookie so lws-login can decide. + * auth_state stays PENDING (=> not admin => fails closed at + * the action gate) until the fetch resolves and pushes the + * result to the browser. + */ + pss->auth_state = SAI_AUTH_STATE_PENDING; + if (saiw_auth_fetch_kick(vhd, pss, wsi)) + lwsl_wsi_err(wsi, "unable to start auth-status fetch"); - lwsl_wsi_notice(wsi, "**** ESTABLISHED WS: final auth_state=%d (has_jwk=%d, cookie_name='%s')", (int)pss->auth_state, vhd->has_jwk, vhd->cookie_name); + lwsl_wsi_notice(wsi, "**** ESTABLISHED WS: auth fetch started (auth_state=%d pending)", (int)pss->auth_state); if (!memcmp((char *)start, "/sai", 4)) start += 4; @@ -709,6 +671,25 @@ http_resp: saiw_browser_state_changed(pss, 0); lws_dll2_remove(&pss->subs_list); lws_sul_cancel(&pss->sul_logcache); + lws_sul_cancel(&pss->sul_auth); + + /* + * Tear down any in-flight auth fetch: detach it from the pss + * so the client callback can no longer touch the pss, and + * close the client wsi so its CLOSED callback frees the + * pending struct promptly rather than lingering. + */ + if (pss->auth_pending) { + struct lws *cwsi = pss->auth_pending->wsi_client; + + pss->auth_pending->wsi_parent = NULL; + pss->auth_pending = NULL; + + if (cwsi) + lws_set_timeout(cwsi, + PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE, + LWS_TO_KILL_ASYNC); + } for (n = 0; n < 4; n++) { if (pss->last_bps[n]) @@ -812,9 +793,265 @@ try_to_reuse: return 0; } +/* + * Resolve auth_state from the accumulated .lws-login-status body and push it + * to the browser. Sets ->done so a late timeout/CLOSED can't double-resolve. + * Detaches the pending struct from the pss. The pending struct itself is + * owned by the client wsi (it is the client wsi's userdata) and is freed when + * the client wsi is destroyed -- NOT here -- so the caller may still touch ap + * after this returns; ap->wsi_parent is cleared so a later close callback is a + * no-op for the pss. + */ +static void +saiw_auth_pending_resolve(struct sai_auth_pending *ap, sai_auth_state_t state) +{ + struct pss *pss; + + if (!ap || ap->done) + return; + ap->done = 1; + + if (ap->wsi_parent) { + /* + * wsi_parent's per-session data is the pss; recover it from + * the wsi user_space. pss->auth_pending == ap is the live + * pointer check; if the pss was CLOSED it NULLed + * auth_pending and wsi_parent. + */ + pss = (struct pss *)lws_wsi_user(ap->wsi_parent); + if (pss && pss->auth_pending == ap) { + pss->auth_state = state; + saiw_browser_queue_auth_state(pss); + pss->auth_pending = NULL; + lws_sul_cancel(&pss->sul_auth); + } + } + + ap->wsi_parent = NULL; /* pss side is settled; don't touch it again */ +} + +/* + * Pull the small set of cooked fields we care about out of the body. The body + * is a flat object produced by lws-login's .lws-login-status handler, so + * lws_json_simple_find() is sufficient and avoids a full JSON parser. + */ +static sai_auth_state_t +saiw_auth_state_from_body(const char *body, int len) +{ + const char *v; + size_t alen; + + v = lws_json_simple_find(body, (size_t)len, "\"logged_in\":", &alen); + if (!v || alen != 1 || v[0] != '1') + return SAI_AUTH_STATE_NOT_LOGGED_IN; + + v = lws_json_simple_find(body, (size_t)len, "\"is_admin\":", &alen); + + return (v && alen == 1 && v[0] == '1') ? + SAI_AUTH_STATE_LOGGED_IN_GRANT_ADMIN : + SAI_AUTH_STATE_LOGGED_IN_NO_GRANT; +} + +/* + * SUL timeout: if the fetch hasn't resolved in time, resolve it as no-grant + * (closed) so the browser isn't left in PENDING forever, then close the + * client wsi (whose CLOSED callback frees the pending struct). + */ +static void +saiw_auth_sul_cb(lws_sorted_usec_list_t *sul) +{ + struct pss *pss = lws_container_of(sul, struct pss, sul_auth); + struct sai_auth_pending *ap; + struct lws *cwsi; + + if (!pss) + return; + + ap = pss->auth_pending; + if (!ap) + return; + + lwsl_wsi_err(pss->wsi, "auth-status fetch timed out"); + + cwsi = ap->wsi_client; /* capture before resolve clears wsi_parent */ + saiw_auth_pending_resolve(ap, SAI_AUTH_STATE_LOGGED_IN_NO_GRANT); + + if (cwsi) /* tearing down the client wsi frees ap via CLOSED */ + lws_set_timeout(cwsi, PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE, + LWS_TO_KILL_ASYNC); +} + +int +saiw_auth_fetch_kick(struct vhd *vhd, struct pss *pss, struct lws *wsi) +{ + struct sai_auth_pending *ap; + struct lws_client_connect_info i; + int n; + + ap = malloc(sizeof(*ap)); + if (!ap) + return 1; + memset(ap, 0, sizeof(*ap)); + ap->wsi_parent = wsi; + + /* + * Copy the browser's raw Cookie header verbatim; we forward it to + * lws-login so IT can validate the session. sai never inspects it. + */ + n = lws_hdr_copy(wsi, ap->cookie_hdr, sizeof(ap->cookie_hdr), + WSI_TOKEN_HTTP_COOKIE); + if (n < 0) + ap->cookie_hdr[0] = '\0'; + + pss->auth_pending = ap; + + lws_sul_schedule(vhd->context, 0, &pss->sul_auth, saiw_auth_sul_cb, + 5 * LWS_US_PER_SEC); + + memset(&i, 0, sizeof(i)); + i.context = vhd->context; + /* + * Bind the client wsi to sai's own vhost explicitly: the protocol + * lookup (lws_vhost_name_to_protocol) runs against wsi->a.vhost, so we + * must use a vhost whose protocol list contains sai_internal_http_client + * (sai registers it on every vhost via pprotocols). Without this lws + * falls back to the "default" / first vhost, which may lack the protocol + * and fall back to the dummy http protocol. + */ + i.vhost = vhd->vhost; + i.address = vhd->self_address; /* "+"-prefixed unix path */ + i.port = 0; /* ignored on the '+' path */ + i.ssl_connection = 0; + i.path = vhd->auth_status_url; + i.host = "localhost"; + i.origin = i.host; + i.method = "GET"; + i.protocol = "sai_internal_http_client"; + i.userdata = ap; /* lws_wsi_user() on client */ + i.pwsi = &ap->wsi_client; + + /* + * lws_client_connect_via_info() returns the new client wsi on success + * (non-NULL) or NULL on synchronous failure. + */ + if (lws_client_connect_via_info(&i)) + return 0; /* connect started: ap now owned by the client wsi */ + + /* synchronous failure to even start: clean up ourselves */ + lws_sul_cancel(&pss->sul_auth); + pss->auth_pending = NULL; + free(ap); + + return 1; +} + +/* + * Internal HTTP client protocol: drives the auth-status GET. Has no + * per-session data of its own; its ->userdata is the struct sai_auth_pending + * set up at connect time. + */ +static int +callback_sai_internal_http_client(struct lws *wsi, + enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct sai_auth_pending *ap = (struct sai_auth_pending *)lws_wsi_user(wsi); + + switch (reason) { + + case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: { + unsigned char **p = (unsigned char **)in; + unsigned char *end = (*p) + len; + + if (!ap) + break; + + /* forward the browser's Cookie so lws-login can validate it */ + if (ap->cookie_hdr[0] && + lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_COOKIE, + (unsigned char *)ap->cookie_hdr, + (int)strlen(ap->cookie_hdr), p, end)) + return -1; + break; + } + + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: { + int avail, take; + + if (!ap || ap->done) + break; + + /* accumulate the (small) JSON body */ + avail = (int)sizeof(ap->body) - 1 - ap->body_len; + take = avail < (int)len ? avail : (int)len; + if (take > 0) { + memcpy(ap->body + ap->body_len, in, (size_t)take); + ap->body_len += take; + ap->body[ap->body_len] = '\0'; + } + break; + } + + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + if (ap && !ap->done) { + sai_auth_state_t st; + + st = saiw_auth_state_from_body(ap->body, ap->body_len); + lwsl_wsi_notice(ap->wsi_parent, + "auth-status fetch completed: state=%d", (int)st); + saiw_auth_pending_resolve(ap, st); + } + /* + * The transaction is done; close the client wsi. This drives + * CLOSED_CLIENT_HTTP below, which frees ap. + */ + return -1; + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + lwsl_notice("%s: auth-status client connect failed\n", __func__); + /* fall through */ + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + if (ap) { + if (!ap->done) { + lwsl_wsi_err(ap->wsi_parent, + "auth-status fetch failed/closed"); + saiw_auth_pending_resolve(ap, + SAI_AUTH_STATE_LOGGED_IN_NO_GRANT); + } + /* + * ap is this wsi's userdata; the wsi is going away now, + * so free it. Nothing else references ap once it is + * detached from the pss (resolve clears wsi_parent). + */ + free(ap); + } + break; + + default: + break; + } + + /* + * Do NOT delegate to lws_callback_http_dummy(): that handler is for + * server-side HTTP and proxied client wsis (it asserts the wsi has a + * parent under LWS_WITH_HTTP_PROXY). This is a standalone internal + * HTTP client -- return 0 like lws-login's callback_lws_login_client. + */ + (void)user; + (void)wsi; + return 0; +} + const struct lws_protocols protocol_ws = { .name = "com-warmcat-sai", .callback = w_callback_ws, .per_session_data_size = sizeof(struct pss), .rx_buffer_size = 0, }; + +const struct lws_protocols protocol_sai_internal_http_client = { + .name = "sai_internal_http_client", + .callback = callback_sai_internal_http_client, + .per_session_data_size = 0, + .rx_buffer_size = 0, +}; diff --git a/src/web/w-private.h b/src/web/w-private.h index 5d0b19a..b9950bc 100644 --- a/src/web/w-private.h +++ b/src/web/w-private.h @@ -60,12 +60,48 @@ enum { }; typedef enum { - SAI_AUTH_STATE_NOT_LOGGED_IN, - SAI_AUTH_STATE_LOGGED_IN_NO_GRANT, - SAI_AUTH_STATE_LOGGED_IN_GRANT_USER, /* < :2 */ - SAI_AUTH_STATE_LOGGED_IN_GRANT_ADMIN /* >= :2 */ + SAI_AUTH_STATE_NOT_LOGGED_IN, /* 0: also the initial pss value */ + SAI_AUTH_STATE_LOGGED_IN_NO_GRANT, /* 1 */ + SAI_AUTH_STATE_LOGGED_IN_GRANT_USER, /* 2: < :2 (unused) */ + SAI_AUTH_STATE_LOGGED_IN_GRANT_ADMIN, /* 3: >= :2 */ + /* + * 4: auth determination is in flight (WS path fetches the cooked + * login status from lws-login asynchronously). The action gate + * treats anything != GRANT_ADMIN as denied, so this fails closed. + * NOT used as a wire value to the browser (we only push a state + * once resolved); defined to keep numeric values stable. + */ + SAI_AUTH_STATE_PENDING } sai_auth_state_t; +/* + * In-flight WS auth fetch state. + * + * sai's WS path does no JWT/grant logic of its own; instead it fetches the + * cooked login status (".lws-login-status") served by the lws-login + * interceptor on sai's own mount, forwarding the browser's Cookie header so + * lws-login (which holds the JWK, grant name and grant level) can decide. + * + * One of these is heap-allocated per pss at WS ESTABLISH and freed when the + * fetch resolves (success or failure) or when the pss is destroyed. It is + * the ->userdata of the internal client wsi, so the client protocol callback + * recovers it with lws_wsi_user(). + */ +struct sai_auth_pending { + struct lws *wsi_parent; /* the sai WS wsi this is for */ + struct lws *wsi_client; /* lws writes the client wsi here */ + + char cookie_hdr[1024]; /* raw Cookie: value to forward */ + + char body[1024]; /* accumulated response body */ + int body_len; + + char done; /* set when resolved, prevents + * double-resolution if both the + * completion callback and the + * timeout/closed try to finish */ +}; + struct pss { struct vhd *vhd; @@ -136,16 +172,32 @@ struct pss { unsigned int resolved_task_offset:1; uint8_t wants_builder_info; sai_auth_state_t auth_state; + + /* async WS auth-fetch (see struct sai_auth_pending) */ + struct sai_auth_pending *auth_pending; + lws_sorted_usec_list_t sul_auth; }; struct vhd { struct lws_context *context; struct lws_vhost *vhost; - struct lws_jwk jwk; - int has_jwk; - char cookie_name[64]; - char jwk_path[256]; + /* + * sai does no JWT/grant validation itself. At WS establish it + * performs an internal HTTP GET of auth_status_url on its own + * (unix-socket) vhost, forwarding the browser's Cookie so the + * lws-login interceptor -- which holds the JWK and grant name -- + * can produce the cooked login status. These pvos tell sai where + * its own vhost is reachable and which path to fetch: + * + * self_address the lws client address of sai's own listener, + * "+"-prefixed for a unix socket, eg + * "+/var/run/sai" (see lws_client_connect_via_info) + * auth_status_url the synthetic path lws-login serves, normally + * "/sai/.lws-login-status" + */ + char self_address[128]; + char auth_status_url[128]; /* pss lists */ struct lws_dll2_owner browsers; @@ -191,8 +243,18 @@ sai_lws_context_from_json(const char *config_dir, const struct lws_protocols **pprotocols, const char *pol); extern const struct lws_protocols protocol_ws; +extern const struct lws_protocols protocol_sai_internal_http_client; extern const lws_ss_info_t ssi_saiw_websrv; +/* + * Kick off the async internal fetch of the cooked login status from lws-login + * for this WS connection. Sets pss->auth_state to PENDING and arranges for + * saiw_browser_queue_auth_state() to be called when it resolves. Returns 0 + * if the fetch was started. + */ +int +saiw_auth_fetch_kick(struct vhd *vhd, struct pss *pss, struct lws *wsi); + int sai_notification_file_upload_cb(void *data, const char *name, const char *filename, char *buf, int len, @@ -251,6 +313,14 @@ int saiw_ws_browser_queue_REQUIRES_LWS_PRE(struct pss *pss, const void *buf, size_t len, enum lws_write_protocol flags); +/* + * Push a com.warmcat.sai.auth_state message to the browser reflecting the + * pss's current auth_state. Used to notify the browser once the async + * login-status fetch resolves (the browser re-evaluates admin UI on receipt). + */ +void +saiw_browser_queue_auth_state(struct pss *pss); + void saiw_browser_state_changed(struct pss *pss, int established); diff --git a/src/web/w-sai.c b/src/web/w-sai.c index 2a7f740..23ab520 100644 --- a/src/web/w-sai.c +++ b/src/web/w-sai.c @@ -57,7 +57,8 @@ static const char * const default_ss_policy = ; static const struct lws_protocols - *pprotocols[] = { &protocol_ws, NULL }; + *pprotocols[] = { &protocol_ws, + &protocol_sai_internal_http_client, NULL }; static void sigint_handler(int sig) { diff --git a/src/web/w-ws-browser.c b/src/web/w-ws-browser.c index 79ee52e..6240ab2 100644 --- a/src/web/w-ws-browser.c +++ b/src/web/w-ws-browser.c @@ -210,6 +210,24 @@ saiw_ws_broadcast_browsers_REQUIRES_LWS_PRE(struct vhd *vhd, const void *buf, } lws_end_foreach_dll(p); } +/* + * Push a com.warmcat.sai.auth_state message reflecting pss->auth_state. Used + * to notify the browser once the async login-status fetch resolves; the + * browser re-evaluates admin UI on receipt (sai.js com.warmcat.sai.auth_state). + */ +void +saiw_browser_queue_auth_state(struct pss *pss) +{ + uint8_t buf[LWS_PRE + 128], *start = buf + LWS_PRE, *p = start, + *end = buf + sizeof(buf); + + p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), + "{\"schema\":\"com.warmcat.sai.auth_state\",\"auth_state\":%d}", + (int)pss->auth_state); + saiw_ws_browser_queue_REQUIRES_LWS_PRE(pss, start, + lws_ptr_diff_size_t(p, start), LWS_WRITE_TEXT); +} + int
Page fetched 0s ago, creation time: 8ms (vhost etag hits: 0%, cache hits: 0%)