Move sending refresh tokens out of JS.
This commit is contained in:
parent
0a0b0c1adb
commit
cc92748747
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
|
||||||
|
Loading…
Reference in New Issue
Block a user