Compare commits
6 Commits
v0.1.0-dev
...
v0.1.0-dev
Author | SHA1 | Date |
---|---|---|
semantic-release-bot | 8c07210ec6 | |
Derrick Hammer | 615954a1f6 | |
Derrick Hammer | fd6c8d3b1f | |
Derrick Hammer | 17eb74483c | |
Derrick Hammer | 15c59603f2 | |
Derrick Hammer | c4e211b04c |
|
@ -1,3 +1,11 @@
|
|||
# [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.52](https://git.lumeweb.com/LumeWeb/libkernel/compare/v0.1.0-develop.51...v0.1.0-develop.52) (2023-09-09)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@lumeweb/libkernel",
|
||||
"version": "0.1.0-develop.53",
|
||||
"version": "0.1.0-develop.54",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@lumeweb/libkernel",
|
||||
"version": "0.1.0-develop.53",
|
||||
"version": "0.1.0-develop.54",
|
||||
"dependencies": {
|
||||
"@lumeweb/libweb": "0.2.0-develop.57",
|
||||
"emittery": "^1.0.1",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@lumeweb/libkernel",
|
||||
"version": "0.1.0-develop.53",
|
||||
"version": "0.1.0-develop.54",
|
||||
"main": "lib/index.js",
|
||||
"type": "module",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import {
|
||||
getKernelIframe,
|
||||
init,
|
||||
kernelAuthLocation,
|
||||
kernelLoadedPromise,
|
||||
loginPromise,
|
||||
logoutPromise,
|
||||
kernelLoadedDefer,
|
||||
loginDefer,
|
||||
logoutDefer,
|
||||
newKernelQuery,
|
||||
} from "./queries.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.
|
||||
//
|
||||
|
@ -33,7 +38,7 @@ import { Err } from "#types.js";
|
|||
|
||||
// loginComplete will resolve when the user has successfully logged in.
|
||||
function loginComplete(): Promise<void> {
|
||||
return loginPromise;
|
||||
return loginDefer.promise;
|
||||
}
|
||||
|
||||
// kernelLoaded will resolve when the user has successfully loaded the kernel.
|
||||
|
@ -41,14 +46,14 @@ function loginComplete(): Promise<void> {
|
|||
//
|
||||
// NOTE: kernelLoaded will not resolve until after loginComplete has resolved.
|
||||
function kernelLoaded(): Promise<Err> {
|
||||
return kernelLoadedPromise;
|
||||
return kernelLoadedDefer.promise;
|
||||
}
|
||||
|
||||
// logoutComplete will resolve when the user has logged out. Note that
|
||||
// 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.
|
||||
function logoutComplete(): Promise<void> {
|
||||
return logoutPromise;
|
||||
return logoutDefer.promise;
|
||||
}
|
||||
|
||||
// openAuthWindow is intended to be used as an onclick target when the user
|
||||
|
@ -71,4 +76,44 @@ function openAuthWindow(): void {
|
|||
});
|
||||
}
|
||||
|
||||
export { loginComplete, kernelLoaded, logoutComplete, openAuthWindow };
|
||||
async function login(key: Uint8Array) {
|
||||
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 };
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { log, logErr } from "./log.js";
|
||||
import { DataFn, Err, ErrTuple } from "#types.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.
|
||||
// It gets called when a query sends a 'response' message.
|
||||
|
@ -66,6 +67,23 @@ let nonceCounter: number;
|
|||
|
||||
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() {
|
||||
nonceSeed = new Uint8Array(16);
|
||||
nonceCounter = 0;
|
||||
|
@ -116,6 +134,28 @@ function handleMessage(event: MessageEvent) {
|
|||
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) {
|
||||
event.data.data = Object.assign({}, event.data);
|
||||
}
|
||||
|
@ -154,15 +194,15 @@ function handleMessage(event: MessageEvent) {
|
|||
// We can't actually establish that init is complete until the
|
||||
// kernel source has been set. This happens async and might happen
|
||||
// after we receive the auth message.
|
||||
sourcePromise.then(() => {
|
||||
initResolve();
|
||||
sourceDefer.promise.then(() => {
|
||||
initDefer.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
if (IS_EXTENSION_ANY && event.data.data.kernelLoaded === "success") {
|
||||
const nonce = nextNonce();
|
||||
queries[nonce] = {
|
||||
resolve: sourceResolve,
|
||||
resolve: sourceDefer.resolve as unknown as queryResolve,
|
||||
};
|
||||
|
||||
const kernelMessage = {
|
||||
|
@ -181,7 +221,7 @@ function handleMessage(event: MessageEvent) {
|
|||
// that the user is logged in.
|
||||
if (!loginResolved && event.data.data.loginComplete) {
|
||||
loginResolved = true;
|
||||
loginResolve();
|
||||
loginDefer.resolve();
|
||||
}
|
||||
|
||||
// If the auth status message says that the kernel loaded, it means
|
||||
|
@ -199,7 +239,7 @@ function handleMessage(event: MessageEvent) {
|
|||
// out, we need to reload the page and reset the auth process.
|
||||
if (event.data.data.logoutComplete) {
|
||||
if (!logoutResolved) {
|
||||
logoutResolve();
|
||||
logoutDefer.resolve();
|
||||
}
|
||||
window.location.reload();
|
||||
}
|
||||
|
@ -248,17 +288,17 @@ function handleMessage(event: MessageEvent) {
|
|||
}
|
||||
|
||||
function launchKernelFrame() {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = "https://kernel.lumeweb.com";
|
||||
iframe.width = "0";
|
||||
iframe.height = "0";
|
||||
iframe.style.border = "0";
|
||||
iframe.style.position = "absolute";
|
||||
document.body.appendChild(iframe);
|
||||
kernelSource = <Window>iframe.contentWindow;
|
||||
kernelIframe = document.createElement("iframe");
|
||||
kernelIframe.src = "https://kernel.lumeweb.com";
|
||||
kernelIframe.width = "0";
|
||||
kernelIframe.height = "0";
|
||||
kernelIframe.style.border = "0";
|
||||
kernelIframe.style.position = "absolute";
|
||||
document.body.appendChild(kernelIframe);
|
||||
kernelSource = <Window>kernelIframe.contentWindow;
|
||||
kernelOrigin = EXTENSION_HOSTED_ORIGIN;
|
||||
kernelAuthLocation = `${EXTENSION_HOSTED_ORIGIN}/auth.html`;
|
||||
sourceResolve();
|
||||
sourceDefer.resolve();
|
||||
|
||||
// Set a timer to fail the login process if the kernel doesn't load in
|
||||
// time.
|
||||
|
@ -267,7 +307,9 @@ function launchKernelFrame() {
|
|||
return;
|
||||
}
|
||||
initResolved = true;
|
||||
initResolve("tried to open kernel in iframe, but hit a timeout");
|
||||
initDefer.resolve(
|
||||
"tried to open kernel in kernelIframe, but hit a timeout" as any,
|
||||
);
|
||||
}, 24000);
|
||||
}
|
||||
|
||||
|
@ -318,7 +360,7 @@ function messageBridge() {
|
|||
kernelAuthLocation = `${EXTENSION_KERNEL_ORIGIN}/auth.html`;
|
||||
log("established connection to bridge, using browser extension for kernel");
|
||||
if (!IS_EXTENSION_ANY) {
|
||||
sourceResolve();
|
||||
sourceDefer.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -357,7 +399,7 @@ function messageBridge() {
|
|||
bridgeResolve([null, null]);
|
||||
}
|
||||
|
||||
return initPromise;
|
||||
return initDefer;
|
||||
}
|
||||
|
||||
// init is a function that returns a promise which will resolve when
|
||||
|
@ -368,23 +410,19 @@ function messageBridge() {
|
|||
// thanks to the 'initialized' variable.
|
||||
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 initResolve: DataFn;
|
||||
let initPromise: Promise<void>;
|
||||
let initDefer: DeferredPromise<void>;
|
||||
let loginResolved = false; // set to true once we know the user is logged in
|
||||
let loginResolve: () => void;
|
||||
let loginPromise: Promise<void>;
|
||||
let loginDefer: DeferredPromise<void>;
|
||||
let kernelLoadedResolved = false; // set to true once the user kernel is loaded
|
||||
let kernelLoadedResolve: (err: Err) => void;
|
||||
let kernelLoadedPromise: Promise<Err>;
|
||||
let kernelLoadedDefer: DeferredPromise<Err>;
|
||||
const logoutResolved = false; // set to true once the user is logged out
|
||||
let logoutResolve: () => void;
|
||||
let logoutPromise: Promise<void>;
|
||||
let sourceResolve: () => void;
|
||||
let sourcePromise: Promise<void>; // resolves when the source is known and set
|
||||
let logoutDefer: DeferredPromise<void>;
|
||||
let sourceDefer: DeferredPromise<void>; // resolves when the source is known and set
|
||||
function init(): Promise<void> {
|
||||
// If init has already been called, just return the init promise.
|
||||
if (initialized) {
|
||||
return initPromise;
|
||||
return initDefer.promise;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
|
@ -394,25 +432,16 @@ function init(): Promise<void> {
|
|||
messageBridge();
|
||||
|
||||
// Create the promises that resolve at various stages of the auth flow.
|
||||
initPromise = new Promise((resolve) => {
|
||||
initResolve = resolve;
|
||||
});
|
||||
loginPromise = new Promise((resolve) => {
|
||||
loginResolve = resolve;
|
||||
});
|
||||
kernelLoadedPromise = new Promise((resolve) => {
|
||||
kernelLoadedResolve = resolve;
|
||||
});
|
||||
logoutPromise = new Promise((resolve) => {
|
||||
logoutResolve = resolve;
|
||||
});
|
||||
sourcePromise = new Promise((resolve) => {
|
||||
sourceResolve = resolve;
|
||||
});
|
||||
initDefer = defer();
|
||||
loginDefer = defer();
|
||||
kernelLoadedDefer = defer();
|
||||
logoutDefer = defer();
|
||||
kernelLoadedDefer = defer();
|
||||
sourceDefer = defer();
|
||||
|
||||
// Return the initPromise, which will resolve when bootloader init is
|
||||
// Return the initDefer, which will resolve when bootloader init is
|
||||
// complete.
|
||||
return initPromise;
|
||||
return initDefer.promise;
|
||||
}
|
||||
|
||||
// callModule is a generic function to call a module. The first input is the
|
||||
|
@ -536,7 +565,7 @@ function newKernelQuery(
|
|||
// implies that init is complete.
|
||||
const getNonce: Promise<string> = new Promise((resolve) => {
|
||||
init().then(() => {
|
||||
kernelLoadedPromise.then(() => {
|
||||
kernelLoadedDefer.promise.then(() => {
|
||||
resolve(nextNonce());
|
||||
});
|
||||
});
|
||||
|
@ -653,8 +682,10 @@ export {
|
|||
connectModule,
|
||||
init,
|
||||
kernelAuthLocation,
|
||||
kernelLoadedPromise,
|
||||
loginPromise,
|
||||
logoutPromise,
|
||||
kernelLoadedDefer,
|
||||
loginDefer,
|
||||
logoutDefer,
|
||||
newKernelQuery,
|
||||
serviceWorkerReady,
|
||||
getKernelIframe,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue