Skip to content

Commit

Permalink
feat: begin hooking up new permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
jurevans committed Oct 24, 2024
1 parent 296cc50 commit 436ce47
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 69 deletions.
7 changes: 6 additions & 1 deletion apps/extension/src/Approvals/ApproveDisconnection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ export const ApproveDisconnection: React.FC = () => {
const requester = useRequester();
const params = useQuery();
const interfaceOrigin = params.get("interfaceOrigin");
const chainId = params.get("chainId")!;

const handleResponse = async (revokeConnection: boolean): Promise<void> => {
if (interfaceOrigin) {
await requester.sendMessage(
Ports.Background,
new DisconnectInterfaceResponseMsg(interfaceOrigin, revokeConnection)
new DisconnectInterfaceResponseMsg(
interfaceOrigin,
chainId,
revokeConnection
)
);
await closeCurrentTab();
}
Expand Down
1 change: 1 addition & 0 deletions apps/extension/src/background/approvals/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ describe("approvals handler", () => {

const disconnectInterfaceResponseMsg = new DisconnectInterfaceResponseMsg(
"",
"chainId",
true
);
handler(env, disconnectInterfaceResponseMsg);
Expand Down
3 changes: 2 additions & 1 deletion apps/extension/src/background/approvals/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,12 @@ const handleDisconnectInterfaceResponseMsg: (
) => InternalHandler<DisconnectInterfaceResponseMsg> = (service) => {
return async (
{ senderTabId: popupTabId },
{ interfaceOrigin, revokeConnection }
{ interfaceOrigin, chainId, revokeConnection }
) => {
return await service.approveDisconnectionResponse(
popupTabId,
interfaceOrigin,
chainId,
revokeConnection
);
};
Expand Down
3 changes: 2 additions & 1 deletion apps/extension/src/background/approvals/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,14 @@ export class DisconnectInterfaceResponseMsg extends Message<void> {

constructor(
public readonly interfaceOrigin: string,
public readonly chainId: string,
public readonly revokeConnection: boolean
) {
super();
}

validate(): void {
validateProps(this, ["interfaceOrigin", "revokeConnection"]);
validateProps(this, ["interfaceOrigin", "chainId", "revokeConnection"]);
}

route(): string {
Expand Down
23 changes: 15 additions & 8 deletions apps/extension/src/background/approvals/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ describe("approvals service", () => {
reject: jest.fn(),
},
};
jest.spyOn(localStorage, "addApprovedOrigin").mockResolvedValue();
jest.spyOn(permissionsService, "enablePermissions").mockResolvedValue();

await service.approveConnectionResponse(
popupTabId,
Expand All @@ -327,8 +327,9 @@ describe("approvals service", () => {
);

expect(service["resolverMap"][popupTabId].resolve).toHaveBeenCalled();
expect(localStorage.addApprovedOrigin).toHaveBeenCalledWith(
interfaceOrigin
expect(permissionsService.enablePermissions).toHaveBeenCalledWith(
interfaceOrigin,
chainId
);
});

Expand Down Expand Up @@ -412,6 +413,7 @@ describe("approvals service", () => {
describe("approveDisconnectionResponse", () => {
it("should approve disconnection response", async () => {
const interfaceOrigin = "origin";
const chainId = "chainId";
const popupTabId = 1;
service["resolverMap"] = {
[popupTabId]: {
Expand All @@ -424,6 +426,7 @@ describe("approvals service", () => {
await service.approveDisconnectionResponse(
popupTabId,
interfaceOrigin,
chainId,
true
);

Expand Down Expand Up @@ -457,10 +460,12 @@ describe("approvals service", () => {
it("should reject connection response", async () => {
const originToRevoke = "origin";

jest.spyOn(localStorage, "removeApprovedOrigin").mockResolvedValue();
jest
.spyOn(permissionsService, "revokeDomainPermissions")
.mockResolvedValue();
await service.revokeConnection(originToRevoke);

expect(localStorage.removeApprovedOrigin).toHaveBeenCalledWith(
expect(permissionsService.revokeDomainPermissions).toHaveBeenCalledWith(
originToRevoke
);
});
Expand Down Expand Up @@ -569,7 +574,7 @@ describe("approvals service", () => {
const chainId = chains.namada.chainId;
jest.spyOn(chainService, "getChain").mockResolvedValue(chains.namada);
jest
.spyOn(localStorage, "getApprovedOrigins")
.spyOn(permissionsService, "getApprovedOrigins")
.mockResolvedValue([origin]);

await expect(service.isConnectionApproved(origin, chainId)).resolves.toBe(
Expand All @@ -581,7 +586,9 @@ describe("approvals service", () => {
const origin = "origin";
const chainId = "chainId";
jest.spyOn(chainService, "getChain").mockResolvedValue(chains.namada);
jest.spyOn(localStorage, "getApprovedOrigins").mockResolvedValue([]);
jest
.spyOn(permissionsService, "getApprovedOrigins")
.mockResolvedValue([]);

await expect(service.isConnectionApproved(origin, chainId)).resolves.toBe(
false
Expand All @@ -593,7 +600,7 @@ describe("approvals service", () => {
const chainId = "chainId";
jest.spyOn(chainService, "getChain").mockResolvedValue(chains.namada);
jest
.spyOn(localStorage, "getApprovedOrigins")
.spyOn(permissionsService, "getApprovedOrigins")
.mockResolvedValue(undefined);

await expect(service.isConnectionApproved(origin, chainId)).resolves.toBe(
Expand Down
28 changes: 18 additions & 10 deletions apps/extension/src/background/approvals/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,15 +190,14 @@ export class ApprovalsService {
interfaceOrigin: string,
chainId: string
): Promise<boolean> {
const approvedOrigins =
(await this.localStorage.getApprovedOrigins()) || [];

const chain = await this.chainService.getChain();
if (chain.chainId !== chainId) {
const permission = await this.permissionsService.permissionsByChain(
interfaceOrigin,
chainId
);
if (!permission || !permission.length) {
return false;
}

return approvedOrigins.includes(interfaceOrigin);
return true;
}

async approveConnection(
Expand Down Expand Up @@ -231,7 +230,11 @@ export class ApprovalsService {

if (allowConnection) {
try {
await this.localStorage.addApprovedOrigin(interfaceOrigin);
await this.permissionsService.enablePermissions(
interfaceOrigin,
chainId,
["accounts", "proofGenKeys", "signing"]
);
// Enable signing for this chain
await this.chainService.updateChain(chainId);
} catch (e) {
Expand All @@ -255,6 +258,7 @@ export class ApprovalsService {
if (isConnected) {
return this.launchApprovalPopup(TopLevelRoute.ApproveDisconnection, {
interfaceOrigin,
chainId,
});
}

Expand All @@ -265,13 +269,17 @@ export class ApprovalsService {
async approveDisconnectionResponse(
popupTabId: number,
interfaceOrigin: string,
chainId: string,
revokeConnection: boolean
): Promise<void> {
const resolvers = this.getResolver(popupTabId);

if (revokeConnection) {
try {
await this.revokeConnection(interfaceOrigin);
await this.permissionsService.revokeChainPermissions(
interfaceOrigin,
chainId
);
} catch (e) {
resolvers.reject(e);
}
Expand All @@ -282,7 +290,7 @@ export class ApprovalsService {
}

async revokeConnection(originToRevoke: string): Promise<void> {
await this.localStorage.removeApprovedOrigin(originToRevoke);
await this.permissionsService.revokeDomainPermissions(originToRevoke);
await this.broadcaster.revokeConnection();
}

Expand Down
13 changes: 13 additions & 0 deletions apps/extension/src/background/permissions/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ export class PermissionsService {
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> {
Expand All @@ -56,4 +65,8 @@ export class PermissionsService {
return permissions[domain][chainId];
}
}

async getApprovedOrigins(): Promise<string[] | undefined> {
return Object.keys((await this.localStorage.getPermissions()) || {});
}
}
56 changes: 14 additions & 42 deletions apps/extension/src/storage/LocalStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,6 @@ const Chain = t.intersection([
]);
type ChainType = t.TypeOf<typeof Chain>;

// TODO: Remove the folllowing once NamadaKeychainPermissions is working!
const NamadaExtensionApprovedOrigins = t.array(t.string);
type NamadaExtensionApprovedOriginsType = t.TypeOf<
typeof NamadaExtensionApprovedOrigins
>;

export type PermissionKind = "accounts" | "signing" | "proofGenKeys";

export const KeychainPermissions: Record<
Expand Down Expand Up @@ -94,12 +88,11 @@ type NamadaExtensionRouterIdType = t.TypeOf<typeof NamadaExtensionRouterId>;

type LocalStorageTypes =
| ChainType
| NamadaExtensionApprovedOriginsType
| NamadaKeychainPermissionsType
| NamadaExtensionRouterIdType;

type LocalStorageSchemas =
| typeof Chain
| typeof NamadaExtensionApprovedOrigins
| typeof NamadaExtensionRouterId
| typeof NamadaKeychainPermissions;

Expand All @@ -112,7 +105,6 @@ export type LocalStorageKeys =

const schemasMap = new Map<LocalStorageSchemas, LocalStorageKeys>([
[Chain, "chains"],
[NamadaExtensionApprovedOrigins, "namadaExtensionApprovedOrigins"],
[NamadaExtensionRouterId, "namadaExtensionRouterId"],
[NamadaKeychainPermissions, "namadaKeychainPermissions"],
]);
Expand All @@ -139,33 +131,6 @@ export class LocalStorage extends ExtStorage {
await this.setRaw(this.getKey(Chain), chain);
}

async getApprovedOrigins(): Promise<
NamadaExtensionApprovedOriginsType | undefined
> {
const data = await this.getRaw(this.getKey(NamadaExtensionApprovedOrigins));

const Schema = t.union([NamadaExtensionApprovedOrigins, t.undefined]);
const decodedData = Schema.decode(data);

if (E.isLeft(decodedData)) {
throw new Error("Approved Origins are not valid");
}

return decodedData.right;
}

async addApprovedOrigin(originToAdd: string): Promise<void> {
const data = (await this.getApprovedOrigins()) || [];
await this.setApprovedOrigins([...data, originToAdd]);
}

async removeApprovedOrigin(originToRemove: string): Promise<void> {
const data = (await this.getApprovedOrigins()) || [];
await this.setApprovedOrigins(
data.filter((origin) => origin !== originToRemove)
);
}

async getRouterId(): Promise<NamadaExtensionRouterIdType | undefined> {
const data = await this.getRaw(this.getKey(NamadaExtensionRouterId));

Expand All @@ -183,12 +148,6 @@ export class LocalStorage extends ExtStorage {
await this.setRaw(this.getKey(NamadaExtensionRouterId), id);
}

private async setApprovedOrigins(
origins: NamadaExtensionApprovedOriginsType
): Promise<void> {
await this.setRaw(this.getKey(NamadaExtensionApprovedOrigins), origins);
}

async getPermissions(): Promise<NamadaKeychainPermissionsType | undefined> {
const data = await this.getRaw(this.getKey(NamadaKeychainPermissions));
const Schema = t.union([NamadaKeychainPermissions, t.undefined]);
Expand Down Expand Up @@ -217,6 +176,19 @@ export class LocalStorage extends ExtStorage {
);
}

async getApprovedOrigins(): Promise<string[] | 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)) {
throw new Error("Stored Keychain permissions are not valid!");
}

return Object.keys(decodedData.right || {});
}

private getKey<S extends LocalStorageSchemas>(schema: S): LocalStorageKeys {
const key = schemasMap.get(schema);
if (!key) {
Expand Down
15 changes: 9 additions & 6 deletions apps/namadillo/src/App/Setup/ExtensionLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useIntegration } from "@namada/integrations";
import { chainParametersAtom } from "atoms/chain";
import {
namadaExtensionAttachStatus,
namadaExtensionConnectionStatus,
} from "atoms/settings";
import { useAtom, useSetAtom } from "jotai";
import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { ReactNode, useEffect } from "react";
import { PageLoader } from "../Common/PageLoader";

Expand All @@ -15,15 +16,17 @@ export const ExtensionLoader = ({
const [attachStatus, setAttachStatus] = useAtom(namadaExtensionAttachStatus);
const setConnectionStatus = useSetAtom(namadaExtensionConnectionStatus);
const integration = useIntegration("namada");
const { data: chain } = useAtomValue(chainParametersAtom);

useEffect(() => {
setAttachStatus(integration.detect() ? "attached" : "detached");

integration.isConnected().then((isConnected) => {
if (isConnected) {
setConnectionStatus("connected");
}
});
chain?.chainId &&
integration.isConnected(chain.chainId).then((isConnected) => {
if (isConnected) {
setConnectionStatus("connected");
}
});
}, [integration]);

if (attachStatus === "pending") {
Expand Down

0 comments on commit 436ce47

Please sign in to comment.