Skip to content

Commit

Permalink
feat: implement ceramic session (#246)
Browse files Browse the repository at this point in the history
* feat: implement ceramic session

* feat: implement ceramic session in connector

* fix: linting errors

* fix: wrong type

* fix: tests

* fix: formatting

* fix: pr review

* fix: linting errors

* fix: pr review

* chore: remove duplicate types, small code refactor

* chore: better check for getVCStore error

---------

Co-authored-by: Martin Domajnko <domajnko.martin@gmail.com>
  • Loading branch information
andyv09 and martines3000 committed Jun 19, 2023
1 parent 75cdf01 commit dc6f985
Show file tree
Hide file tree
Showing 30 changed files with 311 additions and 181 deletions.
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",
"@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';

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

0 comments on commit dc6f985

Please sign in to comment.