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

feat: implement ceramic session #246

Merged
merged 11 commits into from
Jun 19, 2023
Merged
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
4 changes: 3 additions & 1 deletion packages/connector/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@
"dependencies": {
"@blockchain-lab-um/masca-types": "*",
"@blockchain-lab-um/utils": "*",
"@didtools/pkh-ethereum": "0.0.2",
andyv09 marked this conversation as resolved.
Show resolved Hide resolved
"@metamask/detect-provider": "^2.0.0",
"@veramo/core": "5.2.0"
"@veramo/core": "5.2.0",
"did-session": "2.0.1"
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.0.1",
Expand Down
98 changes: 76 additions & 22 deletions packages/connector/src/snap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {
SetCurrentAccountRequestParams,
VerifyDataRequestParams,
} from '@blockchain-lab-um/masca-types';
import type { Result } from '@blockchain-lab-um/utils';
import { ResultObject, type Result } from '@blockchain-lab-um/utils';
import type {
DIDResolutionResult,
IVerifyResult,
Expand All @@ -27,6 +27,8 @@ import type {
W3CVerifiableCredential,
} from '@veramo/core';

import { validateAndSetCeramicSession } from './utils.js';

async function sendSnapMethod<T>(
request: MascaRPCRequest,
snapId: string
Expand All @@ -51,6 +53,8 @@ export async function queryVCs(
this: Masca,
params?: QueryVCsRequestParams
): Promise<Result<QueryVCsRequestResult[]>> {
await validateAndSetCeramicSession(this);

return sendSnapMethod(
{ method: 'queryVCs', params: params ?? {} },
this.snapId
Expand All @@ -68,6 +72,8 @@ export async function createVP(
this: Masca,
params: CreateVPRequestParams
): Promise<Result<VerifiablePresentation>> {
await validateAndSetCeramicSession(this);

return sendSnapMethod(
{
method: 'createVP',
Expand All @@ -90,6 +96,8 @@ export async function saveVC(
vc: W3CVerifiableCredential,
options?: SaveVCOptions
): Promise<Result<SaveVCRequestResult[]>> {
await validateAndSetCeramicSession(this);

return sendSnapMethod(
{
method: 'saveVC',
Expand All @@ -115,6 +123,8 @@ export async function deleteVC(
id: string,
options?: DeleteVCsOptions
): Promise<Result<boolean[]>> {
await validateAndSetCeramicSession(this);

return sendSnapMethod(
{
method: 'deleteVC',
Expand Down Expand Up @@ -266,6 +276,8 @@ export async function createVC(
this: Masca,
params: CreateVCRequestParams
): Promise<Result<VerifiableCredential>> {
await validateAndSetCeramicSession(this);

return sendSnapMethod(
{
method: 'createVC',
Expand Down Expand Up @@ -346,6 +358,40 @@ export async function sendOIDCAuthorizationResponse(
);
}

export async function setCeramicSession(
this: Masca,
serializedSession: string
): Promise<Result<boolean>> {
return sendSnapMethod(
{
method: 'setCeramicSession',
params: { serializedSession },
},
this.snapId
);
}

export async function validateStoredCeramicSession(
this: Masca
): Promise<Result<boolean>> {
return sendSnapMethod(
{
method: 'validateStoredCeramicSession',
},
this.snapId
);
}

const wrapper =
<T extends any[], R>(fn: (...args: T) => Promise<Result<R>>) =>
async (...args: T): Promise<Result<R>> => {
try {
return await fn(...args);
} catch (e) {
return ResultObject.error((e as Error).toString());
}
};

export class Masca {
protected readonly snapId: string;

Expand All @@ -360,26 +406,34 @@ export class Masca {
}

public getMascaApi = (): MascaApi => ({
saveVC: saveVC.bind(this),
queryVCs: queryVCs.bind(this),
createVP: createVP.bind(this),
togglePopups: togglePopups.bind(this),
getDID: getDID.bind(this),
getSelectedMethod: getSelectedMethod.bind(this),
getAvailableMethods: getAvailableMethods.bind(this),
switchDIDMethod: switchDIDMethod.bind(this),
getVCStore: getVCStore.bind(this),
setVCStore: setVCStore.bind(this),
getAvailableVCStores: getAvailableVCStores.bind(this),
deleteVC: deleteVC.bind(this),
getSnapSettings: getSnapSettings.bind(this),
getAccountSettings: getAccountSettings.bind(this),
resolveDID: resolveDID.bind(this),
createVC: createVC.bind(this),
setCurrentAccount: setCurrentAccount.bind(this),
verifyData: verifyData.bind(this),
handleOIDCCredentialOffer: handleOIDCCredentialOffer.bind(this),
handleOIDCAuthorizationRequest: handleOIDCAuthorizationRequest.bind(this),
sendOIDCAuthorizationResponse: sendOIDCAuthorizationResponse.bind(this),
saveVC: wrapper(saveVC.bind(this)),
queryVCs: wrapper(queryVCs.bind(this)),
createVP: wrapper(createVP.bind(this)),
togglePopups: wrapper(togglePopups.bind(this)),
getDID: wrapper(getDID.bind(this)),
getSelectedMethod: wrapper(getSelectedMethod.bind(this)),
getAvailableMethods: wrapper(getAvailableMethods.bind(this)),
switchDIDMethod: wrapper(switchDIDMethod.bind(this)),
getVCStore: wrapper(getVCStore.bind(this)),
setVCStore: wrapper(setVCStore.bind(this)),
getAvailableVCStores: wrapper(getAvailableVCStores.bind(this)),
deleteVC: wrapper(deleteVC.bind(this)),
getSnapSettings: wrapper(getSnapSettings.bind(this)),
getAccountSettings: wrapper(getAccountSettings.bind(this)),
resolveDID: wrapper(resolveDID.bind(this)),
createVC: wrapper(createVC.bind(this)),
setCurrentAccount: wrapper(setCurrentAccount.bind(this)),
verifyData: wrapper(verifyData.bind(this)),
handleOIDCCredentialOffer: wrapper(handleOIDCCredentialOffer.bind(this)),
handleOIDCAuthorizationRequest: wrapper(
handleOIDCAuthorizationRequest.bind(this)
),
sendOIDCAuthorizationResponse: wrapper(
sendOIDCAuthorizationResponse.bind(this)
),
setCeramicSession: wrapper(setCeramicSession.bind(this)),
validateStoredCeramicSession: wrapper(
validateStoredCeramicSession.bind(this)
),
});
}
53 changes: 53 additions & 0 deletions packages/connector/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { isError } from '@blockchain-lab-um/utils';
import { EthereumWebAuth, getAccountId } from '@didtools/pkh-ethereum';
import { DIDSession } from 'did-session';

import { Masca } from './snap.js';
andyv09 marked this conversation as resolved.
Show resolved Hide resolved

export async function validateAndSetCeramicSession(
masca: Masca
): Promise<void> {
// Check if there is valid session in Masca
const api = masca.getMascaApi();

const enabledVCStoresResult = await api.getVCStore();
if (isError(enabledVCStoresResult)) {
throw new Error('Failed to get enabled VC stores.');
}

// Check if ceramic is enabled
if (enabledVCStoresResult.data.ceramic === false) {
return;
}

const session = await api.validateStoredCeramicSession();
if (!isError(session)) {
return;
}

const addresses: string[] = await window.ethereum.request({
method: 'eth_requestAccounts',
});

const accountId = await getAccountId(window.ethereum, addresses[0]);

const authMethod = await EthereumWebAuth.getAuthMethod(
window.ethereum,
accountId
);

let newSession;
try {
newSession = await DIDSession.authorize(authMethod, {
expiresInSecs: 60 * 60 * 24,
resources: [`ceramic://*`],
});
} catch (e) {
throw new Error('User failed to sign session.');
}
const serializedSession = newSession.serialize();
const result = await api.setCeramicSession(serializedSession);
if (isError(result)) {
throw new Error('Failed to set session in Masca.');
}
}
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/blockchain-lab-um/masca.git"
},
"source": {
"shasum": "kpxsf6Amegq82TeSNWiMdndc9CS6n6FMuKKG6mGPGAg=",
"shasum": "O1UXSOxy+oi/+u0TyrzxY8rHpZ+9hrK+QfSdATkhV60=",
"location": {
"npm": {
"filePath": "dist/snap.js",
Expand Down
12 changes: 12 additions & 0 deletions packages/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import { switchMethod } from './rpc/did/switchMethod';
import { handleOIDCAuthorizationRequest } from './rpc/oidc/handleOIDCAuthorizationRequest';
import { handleOIDCCredentialOffer } from './rpc/oidc/handleOIDCCredentialOffer';
import { sendOIDCAuthorizationResponse } from './rpc/oidc/sendOIDCAuthorizationResponse';
import { setCeramicSession } from './rpc/setCeramicSession';
import { togglePopups } from './rpc/snap/configure';
import { setCurrentAccount } from './rpc/snap/setCurrentAccount';
import { validateStoredCeramicSession } from './rpc/validateStoredCeramicSession';
import { createVC } from './rpc/vc/createVC';
import { createVP } from './rpc/vc/createVP';
import { deleteVC } from './rpc/vc/deleteVC';
Expand Down Expand Up @@ -186,6 +188,16 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
request.params as unknown as SendOIDCAuthorizationResponseParams
);
return ResultObject.success(res);
case 'setCeramicSession':
// TODO (andy) validate request params
res = await setCeramicSession(
apiParams,
(request.params as any).serializedSession as string
);
return ResultObject.success(res);
case 'validateStoredCeramicSession':
await validateStoredCeramicSession(apiParams);
return ResultObject.success(true);
default:
throw new Error('Method not found.');
}
Expand Down
62 changes: 1 addition & 61 deletions packages/snap/src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,7 @@
import type {
MascaAccountConfig,
MascaConfig,
} from '@blockchain-lab-um/masca-types';
import { MascaState } from '@blockchain-lab-um/masca-types';
import { BIP44CoinTypeNode } from '@metamask/key-tree';
import { MetaMaskInpageProvider } from '@metamask/providers';
import type { SnapsGlobalObject } from '@metamask/snaps-types';
import type { IIdentifier, IKey, W3CVerifiableCredential } from '@veramo/core';
import type { ManagedPrivateKey } from '@veramo/key-manager';

export type MascaState = {
/**
* Account specific storage
*/
accountState: Record<string, MascaAccountState>;
/**
* Current account
*/
currentAccount: string;
/**
* Configuration for Masca
*/
snapConfig: MascaConfig;
};

export type ExtendedVerifiableCredential = W3CVerifiableCredential & {
/**
* key for dictionary
*/
key: string;
};

/**
* Ceramic Network storedVCs
*/
export type StoredCredentials = {
storedCredentials: ExtendedVerifiableCredential[];
};

/**
* Masca State for a MetaMask address
*/
export type MascaAccountState = {
/**
* Store for {@link SnapPrivateKeyStore}
*/
snapPrivateKeyStore: Record<string, ManagedPrivateKey>;
/**
* Store for {@link SnapKeyStore}
*/
snapKeyStore: Record<string, IKey>;
/**
* Store for {@link SnapDIDStore}
*/
identifiers: Record<string, IIdentifier>;
/**
* Store for {@link SnapVCStore}
*/
vcs: Record<string, W3CVerifiableCredential>;

publicKey: string;
index?: number;
accountConfig: MascaAccountConfig;
};

export type SnapConfirmParams = {
prompt: string;
Expand Down
12 changes: 12 additions & 0 deletions packages/snap/src/rpc/setCeramicSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ApiParams } from '../interfaces';
import { updateSnapState } from '../utils/stateUtils';

export async function setCeramicSession(
params: ApiParams,
serializedSession: string
): Promise<boolean> {
const { state } = params;
state.accountState[state.currentAccount].ceramicSession = serializedSession;
await updateSnapState(snap, state);
return true;
}
12 changes: 12 additions & 0 deletions packages/snap/src/rpc/validateStoredCeramicSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ApiParams } from '../interfaces';
import { validateSession } from '../utils/ceramicUtils';

export async function validateStoredCeramicSession(
params: ApiParams
): Promise<string> {
const { state } = params;
const serializedSession = await validateSession(
state.accountState[state.currentAccount].ceramicSession
);
return serializedSession;
}
Loading