Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/eight-mugs-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@learncard/network-brain-service': patch
---

Correctly invalidate did web cache when claming a boost
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { flattenObject } from '@helpers/objects.helpers';
import { ProfileType } from 'types/profile';
import { clearDidWebCacheForChildProfileManagers } from '@accesslayer/boost/relationships/update';
import { getBoostIdForCredentialInstance } from '@accesslayer/credential/relationships/read';
import { DbTermsType } from 'types/consentflowcontract';

export const createSentCredentialRelationship = async (
Expand All @@ -38,7 +39,9 @@ export const createSentCredentialRelationship = async (
{ model: Credential, where: { id: credential.id }, identifier: 'credential' },
],
})
.create(`(profile)-[r:${Profile.getRelationshipByAlias('credentialSent').name}]->(credential)`)
.create(
`(profile)-[r:${Profile.getRelationshipByAlias('credentialSent').name}]->(credential)`
)
.set('r = $params')
.run();
};
Expand Down Expand Up @@ -66,7 +69,11 @@ export const createReceivedCredentialRelationship = async (
{ model: Profile, where: { profileId: to.profileId }, identifier: 'profile' },
],
})
.create(`(credential)-[r:${Credential.getRelationshipByAlias('credentialReceived').name}]->(profile)`)
.create(
`(credential)-[r:${
Credential.getRelationshipByAlias('credentialReceived').name
}]->(profile)`
)
.set('r = $params')
.run();
};
Expand All @@ -92,7 +99,8 @@ export const setDefaultClaimedRole = async (
.with('boost, role')
.match({ model: Profile, where: { profileId: profile.profileId }, identifier: 'profile' })
.where(
`NOT EXISTS { MATCH (profile)-[:${Boost.getRelationshipByAlias('hasRole').name
`NOT EXISTS { MATCH (profile)-[:${
Boost.getRelationshipByAlias('hasRole').name
}]-(boost)}`
)
.create({
Expand All @@ -105,11 +113,11 @@ export const setDefaultClaimedRole = async (
.run();

try {
const vc = JSON.parse(credential.credential);
const boostId = await getBoostIdForCredentialInstance(credential);

if (vc.boostId) await clearDidWebCacheForChildProfileManagers(vc.boostId);
if (boostId) await clearDidWebCacheForChildProfileManagers(boostId);
} catch (error) {
console.error('Invalid credential JSON?', error);
console.error('Could not clear did:web cache for accepted boost credential', error);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export const getCredentialSentToProfile = async (
to: ProfileType
): Promise<
| {
source: ProfileType;
relationship: ProfileRelationships['credentialSent']['RelationshipProperties'];
target: CredentialInstance;
}
source: ProfileType;
relationship: ProfileRelationships['credentialSent']['RelationshipProperties'];
target: CredentialInstance;
}
| undefined
> => {
const data = (
Expand Down Expand Up @@ -199,7 +199,8 @@ export const getAllCredentialsForProfileTerms = async (

if (!includeReceived) {
query = query.where(
`NOT EXISTS { MATCH (credential)-[:${Credential.getRelationshipByAlias('credentialReceived').name
`NOT EXISTS { MATCH (credential)-[:${
Credential.getRelationshipByAlias('credentialReceived').name
}]-(profile) }`
);
}
Expand Down Expand Up @@ -243,3 +244,23 @@ export const getAllCredentialsForProfileTerms = async (
transaction: inflateObject<any>(record.transaction),
}));
};

export const getBoostIdForCredentialInstance = async (
credential: CredentialInstance
): Promise<string | undefined> => {
const results = convertQueryResultToPropertiesObjectArray<{ boostId: string }>(
await new QueryBuilder()
.match({
related: [
{ identifier: 'credential', model: Credential, where: { id: credential.id } },
Credential.getRelationshipByAlias('instanceOf'),
{ identifier: 'boost', model: Boost },
],
})
.return('boost.id AS boostId')
.limit(1)
.run()
);

return results[0]?.boostId;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { QueryBuilder } from 'neogma';
import { CredentialInstance, Credential, Boost, Profile, ClaimHook, Role } from '@models';
import { ProfileType } from 'types/profile';
import { clearDidWebCacheForChildProfileManagers } from '@accesslayer/boost/relationships/update';
import { getBoostIdForCredentialInstance } from '@accesslayer/credential/relationships/read';
import { getAdminRole } from '@accesslayer/role/read';

const processPermissionsClaimHooks = async (
Expand Down Expand Up @@ -128,10 +129,10 @@ export const processClaimHooks = async (
]);

try {
const vc = JSON.parse(credential.credential);
const boostId = await getBoostIdForCredentialInstance(credential);

if (vc.boostId) await clearDidWebCacheForChildProfileManagers(vc.boostId);
if (boostId) await clearDidWebCacheForChildProfileManagers(boostId);
} catch (error) {
console.error('Invalid credential JSON?', error);
console.error('Could not clear did:web cache for accepted boost credential', error);
}
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { vi } from 'vitest';
import { getClient, getUser } from './helpers/getClient';
import { testVc, sendCredential } from './helpers/send';
import { testVc, sendBoost, sendCredential, testUnsignedBoost } from './helpers/send';
import { Profile, Credential } from '@models';
import * as Notifications from '@helpers/notifications.helpers';
import { addNotificationToQueueSpy } from './helpers/spies';
import {
getDidDocForProfile,
getDidDocForProfileManager,
setDidDocForProfile,
setDidDocForProfileManager,
} from '@cache/did-docs';

const noAuthClient = getClient();
let userA: Awaited<ReturnType<typeof getUser>>;
Expand Down Expand Up @@ -173,6 +179,47 @@ describe('Credentials', () => {
message: expect.stringContaining('already been received'),
});
});

it('should clear did:web cache for managed profiles when accepting a boost that grants canManageChildrenProfiles', async () => {
const boostUri = await userA.clients.fullAuth.boost.createBoost({
credential: testUnsignedBoost,
claimPermissions: { canManageChildrenProfiles: true },
});

const managerDid =
await userA.clients.fullAuth.profileManager.createChildProfileManager({
parentUri: boostUri,
profile: {},
});

const managerId = managerDid.split(':')[4]!;

const managerClient = getClient({ did: managerDid, isChallengeValid: true });

const managedProfileId = 'managed-profile';

await managerClient.profileManager.createManagedProfile({
profileId: managedProfileId,
});

await setDidDocForProfileManager(managerId, { id: managerDid } as any);
await setDidDocForProfile(managedProfileId, { id: managedProfileId } as any);

expect(await getDidDocForProfileManager(managerId)).toBeTruthy();
expect(await getDidDocForProfile(managedProfileId)).toBeTruthy();

const credentialUri = await sendBoost(
{ profileId: 'usera', user: userA },
{ profileId: 'userb', user: userB },
boostUri,
false
);

await userB.clients.fullAuth.credential.acceptCredential({ uri: credentialUri });

expect(await getDidDocForProfileManager(managerId)).toBeFalsy();
expect(await getDidDocForProfile(managedProfileId)).toBeFalsy();
});
});

describe('receivedCredentials', () => {
Expand Down
Loading