Skip to content

Commit

Permalink
feat: add permissions service, storage schema
Browse files Browse the repository at this point in the history
  • Loading branch information
jurevans committed Dec 2, 2024
1 parent eece967 commit e54e760
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 2 deletions.
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

0 comments on commit e54e760

Please sign in to comment.