From bd7c503e7b9d70cafff3dc103037cd374341d11d Mon Sep 17 00:00:00 2001 From: Trygve Aaberge Date: Wed, 30 Oct 2024 18:57:39 +0100 Subject: [PATCH] relay/api: support passing auth in sub protocol header The API for connecting to WebSockets in browsers unfortunately doesn't support setting any Authorization header. This means that before this commit it was impossible to connect to the API relay from a web browser. The only thing that can be set apart from the URL is the Sec-WebSocket-Protocol header. Therefore this allows you to send the auth token in this header. This is a weird way to send auth, but it seems to be the best one that makes it possible for browsers to connect. Kubernetes also does it this way: https://github.com/kubernetes/kubernetes/pull/47740 Here is a post describing the different ways to make it possible for a browser to authenticate against a websocket connection, and it also recommends doing it this way: https://stackoverflow.com/questions/4361173/http-headers-in-websockets-client-api/77060459#77060459 Note that when this header is used to pass auth, the client also needs to specify the `api.weechat` sub protocol. This is because the client and server have to agree on a sub protocol when this header is specified, and in order to not send the fake protocol used for auth back to the client, we require specifying the protocol `api.weechat`, which the server then returns to the client. This is only necessary when the Sec-WebSocket-Protocol header is used. If the Authorization header is used for auth as before, nothing changes. --- src/plugins/relay/relay-http.c | 56 ++++++++++++++++++++++++----- src/plugins/relay/relay-websocket.c | 30 +++++++++++++--- src/plugins/relay/relay-websocket.h | 2 ++ 3 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/plugins/relay/relay-http.c b/src/plugins/relay/relay-http.c index 6a522e534..8cde856b9 100644 --- a/src/plugins/relay/relay-http.c +++ b/src/plugins/relay/relay-http.c @@ -560,15 +560,18 @@ relay_http_add_to_body (struct t_relay_http_request *request, int relay_http_get_auth_status (struct t_relay_client *client) { - const char *auth, *client_totp, *pos; + const char *auth, *sec_websocket_protocol, *client_totp, *pos; char *relay_password, *totp_secret, *info_totp_args, *info_totp; char *user_pass; - int rc, length, totp_ok; + char **protocol_array; + int rc, i, length, protocol_count, use_base64url, totp_ok; rc = 0; relay_password = NULL; + protocol_array = NULL; totp_secret = NULL; user_pass = NULL; + use_base64url = 0; relay_password = weechat_string_eval_expression ( weechat_config_string (relay_config_network_password), @@ -589,13 +592,43 @@ relay_http_get_auth_status (struct t_relay_client *client) if (relay_password[0]) { auth = weechat_hashtable_get (client->http_req->headers, "authorization"); - if (!auth || (weechat_strncasecmp (auth, "basic ", 6) != 0)) + + if (auth) { - rc = -1; - goto end; + if (weechat_strncasecmp (auth, "basic ", 6) != 0) + { + rc = -1; + goto end; + } + + pos = auth + 6; + } + else + { + sec_websocket_protocol = weechat_hashtable_get ( + client->http_req->headers, "sec-websocket-protocol"); + protocol_array = weechat_string_split (sec_websocket_protocol, + ",", " ", 0, 0, &protocol_count); + + pos = NULL; + for (i = 0; i < protocol_count; i++) + { + if (strncmp (protocol_array[i], + "base64url.bearer.authorization.weechat.", 39) == 0) + { + pos = protocol_array[i] + 39; + use_base64url = 1; + break; + } + } + + if (!pos) + { + rc = -1; + goto end; + } } - pos = auth + 6; while (pos[0] == ' ') { pos++; @@ -608,7 +641,8 @@ relay_http_get_auth_status (struct t_relay_client *client) rc = -8; goto end; } - length = weechat_string_base_decode ("64", pos, user_pass); + length = weechat_string_base_decode ((use_base64url) ? "64url" : "64", + pos, user_pass); if (length < 0) { rc = -2; @@ -616,7 +650,9 @@ relay_http_get_auth_status (struct t_relay_client *client) } if (strncmp (user_pass, "plain:", 6) == 0) { - switch (relay_auth_check_password_plain (client, user_pass + 6, relay_password)) + switch (relay_auth_check_password_plain (client, + user_pass + 6, + relay_password)) { case 0: /* password OK */ break; @@ -631,7 +667,8 @@ relay_http_get_auth_status (struct t_relay_client *client) } else if (strncmp (user_pass, "hash:", 5) == 0) { - switch (relay_auth_password_hash (client, user_pass + 5, relay_password)) + switch (relay_auth_password_hash (client, user_pass + 5, + relay_password)) { case 0: /* password OK */ break; @@ -692,6 +729,7 @@ relay_http_get_auth_status (struct t_relay_client *client) } end: + weechat_string_free_split (protocol_array); free (relay_password); free (totp_secret); free (user_pass); diff --git a/src/plugins/relay/relay-websocket.c b/src/plugins/relay/relay-websocket.c index 330aac959..2bc023889 100644 --- a/src/plugins/relay/relay-websocket.c +++ b/src/plugins/relay/relay-websocket.c @@ -400,10 +400,12 @@ relay_websocket_parse_extensions (const char *extensions, char * relay_websocket_build_handshake (struct t_relay_http_request *request) { - const char *sec_websocket_key; + const char *sec_websocket_key, *sec_websocket_protocol_request; char *key, sec_websocket_accept[128], handshake[4096], hash[160 / 8]; - char **extensions, str_window_bits[128], sec_websocket_extensions[1024]; - int length, hash_size; + char **extensions, **protocol_array, str_window_bits[128]; + char sec_websocket_extensions[1024]; + char sec_websocket_protocol[1024]; + int i, length, hash_size, protocol_count; if (!request) return NULL; @@ -481,6 +483,24 @@ relay_websocket_build_handshake (struct t_relay_http_request *request) sec_websocket_extensions[0] = '\0'; } + sec_websocket_protocol_request = weechat_hashtable_get ( + request->headers, "sec-websocket-protocol"); + protocol_array = weechat_string_split (sec_websocket_protocol_request, + ",", " ", 0, 0, &protocol_count); + + sec_websocket_protocol[0] = '\0'; + for (i = 0; i < protocol_count; i++) + { + if (strcmp (protocol_array[i], WEBSOCKET_SUB_PROTOCOL_API_WEECHAT) == 0) + { + snprintf ( + sec_websocket_protocol, sizeof (sec_websocket_protocol), + "Sec-WebSocket-Protocol: %s\r\n", + WEBSOCKET_SUB_PROTOCOL_API_WEECHAT); + break; + } + } + /* build the handshake (it will be sent as-is to client) */ snprintf (handshake, sizeof (handshake), "HTTP/1.1 101 Switching Protocols\r\n" @@ -488,9 +508,11 @@ relay_websocket_build_handshake (struct t_relay_http_request *request) "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: %s\r\n" "%s" + "%s" "\r\n", sec_websocket_accept, - sec_websocket_extensions); + sec_websocket_extensions, + sec_websocket_protocol); return strdup (handshake); } diff --git a/src/plugins/relay/relay-websocket.h b/src/plugins/relay/relay-websocket.h index 5845a4e8f..e1d4071b8 100644 --- a/src/plugins/relay/relay-websocket.h +++ b/src/plugins/relay/relay-websocket.h @@ -37,6 +37,8 @@ #define WEBSOCKET_FRAME_OPCODE_PING 0x09 #define WEBSOCKET_FRAME_OPCODE_PONG 0x0A +#define WEBSOCKET_SUB_PROTOCOL_API_WEECHAT "api.weechat" + struct t_relay_client; struct t_relay_http_request;