eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import "testing"
4
5// The push/APNs tables must never retain a usable session token — only its hash.
6func TestPushTokensHashedAtRest(t *testing.T) {
7 db := newTestDB(t)
8 tok := mintDeviceToken(t, db, "x")
9
10 if err := db.addSubscription(PushSub{Endpoint: "https://e/1", Token: tok, P256dh: "p", Auth: "a"}); err != nil {
11 t.Fatalf("addSubscription: %v", err)
12 }
13 if err := db.addAPNsToken(APNsToken{APNsToken: "apns-1", Token: tok}); err != nil {
14 t.Fatalf("addAPNsToken: %v", err)
15 }
16
17 check := func(table, keyCol, keyVal string) {
18 var storedTok, th string
19 if err := db.sql.QueryRow(
20 "SELECT token, COALESCE(token_hash, '') FROM "+table+" WHERE "+keyCol+" = ?", keyVal,
21 ).Scan(&storedTok, &th); err != nil {
22 t.Fatalf("%s row: %v", table, err)
23 }
24 if storedTok != "" {
25 t.Errorf("%s retains plaintext token %q, want empty", table, storedTok)
26 }
27 if th != hashToken(tok) {
28 t.Errorf("%s token_hash = %q, want hash of the token", table, th)
29 }
30 }
31 check("push_subscriptions", "endpoint", "https://e/1")
32 check("apns_tokens", "apns_token", "apns-1")
33
34 // Delete-by-raw-token still resolves via the stored hash.
35 if err := db.deleteSubscriptionsForToken(tok); err != nil {
36 t.Fatalf("deleteSubscriptionsForToken: %v", err)
37 }
38 if subs, _ := db.listSubscriptions(); len(subs) != 0 {
39 t.Fatalf("delete-by-token after hashing left %d sub(s)", len(subs))
40 }
41}
42
43// Rows created before this migration stored the token in clear; the backfill
44// hashes them and scrubs the plaintext.
45func TestBackfillScrubsPlaintextPushTokens(t *testing.T) {
46 db := newTestDB(t)
47 if _, err := db.sql.Exec(
48 "INSERT INTO push_subscriptions(endpoint, token, p256dh, auth, created_at) VALUES('https://e/old','rawtok','p','a',0)",
49 ); err != nil {
50 t.Fatalf("seed push: %v", err)
51 }
52 if _, err := db.sql.Exec(
53 "INSERT INTO apns_tokens(apns_token, token, sandbox, created_at) VALUES('apns-old','rawtok',0,0)",
54 ); err != nil {
55 t.Fatalf("seed apns: %v", err)
56 }
57
58 if err := db.backfillPushTokenHashes(); err != nil {
59 t.Fatalf("backfill: %v", err)
60 }
61
62 for _, tc := range []struct{ table, keyCol, keyVal string }{
63 {"push_subscriptions", "endpoint", "https://e/old"},
64 {"apns_tokens", "apns_token", "apns-old"},
65 } {
66 var tok, th string
67 if err := db.sql.QueryRow(
68 "SELECT token, COALESCE(token_hash, '') FROM "+tc.table+" WHERE "+tc.keyCol+" = ?", tc.keyVal,
69 ).Scan(&tok, &th); err != nil {
70 t.Fatalf("%s: %v", tc.table, err)
71 }
72 if tok != "" {
73 t.Errorf("%s: backfill left plaintext %q", tc.table, tok)
74 }
75 if th != hashToken("rawtok") {
76 t.Errorf("%s: backfill token_hash = %q, want hash of rawtok", tc.table, th)
77 }
78 }
79}
80
81// removeUser must drop the user's APNs tokens. This also exercises the token_hash
82// join that replaced a query referencing a non-existent device_tokens.token column.
83func TestRemoveUserDropsAPNsTokens(t *testing.T) {
84 db := newTestDB(t)
85 if err := db.createKeyInvite("k1", "alice", "w", testClaimHash, 0); err != nil {
86 t.Fatalf("createKeyInvite: %v", err)
87 }
88 tok, ok, err := db.claimKeyInvite("k1", "phone", "", testClaimSecret)
89 if err != nil || !ok {
90 t.Fatalf("claimKeyInvite: ok=%v err=%v", ok, err)
91 }
92 if err := db.addAPNsToken(APNsToken{APNsToken: "apns-alice", Token: tok}); err != nil {
93 t.Fatalf("addAPNsToken: %v", err)
94 }
95 if toks, _ := db.listAPNsTokens(); len(toks) != 1 {
96 t.Fatalf("precondition: want 1 apns token, got %d", len(toks))
97 }
98 if err := db.removeUser("alice"); err != nil {
99 t.Fatalf("removeUser: %v", err)
100 }
101 if toks, _ := db.listAPNsTokens(); len(toks) != 0 {
102 t.Fatalf("removeUser left %d apns token(s)", len(toks))
103 }
104}
105