Compare commits
17 Commits
085f62aadf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c9654b480 | |||
| 14e36308f9 | |||
| cd8df2fe15 | |||
| 8abcdd1e7d | |||
| 97aeff60cc | |||
| 86d6a5c049 | |||
| f6f815eec1 | |||
| 73a1c1d978 | |||
| 5445072d36 | |||
| d9a2519e9b | |||
| 687a85dbd8 | |||
| 9e25aa1c54 | |||
| e309f519f2 | |||
| 5ccd9f16c3 | |||
| 7d596ebd3b | |||
| 938f728eb9 | |||
| 6e8a0031a8 |
21
GNUmakefile
21
GNUmakefile
@@ -1213,7 +1213,26 @@ out/tildefriends-x86_64.AppImage: out/release/tildefriends out/data.zip
|
|||||||
@cd out; ./appimagetool --appimage-extract; cd ..
|
@cd out; ./appimagetool --appimage-extract; cd ..
|
||||||
@cd out; unset SOURCE_DATE_EPOCH; PATH=$$PATH:squashfs-root/usr/bin ARCH=x86_64 squashfs-root/usr/bin/appimagetool -u 'zsync|https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage.zsync' tildefriends.AppDir tildefriends-x86_64.AppImage; cd ..
|
@cd out; unset SOURCE_DATE_EPOCH; PATH=$$PATH:squashfs-root/usr/bin ARCH=x86_64 squashfs-root/usr/bin/appimagetool -u 'zsync|https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage.zsync' tildefriends.AppDir tildefriends-x86_64.AppImage; cd ..
|
||||||
|
|
||||||
appimage: out/tildefriends-x86_64.AppImage ## Build an AppImage.
|
out/tildefriends-aarch64.AppImage: out/armrelease/tildefriends out/data.zip
|
||||||
|
@echo "[appimage] $$@"
|
||||||
|
@rm -rf out/tildefriends_aarch64.AppDir
|
||||||
|
@mkdir -p out/tildefriends_aarch64.AppDir/usr/bin
|
||||||
|
@mkdir -p out/tildefriends_aarch64.AppDir/usr/share/applications
|
||||||
|
@mkdir -p out/tildefriends_aarch64.AppDir/usr/share/icons/hicolor/scalable/apps
|
||||||
|
@mkdir -p out/tildefriends_aarch64.AppDir/usr/share/tildefriends
|
||||||
|
@echo $(APPIMAGETOOL_MD5) > out/appimagetool.md5
|
||||||
|
@test -x out/appimagetool || curl -q -L -o out/appimagetool $(APPIMAGETOOL_URL) && md5sum -c out/appimagetool.md5 && chmod +x out/appimagetool
|
||||||
|
@echo "[Desktop Entry]\nName=tildefriends\nExec=/usr/bin/tildefriends\nIcon=/usr/share/icons/hicolor/scalable/apps/tildefriends\nType=Application\nCategories=Network" > out/tildefriends_aarch64.AppDir/tildefriends.desktop
|
||||||
|
@cp src/ios/tildefriends.svg out/tildefriends_aarch64.AppDir/usr/share/icons/hicolor/scalable/apps/
|
||||||
|
@cp src/ios/tildefriends.svg out/tildefriends_aarch64.AppDir/
|
||||||
|
@cp out/armrelease/tildefriends out/tildefriends_aarch64.AppDir/usr/bin/
|
||||||
|
@cp out/data.zip out/tildefriends_aarch64.AppDir/usr/share/tildefriends/data.zip
|
||||||
|
@echo "#!/bin/sh\n\$${APPDIR}/usr/bin/tildefriends run -z \$$APPDIR/usr/share/tildefriends/data.zip" > out/tildefriends_aarch64.AppDir/AppRun
|
||||||
|
@chmod +x out/tildefriends_aarch64.AppDir/AppRun
|
||||||
|
@cd out; ./appimagetool --appimage-extract; cd ..
|
||||||
|
@cd out; unset SOURCE_DATE_EPOCH; PATH=$$PATH:squashfs-root/usr/bin ARCH=arm_aarch64 squashfs-root/usr/bin/appimagetool -u 'zsync|https://dev.tildefriends.net/releases/tildefriends-aarch64.AppImage.zsync' tildefriends_aarch64.AppDir tildefriends-aarch64.AppImage; cd ..
|
||||||
|
|
||||||
|
appimage: out/tildefriends-x86_64.AppImage out/tildefriends-aarch64.AppImage ## Build AppImages.
|
||||||
.PHONY: appimage
|
.PHONY: appimage
|
||||||
|
|
||||||
flatpak: out/ ## Build a flatpak.
|
flatpak: out/ ## Build a flatpak.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "📚",
|
"emoji": "📖",
|
||||||
"previous": "&EO5ifwzemEeSJsN6SJ2VTyE+sqnwU2gikIngQimwnDo=.sha256"
|
"previous": "&u7ri5Gi1AK6SbWRmc3S8vN40QrWL90/DKDiDTeDDiPQ=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,12 +46,25 @@ async function main() {
|
|||||||
fields.key,
|
fields.key,
|
||||||
RANK() OVER (PARTITION BY messages.author, messages.content ->> '$.about', fields.key ORDER BY messages.sequence DESC) AS rank,
|
RANK() OVER (PARTITION BY messages.author, messages.content ->> '$.about', fields.key ORDER BY messages.sequence DESC) AS rank,
|
||||||
fields.value
|
fields.value
|
||||||
FROM messages, json_each(messages.content) AS fields, json_each(?) AS book, json_each(?) AS following
|
FROM messages, json_each(messages.content) AS fields, json_each(?1) AS book, json_each(?2) AS following
|
||||||
ON messages.author = following.value
|
ON messages.author = following.value
|
||||||
WHERE
|
WHERE
|
||||||
messages.content ->> 'type' = 'about'
|
messages.content ->> 'type' = 'about'
|
||||||
AND messages.content ->> '$.about' = book.value
|
AND messages.content ->> '$.about' = book.value
|
||||||
AND NOT fields.key IN ('about', 'type')
|
AND NOT fields.key IN ('about', 'type')
|
||||||
|
UNION
|
||||||
|
SELECT
|
||||||
|
messages.author,
|
||||||
|
messages.content ->> '$.updates' AS about,
|
||||||
|
fields.key,
|
||||||
|
RANK() OVER (PARTITION BY messages.author, messages.content ->> '$.updates', fields.key ORDER BY messages.sequence DESC) AS rank,
|
||||||
|
fields.value
|
||||||
|
FROM messages, json_each(messages.content) AS fields, json_each(?1) AS book, json_each(?2) AS following
|
||||||
|
ON messages.author = following.value
|
||||||
|
WHERE
|
||||||
|
messages.content ->> 'type' = 'bookclubUpdate'
|
||||||
|
AND messages.content ->> '$.updates' = book.value
|
||||||
|
AND NOT fields.key IN ('about', 'updates', 'type')
|
||||||
) WHERE rank = 1
|
) WHERE rank = 1
|
||||||
GROUP BY author, about
|
GROUP BY author, about
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🦀",
|
"emoji": "🦀",
|
||||||
"previous": "&S0BuSm19vBzA6Gf97/rYcwndKyb55MxoITnz7xRjlzg=.sha256"
|
"previous": "&aZw1V6n7AbOjTcVobViT2BpsIo7x1PFY950HHJpdBfE=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -866,16 +866,20 @@ class TfElement extends LitElement {
|
|||||||
this.is_administrator
|
this.is_administrator
|
||||||
? html`
|
? html`
|
||||||
<button
|
<button
|
||||||
class=${'w3-bar-item w3-button w3-circle w3-ripple w3-right' +
|
class="w3-bar-item w3-button w3-circle w3-right"
|
||||||
(this.connections?.some((x) => x.flags.one_shot)
|
|
||||||
? ' w3-spin'
|
|
||||||
: '')}
|
|
||||||
@click=${this.refresh}
|
@click=${this.refresh}
|
||||||
>
|
>
|
||||||
↻
|
<span
|
||||||
|
style="display: inline-block"
|
||||||
|
class=${this.connections?.some((x) => x.flags.one_shot)
|
||||||
|
? ' w3-spin'
|
||||||
|
: ''}
|
||||||
|
>
|
||||||
|
↻
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="w3-bar-item w3-button w3-ripple w3-right"
|
class="w3-bar-item w3-button w3-right"
|
||||||
@click=${this.toggle_stay_connected}
|
@click=${this.toggle_stay_connected}
|
||||||
>
|
>
|
||||||
${this.stay_connected ? '🔗' : '⛓️💥'}
|
${this.stay_connected ? '🔗' : '⛓️💥'}
|
||||||
|
|||||||
@@ -472,7 +472,9 @@ class TfMessageElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copy_id(event) {
|
copy_id(event) {
|
||||||
navigator.clipboard.writeText(this.message?.id);
|
navigator.clipboard.writeText(this.message?.id).catch(function (e) {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_menu(event) {
|
toggle_menu(event) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "👋",
|
"emoji": "👋",
|
||||||
"previous": "&IwbeqN5jcUWa8wbnWxduiwel9VldRO/dARDjljX33PM=.sha256"
|
"previous": "&sVSmI40DUgnS4TUa2AiKrlNj+qN3WDeXII3364OSMIo=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,11 +268,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sandbox Section -->
|
<!-- Sandbox Section -->
|
||||||
<div class="w3-padding-64 w3-grey">
|
<div class="w3-padding-64 w3-pale-blue">
|
||||||
<div class="w3-row-padding">
|
<div class="w3-row-padding">
|
||||||
<div class="w3-col">
|
<div class="w3-col">
|
||||||
<h1 class="w3-jumbo" style="text-align: right">
|
<h1 class="w3-jumbo" style="text-align: right">
|
||||||
<b>Sandbox Security</b>
|
<b>App Sandboxes</b>
|
||||||
</h1>
|
</h1>
|
||||||
<i
|
<i
|
||||||
class="fa fa-road-barrier w3-right w3-jumbo w3-text-yellow"
|
class="fa fa-road-barrier w3-right w3-jumbo w3-text-yellow"
|
||||||
@@ -280,7 +280,7 @@
|
|||||||
></i>
|
></i>
|
||||||
<p>
|
<p>
|
||||||
Tilde Friends tries to make sure apps can be trusted using similar
|
Tilde Friends tries to make sure apps can be trusted using similar
|
||||||
techniques to how web browsers and operating systems do it.
|
techniques to web browsers and operating systems.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This is all a work in progress, and it varies by platform, so don't
|
This is all a work in progress, and it varies by platform, so don't
|
||||||
@@ -294,16 +294,24 @@
|
|||||||
|
|
||||||
<!-- Technlology Section -->
|
<!-- Technlology Section -->
|
||||||
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
|
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
|
||||||
<h1 class="w3-jumbo"><b>Built to Last</b></h1>
|
<h1 class="w3-jumbo"><b>One Pile of Code</b></h1>
|
||||||
<p>
|
<div class="w3-left-align">
|
||||||
Tilde Friends strives to use only simple and widely adopted dependencies
|
<p>
|
||||||
in order to keep it easy to build for all sorts of platforms and
|
Tilde Friends diverges from the Node.js web of modules from which
|
||||||
maintainable for a very long time.
|
Secure Scuttlebutt was first developed. Here we strive to maintain a
|
||||||
</p>
|
single C program that works as a foundation for building a wide
|
||||||
<p>
|
variety of social and other applications.
|
||||||
Though of course for building Tilde Friends apps, you are free to use
|
</p>
|
||||||
whatever fits on top.
|
<p>
|
||||||
</p>
|
Tilde Friends uses only a handful of simple and widely adopted
|
||||||
|
dependencies in order to keep it easy to build for all sorts of
|
||||||
|
platforms and maintainable for a very long time.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Though of course for building Tilde Friends apps, you are free to use
|
||||||
|
whatever works.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="w3-row" style="margin-top: 64px">
|
<div class="w3-row" style="margin-top: 64px">
|
||||||
<a
|
<a
|
||||||
@@ -373,7 +381,7 @@
|
|||||||
<footer class="w3-container w3-padding-32 w3-blue-grey w3-center w3-xlarge">
|
<footer class="w3-container w3-padding-32 w3-blue-grey w3-center w3-xlarge">
|
||||||
<p class="w3-medium">
|
<p class="w3-medium">
|
||||||
This page and Tilde Friends itself was made by Cory mostly in coffee
|
This page and Tilde Friends itself was made by Cory mostly in coffee
|
||||||
shops and a local pizza place.
|
shops and a local pizza place while listening to Gary's Bangers.
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1037,6 +1037,7 @@ async function update_html() {
|
|||||||
let new_html = new_doc.getElementById('document').attributes.srcdoc.value;
|
let new_html = new_doc.getElementById('document').attributes.srcdoc.value;
|
||||||
let iframe = document.getElementById('document');
|
let iframe = document.getElementById('document');
|
||||||
let sandbox = iframe.sandbox;
|
let sandbox = iframe.sandbox;
|
||||||
|
let allow = iframe.allow;
|
||||||
|
|
||||||
let iframe_parent = iframe.parentNode;
|
let iframe_parent = iframe.parentNode;
|
||||||
iframe_parent.removeChild(iframe);
|
iframe_parent.removeChild(iframe);
|
||||||
@@ -1045,6 +1046,7 @@ async function update_html() {
|
|||||||
new_iframe.sandbox = sandbox;
|
new_iframe.sandbox = sandbox;
|
||||||
new_iframe.id = 'document';
|
new_iframe.id = 'document';
|
||||||
new_iframe.srcdoc = new_html;
|
new_iframe.srcdoc = new_html;
|
||||||
|
new_iframe.allow = allow;
|
||||||
iframe_parent.appendChild(new_iframe);
|
iframe_parent.appendChild(new_iframe);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(error);
|
alert(error);
|
||||||
|
|||||||
39
core/core.js
39
core/core.js
@@ -375,46 +375,7 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
|
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
|
||||||
);
|
);
|
||||||
imports.ssb.createIdentity = () => process.createIdentity();
|
imports.ssb.createIdentity = () => process.createIdentity();
|
||||||
imports.ssb.addIdentity = function (id) {
|
|
||||||
if (
|
|
||||||
process.credentials &&
|
|
||||||
process.credentials.session &&
|
|
||||||
process.credentials.session.name
|
|
||||||
) {
|
|
||||||
return Promise.resolve(
|
|
||||||
imports.core.permissionTest('ssb_id_add')
|
|
||||||
).then(function () {
|
|
||||||
return ssb.addIdentity(process.credentials.session.name, id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
imports.ssb.deleteIdentity = function (id) {
|
|
||||||
if (
|
|
||||||
process.credentials &&
|
|
||||||
process.credentials.session &&
|
|
||||||
process.credentials.session.name
|
|
||||||
) {
|
|
||||||
return Promise.resolve(
|
|
||||||
imports.core.permissionTest('ssb_id_delete')
|
|
||||||
).then(function () {
|
|
||||||
return ssb.deleteIdentity(process.credentials.session.name, id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
|
imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
|
||||||
imports.ssb.getPrivateKey = function (id) {
|
|
||||||
if (
|
|
||||||
process.credentials &&
|
|
||||||
process.credentials.session &&
|
|
||||||
process.credentials.session.name
|
|
||||||
) {
|
|
||||||
return Promise.resolve(
|
|
||||||
imports.core.permissionTest('ssb_id_export')
|
|
||||||
).then(function () {
|
|
||||||
return ssb.getPrivateKey(process.credentials.session.name, id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (
|
if (
|
||||||
process.credentials &&
|
process.credentials &&
|
||||||
process.credentials.session &&
|
process.credentials.session &&
|
||||||
|
|||||||
@@ -178,6 +178,7 @@
|
|||||||
<iframe
|
<iframe
|
||||||
id="document"
|
id="document"
|
||||||
sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-popups allow-downloads"
|
sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-popups allow-downloads"
|
||||||
|
allow="clipboard-write"
|
||||||
></iframe>
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
6
deps/codemirror_src/package-lock.json
generated
vendored
6
deps/codemirror_src/package-lock.json
generated
vendored
@@ -186,9 +186,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/view": {
|
"node_modules/@codemirror/view": {
|
||||||
"version": "6.39.6",
|
"version": "6.39.7",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.6.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.7.tgz",
|
||||||
"integrity": "sha512-/N+SoP5NndJjkGInp3BwlUa3KQKD6bDo0TV6ep37ueAdQ7BVu/PqlZNywmgjCq0MQoZadZd8T+MZucSr7fktyQ==",
|
"integrity": "sha512-3Vif9hnNHJnl2YgOtkR/wzGzhYcQ8gy3LGdUhkLUU8xSBbgsTxrE8he/CMTpeINm5TgxLe2FmzvF6IYQL/BSAg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.5.0",
|
"@codemirror/state": "^6.5.0",
|
||||||
|
|||||||
258
src/api.js.c
258
src/api.js.c
@@ -1728,6 +1728,261 @@ static JSValue _tf_ssb_appendMessageWithIdentity(JSContext* context, JSValueCons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct _add_identity_t
|
||||||
|
{
|
||||||
|
uint8_t key[crypto_sign_SECRETKEYBYTES / 2];
|
||||||
|
bool added;
|
||||||
|
JSValue result;
|
||||||
|
JSValue promise[2];
|
||||||
|
char user[];
|
||||||
|
} add_identity_t;
|
||||||
|
|
||||||
|
static void _tf_ssb_add_identity_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
add_identity_t* work = user_data;
|
||||||
|
uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
|
||||||
|
unsigned char seed[crypto_sign_SEEDBYTES];
|
||||||
|
uint8_t secret_key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
||||||
|
memcpy(secret_key, work->key, sizeof(secret_key) / 2);
|
||||||
|
if (crypto_sign_ed25519_sk_to_seed(seed, secret_key) == 0 && crypto_sign_seed_keypair(public_key, secret_key, seed) == 0)
|
||||||
|
{
|
||||||
|
char public_key_b64[512];
|
||||||
|
tf_base64_encode(public_key, sizeof(public_key), public_key_b64, sizeof(public_key_b64));
|
||||||
|
snprintf(public_key_b64 + strlen(public_key_b64), sizeof(public_key_b64) - strlen(public_key_b64), ".ed25519");
|
||||||
|
|
||||||
|
uint8_t combined[crypto_sign_SECRETKEYBYTES];
|
||||||
|
memcpy(combined, work->key, sizeof(work->key));
|
||||||
|
memcpy(combined + sizeof(work->key), public_key, sizeof(public_key));
|
||||||
|
char combined_b64[512];
|
||||||
|
tf_base64_encode(combined, sizeof(combined), combined_b64, sizeof(combined_b64));
|
||||||
|
snprintf(combined_b64 + strlen(combined_b64), sizeof(combined_b64) - strlen(combined_b64), ".ed25519");
|
||||||
|
|
||||||
|
work->added = tf_ssb_db_identity_add(ssb, work->user, public_key_b64, combined_b64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _tf_ssb_add_identity_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
add_identity_t* work = user_data;
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue result = JS_IsUndefined(work->result) ? (work->added ? JS_TRUE : JS_UNDEFINED) : work->result;
|
||||||
|
JSValue error = JS_Call(context, work->promise[work->added ? 0 : 1], JS_UNDEFINED, 1, &result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
tf_util_report_error(context, error);
|
||||||
|
JS_FreeValue(context, error);
|
||||||
|
JS_FreeValue(context, work->result);
|
||||||
|
JS_FreeValue(context, work->promise[0]);
|
||||||
|
JS_FreeValue(context, work->promise[1]);
|
||||||
|
tf_free(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _tf_ssb_add_identity_permission_callback(JSContext* context, bool granted, JSValue value, void* user_data)
|
||||||
|
{
|
||||||
|
add_identity_t* work = user_data;
|
||||||
|
tf_task_t* task = tf_task_get(context);
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
if (granted)
|
||||||
|
{
|
||||||
|
tf_ssb_run_work(ssb, _tf_ssb_add_identity_work, _tf_ssb_add_identity_after_work, work);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
work->result = JS_DupValue(context, value);
|
||||||
|
_tf_ssb_add_identity_after_work(ssb, -1, work);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _tf_ssb_addIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||||
|
{
|
||||||
|
JSValue process = data[0];
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
|
const char* user = _tf_ssb_get_process_credentials_session_name(context, process);
|
||||||
|
size_t user_length = user ? strlen(user) : 0;
|
||||||
|
|
||||||
|
JSValue buffer = JS_UNDEFINED;
|
||||||
|
size_t length = 0;
|
||||||
|
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
|
||||||
|
if (!array)
|
||||||
|
{
|
||||||
|
size_t offset;
|
||||||
|
size_t element_size;
|
||||||
|
buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
|
||||||
|
if (!JS_IsException(buffer))
|
||||||
|
{
|
||||||
|
array = tf_util_try_get_array_buffer(context, &length, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array)
|
||||||
|
{
|
||||||
|
if (length == crypto_sign_SECRETKEYBYTES / 2)
|
||||||
|
{
|
||||||
|
add_identity_t* work = tf_malloc(sizeof(add_identity_t) + user_length + 1);
|
||||||
|
*work = (add_identity_t) { .result = JS_UNDEFINED };
|
||||||
|
memcpy(work->key, array, sizeof(work->key));
|
||||||
|
if (user)
|
||||||
|
{
|
||||||
|
memcpy(work->user, user, user_length + 1);
|
||||||
|
}
|
||||||
|
result = JS_NewPromiseCapability(context, work->promise);
|
||||||
|
_tf_ssb_permission_test(context, process, "ssb_id_add", "Add an identity.", _tf_ssb_add_identity_permission_callback, work);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = JS_ThrowInternalError(context, "Unexpected private key size: %d vs. %d\n", (int)length, crypto_sign_SECRETKEYBYTES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = JS_ThrowInternalError(context, "Expected array argument.");
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, buffer);
|
||||||
|
JS_FreeCString(context, user);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct _delete_identity_t
|
||||||
|
{
|
||||||
|
char id[k_id_base64_len];
|
||||||
|
bool deleted;
|
||||||
|
JSValue result;
|
||||||
|
JSValue promise[2];
|
||||||
|
char user[];
|
||||||
|
} delete_identity_t;
|
||||||
|
|
||||||
|
static void _tf_ssb_delete_identity_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
delete_identity_t* work = user_data;
|
||||||
|
work->deleted = tf_ssb_db_identity_delete(ssb, work->user, work->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _tf_ssb_delete_identity_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
delete_identity_t* work = user_data;
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue result = work->deleted ? JS_TRUE : JS_FALSE;
|
||||||
|
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
tf_util_report_error(context, error);
|
||||||
|
JS_FreeValue(context, error);
|
||||||
|
JS_FreeValue(context, work->promise[0]);
|
||||||
|
JS_FreeValue(context, work->promise[1]);
|
||||||
|
tf_free(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _tf_ssb_delete_identity_permission_callback(JSContext* context, bool granted, JSValue value, void* user_data)
|
||||||
|
{
|
||||||
|
delete_identity_t* work = user_data;
|
||||||
|
tf_task_t* task = tf_task_get(context);
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
if (granted)
|
||||||
|
{
|
||||||
|
tf_ssb_run_work(ssb, _tf_ssb_delete_identity_work, _tf_ssb_delete_identity_after_work, work);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
work->result = JS_DupValue(context, value);
|
||||||
|
_tf_ssb_delete_identity_after_work(ssb, -1, work);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _tf_ssb_deleteIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||||
|
{
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
|
JSValue process = data[0];
|
||||||
|
const char* user = _tf_ssb_get_process_credentials_session_name(context, process);
|
||||||
|
const char* id = JS_ToCString(context, argv[0]);
|
||||||
|
if (id && user)
|
||||||
|
{
|
||||||
|
size_t user_length = strlen(user);
|
||||||
|
delete_identity_t* work = tf_malloc(sizeof(delete_identity_t) + user_length + 1);
|
||||||
|
*work = (delete_identity_t) { .result = JS_UNDEFINED };
|
||||||
|
tf_string_set(work->id, sizeof(work->id), *id == '@' ? id + 1 : id);
|
||||||
|
memcpy(work->user, user, user_length + 1);
|
||||||
|
result = JS_NewPromiseCapability(context, work->promise);
|
||||||
|
_tf_ssb_permission_test(context, process, "ssb_id_delete", "Delete an identity.", _tf_ssb_delete_identity_permission_callback, work);
|
||||||
|
}
|
||||||
|
JS_FreeCString(context, id);
|
||||||
|
JS_FreeCString(context, user);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct _get_private_key_t
|
||||||
|
{
|
||||||
|
JSContext* context;
|
||||||
|
JSValue result;
|
||||||
|
JSValue promise[2];
|
||||||
|
char id[k_id_base64_len];
|
||||||
|
uint8_t private_key[crypto_sign_SECRETKEYBYTES];
|
||||||
|
bool got_private_key;
|
||||||
|
char user[];
|
||||||
|
} get_private_key_t;
|
||||||
|
|
||||||
|
static void _tf_ssb_get_private_key_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
get_private_key_t* work = user_data;
|
||||||
|
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, work->user, work->id, work->private_key, sizeof(work->private_key));
|
||||||
|
if (!work->got_private_key && tf_ssb_db_user_has_permission(ssb, NULL, work->user, "administration"))
|
||||||
|
{
|
||||||
|
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, ":admin", work->id, work->private_key, sizeof(work->private_key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _tf_ssb_get_private_key_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
get_private_key_t* work = user_data;
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
|
JSContext* context = work->context;
|
||||||
|
if (work->got_private_key && JS_IsUndefined(work->result))
|
||||||
|
{
|
||||||
|
result = tf_util_new_uint8_array(context, work->private_key, sizeof(work->private_key) / 2);
|
||||||
|
}
|
||||||
|
JSValue error = JS_Call(context, work->promise[work->got_private_key ? 0 : 1], JS_UNDEFINED, 1, &result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
tf_util_report_error(context, error);
|
||||||
|
JS_FreeValue(context, error);
|
||||||
|
JS_FreeValue(context, work->promise[0]);
|
||||||
|
JS_FreeValue(context, work->promise[1]);
|
||||||
|
JS_FreeValue(context, work->result);
|
||||||
|
tf_free(work);
|
||||||
|
}
|
||||||
|
static void _tf_ssb_get_private_key_permission_callback(JSContext* context, bool granted, JSValue value, void* user_data)
|
||||||
|
{
|
||||||
|
get_private_key_t* work = user_data;
|
||||||
|
tf_task_t* task = tf_task_get(context);
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
if (granted)
|
||||||
|
{
|
||||||
|
tf_ssb_run_work(ssb, _tf_ssb_get_private_key_work, _tf_ssb_get_private_key_after_work, work);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
work->result = JS_DupValue(context, value);
|
||||||
|
_tf_ssb_get_private_key_after_work(ssb, -1, work);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _tf_ssb_getPrivateKey(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||||
|
{
|
||||||
|
JSValue process = data[0];
|
||||||
|
const char* user = _tf_ssb_get_process_credentials_session_name(context, process);
|
||||||
|
size_t user_length = user ? strlen(user) : 0;
|
||||||
|
const char* id = JS_ToCString(context, argv[0]);
|
||||||
|
get_private_key_t* work = tf_malloc(sizeof(get_private_key_t) + user_length + 1);
|
||||||
|
*work = (get_private_key_t) { .context = context, .result = JS_UNDEFINED };
|
||||||
|
if (user)
|
||||||
|
{
|
||||||
|
memcpy(work->user, user, user_length + 1);
|
||||||
|
}
|
||||||
|
tf_string_set(work->id, sizeof(work->id), id);
|
||||||
|
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||||
|
_tf_ssb_permission_test(context, process, "ssb_id_export", "Export a private key.", _tf_ssb_get_private_key_permission_callback, work);
|
||||||
|
|
||||||
|
JS_FreeCString(context, user);
|
||||||
|
JS_FreeCString(context, id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
{
|
{
|
||||||
JSValue imports = argv[0];
|
JSValue imports = argv[0];
|
||||||
@@ -1758,6 +2013,9 @@ static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_va
|
|||||||
JS_SetPropertyStr(context, ssb, "privateMessageEncrypt", JS_NewCFunctionData(context, _tf_ssb_private_message_encrypt, 3, 0, 1, &process));
|
JS_SetPropertyStr(context, ssb, "privateMessageEncrypt", JS_NewCFunctionData(context, _tf_ssb_private_message_encrypt, 3, 0, 1, &process));
|
||||||
JS_SetPropertyStr(context, ssb, "privateMessageDecrypt", JS_NewCFunctionData(context, _tf_ssb_private_message_decrypt, 2, 0, 1, &process));
|
JS_SetPropertyStr(context, ssb, "privateMessageDecrypt", JS_NewCFunctionData(context, _tf_ssb_private_message_decrypt, 2, 0, 1, &process));
|
||||||
JS_SetPropertyStr(context, ssb, "appendMessageWithIdentity", JS_NewCFunctionData(context, _tf_ssb_appendMessageWithIdentity, 2, 0, 1, &process));
|
JS_SetPropertyStr(context, ssb, "appendMessageWithIdentity", JS_NewCFunctionData(context, _tf_ssb_appendMessageWithIdentity, 2, 0, 1, &process));
|
||||||
|
JS_SetPropertyStr(context, ssb, "addIdentity", JS_NewCFunctionData(context, _tf_ssb_addIdentity, 1, 0, 1, &process));
|
||||||
|
JS_SetPropertyStr(context, ssb, "deleteIdentity", JS_NewCFunctionData(context, _tf_ssb_deleteIdentity, 1, 0, 1, &process));
|
||||||
|
JS_SetPropertyStr(context, ssb, "getPrivateKey", JS_NewCFunctionData(context, _tf_ssb_getPrivateKey, 1, 0, 1, &process));
|
||||||
JS_FreeValue(context, ssb);
|
JS_FreeValue(context, ssb);
|
||||||
|
|
||||||
JSValue credentials = JS_GetPropertyStr(context, process, "credentials");
|
JSValue credentials = JS_GetPropertyStr(context, process, "credentials");
|
||||||
|
|||||||
@@ -671,7 +671,7 @@ static void _httpd_auth_query_after_work(tf_ssb_t* ssb, int status, void* user_d
|
|||||||
JSValue out_permissions = JS_NewObject(context);
|
JSValue out_permissions = JS_NewObject(context);
|
||||||
JS_SetPropertyStr(context, work->credentials, "permissions", out_permissions);
|
JS_SetPropertyStr(context, work->credentials, "permissions", out_permissions);
|
||||||
JSValue permissions = !JS_IsUndefined(settings_value) ? JS_GetPropertyStr(context, settings_value, "permissions") : JS_UNDEFINED;
|
JSValue permissions = !JS_IsUndefined(settings_value) ? JS_GetPropertyStr(context, settings_value, "permissions") : JS_UNDEFINED;
|
||||||
JSValue user_permissions = !JS_IsUndefined(permissions) ? JS_GetPropertyStr(context, permissions, name_string) : JS_UNDEFINED;
|
JSValue user_permissions = name_string && !JS_IsUndefined(permissions) ? JS_GetPropertyStr(context, permissions, name_string) : JS_UNDEFINED;
|
||||||
int length = !JS_IsUndefined(user_permissions) ? tf_util_get_length(context, user_permissions) : 0;
|
int length = !JS_IsUndefined(user_permissions) ? tf_util_get_length(context, user_permissions) : 0;
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
@@ -743,7 +743,6 @@ static void _httpd_auth_query_after_work(tf_ssb_t* ssb, int status, void* user_d
|
|||||||
tf_free((void*)cookie);
|
tf_free((void*)cookie);
|
||||||
JS_FreeCString(context, name_string);
|
JS_FreeCString(context, name_string);
|
||||||
|
|
||||||
// tf_http_request_unref(request);
|
|
||||||
request->on_message = _httpd_app_on_message;
|
request->on_message = _httpd_app_on_message;
|
||||||
request->on_close = _httpd_app_on_close;
|
request->on_close = _httpd_app_on_close;
|
||||||
request->context = context;
|
request->context = context;
|
||||||
@@ -769,18 +768,19 @@ void tf_httpd_endpoint_app_socket(tf_http_request_t* request)
|
|||||||
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session);
|
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session);
|
||||||
tf_free((void*)session);
|
tf_free((void*)session);
|
||||||
|
|
||||||
|
JSValue credentials = JS_NewObject(context);
|
||||||
if (!JS_IsUndefined(jwt))
|
if (!JS_IsUndefined(jwt))
|
||||||
{
|
{
|
||||||
JSValue credentials = JS_NewObject(context);
|
|
||||||
JS_SetPropertyStr(context, credentials, "session", jwt);
|
JS_SetPropertyStr(context, credentials, "session", jwt);
|
||||||
tf_http_request_ref(request);
|
|
||||||
app_t* work = tf_malloc(sizeof(app_t));
|
|
||||||
*work = (app_t) {
|
|
||||||
.request = request,
|
|
||||||
.credentials = credentials,
|
|
||||||
.timer = { .data = work },
|
|
||||||
};
|
|
||||||
uv_timer_init(tf_ssb_get_loop(ssb), &work->timer);
|
|
||||||
tf_ssb_run_work(ssb, _httpd_auth_query_work, _httpd_auth_query_after_work, work);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
app_t* work = tf_malloc(sizeof(app_t));
|
||||||
|
*work = (app_t) {
|
||||||
|
.request = request,
|
||||||
|
.credentials = credentials,
|
||||||
|
.timer = { .data = work },
|
||||||
|
};
|
||||||
|
uv_timer_init(tf_ssb_get_loop(ssb), &work->timer);
|
||||||
|
tf_ssb_run_work(ssb, _httpd_auth_query_work, _httpd_auth_query_after_work, work);
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/ssb.db.c
10
src/ssb.db.c
@@ -171,6 +171,10 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
|
|||||||
_tf_ssb_db_exec(db, "DROP TABLE messages_stats");
|
_tf_ssb_db_exec(db, "DROP TABLE messages_stats");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (_tf_ssb_db_has_rows(db, "SELECT * FROM sqlite_schema WHERE type = 'trigger' AND name = 'messages_ai_stats' AND NOT sql LIKE '%excluded.size%'"))
|
||||||
|
{
|
||||||
|
_tf_ssb_db_exec(db, "DROP TABLE messages_stats");
|
||||||
|
}
|
||||||
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_stats')"))
|
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_stats')"))
|
||||||
{
|
{
|
||||||
_tf_ssb_db_exec(db, "BEGIN TRANSACTION");
|
_tf_ssb_db_exec(db, "BEGIN TRANSACTION");
|
||||||
@@ -186,10 +190,12 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
|
|||||||
"messages GROUP BY author");
|
"messages GROUP BY author");
|
||||||
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
|
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
|
||||||
}
|
}
|
||||||
|
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ai_stats");
|
||||||
_tf_ssb_db_exec(db,
|
_tf_ssb_db_exec(db,
|
||||||
"CREATE TRIGGER IF NOT EXISTS messages_ai_stats AFTER INSERT ON messages BEGIN INSERT INTO messages_stats(author, max_sequence, max_timestamp, size) VALUES (new.author, "
|
"CREATE TRIGGER IF NOT EXISTS messages_ai_stats AFTER INSERT ON messages BEGIN INSERT INTO messages_stats(author, max_sequence, max_timestamp, size) VALUES (new.author, "
|
||||||
"new.sequence, new.timestamp, length(json(new.content))) ON CONFLICT DO UPDATE SET max_sequence = MAX(max_sequence, new.sequence), max_timestamp = MAX(max_timestamp, "
|
"new.sequence, new.timestamp, length(json(new.content))) ON CONFLICT DO UPDATE SET max_sequence = MAX(max_sequence, excluded.max_sequence), max_timestamp = "
|
||||||
"new.timestamp), size = size + length(json(new.content)); END");
|
"MAX(max_timestamp, "
|
||||||
|
"excluded.max_timestamp), size = size + excluded.size; END");
|
||||||
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ad_stats");
|
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ad_stats");
|
||||||
_tf_ssb_db_exec(db,
|
_tf_ssb_db_exec(db,
|
||||||
"CREATE TRIGGER IF NOT EXISTS messages_ad_stats AFTER DELETE ON messages BEGIN "
|
"CREATE TRIGGER IF NOT EXISTS messages_ad_stats AFTER DELETE ON messages BEGIN "
|
||||||
|
|||||||
206
src/ssb.js.c
206
src/ssb.js.c
@@ -96,209 +96,6 @@ static JSValue _tf_ssb_createIdentity(JSContext* context, JSValueConst this_val,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct _add_identity_t
|
|
||||||
{
|
|
||||||
uint8_t key[crypto_sign_SECRETKEYBYTES / 2];
|
|
||||||
bool added;
|
|
||||||
JSValue promise[2];
|
|
||||||
char user[];
|
|
||||||
} add_identity_t;
|
|
||||||
|
|
||||||
static void _tf_ssb_add_identity_work(tf_ssb_t* ssb, void* user_data)
|
|
||||||
{
|
|
||||||
add_identity_t* work = user_data;
|
|
||||||
uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
|
|
||||||
unsigned char seed[crypto_sign_SEEDBYTES];
|
|
||||||
uint8_t secret_key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
|
||||||
memcpy(secret_key, work->key, sizeof(secret_key) / 2);
|
|
||||||
if (crypto_sign_ed25519_sk_to_seed(seed, secret_key) == 0 && crypto_sign_seed_keypair(public_key, secret_key, seed) == 0)
|
|
||||||
{
|
|
||||||
char public_key_b64[512];
|
|
||||||
tf_base64_encode(public_key, sizeof(public_key), public_key_b64, sizeof(public_key_b64));
|
|
||||||
snprintf(public_key_b64 + strlen(public_key_b64), sizeof(public_key_b64) - strlen(public_key_b64), ".ed25519");
|
|
||||||
|
|
||||||
uint8_t combined[crypto_sign_SECRETKEYBYTES];
|
|
||||||
memcpy(combined, work->key, sizeof(work->key));
|
|
||||||
memcpy(combined + sizeof(work->key), public_key, sizeof(public_key));
|
|
||||||
char combined_b64[512];
|
|
||||||
tf_base64_encode(combined, sizeof(combined), combined_b64, sizeof(combined_b64));
|
|
||||||
snprintf(combined_b64 + strlen(combined_b64), sizeof(combined_b64) - strlen(combined_b64), ".ed25519");
|
|
||||||
|
|
||||||
work->added = tf_ssb_db_identity_add(ssb, work->user, public_key_b64, combined_b64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _tf_ssb_add_identity_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
|
||||||
{
|
|
||||||
add_identity_t* work = user_data;
|
|
||||||
JSContext* context = tf_ssb_get_context(ssb);
|
|
||||||
JSValue result = work->added ? JS_TRUE : JS_UNDEFINED;
|
|
||||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
|
||||||
JS_FreeValue(context, result);
|
|
||||||
tf_util_report_error(context, error);
|
|
||||||
JS_FreeValue(context, error);
|
|
||||||
JS_FreeValue(context, work->promise[0]);
|
|
||||||
JS_FreeValue(context, work->promise[1]);
|
|
||||||
tf_free(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue _tf_ssb_addIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
||||||
{
|
|
||||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
|
||||||
JSValue result = JS_UNDEFINED;
|
|
||||||
if (ssb)
|
|
||||||
{
|
|
||||||
size_t user_length = 0;
|
|
||||||
const char* user = JS_ToCStringLen(context, &user_length, argv[0]);
|
|
||||||
|
|
||||||
JSValue buffer = JS_UNDEFINED;
|
|
||||||
size_t length = 0;
|
|
||||||
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[1]);
|
|
||||||
if (!array)
|
|
||||||
{
|
|
||||||
size_t offset;
|
|
||||||
size_t element_size;
|
|
||||||
buffer = tf_util_try_get_typed_array_buffer(context, argv[1], &offset, &length, &element_size);
|
|
||||||
if (!JS_IsException(buffer))
|
|
||||||
{
|
|
||||||
array = tf_util_try_get_array_buffer(context, &length, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (array)
|
|
||||||
{
|
|
||||||
if (length == crypto_sign_SECRETKEYBYTES / 2)
|
|
||||||
{
|
|
||||||
add_identity_t* work = tf_malloc(sizeof(add_identity_t) + user_length + 1);
|
|
||||||
*work = (add_identity_t) { 0 };
|
|
||||||
memcpy(work->key, array, sizeof(work->key));
|
|
||||||
memcpy(work->user, user, user_length + 1);
|
|
||||||
result = JS_NewPromiseCapability(context, work->promise);
|
|
||||||
tf_ssb_run_work(ssb, _tf_ssb_add_identity_work, _tf_ssb_add_identity_after_work, work);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = JS_ThrowInternalError(context, "Unexpected private key size: %d vs. %d\n", (int)length, crypto_sign_SECRETKEYBYTES);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = JS_ThrowInternalError(context, "Expected array argument.");
|
|
||||||
}
|
|
||||||
JS_FreeValue(context, buffer);
|
|
||||||
JS_FreeCString(context, user);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct _delete_identity_t
|
|
||||||
{
|
|
||||||
char id[k_id_base64_len];
|
|
||||||
bool deleted;
|
|
||||||
JSValue promise[2];
|
|
||||||
char user[];
|
|
||||||
} delete_identity_t;
|
|
||||||
|
|
||||||
static void _tf_ssb_delete_identity_work(tf_ssb_t* ssb, void* user_data)
|
|
||||||
{
|
|
||||||
delete_identity_t* work = user_data;
|
|
||||||
work->deleted = tf_ssb_db_identity_delete(ssb, work->user, work->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _tf_ssb_delete_identity_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
|
||||||
{
|
|
||||||
delete_identity_t* work = user_data;
|
|
||||||
JSContext* context = tf_ssb_get_context(ssb);
|
|
||||||
JSValue result = work->deleted ? JS_TRUE : JS_FALSE;
|
|
||||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
|
||||||
JS_FreeValue(context, result);
|
|
||||||
tf_util_report_error(context, error);
|
|
||||||
JS_FreeValue(context, error);
|
|
||||||
JS_FreeValue(context, work->promise[0]);
|
|
||||||
JS_FreeValue(context, work->promise[1]);
|
|
||||||
tf_free(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue _tf_ssb_deleteIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
||||||
{
|
|
||||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
|
||||||
JSValue result = JS_UNDEFINED;
|
|
||||||
if (ssb)
|
|
||||||
{
|
|
||||||
size_t user_length = 0;
|
|
||||||
const char* user = JS_ToCStringLen(context, &user_length, argv[0]);
|
|
||||||
const char* id = JS_ToCString(context, argv[1]);
|
|
||||||
if (id && user)
|
|
||||||
{
|
|
||||||
delete_identity_t* work = tf_malloc(sizeof(delete_identity_t) + user_length + 1);
|
|
||||||
*work = (delete_identity_t) { 0 };
|
|
||||||
tf_string_set(work->id, sizeof(work->id), *id == '@' ? id + 1 : id);
|
|
||||||
memcpy(work->user, user, user_length + 1);
|
|
||||||
result = JS_NewPromiseCapability(context, work->promise);
|
|
||||||
tf_ssb_run_work(ssb, _tf_ssb_delete_identity_work, _tf_ssb_delete_identity_after_work, work);
|
|
||||||
}
|
|
||||||
JS_FreeCString(context, id);
|
|
||||||
JS_FreeCString(context, user);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct _get_private_key_t
|
|
||||||
{
|
|
||||||
JSContext* context;
|
|
||||||
JSValue promise[2];
|
|
||||||
char id[k_id_base64_len];
|
|
||||||
uint8_t private_key[crypto_sign_SECRETKEYBYTES];
|
|
||||||
bool got_private_key;
|
|
||||||
char user[];
|
|
||||||
} get_private_key_t;
|
|
||||||
|
|
||||||
static void _tf_ssb_get_private_key_work(tf_ssb_t* ssb, void* user_data)
|
|
||||||
{
|
|
||||||
get_private_key_t* work = user_data;
|
|
||||||
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, work->user, work->id, work->private_key, sizeof(work->private_key));
|
|
||||||
if (!work->got_private_key && tf_ssb_db_user_has_permission(ssb, NULL, work->user, "administration"))
|
|
||||||
{
|
|
||||||
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, ":admin", work->id, work->private_key, sizeof(work->private_key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _tf_ssb_get_private_key_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
|
||||||
{
|
|
||||||
get_private_key_t* work = user_data;
|
|
||||||
JSValue result = JS_UNDEFINED;
|
|
||||||
JSContext* context = work->context;
|
|
||||||
if (work->got_private_key)
|
|
||||||
{
|
|
||||||
result = tf_util_new_uint8_array(context, work->private_key, sizeof(work->private_key) / 2);
|
|
||||||
}
|
|
||||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
|
||||||
JS_FreeValue(context, result);
|
|
||||||
tf_util_report_error(context, error);
|
|
||||||
JS_FreeValue(context, error);
|
|
||||||
JS_FreeValue(context, work->promise[0]);
|
|
||||||
JS_FreeValue(context, work->promise[1]);
|
|
||||||
tf_free(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue _tf_ssb_getPrivateKey(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
||||||
{
|
|
||||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
|
||||||
size_t user_length = 0;
|
|
||||||
const char* user = JS_ToCStringLen(context, &user_length, argv[0]);
|
|
||||||
const char* id = JS_ToCString(context, argv[1]);
|
|
||||||
get_private_key_t* work = tf_malloc(sizeof(get_private_key_t) + user_length + 1);
|
|
||||||
*work = (get_private_key_t) { .context = context };
|
|
||||||
memcpy(work->user, user, user_length + 1);
|
|
||||||
tf_string_set(work->id, sizeof(work->id), id);
|
|
||||||
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
|
||||||
tf_ssb_run_work(ssb, _tf_ssb_get_private_key_work, _tf_ssb_get_private_key_after_work, work);
|
|
||||||
|
|
||||||
JS_FreeCString(context, user);
|
|
||||||
JS_FreeCString(context, id);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue _tf_ssb_getServerIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
static JSValue _tf_ssb_getServerIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
{
|
{
|
||||||
JSValue result = JS_UNDEFINED;
|
JSValue result = JS_UNDEFINED;
|
||||||
@@ -1703,9 +1500,6 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
|
|||||||
|
|
||||||
/* Requires an identity. */
|
/* Requires an identity. */
|
||||||
JS_SetPropertyStr(context, object, "createIdentity", JS_NewCFunction(context, _tf_ssb_createIdentity, "createIdentity", 1));
|
JS_SetPropertyStr(context, object, "createIdentity", JS_NewCFunction(context, _tf_ssb_createIdentity, "createIdentity", 1));
|
||||||
JS_SetPropertyStr(context, object, "addIdentity", JS_NewCFunction(context, _tf_ssb_addIdentity, "addIdentity", 2));
|
|
||||||
JS_SetPropertyStr(context, object, "deleteIdentity", JS_NewCFunction(context, _tf_ssb_deleteIdentity, "deleteIdentity", 2));
|
|
||||||
JS_SetPropertyStr(context, object, "getPrivateKey", JS_NewCFunction(context, _tf_ssb_getPrivateKey, "getPrivateKey", 2));
|
|
||||||
JS_SetPropertyStr(context, object, "setUserPermission", JS_NewCFunction(context, _tf_ssb_set_user_permission, "setUserPermission", 5));
|
JS_SetPropertyStr(context, object, "setUserPermission", JS_NewCFunction(context, _tf_ssb_set_user_permission, "setUserPermission", 5));
|
||||||
|
|
||||||
/* Does not require an identity. */
|
/* Does not require an identity. */
|
||||||
|
|||||||
@@ -114,6 +114,21 @@ static int _ssb_test_count_messages(tf_ssb_t* ssb)
|
|||||||
return count.count;
|
return count.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _dump_stats_callback(JSValue row, void* user_data)
|
||||||
|
{
|
||||||
|
JSContext* context = tf_ssb_get_context(user_data);
|
||||||
|
JSValue json = JS_JSONStringify(context, row, JS_UNDEFINED, JS_UNDEFINED);
|
||||||
|
const char* string = JS_ToCString(context, json);
|
||||||
|
tf_printf("%s\n", string);
|
||||||
|
JS_FreeCString(context, string);
|
||||||
|
JS_FreeValue(context, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _ssb_test_dump_messages_stats(tf_ssb_t* ssb)
|
||||||
|
{
|
||||||
|
tf_ssb_db_visit_query(ssb, "SELECT * FROM messages_stats", JS_UNDEFINED, _dump_stats_callback, ssb);
|
||||||
|
}
|
||||||
|
|
||||||
static void _message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, void* user_data)
|
static void _message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, void* user_data)
|
||||||
{
|
{
|
||||||
++*(int*)user_data;
|
++*(int*)user_data;
|
||||||
@@ -248,6 +263,8 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
|
|||||||
JS_FreeValue(context0, signed_message);
|
JS_FreeValue(context0, signed_message);
|
||||||
_wait_stored(ssb0, &stored);
|
_wait_stored(ssb0, &stored);
|
||||||
JS_FreeValue(context0, obj);
|
JS_FreeValue(context0, obj);
|
||||||
|
_ssb_test_dump_messages_stats(ssb0);
|
||||||
|
uv_sleep(1000);
|
||||||
|
|
||||||
obj = JS_NewObject(context0);
|
obj = JS_NewObject(context0);
|
||||||
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
|
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
|
||||||
@@ -258,6 +275,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
|
|||||||
JS_FreeValue(context0, signed_message);
|
JS_FreeValue(context0, signed_message);
|
||||||
_wait_stored(ssb0, &stored);
|
_wait_stored(ssb0, &stored);
|
||||||
JS_FreeValue(context0, obj);
|
JS_FreeValue(context0, obj);
|
||||||
|
_ssb_test_dump_messages_stats(ssb0);
|
||||||
|
|
||||||
obj = JS_NewObject(context0);
|
obj = JS_NewObject(context0);
|
||||||
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
|
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
|
||||||
@@ -273,6 +291,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
|
|||||||
JS_FreeValue(context0, signed_message);
|
JS_FreeValue(context0, signed_message);
|
||||||
_wait_stored(ssb0, &stored);
|
_wait_stored(ssb0, &stored);
|
||||||
JS_FreeValue(context0, obj);
|
JS_FreeValue(context0, obj);
|
||||||
|
_ssb_test_dump_messages_stats(ssb0);
|
||||||
|
|
||||||
uint8_t* b0;
|
uint8_t* b0;
|
||||||
size_t s0 = 0;
|
size_t s0 = 0;
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ success = False
|
|||||||
try:
|
try:
|
||||||
options = webdriver.FirefoxOptions()
|
options = webdriver.FirefoxOptions()
|
||||||
service = Service(log_output = 'out/geckodriver.log')
|
service = Service(log_output = 'out/geckodriver.log')
|
||||||
#options.add_argument('--headless')
|
options.add_argument('--headless')
|
||||||
driver = webdriver.Firefox(options = options, service = service)
|
driver = webdriver.Firefox(options = options, service = service)
|
||||||
wait = WebDriverWait(driver, 10)
|
wait = WebDriverWait(driver, 10)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user