Compare commits

..

No commits in common. "v0.1.0-develop.54" and "v0.1.0-develop.53" have entirely different histories.

5 changed files with 58 additions and 142 deletions

View File

@ -1,11 +1,3 @@
# [0.1.0-develop.54](https://git.lumeweb.com/LumeWeb/libkernel/compare/v0.1.0-develop.53...v0.1.0-develop.54) (2023-09-11)
### Features
* add login function that calls exchangeCommunicationKeys and setLoginKey ([fd6c8d3](https://git.lumeweb.com/LumeWeb/libkernel/commit/fd6c8d3b1f8a62e6d0d30cef112d7feb3980c36e))
* add service worker support ([15c5960](https://git.lumeweb.com/LumeWeb/libkernel/commit/15c59603f2f9b50531f82dcd36294719c7185ed6))
# [0.1.0-develop.53](https://git.lumeweb.com/LumeWeb/libkernel/compare/v0.1.0-develop.52...v0.1.0-develop.53) (2023-09-09) # [0.1.0-develop.53](https://git.lumeweb.com/LumeWeb/libkernel/compare/v0.1.0-develop.52...v0.1.0-develop.53) (2023-09-09)
# [0.1.0-develop.52](https://git.lumeweb.com/LumeWeb/libkernel/compare/v0.1.0-develop.51...v0.1.0-develop.52) (2023-09-09) # [0.1.0-develop.52](https://git.lumeweb.com/LumeWeb/libkernel/compare/v0.1.0-develop.51...v0.1.0-develop.52) (2023-09-09)

4
npm-shrinkwrap.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "@lumeweb/libkernel", "name": "@lumeweb/libkernel",
"version": "0.1.0-develop.54", "version": "0.1.0-develop.53",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@lumeweb/libkernel", "name": "@lumeweb/libkernel",
"version": "0.1.0-develop.54", "version": "0.1.0-develop.53",
"dependencies": { "dependencies": {
"@lumeweb/libweb": "0.2.0-develop.57", "@lumeweb/libweb": "0.2.0-develop.57",
"emittery": "^1.0.1", "emittery": "^1.0.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "@lumeweb/libkernel", "name": "@lumeweb/libkernel",
"version": "0.1.0-develop.54", "version": "0.1.0-develop.53",
"main": "lib/index.js", "main": "lib/index.js",
"type": "module", "type": "module",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",

View File

@ -1,16 +1,11 @@
import { import {
getKernelIframe,
init, init,
kernelAuthLocation, kernelAuthLocation,
kernelLoadedDefer, kernelLoadedPromise,
loginDefer, loginPromise,
logoutDefer, logoutPromise,
newKernelQuery,
} from "./queries.js"; } from "./queries.js";
import { Err } from "#types.js"; import { Err } from "#types.js";
import { x25519 } from "@noble/curves/ed25519";
import { bytesToHex, hexToBytes, randomBytes } from "@lumeweb/libweb";
import { secretbox } from "@noble/ciphers/salsa";
// There are 5 stages of auth. // There are 5 stages of auth.
// //
@ -38,7 +33,7 @@ import { secretbox } from "@noble/ciphers/salsa";
// loginComplete will resolve when the user has successfully logged in. // loginComplete will resolve when the user has successfully logged in.
function loginComplete(): Promise<void> { function loginComplete(): Promise<void> {
return loginDefer.promise; return loginPromise;
} }
// kernelLoaded will resolve when the user has successfully loaded the kernel. // kernelLoaded will resolve when the user has successfully loaded the kernel.
@ -46,14 +41,14 @@ function loginComplete(): Promise<void> {
// //
// NOTE: kernelLoaded will not resolve until after loginComplete has resolved. // NOTE: kernelLoaded will not resolve until after loginComplete has resolved.
function kernelLoaded(): Promise<Err> { function kernelLoaded(): Promise<Err> {
return kernelLoadedDefer.promise; return kernelLoadedPromise;
} }
// logoutComplete will resolve when the user has logged out. Note that // logoutComplete will resolve when the user has logged out. Note that
// logoutComplete will only resolve if the user logged in first - if the user // logoutComplete will only resolve if the user logged in first - if the user
// was not logged in to begin with, this promise will not resolve. // was not logged in to begin with, this promise will not resolve.
function logoutComplete(): Promise<void> { function logoutComplete(): Promise<void> {
return logoutDefer.promise; return logoutPromise;
} }
// openAuthWindow is intended to be used as an onclick target when the user // openAuthWindow is intended to be used as an onclick target when the user
@ -76,44 +71,4 @@ function openAuthWindow(): void {
}); });
} }
async function login(key: Uint8Array) { export { loginComplete, kernelLoaded, logoutComplete, openAuthWindow };
let privKey = x25519.utils.randomPrivateKey();
const iframe = getKernelIframe();
if (!iframe) {
return;
}
let pubKeyRet = await newKernelQuery(
"exchangeCommunicationKeys",
{
data: bytesToHex(x25519.getPublicKey(privKey)),
},
false,
);
let pubKeyT = await pubKeyRet[1];
if (pubKeyT[1]) {
alert(`Failed to login: could not get communication key: ${pubKeyT}`);
return;
}
let pubKey = hexToBytes(pubKeyT[1]?.[0] as string);
const secret = x25519.getSharedSecret(privKey, pubKey);
const nonce = randomBytes(24);
const box = secretbox(secret, nonce);
const ciphertext = box.seal(key);
await newKernelQuery(
"setLoginKey",
{
data: bytesToHex(ciphertext),
nonce: bytesToHex(nonce),
},
false,
);
}
export { loginComplete, kernelLoaded, logoutComplete, openAuthWindow, login };

View File

@ -1,7 +1,6 @@
import { log, logErr } from "./log.js"; import { log, logErr } from "./log.js";
import { DataFn, Err, ErrTuple } from "#types.js"; import { DataFn, Err, ErrTuple } from "#types.js";
import { bufToB64, encodeU64 } from "#util.js"; import { bufToB64, encodeU64 } from "#util.js";
import defer, { DeferredPromise } from "p-defer";
// queryResolve is the 'resolve' value of a promise that returns an ErrTuple. // queryResolve is the 'resolve' value of a promise that returns an ErrTuple.
// It gets called when a query sends a 'response' message. // It gets called when a query sends a 'response' message.
@ -67,23 +66,6 @@ let nonceCounter: number;
let bgConn: any; let bgConn: any;
let serviceWorker: ServiceWorker;
let kernelIframe: HTMLIFrameElement;
function getKernelIframe() {
return kernelIframe;
}
async function serviceWorkerReady() {
const sw = await navigator.serviceWorker.ready;
navigator.serviceWorker.onmessage = (...args) => handleMessage(...args);
serviceWorker = sw.active as ServiceWorker;
}
function getServiceWorker() {
return serviceWorker;
}
function initNonce() { function initNonce() {
nonceSeed = new Uint8Array(16); nonceSeed = new Uint8Array(16);
nonceCounter = 0; nonceCounter = 0;
@ -134,28 +116,6 @@ function handleMessage(event: MessageEvent) {
return; return;
} }
if (event.source === kernelIframe?.contentWindow) {
if (
["response", "queryUpdate", "responseNonce", "responseUpdate"].includes(
event.data.method,
) &&
event.data.sw
) {
delete event.data.sw;
serviceWorker?.postMessage(event.data);
return;
}
}
if (event.source === serviceWorker) {
if (["moduleCall", "queryUpdate", "response"].includes(event.data.method)) {
kernelIframe?.contentWindow?.postMessage(
{ ...event.data, sw: true },
"https://kernel.lumeweb.com",
);
return;
}
}
if (IS_EXTENSION && !event.data?.data) { if (IS_EXTENSION && !event.data?.data) {
event.data.data = Object.assign({}, event.data); event.data.data = Object.assign({}, event.data);
} }
@ -194,15 +154,15 @@ function handleMessage(event: MessageEvent) {
// We can't actually establish that init is complete until the // We can't actually establish that init is complete until the
// kernel source has been set. This happens async and might happen // kernel source has been set. This happens async and might happen
// after we receive the auth message. // after we receive the auth message.
sourceDefer.promise.then(() => { sourcePromise.then(() => {
initDefer.resolve(); initResolve();
}); });
} }
if (IS_EXTENSION_ANY && event.data.data.kernelLoaded === "success") { if (IS_EXTENSION_ANY && event.data.data.kernelLoaded === "success") {
const nonce = nextNonce(); const nonce = nextNonce();
queries[nonce] = { queries[nonce] = {
resolve: sourceDefer.resolve as unknown as queryResolve, resolve: sourceResolve,
}; };
const kernelMessage = { const kernelMessage = {
@ -221,7 +181,7 @@ function handleMessage(event: MessageEvent) {
// that the user is logged in. // that the user is logged in.
if (!loginResolved && event.data.data.loginComplete) { if (!loginResolved && event.data.data.loginComplete) {
loginResolved = true; loginResolved = true;
loginDefer.resolve(); loginResolve();
} }
// If the auth status message says that the kernel loaded, it means // If the auth status message says that the kernel loaded, it means
@ -239,7 +199,7 @@ function handleMessage(event: MessageEvent) {
// out, we need to reload the page and reset the auth process. // out, we need to reload the page and reset the auth process.
if (event.data.data.logoutComplete) { if (event.data.data.logoutComplete) {
if (!logoutResolved) { if (!logoutResolved) {
logoutDefer.resolve(); logoutResolve();
} }
window.location.reload(); window.location.reload();
} }
@ -288,17 +248,17 @@ function handleMessage(event: MessageEvent) {
} }
function launchKernelFrame() { function launchKernelFrame() {
kernelIframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
kernelIframe.src = "https://kernel.lumeweb.com"; iframe.src = "https://kernel.lumeweb.com";
kernelIframe.width = "0"; iframe.width = "0";
kernelIframe.height = "0"; iframe.height = "0";
kernelIframe.style.border = "0"; iframe.style.border = "0";
kernelIframe.style.position = "absolute"; iframe.style.position = "absolute";
document.body.appendChild(kernelIframe); document.body.appendChild(iframe);
kernelSource = <Window>kernelIframe.contentWindow; kernelSource = <Window>iframe.contentWindow;
kernelOrigin = EXTENSION_HOSTED_ORIGIN; kernelOrigin = EXTENSION_HOSTED_ORIGIN;
kernelAuthLocation = `${EXTENSION_HOSTED_ORIGIN}/auth.html`; kernelAuthLocation = `${EXTENSION_HOSTED_ORIGIN}/auth.html`;
sourceDefer.resolve(); sourceResolve();
// Set a timer to fail the login process if the kernel doesn't load in // Set a timer to fail the login process if the kernel doesn't load in
// time. // time.
@ -307,9 +267,7 @@ function launchKernelFrame() {
return; return;
} }
initResolved = true; initResolved = true;
initDefer.resolve( initResolve("tried to open kernel in iframe, but hit a timeout");
"tried to open kernel in kernelIframe, but hit a timeout" as any,
);
}, 24000); }, 24000);
} }
@ -360,7 +318,7 @@ function messageBridge() {
kernelAuthLocation = `${EXTENSION_KERNEL_ORIGIN}/auth.html`; kernelAuthLocation = `${EXTENSION_KERNEL_ORIGIN}/auth.html`;
log("established connection to bridge, using browser extension for kernel"); log("established connection to bridge, using browser extension for kernel");
if (!IS_EXTENSION_ANY) { if (!IS_EXTENSION_ANY) {
sourceDefer.resolve(); sourceResolve();
} }
}); });
@ -399,7 +357,7 @@ function messageBridge() {
bridgeResolve([null, null]); bridgeResolve([null, null]);
} }
return initDefer; return initPromise;
} }
// init is a function that returns a promise which will resolve when // init is a function that returns a promise which will resolve when
@ -410,19 +368,23 @@ function messageBridge() {
// thanks to the 'initialized' variable. // thanks to the 'initialized' variable.
let initialized = false; // set to true once 'init()' has been called let initialized = false; // set to true once 'init()' has been called
let initResolved = false; // set to true once we know the bootloader is working let initResolved = false; // set to true once we know the bootloader is working
let initDefer: DeferredPromise<void>; let initResolve: DataFn;
let initPromise: Promise<void>;
let loginResolved = false; // set to true once we know the user is logged in let loginResolved = false; // set to true once we know the user is logged in
let loginDefer: DeferredPromise<void>; let loginResolve: () => void;
let loginPromise: Promise<void>;
let kernelLoadedResolved = false; // set to true once the user kernel is loaded let kernelLoadedResolved = false; // set to true once the user kernel is loaded
let kernelLoadedResolve: (err: Err) => void; let kernelLoadedResolve: (err: Err) => void;
let kernelLoadedDefer: DeferredPromise<Err>; let kernelLoadedPromise: Promise<Err>;
const logoutResolved = false; // set to true once the user is logged out const logoutResolved = false; // set to true once the user is logged out
let logoutDefer: DeferredPromise<void>; let logoutResolve: () => void;
let sourceDefer: DeferredPromise<void>; // resolves when the source is known and set let logoutPromise: Promise<void>;
let sourceResolve: () => void;
let sourcePromise: Promise<void>; // resolves when the source is known and set
function init(): Promise<void> { function init(): Promise<void> {
// If init has already been called, just return the init promise. // If init has already been called, just return the init promise.
if (initialized) { if (initialized) {
return initDefer.promise; return initPromise;
} }
initialized = true; initialized = true;
@ -432,16 +394,25 @@ function init(): Promise<void> {
messageBridge(); messageBridge();
// Create the promises that resolve at various stages of the auth flow. // Create the promises that resolve at various stages of the auth flow.
initDefer = defer(); initPromise = new Promise((resolve) => {
loginDefer = defer(); initResolve = resolve;
kernelLoadedDefer = defer(); });
logoutDefer = defer(); loginPromise = new Promise((resolve) => {
kernelLoadedDefer = defer(); loginResolve = resolve;
sourceDefer = defer(); });
kernelLoadedPromise = new Promise((resolve) => {
kernelLoadedResolve = resolve;
});
logoutPromise = new Promise((resolve) => {
logoutResolve = resolve;
});
sourcePromise = new Promise((resolve) => {
sourceResolve = resolve;
});
// Return the initDefer, which will resolve when bootloader init is // Return the initPromise, which will resolve when bootloader init is
// complete. // complete.
return initDefer.promise; return initPromise;
} }
// callModule is a generic function to call a module. The first input is the // callModule is a generic function to call a module. The first input is the
@ -565,7 +536,7 @@ function newKernelQuery(
// implies that init is complete. // implies that init is complete.
const getNonce: Promise<string> = new Promise((resolve) => { const getNonce: Promise<string> = new Promise((resolve) => {
init().then(() => { init().then(() => {
kernelLoadedDefer.promise.then(() => { kernelLoadedPromise.then(() => {
resolve(nextNonce()); resolve(nextNonce());
}); });
}); });
@ -682,10 +653,8 @@ export {
connectModule, connectModule,
init, init,
kernelAuthLocation, kernelAuthLocation,
kernelLoadedDefer, kernelLoadedPromise,
loginDefer, loginPromise,
logoutDefer, logoutPromise,
newKernelQuery, newKernelQuery,
serviceWorkerReady,
getKernelIframe,
}; };