Remove auth.js. #7

This commit is contained in:
Cory McWilliams 2024-04-02 20:11:36 -04:00
parent cc92748747
commit 9f3171e3f1
8 changed files with 91 additions and 146 deletions

View File

@ -1,4 +1,3 @@
import * as auth from './auth.js';
import * as core from './core.js';
let g_next_id = 1;
@ -87,7 +86,7 @@ App.prototype.send = function (message) {
function socket(request, response, client) {
let process;
let options = {};
let credentials = auth.query(request.headers);
let credentials = httpd.auth_query(request.headers);
response.onClose = async function () {
if (process && process.task) {

View File

@ -1,130 +0,0 @@
import * as core from './core.js';
/**
* TODOC
* @param {string} value
* @returns
*/
function unb64url(value) {
value = value.replaceAll('-', '+').replaceAll('_', '/');
let remainder = value.length % 4;
if (remainder == 3) {
return value + '=';
} else if (remainder == 2) {
return value + '==';
} else {
return value;
}
}
/**
* Validates a JWT ?
* @param {*} session TODOC
* @returns
*/
function readSession(session) {
let jwt_parts = session?.split('.');
if (jwt_parts?.length === 3) {
let [header, payload, signature] = jwt_parts;
header = JSON.parse(utf8Decode(base64Decode(unb64url(header))));
if (header.typ === 'JWT' && header.alg === 'HS256') {
signature = unb64url(signature);
let id = ssb.getIdentities(':auth');
if (id?.length && ssb.hmacsha256verify(id[0], payload, signature)) {
const result = JSON.parse(utf8Decode(base64Decode(unb64url(payload))));
const now = new Date().valueOf();
if (now < result.exp) {
print(`JWT valid for another ${(result.exp - now) / 1000} seconds.`);
return result;
} else {
print(`JWT expired by ${(now - result.exp) / 1000} seconds.`);
}
} else {
print('JWT verification failed.');
}
} else {
print('Invalid JWT header.');
}
}
}
/**
* TODOC
* @param {*} headers most likely an object
* @returns
*/
function getCookies(headers) {
let cookies = {};
if (headers.cookie) {
let parts = headers.cookie.split(/,|;/);
for (let i in parts) {
let equals = parts[i].indexOf('=');
let name = parts[i].substring(0, equals).trim();
let value = parts[i].substring(equals + 1).trim();
cookies[name] = value;
}
}
return cookies;
}
/**
* Gets a user's permissions based on it's session ?
* @param {*} session TODOC
* @returns
*/
function getPermissions(session) {
let permissions;
let entry = readSession(session);
if (entry) {
permissions = getPermissionsForUser(entry.name);
permissions.authenticated = entry.name !== 'guest';
}
return permissions || {};
}
/**
* Get a user's permissions ?
* @param {string} userName TODOC
* @returns
*/
function getPermissionsForUser(userName) {
let permissions = {};
if (
core.globalSettings &&
core.globalSettings.permissions &&
core.globalSettings.permissions[userName]
) {
for (let i in core.globalSettings.permissions[userName]) {
permissions[core.globalSettings.permissions[userName][i]] = true;
}
}
return permissions;
}
/**
* TODOC
* @param {*} headers
* @returns
*/
function query(headers) {
let session = getCookies(headers).session;
let entry;
let autologin = tildefriends.args.autologin;
if ((entry = autologin ? {name: autologin} : readSession(session))) {
return {
session: entry,
permissions: autologin
? getPermissionsForUser(autologin)
: getPermissions(session),
};
}
}
export {query};

View File

@ -1,5 +1,4 @@
import * as app from './app.js';
import * as auth from './auth.js';
import * as form from './form.js';
import * as http from './http.js';
@ -967,7 +966,7 @@ async function useAppHandler(
},
respond: do_resolve,
},
credentials: auth.query(headers),
credentials: httpd.auth_query(headers),
packageOwner: packageOwner,
packageName: packageName,
}
@ -1098,7 +1097,7 @@ async function blobHandler(request, response, blobId, uri) {
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1];
let appName = match[2];
let credentials = auth.query(request.headers);
let credentials = httpd.auth_query(request.headers);
if (
credentials &&
credentials.session &&
@ -1161,7 +1160,7 @@ async function blobHandler(request, response, blobId, uri) {
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1];
let appName = match[2];
let credentials = auth.query(request.headers);
let credentials = https.auth_query(request.headers);
if (
credentials &&
credentials.session &&

View File

@ -36,6 +36,7 @@
const int64_t k_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000;
static JSValue _authenticate_jwt(JSContext* context, const char* jwt);
static const char* _get_property(tf_ssb_t* ssb, const char* id, const char* key);
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);
@ -443,6 +444,58 @@ static JSValue _httpd_set_http_redirect(JSContext* context, JSValueConst this_va
return JS_UNDEFINED;
}
static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
JSValue headers = argv[0];
if (JS_IsUndefined(headers))
{
return JS_UNDEFINED;
}
JSValue cookie = JS_GetPropertyStr(context, headers, "cookie");
const char* cookie_string = JS_ToCString(context, cookie);
const char* session = tf_http_get_cookie(cookie_string, "session");
JSValue entry = _authenticate_jwt(context, session);
tf_free((void*)session);
JS_FreeCString(context, cookie_string);
JS_FreeValue(context, cookie);
JSValue result = JS_UNDEFINED;
if (!JS_IsUndefined(entry))
{
result = JS_NewObject(context);
JS_SetPropertyStr(context, result, "session", entry);
JSValue out_permissions = JS_NewObject(context);
JS_SetPropertyStr(context, result, "permissions", out_permissions);
JSValue name = JS_GetPropertyStr(context, entry, "name");
const char* name_string = JS_ToCString(context, name);
const char* settings = _get_property(ssb, "core", "settings");
JSValue settings_value = settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED;
JSValue permissions = JS_GetPropertyStr(context, settings_value, "permissions");
JSValue user_permissions = JS_GetPropertyStr(context, permissions, name_string);
int length = tf_util_get_length(context, user_permissions);
for (int i = 0; i < length; i++)
{
JSValue permission = JS_GetPropertyUint32(context, user_permissions, i);
const char* permission_string = JS_ToCString(context, permission);
JS_SetPropertyStr(context, out_permissions, permission_string, JS_TRUE);
JS_FreeCString(context, permission_string);
JS_FreeValue(context, permission);
}
JS_FreeValue(context, user_permissions);
JS_FreeValue(context, permissions);
JS_FreeValue(context, settings_value);
tf_free((void*)settings);
JS_FreeCString(context, name_string);
JS_FreeValue(context, name);
}
return result;
}
static void _httpd_finalizer(JSRuntime* runtime, JSValue value)
{
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
@ -965,13 +1018,14 @@ static JSValue _authenticate_jwt(JSContext* context, const char* jwt)
return JS_UNDEFINED;
}
uint8_t header[256];
uint8_t header[256] = { 0 };
size_t actual_length = 0;
if (sodium_base642bin(header, sizeof(header), jwt, dot[0], NULL, &actual_length, NULL, sodium_base64_VARIANT_URLSAFE_NO_PADDING) != 0)
if (sodium_base642bin(header, sizeof(header), jwt, dot[0], NULL, &actual_length, NULL, sodium_base64_VARIANT_URLSAFE_NO_PADDING) != 0 || actual_length >= sizeof(header))
{
return JS_UNDEFINED;
}
header[actual_length] = '\0';
JSValue header_value = JS_ParseJSON(context, (const char*)header, actual_length, NULL);
bool header_valid = _string_property_equals(context, header_value, "typ", "JWT") && _string_property_equals(context, header_value, "alg", "HS256");
JS_FreeValue(context, header_value);
@ -986,19 +1040,21 @@ static JSValue _authenticate_jwt(JSContext* context, const char* jwt)
tf_ssb_db_identity_visit(ssb, ":auth", _public_key_visit, public_key_b64);
const char* payload = jwt + dot[0] + 1;
size_t payload_length = dot[1] - dot[0];
if (!tf_ssb_hmacsha256_verify(public_key_b64, payload, payload_length, jwt + dot[1] + 1))
size_t payload_length = dot[1] - dot[0] - 1;
if (!tf_ssb_hmacsha256_verify(public_key_b64, payload, payload_length, jwt + dot[1] + 1, true))
{
return JS_UNDEFINED;
}
uint8_t payload_bin[256];
size_t actual_payload_length = 0;
if (sodium_base642bin(payload_bin, sizeof(payload_bin), payload, payload_length, NULL, &actual_payload_length, NULL, sodium_base64_VARIANT_URLSAFE_NO_PADDING) != 0)
if (sodium_base642bin(payload_bin, sizeof(payload_bin), payload, payload_length, NULL, &actual_payload_length, NULL, sodium_base64_VARIANT_URLSAFE_NO_PADDING) != 0 ||
actual_payload_length >= sizeof(payload_bin))
{
return JS_UNDEFINED;
}
payload_bin[actual_payload_length] = '\0';
JSValue parsed = JS_ParseJSON(context, (const char*)payload_bin, actual_payload_length, NULL);
JSValue exp = JS_GetPropertyStr(context, parsed, "exp");
int64_t exp_value = 0;
@ -1539,6 +1595,7 @@ void tf_httpd_register(JSContext* context)
JS_SetPropertyStr(context, httpd, "all", JS_NewCFunction(context, _httpd_endpoint_all, "all", 2));
JS_SetPropertyStr(context, httpd, "start", JS_NewCFunction(context, _httpd_endpoint_start, "start", 2));
JS_SetPropertyStr(context, httpd, "set_http_redirect", JS_NewCFunction(context, _httpd_set_http_redirect, "set_http_redirect", 1));
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
JS_SetPropertyStr(context, global, "httpd", httpd);
JS_FreeValue(context, global);
}

View File

@ -3911,7 +3911,7 @@ void tf_ssb_schedule_work(tf_ssb_t* ssb, int delay_ms, void (*callback)(tf_ssb_t
uv_unref((uv_handle_t*)&timer->timer);
}
bool tf_ssb_hmacsha256_verify(const char* public_key, const void* payload, size_t payload_length, const char* signature)
bool tf_ssb_hmacsha256_verify(const char* public_key, const void* payload, size_t payload_length, const char* signature, bool signature_is_urlb64)
{
bool result = false;
@ -3926,7 +3926,8 @@ bool tf_ssb_hmacsha256_verify(const char* public_key, const void* payload, size_
if (tf_base64_decode(public_key_start, public_key_end - public_key_start, bin_public_key, sizeof(bin_public_key)) > 0)
{
uint8_t bin_signature[crypto_sign_BYTES] = { 0 };
if (tf_base64_decode(signature, strlen(signature), bin_signature, sizeof(bin_signature)) > 0)
if (sodium_base642bin(bin_signature, sizeof(bin_signature), signature, strlen(signature), NULL, NULL, NULL,
signature_is_urlb64 ? sodium_base64_VARIANT_URLSAFE_NO_PADDING : sodium_base64_VARIANT_ORIGINAL) == 0)
{
if (crypto_sign_verify_detached(bin_signature, (const uint8_t*)payload, payload_length, bin_public_key) == 0)
{

View File

@ -1179,8 +1179,16 @@ bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const c
}
}
}
else
{
tf_printf("Bind failed: %s.\n", sqlite3_errmsg(db));
}
sqlite3_finalize(statement);
}
else
{
tf_printf("Prepare failed: %s.\n", sqlite3_errmsg(db));
}
tf_ssb_release_db_reader(ssb, db);
return success;
}

View File

@ -957,6 +957,15 @@ void tf_ssb_set_room_name(tf_ssb_t* ssb, const char* room_name);
*/
void tf_ssb_schedule_work(tf_ssb_t* ssb, int delay_ms, void (*callback)(tf_ssb_t* ssb, void* user_data), void* user_data);
bool tf_ssb_hmacsha256_verify(const char* public_key, const void* payload, size_t payload_length, const char* signature);
/**
** Verify a signature.
** @param public_key The public key for which the message was signed.
** @param payload The signed payload.
** @param payload_length The length of the signed payload in bytes.
** @param signature The signature.
** @param signature_is_urlb64 True if the signature is in URL base64 format, otherwise standard base64.
** @return true If the message was successfully verified.
*/
bool tf_ssb_hmacsha256_verify(const char* public_key, const void* payload, size_t payload_length, const char* signature, bool signature_is_urlb64);
/** @} */

View File

@ -606,7 +606,7 @@ static void _test_file(const tf_test_options_t* options)
"});\n");
char command[256];
snprintf(command, sizeof(command), "%s run --db-path=:memory: -s out/test.js" TEST_ARGS, options->exe_path);
snprintf(command, sizeof(command), "%s run --db-path=out/test.db -s out/test.js" TEST_ARGS, options->exe_path);
tf_printf("%s\n", command);
int result = system(command);
tf_printf("returned %d\n", WEXITSTATUS(result));
@ -634,13 +634,15 @@ static void _test_sign(const tf_test_options_t* options)
" exit(1);\n"
"}\n");
unlink("out/test_db0.sqlite");
char command[256];
snprintf(command, sizeof(command), "%s run --db-path=:memory: -s out/test.js" TEST_ARGS, options->exe_path);
snprintf(command, sizeof(command), "%s run --db-path=out/test_db0.sqlite -s out/test.js" TEST_ARGS, options->exe_path);
tf_printf("%s\n", command);
int result = system(command);
tf_printf("returned %d\n", WEXITSTATUS(result));
assert(WIFEXITED(result));
assert(WEXITSTATUS(result) == 0);
unlink("out/test_db0.sqlite");
unlink("out/test.js");
}