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 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}; | ||||
|   | ||||
							
								
								
									
										67
									
								
								core/auth.js
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								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}; | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user