Files
plebeian-signer/projects/chrome/src/background-common.ts
2025-02-01 23:23:37 +01:00

284 lines
7.1 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
import {
BrowserSessionData,
BrowserSyncData,
BrowserSyncFlow,
CryptoHelper,
GootiMetaData,
Identity_DECRYPTED,
Nip07Method,
Nip07MethodPolicy,
NostrHelper,
Permission_DECRYPTED,
Permission_ENCRYPTED,
} from '@common';
import { ChromeMetaHandler } from './app/common/data/chrome-meta-handler';
import { Event, EventTemplate, finalizeEvent, nip04 } from 'nostr-tools';
export const debug = function (message: any) {
const dateString = new Date().toISOString();
console.log(`[Gooti - ${dateString}]: ${JSON.stringify(message)}`);
};
export type PromptResponse =
| 'reject'
| 'reject-once'
| 'approve'
| 'approve-once';
export interface PromptResponseMessage {
id: string;
response: PromptResponse;
}
export interface BackgroundRequestMessage {
method: Nip07Method;
params: any;
host: string;
}
export const getBrowserSessionData = async function (): Promise<
BrowserSessionData | undefined
> {
const browserSessionData = await chrome.storage.session.get(null);
if (Object.keys(browserSessionData).length === 0) {
return undefined;
}
return browserSessionData as BrowserSessionData;
};
export const getBrowserSyncData = async function (): Promise<
BrowserSyncData | undefined
> {
const gootiMetaHandler = new ChromeMetaHandler();
const gootiMetaData =
(await gootiMetaHandler.loadFullData()) as GootiMetaData;
let browserSyncData: BrowserSyncData | undefined;
if (gootiMetaData.syncFlow === BrowserSyncFlow.NO_SYNC) {
browserSyncData = (await chrome.storage.local.get(null)) as BrowserSyncData;
} else if (gootiMetaData.syncFlow === BrowserSyncFlow.BROWSER_SYNC) {
browserSyncData = (await chrome.storage.sync.get(null)) as BrowserSyncData;
}
return browserSyncData;
};
export const savePermissionsToBrowserSyncStorage = async function (
permissions: Permission_ENCRYPTED[]
): Promise<void> {
const gootiMetaHandler = new ChromeMetaHandler();
const gootiMetaData =
(await gootiMetaHandler.loadFullData()) as GootiMetaData;
if (gootiMetaData.syncFlow === BrowserSyncFlow.NO_SYNC) {
await chrome.storage.local.set({ permissions });
} else if (gootiMetaData.syncFlow === BrowserSyncFlow.BROWSER_SYNC) {
await chrome.storage.sync.set({ permissions });
}
};
export const checkPermissions = function (
browserSessionData: BrowserSessionData,
identity: Identity_DECRYPTED,
host: string,
method: Nip07Method,
params: any
): boolean | undefined {
const permissions = browserSessionData.permissions.filter(
(x) =>
x.identityId === identity.id && x.host === host && x.method === method
);
if (permissions.length === 0) {
return undefined;
}
if (method === 'getPublicKey') {
// No evaluation of params required.
return permissions.every((x) => x.methodPolicy === 'allow');
}
if (method === 'getRelays') {
// No evaluation of params required.
return permissions.every((x) => x.methodPolicy === 'allow');
}
if (method === 'signEvent') {
// Evaluate params.
const eventTemplate = params as EventTemplate;
if (
permissions.find(
(x) => x.methodPolicy === 'allow' && typeof x.kind === 'undefined'
)
) {
return true;
}
if (
permissions.some(
(x) => x.methodPolicy === 'allow' && x.kind === eventTemplate.kind
)
) {
return true;
}
if (
permissions.some(
(x) => x.methodPolicy === 'deny' && x.kind === eventTemplate.kind
)
) {
return false;
}
return undefined;
}
if (method === 'nip04.encrypt') {
// No evaluation of params required.
return permissions.every((x) => x.methodPolicy === 'allow');
}
if (method === 'nip04.decrypt') {
// No evaluation of params required.
return permissions.every((x) => x.methodPolicy === 'allow');
}
return undefined;
};
export const storePermission = async function (
browserSessionData: BrowserSessionData,
identity: Identity_DECRYPTED,
host: string,
method: Nip07Method,
methodPolicy: Nip07MethodPolicy,
kind?: number
) {
const browserSyncData = await getBrowserSyncData();
if (!browserSyncData) {
throw new Error(`Could not retrieve sync data`);
}
const permission: Permission_DECRYPTED = {
id: crypto.randomUUID(),
identityId: identity.id,
host,
method,
methodPolicy,
kind,
};
// Store session data
await chrome.storage.session.set({
permissions: [...browserSessionData.permissions, permission],
});
// Encrypt permission to store in sync storage (depending on sync flow).
const encryptedPermission = await encryptPermission(
permission,
browserSessionData.iv,
browserSessionData.vaultPassword as string
);
await savePermissionsToBrowserSyncStorage([
...browserSyncData.permissions,
encryptedPermission,
]);
};
export const getPosition = async function (width: number, height: number) {
let left = 0;
let top = 0;
try {
const lastFocused = await chrome.windows.getLastFocused();
if (
lastFocused &&
lastFocused.top !== undefined &&
lastFocused.left !== undefined &&
lastFocused.width !== undefined &&
lastFocused.height !== undefined
) {
// Position window in the center of the lastFocused window
top = Math.round(lastFocused.top + (lastFocused.height - height) / 2);
left = Math.round(lastFocused.left + (lastFocused.width - width) / 2);
} else {
console.error('Last focused window properties are undefined.');
}
} catch (error) {
console.error('Error getting window position:', error);
}
return {
top,
left,
};
};
export const signEvent = function (
eventTemplate: EventTemplate,
privkey: string
): Event {
return finalizeEvent(eventTemplate, NostrHelper.hex2bytes(privkey));
};
export const nip04Encrypt = async function (
privkey: string,
peerPubkey: string,
plaintext: string
): Promise<string> {
return await nip04.encrypt(
NostrHelper.hex2bytes(privkey),
peerPubkey,
plaintext
);
};
export const nip04Decrypt = async function (
privkey: string,
peerPubkey: string,
ciphertext: string
): Promise<string> {
return await nip04.decrypt(
NostrHelper.hex2bytes(privkey),
peerPubkey,
ciphertext
);
};
const encryptPermission = async function (
permission: Permission_DECRYPTED,
iv: string,
password: string
): Promise<Permission_ENCRYPTED> {
const encryptedPermission: Permission_ENCRYPTED = {
id: await encrypt(permission.id, iv, password),
identityId: await encrypt(permission.identityId, iv, password),
host: await encrypt(permission.host, iv, password),
method: await encrypt(permission.method, iv, password),
methodPolicy: await encrypt(permission.methodPolicy, iv, password),
};
if (typeof permission.kind !== 'undefined') {
encryptedPermission.kind = await encrypt(
permission.kind.toString(),
iv,
password
);
}
return encryptedPermission;
};
const encrypt = async function (
value: string,
iv: string,
password: string
): Promise<string> {
return await CryptoHelper.encrypt(value, iv, password);
};