carlos / contra Public

Clone
git clone https://amadan.net/carlos/contra

Plain git — no account needed to clone.

Download

Download this file

1//go:build browser
2
3package contratest
4
5import (
6 "strings"
7 "testing"
8
9 "github.com/carlosframework/rastrillo/harness"
10 "github.com/chromedp/chromedp"
11)
12
13// Two accounts sharing a vault, in a real engine.
14//
15// The Go and node suites each prove a half. This is the only test where one
16// account's browser seals a vault keyset and ANOTHER account's browser opens
17// it, through the real API, with the real ECDH, and reads a real item out of
18// it. It is the walk a person actually does.
19//
20// One browser profile, two accounts. Only the SESSION changes between them —
21// localStorage is left alone, because the Account Key cache is keyed by
22// account id (`contra/v1/account-key/<id>`) and the two coexist. That is not
23// a shortcut around the test; it is what a shared family machine looks like.
24
25func signOut(t *testing.T, rig *harness.Rig) {
26 t.Helper()
27 rig.Run(
28 chromedp.Navigate(rig.Origin+"/vault"),
29 chromedp.Click(`form[action="/auth/signout"] button`, chromedp.ByQuery),
30 chromedp.WaitVisible(`body`, chromedp.ByQuery),
31 )
32}
33
34func signIn(t *testing.T, rig *harness.Rig, email, passphrase string) {
35 t.Helper()
36 rig.Run(
37 chromedp.Navigate(rig.Origin+"/signin"),
38 chromedp.SendKeys(`input[name=email]`, email, chromedp.ByQuery),
39 chromedp.SendKeys(`input[name=passphrase]`, passphrase, chromedp.ByQuery),
40 chromedp.Click(`button[type=submit]`, chromedp.ByQuery),
41 chromedp.WaitVisible(`form[action="/auth/signout"]`, chromedp.ByQuery),
42 )
43}
44
45func unlockActions(rig *harness.Rig, passphrase string) []chromedp.Action {
46 return []chromedp.Action{
47 chromedp.Navigate(rig.Origin + "/vault"),
48 chromedp.WaitVisible(`[data-unlock]`, chromedp.ByQuery),
49 chromedp.SendKeys(`[data-unlock] input[name=passphrase]`, passphrase, chromedp.ByQuery),
50 chromedp.Click(`[data-unlock] button[type=submit]`, chromedp.ByQuery),
51 chromedp.WaitVisible(`[data-item-form]`, chromedp.ByQuery),
52 }
53}
54
55func TestBrowserTwoAccountsShareAVault(t *testing.T) {
56 rig, box := newRig(t)
57
58 const ownerEmail = "share-owner@example.invalid"
59 const ownerPass = "a generated test passphrase 8f3a1c"
60 const guestEmail = "share-guest@example.invalid"
61 const guestPass = "a different generated one 4d2e9b"
62
63 // The guest signs up first, so their account id exists to be shared with.
64 signUpInBrowser(t, rig, box, guestEmail, guestPass)
65 rig.Run(unlockActions(rig, guestPass)...)
66
67 var guestID string
68 rig.Run(chromedp.Text(`[data-my-account]`, &guestID, chromedp.ByQuery))
69 guestID = strings.TrimSpace(guestID)
70 if len(guestID) != 32 {
71 t.Fatalf("the guest's account id is not 32 hex characters: %q", guestID)
72 }
73 rig.Screen("[data-sharing]", "the sharing panel, before anything is shared")
74 signOut(t, rig)
75
76 // The owner signs up, adds an item, and shares with the id the guest read
77 // off their own screen — which is the only handle there is, because there
78 // is no directory to look anybody up in.
79 signUpInBrowser(t, rig, box, ownerEmail, ownerPass)
80 rig.Run(unlockActions(rig, ownerPass)...)
81 rig.Run(
82 chromedp.SendKeys(`[data-item-form] input[name=title]`, "Jamaica Bakery", chromedp.ByQuery),
83 chromedp.SendKeys(`[data-item-form] input[name=password]`, "generated-not-real-8f3a", chromedp.ByQuery),
84 chromedp.Click(`[data-item-form] [data-save]`, chromedp.ByQuery),
85 chromedp.WaitVisible(`[data-items] li`, chromedp.ByQuery),
86 chromedp.SendKeys(`[data-share-form] input[name=account_id]`, guestID, chromedp.ByQuery),
87 chromedp.Click(`[data-share-form] button[type=submit]`, chromedp.ByQuery),
88 chromedp.Poll(
89 `document.querySelector('[data-members]').textContent.includes('`+guestID+`')`,
90 nil),
91 )
92 rig.Screen("[data-sharing]", "shared with one member")
93 signOut(t, rig)
94
95 // The guest opens the owner's item. This is the assertion the whole
96 // feature exists for: sealed by one account's browser under one set of
97 // keys, opened by another under theirs.
98 signIn(t, rig, guestEmail, guestPass)
99 rig.Run(unlockActions(rig, guestPass)...)
100
101 // The guest has two vaults now — their own, and the one shared with them
102 // — so the picker has to be used. Their own sorts first.
103 rig.Run(
104 chromedp.WaitVisible(`[data-vault-picker]`, chromedp.ByQuery),
105 chromedp.SetValue(`[data-vault-picker]`, "1", chromedp.ByQuery),
106 chromedp.Evaluate(`document.querySelector('[data-vault-picker]')`+
107 `.dispatchEvent(new Event('change'))`, nil),
108 chromedp.Poll(
109 `document.querySelector('[data-items]').textContent.includes('Jamaica')`,
110 nil),
111 )
112
113 var listed string
114 rig.Run(chromedp.Text(`[data-items]`, &listed, chromedp.ByQuery))
115 if !strings.Contains(listed, "Jamaica Bakery") {
116 t.Fatalf("the member cannot see the shared item: %q", listed)
117 }
118 rig.Screen("[data-vault]", "a vault opened by the account it was shared with")
119
120 // And a member is told they cannot change who else can see it, rather
121 // than being shown a control that fails.
122 var panel string
123 rig.Run(chromedp.Text(`[data-sharing]`, &panel, chromedp.ByQuery))
124 if !strings.Contains(panel, "shared with you by somebody else") {
125 t.Errorf("a member is not told whose vault this is: %q", panel)
126 }
127
128 // The owner removes them, which rotates the vault, and the member loses
129 // it. Nothing already downloaded is un-downloaded — which is exactly why
130 // the screen says so before the button.
131 signOut(t, rig)
132 signIn(t, rig, ownerEmail, ownerPass)
133 rig.Run(unlockActions(rig, ownerPass)...)
134 rig.Run(
135 chromedp.Click(`[data-understood]`, chromedp.ByQuery),
136 chromedp.Click(`[data-members] [data-remove]`, chromedp.ByQuery),
137 chromedp.Poll(
138 `document.querySelector('[data-share-status]').textContent.includes('new key')`,
139 nil),
140 )
141 rig.Screen("[data-sharing]", "after removing a member")
142
143 signOut(t, rig)
144 signIn(t, rig, guestEmail, guestPass)
145 rig.Run(
146 chromedp.Navigate(rig.Origin+"/vault"),
147 chromedp.WaitVisible(`[data-unlock]`, chromedp.ByQuery),
148 chromedp.SendKeys(`[data-unlock] input[name=passphrase]`, guestPass, chromedp.ByQuery),
149 chromedp.Click(`[data-unlock] button[type=submit]`, chromedp.ByQuery),
150 chromedp.WaitVisible(`[data-item-form]`, chromedp.ByQuery),
151 )
152 rig.Run(chromedp.Text(`[data-items]`, &listed, chromedp.ByQuery))
153 if strings.Contains(listed, "Jamaica Bakery") {
154 t.Error("a removed member can still read the vault")
155 }
156}
157