Move sending refresh tokens out of JS.
This commit is contained in:
		
							
								
								
									
										10
									
								
								core/app.js
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								core/app.js
									
									
									
									
									
								
							@@ -88,7 +88,6 @@ function socket(request, response, client) {
 | 
				
			|||||||
	let process;
 | 
						let process;
 | 
				
			||||||
	let options = {};
 | 
						let options = {};
 | 
				
			||||||
	let credentials = auth.query(request.headers);
 | 
						let credentials = auth.query(request.headers);
 | 
				
			||||||
	let refresh = auth.makeRefresh(credentials);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	response.onClose = async function () {
 | 
						response.onClose = async function () {
 | 
				
			||||||
		if (process && process.task) {
 | 
							if (process && process.task) {
 | 
				
			||||||
@@ -241,14 +240,7 @@ function socket(request, response, client) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	response.upgrade(
 | 
						response.upgrade(100, {});
 | 
				
			||||||
		100,
 | 
					 | 
				
			||||||
		refresh
 | 
					 | 
				
			||||||
			? {
 | 
					 | 
				
			||||||
					'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`,
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			: {}
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {socket, App};
 | 
					export {socket, App};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										67
									
								
								core/auth.js
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								core/auth.js
									
									
									
									
									
								
							@@ -1,23 +1,4 @@
 | 
				
			|||||||
import * as core from './core.js';
 | 
					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
 | 
					 * 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 ?
 | 
					 * Validates a JWT ?
 | 
				
			||||||
 * @param {*} session TODOC
 | 
					 * @param {*} session TODOC
 | 
				
			||||||
@@ -178,18 +127,4 @@ function query(headers) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					export {query};
 | 
				
			||||||
 * 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};
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const int64_t k_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000;
 | 
					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 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_class_id;
 | 
				
			||||||
static JSClassID _httpd_request_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[headers_count * 2 + 1] = key;
 | 
				
			||||||
		headers_count++;
 | 
							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;
 | 
							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)
 | 
							if (send_version)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
@@ -342,6 +361,8 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va
 | 
				
			|||||||
			JS_FreeCString(context, headers[i * 2 + 1]);
 | 
								JS_FreeCString(context, headers[i * 2 + 1]);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tf_free((void*)cookie);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		request->on_message = _httpd_message_callback;
 | 
							request->on_message = _httpd_message_callback;
 | 
				
			||||||
		request->on_close = _httpd_websocket_close_callback;
 | 
							request->on_close = _httpd_websocket_close_callback;
 | 
				
			||||||
		request->context = context;
 | 
							request->context = context;
 | 
				
			||||||
@@ -833,19 +854,25 @@ typedef struct _login_request_t
 | 
				
			|||||||
	bool session_is_new;
 | 
						bool session_is_new;
 | 
				
			||||||
} login_request_t;
 | 
					} 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)
 | 
					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;
 | 
						login_request_t* login = user_data;
 | 
				
			||||||
	tf_http_request_t* request = login->request;
 | 
						tf_http_request_t* request = login->request;
 | 
				
			||||||
	if (result >= 0)
 | 
						if (result >= 0)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		const char* k_pattern = "session=%s; path=/; Max-Age=%" PRId64 "; %sSameSite=Strict; HttpOnly";
 | 
							const char* cookie = _make_set_session_cookie_header(request, login->session_cookie);
 | 
				
			||||||
		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* headers[] = {
 | 
							const char* headers[] = {
 | 
				
			||||||
			"Content-Type",
 | 
								"Content-Type",
 | 
				
			||||||
			"text/html; charset=utf-8",
 | 
								"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_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		tf_free(cookie);
 | 
							tf_free((void*)cookie);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	else
 | 
						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)
 | 
					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 };
 | 
						uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
 | 
				
			||||||
	if (!_get_auth_private_key(ssb, private_key))
 | 
						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"));
 | 
								snprintf(url, sizeof(url), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
 | 
				
			||||||
			return_url = url;
 | 
								return_url = url;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		const char* k_pattern = "session=%s; path=/; Max-Age=%" PRId64 "; %sSameSite=Strict; HttpOnly";
 | 
							const char* cookie = _make_set_session_cookie_header(request, send_session);
 | 
				
			||||||
		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* headers[] = {
 | 
							const char* headers[] = {
 | 
				
			||||||
			"Location",
 | 
								"Location",
 | 
				
			||||||
			return_url,
 | 
								return_url,
 | 
				
			||||||
@@ -1350,7 +1375,7 @@ static void _httpd_endpoint_login(tf_http_request_t* request)
 | 
				
			|||||||
			cookie ? cookie : "",
 | 
								cookie ? cookie : "",
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
 | 
							tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
 | 
				
			||||||
		tf_free(cookie);
 | 
							tf_free((void*)cookie);
 | 
				
			||||||
		tf_free((void*)send_session);
 | 
							tf_free((void*)send_session);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user