From cc92748747d88a86f96c4fcbb049267d2efb8377 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Tue, 2 Apr 2024 12:42:31 -0400 Subject: [PATCH] Move sending refresh tokens out of JS. --- core/app.js | 10 +------- core/auth.js | 67 +------------------------------------------------- src/httpd.js.c | 57 ++++++++++++++++++++++++++++++------------ 3 files changed, 43 insertions(+), 91 deletions(-) diff --git a/core/app.js b/core/app.js index ee386ae5..601dceba 100644 --- a/core/app.js +++ b/core/app.js @@ -88,7 +88,6 @@ function socket(request, response, client) { let process; let options = {}; let credentials = auth.query(request.headers); - let refresh = auth.makeRefresh(credentials); response.onClose = async function () { if (process && process.task) { @@ -241,14 +240,7 @@ function socket(request, response, client) { } }; - response.upgrade( - 100, - refresh - ? { - 'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`, - } - : {} - ); + response.upgrade(100, {}); } export {socket, App}; diff --git a/core/auth.js b/core/auth.js index 0253ab5c..4114b1d8 100644 --- a/core/auth.js +++ b/core/auth.js @@ -1,23 +1,4 @@ import * as core from './core.js'; -import * as form from './form.js'; - -const kRefreshInterval = 1 * 7 * 24 * 60 * 60 * 1000; - -/** - * Makes a Base64 value URL safe - * @param {string} value - * @returns TODOC - */ -function b64url(value) { - value = value.replaceAll('+', '-').replaceAll('/', '_'); - let equals = value.indexOf('='); - - if (equals !== -1) { - return value.substring(0, equals); - } else { - return value; - } -} /** * TODOC @@ -37,38 +18,6 @@ function unb64url(value) { } } -/** - * Creates a JSON Web Token - * @param {object} payload Object: {"name": "username"} - * @returns the JWT - */ -function makeJwt(payload) { - const ids = ssb.getIdentities(':auth'); - let id; - - if (ids?.length) { - id = ids[0]; - } else { - id = ssb.createIdentity(':auth'); - } - - const final_payload = b64url( - base64Encode( - JSON.stringify( - Object.assign({}, payload, { - exp: new Date().valueOf() + kRefreshInterval, - }) - ) - ) - ); - const jwt = [ - b64url(base64Encode(JSON.stringify({alg: 'HS256', typ: 'JWT'}))), - final_payload, - b64url(ssb.hmacsha256sign(final_payload, ':auth', id)), - ].join('.'); - return jwt; -} - /** * Validates a JWT ? * @param {*} session TODOC @@ -178,18 +127,4 @@ function query(headers) { } } -/** - * Refreshes a JWT ? - * @param {*} credentials TODOC - * @returns - */ -function makeRefresh(credentials) { - if (credentials?.session?.name) { - return { - token: makeJwt({name: credentials.session.name}), - interval: kRefreshInterval, - }; - } -} - -export {query, makeRefresh}; +export {query}; diff --git a/src/httpd.js.c b/src/httpd.js.c index 1fb74aea..b7057ae9 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -35,7 +35,10 @@ const int64_t k_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000; +static JSValue _authenticate_jwt(JSContext* context, const char* jwt); static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); +static const char* _make_session_jwt(tf_ssb_t* ssb, const char* name); +static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie); static JSClassID _httpd_class_id; static JSClassID _httpd_request_class_id; @@ -323,6 +326,22 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va headers[headers_count * 2 + 1] = key; headers_count++; + tf_ssb_t* ssb = tf_task_get_ssb(tf_task_get(context)); + const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session"); + JSValue jwt = _authenticate_jwt(context, session); + tf_free((void*)session); + JSValue name = JS_GetPropertyStr(context, jwt, "name"); + const char* name_string = JS_ToCString(context, name); + const char* session_token = _make_session_jwt(ssb, name_string); + const char* cookie = _make_set_session_cookie_header(request, session_token); + tf_free((void*)session_token); + JS_FreeCString(context, name_string); + JS_FreeValue(context, name); + JS_FreeValue(context, jwt); + headers[headers_count * 2 + 0] = "Set-Cookie"; + headers[headers_count * 2 + 1] = cookie ? cookie : ""; + headers_count++; + bool send_version = !tf_http_request_get_header(request, "sec-websocket-version") || strcmp(tf_http_request_get_header(request, "sec-websocket-version"), "13") != 0; if (send_version) { @@ -342,6 +361,8 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va JS_FreeCString(context, headers[i * 2 + 1]); } + tf_free((void*)cookie); + request->on_message = _httpd_message_callback; request->on_close = _httpd_websocket_close_callback; request->context = context; @@ -833,19 +854,25 @@ typedef struct _login_request_t bool session_is_new; } login_request_t; +static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie) +{ + const char* k_pattern = "session=%s; path=/; Max-Age=%" PRId64 "; %sSameSite=Strict; HttpOnly"; + int length = session_cookie ? snprintf(NULL, 0, k_pattern, session_cookie, k_refresh_interval, request->is_tls ? "Secure; " : "") : 0; + char* cookie = length ? tf_malloc(length + 1) : NULL; + if (cookie) + { + snprintf(cookie, length + 1, k_pattern, session_cookie, k_refresh_interval, request->is_tls ? "Secure; " : ""); + } + return cookie; +} + static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char* path, int result, const void* data, void* user_data) { login_request_t* login = user_data; tf_http_request_t* request = login->request; if (result >= 0) { - const char* k_pattern = "session=%s; path=/; Max-Age=%" PRId64 "; %sSameSite=Strict; HttpOnly"; - int length = login->session_cookie ? snprintf(NULL, 0, k_pattern, login->session_cookie, k_refresh_interval, request->is_tls ? "Secure; " : "") : 0; - char* cookie = length ? tf_malloc(length + 1) : NULL; - if (cookie) - { - snprintf(cookie, length + 1, k_pattern, login->session_cookie, k_refresh_interval, request->is_tls ? "Secure; " : ""); - } + const char* cookie = _make_set_session_cookie_header(request, login->session_cookie); const char* headers[] = { "Content-Type", "text/html; charset=utf-8", @@ -884,7 +911,7 @@ static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char { tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result); } - tf_free(cookie); + tf_free((void*)cookie); } else { @@ -1137,6 +1164,10 @@ static bool _get_auth_private_key(tf_ssb_t* ssb, uint8_t* out_private_key) static const char* _make_session_jwt(tf_ssb_t* ssb, const char* name) { + if (!name || !*name) + { + return NULL; + } uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 }; if (!_get_auth_private_key(ssb, private_key)) { @@ -1336,13 +1367,7 @@ static void _httpd_endpoint_login(tf_http_request_t* request) snprintf(url, sizeof(url), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host")); return_url = url; } - const char* k_pattern = "session=%s; path=/; Max-Age=%" PRId64 "; %sSameSite=Strict; HttpOnly"; - int length = send_session ? snprintf(NULL, 0, k_pattern, send_session, k_refresh_interval, request->is_tls ? "Secure; " : "") : 0; - char* cookie = length ? tf_malloc(length + 1) : NULL; - if (cookie) - { - snprintf(cookie, length + 1, k_pattern, send_session, k_refresh_interval, request->is_tls ? "Secure; " : ""); - } + const char* cookie = _make_set_session_cookie_header(request, send_session); const char* headers[] = { "Location", return_url, @@ -1350,7 +1375,7 @@ static void _httpd_endpoint_login(tf_http_request_t* request) cookie ? cookie : "", }; tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0); - tf_free(cookie); + tf_free((void*)cookie); tf_free((void*)send_session); } else