Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: feat/1078 - Extension permissions updates #1361

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/extension/src/background/permissions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./service";
72 changes: 72 additions & 0 deletions apps/extension/src/background/permissions/service.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
const existingPermissions = await this.localStorage.getPermissions();
const newPermissions = [...new Set<PermissionKind>(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<void> {
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<void> {
const updatedPermissions = await this.localStorage.getPermissions();
if (!updatedPermissions || !updatedPermissions[domain]) {
return;
}
delete updatedPermissions[domain];
await this.localStorage.setPermissions(updatedPermissions);
}

async permissionsByDomain(
domain: string
): Promise<Record<string, AllowedPermissions> | undefined> {
const permissions = await this.localStorage.getPermissions();

if (permissions && permissions[domain]) {
return permissions[domain];
}
}

async permissionsByChain(
domain: string,
chainId: string
): Promise<AllowedPermissions | undefined> {
const permissions = await this.localStorage.getPermissions();
if (permissions && permissions[domain] && permissions[domain][chainId]) {
return permissions[domain][chainId];
}
}

async getApprovedOrigins(): Promise<string[] | undefined> {
return await this.localStorage.getApprovedOrigins();
}
}
66 changes: 64 additions & 2 deletions apps/extension/src/storage/LocalStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,59 @@ type NamadaExtensionApprovedOriginsType = t.TypeOf<
const NamadaExtensionRouterId = t.number;
type NamadaExtensionRouterIdType = t.TypeOf<typeof NamadaExtensionRouterId>;

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<LocalStorageSchemas, LocalStorageKeys>([
[ChainId, "chainId"],
[NamadaExtensionApprovedOrigins, "namadaExtensionApprovedOrigins"],
[NamadaExtensionRouterId, "namadaExtensionRouterId"],
[NamadaKeychainPermissions, "namadaKeychainPermissions"],
]);

export class LocalStorage extends ExtStorage {
Expand Down Expand Up @@ -105,6 +138,35 @@ export class LocalStorage extends ExtStorage {
await this.setRaw(this.getKey(NamadaExtensionRouterId), id);
}

async getPermissions(): Promise<NamadaKeychainPermissionsType | undefined> {
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<void> {
// 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<void> {
Expand Down