From aaf639bcfb7f3871739b0d4e1df9b3e84c678f18 Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Mon, 2 Dec 2024 08:59:36 -0500 Subject: [PATCH] feat: add permissions service, storage schema --- .../src/background/permissions/index.ts | 1 + .../src/background/permissions/service.ts | 72 +++++++++++++++++++ apps/extension/src/storage/LocalStorage.ts | 66 ++++++++++++++++- 3 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 apps/extension/src/background/permissions/index.ts create mode 100644 apps/extension/src/background/permissions/service.ts diff --git a/apps/extension/src/background/permissions/index.ts b/apps/extension/src/background/permissions/index.ts new file mode 100644 index 0000000000..6261f89636 --- /dev/null +++ b/apps/extension/src/background/permissions/index.ts @@ -0,0 +1 @@ +export * from "./service"; diff --git a/apps/extension/src/background/permissions/service.ts b/apps/extension/src/background/permissions/service.ts new file mode 100644 index 0000000000..0305ad406e --- /dev/null +++ b/apps/extension/src/background/permissions/service.ts @@ -0,0 +1,72 @@ +import { AllowedPermissions, LocalStorage, PermissionKind } from "storage"; + +export class PermissionsService { + constructor(protected readonly localStorage: LocalStorage) {} + + async enablePermissions( + domain: string, + chainId: string, + allowed: AllowedPermissions + ): Promise { + const existingPermissions = await this.localStorage.getPermissions(); + const newPermissions = [...new Set(allowed)]; + + if (existingPermissions) { + existingPermissions[domain] = existingPermissions[domain] || {}; + existingPermissions[domain][chainId] = newPermissions; + return await this.localStorage.setPermissions(existingPermissions); + } + + return await this.localStorage.setPermissions({ + [domain]: { + [chainId]: newPermissions, + }, + }); + } + + async revokeChainPermissions(domain: string, chainId: string): Promise { + const updatedPermissions = await this.localStorage.getPermissions(); + if ( + !updatedPermissions || + !updatedPermissions[domain] || + !updatedPermissions[domain][chainId] + ) { + return; + } + delete updatedPermissions[domain][chainId]; + await this.localStorage.setPermissions(updatedPermissions); + } + + async revokeDomainPermissions(domain: string): Promise { + const updatedPermissions = await this.localStorage.getPermissions(); + if (!updatedPermissions || !updatedPermissions[domain]) { + return; + } + delete updatedPermissions[domain]; + await this.localStorage.setPermissions(updatedPermissions); + } + + async permissionsByDomain( + domain: string + ): Promise | undefined> { + const permissions = await this.localStorage.getPermissions(); + + if (permissions && permissions[domain]) { + return permissions[domain]; + } + } + + async permissionsByChain( + domain: string, + chainId: string + ): Promise { + const permissions = await this.localStorage.getPermissions(); + if (permissions && permissions[domain] && permissions[domain][chainId]) { + return permissions[domain][chainId]; + } + } + + async getApprovedOrigins(): Promise { + return await this.localStorage.getApprovedOrigins(); + } +} diff --git a/apps/extension/src/storage/LocalStorage.ts b/apps/extension/src/storage/LocalStorage.ts index d6e3c56e83..cb6a60837e 100644 --- a/apps/extension/src/storage/LocalStorage.ts +++ b/apps/extension/src/storage/LocalStorage.ts @@ -15,26 +15,59 @@ type NamadaExtensionApprovedOriginsType = t.TypeOf< const NamadaExtensionRouterId = t.number; type NamadaExtensionRouterIdType = t.TypeOf; +export type PermissionKind = "accounts" | "signing" | "proofGenKeys"; + +export const KeychainPermissions: Record< + PermissionKind, + { description: string } +> = { + accounts: { + description: "Allow approved clients to read account public data", + }, + signing: { description: "Allow approved clients to sign transactions" }, + proofGenKeys: { + description: + "Allow approved clients to request proof generation keys for shielded accounts", + }, +}; +export type AllowedPermissions = (keyof typeof KeychainPermissions)[]; + +// Define keychain permissions schema +const PermissionDomain = t.string; +const PermissionChainId = t.string; +const NamadaKeychainPermissions = t.record( + PermissionDomain, + t.record(PermissionChainId, t.array(t.keyof(KeychainPermissions))) +); + +// Export keychain permissions type +export type NamadaKeychainPermissionsType = t.TypeOf< + typeof NamadaKeychainPermissions +>; type LocalStorageTypes = | ChainIdType | NamadaExtensionApprovedOriginsType - | NamadaExtensionRouterIdType; + | NamadaExtensionRouterIdType + | NamadaKeychainPermissionsType; type LocalStorageSchemas = | typeof ChainId | typeof NamadaExtensionApprovedOrigins - | typeof NamadaExtensionRouterId; + | typeof NamadaExtensionRouterId + | typeof NamadaKeychainPermissions; export type LocalStorageKeys = | "chainId" | "namadaExtensionApprovedOrigins" | "namadaExtensionRouterId" + | "namadaKeychainPermissions" | "tabs"; const schemasMap = new Map([ [ChainId, "chainId"], [NamadaExtensionApprovedOrigins, "namadaExtensionApprovedOrigins"], [NamadaExtensionRouterId, "namadaExtensionRouterId"], + [NamadaKeychainPermissions, "namadaKeychainPermissions"], ]); export class LocalStorage extends ExtStorage { @@ -105,6 +138,35 @@ export class LocalStorage extends ExtStorage { await this.setRaw(this.getKey(NamadaExtensionRouterId), id); } + async getPermissions(): Promise { + const data = await this.getRaw(this.getKey(NamadaKeychainPermissions)); + const Schema = t.union([NamadaKeychainPermissions, t.undefined]); + const decodedData = Schema.decode(data); + + if (E.isLeft(decodedData)) { + return; + } + + return decodedData.right; + } + + async setPermissions( + permissions: NamadaKeychainPermissionsType + ): Promise { + // Validate permissions against schema + const Schema = t.union([NamadaKeychainPermissions, t.undefined]); + const decodedData = Schema.decode(permissions); + + if (E.isLeft(decodedData)) { + throw new Error("Invalid permissions data!"); + } + + await this.setRaw( + this.getKey(NamadaKeychainPermissions), + decodedData.right + ); + } + private async setApprovedOrigins( origins: NamadaExtensionApprovedOriginsType ): Promise {