Skip to content

Commit 64b54ee

Browse files
authored
Merge pull request #28 from constructive-io/feat/principals-ui
feat(desktop): principals in the app — create one, see its scope, mint keys as it
2 parents 8dccfc3 + e7431c2 commit 64b54ee

6 files changed

Lines changed: 463 additions & 3 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import type { PrincipalRecord } from '../src/shared/api';
4+
import { principalReach } from '../src/shared/principal';
5+
6+
const principal = (overrides: Partial<PrincipalRecord> = {}): PrincipalRecord => ({
7+
principalId: 'principal-1',
8+
name: 'ci-deploy',
9+
ownerId: 'user-1',
10+
isReadOnly: false,
11+
bypassStepUp: false,
12+
useAdminOwner: true,
13+
entityIds: ['org-1'],
14+
scopes: [],
15+
...overrides,
16+
});
17+
18+
describe('principalReach', () => {
19+
it('says it inherits, rather than showing nothing, when no scope is overridden', () => {
20+
expect(principalReach(principal())).toBe('inherits you everywhere it is scoped');
21+
});
22+
23+
it('names the restrictions it does carry', () => {
24+
const text = principalReach(principal({ isReadOnly: true, bypassStepUp: true }));
25+
expect(text).toContain('read-only');
26+
expect(text).toContain('skips step-up');
27+
});
28+
29+
it('shows a scope mask, and says when the scope is switched off', () => {
30+
const text = principalReach(
31+
principal({
32+
scopes: [
33+
{
34+
membershipType: 2,
35+
allowedMask: '0011',
36+
isActive: false,
37+
isReadOnly: true,
38+
useAdminOwner: false,
39+
},
40+
],
41+
})
42+
);
43+
expect(text).toContain('scope 2');
44+
expect(text).toContain('disabled');
45+
expect(text).toContain('mask 0011');
46+
});
47+
48+
it('calls an absent mask inherited, because it is not an empty one', () => {
49+
const text = principalReach(
50+
principal({
51+
scopes: [
52+
{
53+
membershipType: 1,
54+
allowedMask: null,
55+
isActive: true,
56+
isReadOnly: false,
57+
useAdminOwner: true,
58+
},
59+
],
60+
})
61+
);
62+
expect(text).toContain('mask inherited');
63+
});
64+
});

apps/desktop/src/main/ipc.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as path from 'path';
1010
import {
1111
CHANNELS,
1212
CreateKeyRequest,
13+
CreatePrincipalRequest,
1314
FieldPurpose,
1415
ItemKind,
1516
SignInRequest,
@@ -242,6 +243,11 @@ export const registerIpc = (service: VaultService): void => {
242243
expiresIn: days === undefined ? undefined : { days: assertInt(days, 1, 3650) },
243244
accessLevel:
244245
request?.accessLevel === undefined ? undefined : assertString(request.accessLevel),
246+
principalId:
247+
request?.principalId === undefined ? undefined : assertString(request.principalId),
248+
orgId: request?.orgId === undefined ? undefined : assertString(request.orgId),
249+
databaseId:
250+
request?.databaseId === undefined ? undefined : assertString(request.databaseId),
245251
},
246252
proof(stepUp)
247253
);
@@ -256,6 +262,40 @@ export const registerIpc = (service: VaultService): void => {
256262
await accounts().revokeApiKey(assertString(itemId), proof(stepUp));
257263
service.scheduleSave();
258264
});
265+
handle(
266+
CHANNELS.accountsAssignKeyDatabase,
267+
async (itemId: string, databaseId: string) => {
268+
await accounts().assignKeyToDatabase(assertString(itemId), assertString(databaseId));
269+
service.scheduleSave();
270+
}
271+
);
272+
handle(CHANNELS.accountsPrincipals, (accountItemId: string) =>
273+
accounts().listPrincipals(assertString(accountItemId))
274+
);
275+
handle(
276+
CHANNELS.accountsCreatePrincipal,
277+
(accountItemId: string, request: CreatePrincipalRequest, stepUp?: StepUpProof) =>
278+
accounts().createPrincipal(
279+
assertString(accountItemId),
280+
{
281+
name: assertString(request?.name),
282+
orgId: assertString(request?.orgId),
283+
isReadOnly: Boolean(request?.isReadOnly),
284+
bypassStepUp: Boolean(request?.bypassStepUp),
285+
},
286+
proof(stepUp)
287+
)
288+
);
289+
handle(
290+
CHANNELS.accountsDeletePrincipal,
291+
async (accountItemId: string, principalId: string, stepUp?: StepUpProof) => {
292+
await accounts().deletePrincipal(
293+
assertString(accountItemId),
294+
assertString(principalId),
295+
proof(stepUp)
296+
);
297+
}
298+
);
259299
handle(CHANNELS.accountsLinkTotp, async (accountItemId: string, totpItemId: string) => {
260300
await accounts().linkTotp(assertString(accountItemId), assertString(totpItemId));
261301
service.scheduleSave();

apps/desktop/src/preload/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ const api: DcryptApi & {
6363
invoke(CHANNELS.accountsCreateKey, accountItemId, request, stepUp),
6464
revealKey: (itemId) => invoke(CHANNELS.accountsRevealKey, itemId),
6565
revokeKey: (itemId, stepUp) => invoke(CHANNELS.accountsRevokeKey, itemId, stepUp),
66+
assignKeyToDatabase: (itemId, databaseId) =>
67+
invoke(CHANNELS.accountsAssignKeyDatabase, itemId, databaseId),
68+
principals: (accountItemId) => invoke(CHANNELS.accountsPrincipals, accountItemId),
69+
createPrincipal: (accountItemId, request, stepUp) =>
70+
invoke(CHANNELS.accountsCreatePrincipal, accountItemId, request, stepUp),
71+
deletePrincipal: (accountItemId, principalId, stepUp) =>
72+
invoke(CHANNELS.accountsDeletePrincipal, accountItemId, principalId, stepUp),
6673
linkTotp: (accountItemId, totpItemId) =>
6774
invoke(CHANNELS.accountsLinkTotp, accountItemId, totpItemId),
6875
unlinkTotp: (accountItemId) => invoke(CHANNELS.accountsUnlinkTotp, accountItemId),

0 commit comments

Comments
 (0)