From d6deece5462de57135b8655a98fe9cec4d099298 Mon Sep 17 00:00:00 2001
From: Gabriel Handford
Date: Wed, 29 Jul 2020 20:47:21 -0700
Subject: [PATCH] user: Search by kid (reverse lookups), expired max-age
---
convert.go | 20 +++++++++++
convert_test.go | 20 +++++++++++
id.go | 19 +++++++----
testdata/kid.spew | 10 +++---
testdata/kid2.spew | 2 +-
testdata/user.spew | 10 +++---
user/search.go | 82 ++++++++++++++++++++++-----------------------
user/search_test.go | 38 +++++++++++----------
user/store.go | 44 +++++++++++++++++++++++-
9 files changed, 167 insertions(+), 78 deletions(-)
create mode 100644 convert.go
create mode 100644 convert_test.go
diff --git a/convert.go b/convert.go
new file mode 100644
index 0000000..9b6a145
--- /dev/null
+++ b/convert.go
@@ -0,0 +1,20 @@
+package keys
+
+import "github.com/pkg/errors"
+
+// Convert tries to convert a Key to another key type.
+// This currently only converts a EdX25519Public key to a X25519Public key.
+func Convert(key Key, to KeyType) (Key, error) {
+ from := key.Type()
+ switch from {
+ case EdX25519Public:
+ if to == X25519Public {
+ pk, err := NewEdX25519PublicKeyFromID(key.ID())
+ if err != nil {
+ return nil, err
+ }
+ return pk.X25519PublicKey(), nil
+ }
+ }
+ return nil, errors.Errorf("failed to convert %s to %s", from, to)
+}
diff --git a/convert_test.go b/convert_test.go
new file mode 100644
index 0000000..02dacbd
--- /dev/null
+++ b/convert_test.go
@@ -0,0 +1,20 @@
+package keys_test
+
+import (
+ "testing"
+
+ "github.com/keys-pub/keys"
+ "github.com/stretchr/testify/require"
+)
+
+func TestConvert(t *testing.T) {
+ alice := keys.NewEdX25519KeyFromSeed(testSeed(0x01))
+
+ bpk, err := keys.Convert(alice.ID(), keys.X25519Public)
+ require.NoError(t, err)
+ require.Equal(t, keys.ID("kbx1rvd43h2sag2tvrdp0duse5p82nvhpjd6hpjwhv7q7vqklega8atshec5ws"), bpk.ID())
+
+ bpk, err = keys.Convert(alice.PublicKey(), keys.X25519Public)
+ require.NoError(t, err)
+ require.Equal(t, keys.ID("kbx1rvd43h2sag2tvrdp0duse5p82nvhpjd6hpjwhv7q7vqklega8atshec5ws"), bpk.ID())
+}
diff --git a/id.go b/id.go
index b584bbf..f56bba8 100644
--- a/id.go
+++ b/id.go
@@ -116,19 +116,24 @@ func (i ID) IsX25519() bool {
return hrp == x25519KeyHRP
}
-// PublicKeyType returns public key type that ID represents or empty string if unknown.
-func (i ID) PublicKeyType() KeyType {
- hrp, _, err := i.Decode()
- if err != nil {
- return ""
- }
+func hrpToKeyType(hrp string) KeyType {
switch hrp {
case edx25519KeyHRP:
return EdX25519Public
case x25519KeyHRP:
return X25519Public
+ default:
+ return ""
+ }
+}
+
+// PublicKeyType returns public key type that ID represents or empty string if unknown.
+func (i ID) PublicKeyType() KeyType {
+ hrp, _, err := i.Decode()
+ if err != nil {
+ return ""
}
- return ""
+ return hrpToKeyType(hrp)
}
// ID for Keys interface.
diff --git a/testdata/kid.spew b/testdata/kid.spew
index 9044965..779df3f 100644
--- a/testdata/kid.spew
+++ b/testdata/kid.spew
@@ -1,7 +1,7 @@
/kid/kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077 {"kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"}
-/kid/kex1a4yj333g68pvd6hfqvufqkv4vy54jfe6t33ljd3kc9rpfty8xlgs2u3qxr {"kid":"kex1a4yj333g68pvd6hfqvufqkv4vy54jfe6t33ljd3kc9rpfty8xlgs2u3qxr","result":{"status":"ok","ts":1234567890043,"user":{"k":"kex1a4yj333g68pvd6hfqvufqkv4vy54jfe6t33ljd3kc9rpfty8xlgs2u3qxr","n":"alice","sq":1,"sr":"twitter","u":"https://twitter.com/alice/status/1"},"vts":1234567890043}}
+/kid/kex1a4yj333g68pvd6hfqvufqkv4vy54jfe6t33ljd3kc9rpfty8xlgs2u3qxr {"kid":"kex1a4yj333g68pvd6hfqvufqkv4vy54jfe6t33ljd3kc9rpfty8xlgs2u3qxr","result":{"status":"ok","ts":1234567890051,"user":{"k":"kex1a4yj333g68pvd6hfqvufqkv4vy54jfe6t33ljd3kc9rpfty8xlgs2u3qxr","n":"alice","sq":1,"sr":"twitter","u":"https://twitter.com/alice/status/1"},"vts":1234567890051}}
/kid/kex1gwnjuu2yq9mzmantdrpxm77ly6p24myly36wefrp8epy5ra6l57qj97xgz {"kid":"kex1gwnjuu2yq9mzmantdrpxm77ly6p24myly36wefrp8epy5ra6l57qj97xgz","result":{"status":"ok","ts":1234567890005,"user":{"k":"kex1gwnjuu2yq9mzmantdrpxm77ly6p24myly36wefrp8epy5ra6l57qj97xgz","n":"name10","sq":1,"sr":"github","u":"https://gist.github.com/name10/1"},"vts":1234567890005}}
-/kid/kex1jx3g5zm58q2e8fxeg62hjgyfy6hu3tvzezpekajyxkdeaw56fvaq46atcz {"kid":"kex1jx3g5zm58q2e8fxeg62hjgyfy6hu3tvzezpekajyxkdeaw56fvaq46atcz","result":{"status":"ok","ts":1234567890020,"user":{"k":"kex1jx3g5zm58q2e8fxeg62hjgyfy6hu3tvzezpekajyxkdeaw56fvaq46atcz","n":"name13","sq":1,"sr":"github","u":"https://gist.github.com/name13/1"},"vts":1234567890020}}
-/kid/kex1p0h0t20x08n28cf5lcncx7llxtrukh6agn4qn09su4pt444ycrxq2x9f8c {"kid":"kex1p0h0t20x08n28cf5lcncx7llxtrukh6agn4qn09su4pt444ycrxq2x9f8c","result":{"status":"ok","ts":1234567890025,"user":{"k":"kex1p0h0t20x08n28cf5lcncx7llxtrukh6agn4qn09su4pt444ycrxq2x9f8c","n":"name14","sq":1,"sr":"github","u":"https://gist.github.com/name14/1"},"vts":1234567890025}}
-/kid/kex1pdgn4kd5jfqptjsfqtks0yzy6wk9m0kzxphsd9yvzrdgadhrnuksyklezm {"kid":"kex1pdgn4kd5jfqptjsfqtks0yzy6wk9m0kzxphsd9yvzrdgadhrnuksyklezm","result":{"status":"ok","ts":1234567890015,"user":{"k":"kex1pdgn4kd5jfqptjsfqtks0yzy6wk9m0kzxphsd9yvzrdgadhrnuksyklezm","n":"name12","sq":1,"sr":"github","u":"https://gist.github.com/name12/1"},"vts":1234567890015}}
-/kid/kex1v6l8uvev0fznxv4an5987lds2h6utmc6q6k6vmvckw0mdqgvguaq4850kc {"kid":"kex1v6l8uvev0fznxv4an5987lds2h6utmc6q6k6vmvckw0mdqgvguaq4850kc","result":{"status":"ok","ts":1234567890010,"user":{"k":"kex1v6l8uvev0fznxv4an5987lds2h6utmc6q6k6vmvckw0mdqgvguaq4850kc","n":"name11","sq":1,"sr":"github","u":"https://gist.github.com/name11/1"},"vts":1234567890010}}
+/kid/kex1jx3g5zm58q2e8fxeg62hjgyfy6hu3tvzezpekajyxkdeaw56fvaq46atcz {"kid":"kex1jx3g5zm58q2e8fxeg62hjgyfy6hu3tvzezpekajyxkdeaw56fvaq46atcz","result":{"status":"ok","ts":1234567890023,"user":{"k":"kex1jx3g5zm58q2e8fxeg62hjgyfy6hu3tvzezpekajyxkdeaw56fvaq46atcz","n":"name13","sq":1,"sr":"github","u":"https://gist.github.com/name13/1"},"vts":1234567890023}}
+/kid/kex1p0h0t20x08n28cf5lcncx7llxtrukh6agn4qn09su4pt444ycrxq2x9f8c {"kid":"kex1p0h0t20x08n28cf5lcncx7llxtrukh6agn4qn09su4pt444ycrxq2x9f8c","result":{"status":"ok","ts":1234567890029,"user":{"k":"kex1p0h0t20x08n28cf5lcncx7llxtrukh6agn4qn09su4pt444ycrxq2x9f8c","n":"name14","sq":1,"sr":"github","u":"https://gist.github.com/name14/1"},"vts":1234567890029}}
+/kid/kex1pdgn4kd5jfqptjsfqtks0yzy6wk9m0kzxphsd9yvzrdgadhrnuksyklezm {"kid":"kex1pdgn4kd5jfqptjsfqtks0yzy6wk9m0kzxphsd9yvzrdgadhrnuksyklezm","result":{"status":"ok","ts":1234567890017,"user":{"k":"kex1pdgn4kd5jfqptjsfqtks0yzy6wk9m0kzxphsd9yvzrdgadhrnuksyklezm","n":"name12","sq":1,"sr":"github","u":"https://gist.github.com/name12/1"},"vts":1234567890017}}
+/kid/kex1v6l8uvev0fznxv4an5987lds2h6utmc6q6k6vmvckw0mdqgvguaq4850kc {"kid":"kex1v6l8uvev0fznxv4an5987lds2h6utmc6q6k6vmvckw0mdqgvguaq4850kc","result":{"status":"ok","ts":1234567890011,"user":{"k":"kex1v6l8uvev0fznxv4an5987lds2h6utmc6q6k6vmvckw0mdqgvguaq4850kc","n":"name11","sq":1,"sr":"github","u":"https://gist.github.com/name11/1"},"vts":1234567890011}}
diff --git a/testdata/kid2.spew b/testdata/kid2.spew
index d43b786..e9bc3d6 100644
--- a/testdata/kid2.spew
+++ b/testdata/kid2.spew
@@ -1 +1 @@
-/kid/kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077 {"kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","result":{"err":"http error 404","status":"resource-not-found","ts":1234567890009,"user":{"k":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","n":"alice","sq":1,"sr":"github","u":"https://gist.github.com/alice/1"},"vts":1234567890003}}
+/kid/kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077 {"kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","result":{"err":"http error 404","status":"resource-not-found","ts":1234567890011,"user":{"k":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","n":"alice","sq":1,"sr":"github","u":"https://gist.github.com/alice/1"},"vts":1234567890003}}
diff --git a/testdata/user.spew b/testdata/user.spew
index 6d12715..9b2c597 100644
--- a/testdata/user.spew
+++ b/testdata/user.spew
@@ -1,6 +1,6 @@
-/user/alice@twitter {"kid":"kex1a4yj333g68pvd6hfqvufqkv4vy54jfe6t33ljd3kc9rpfty8xlgs2u3qxr","result":{"status":"ok","ts":1234567890043,"user":{"k":"kex1a4yj333g68pvd6hfqvufqkv4vy54jfe6t33ljd3kc9rpfty8xlgs2u3qxr","n":"alice","sq":1,"sr":"twitter","u":"https://twitter.com/alice/status/1"},"vts":1234567890043}}
+/user/alice@twitter {"kid":"kex1a4yj333g68pvd6hfqvufqkv4vy54jfe6t33ljd3kc9rpfty8xlgs2u3qxr","result":{"status":"ok","ts":1234567890051,"user":{"k":"kex1a4yj333g68pvd6hfqvufqkv4vy54jfe6t33ljd3kc9rpfty8xlgs2u3qxr","n":"alice","sq":1,"sr":"twitter","u":"https://twitter.com/alice/status/1"},"vts":1234567890051}}
/user/name10@github {"kid":"kex1gwnjuu2yq9mzmantdrpxm77ly6p24myly36wefrp8epy5ra6l57qj97xgz","result":{"status":"ok","ts":1234567890005,"user":{"k":"kex1gwnjuu2yq9mzmantdrpxm77ly6p24myly36wefrp8epy5ra6l57qj97xgz","n":"name10","sq":1,"sr":"github","u":"https://gist.github.com/name10/1"},"vts":1234567890005}}
-/user/name11@github {"kid":"kex1v6l8uvev0fznxv4an5987lds2h6utmc6q6k6vmvckw0mdqgvguaq4850kc","result":{"status":"ok","ts":1234567890010,"user":{"k":"kex1v6l8uvev0fznxv4an5987lds2h6utmc6q6k6vmvckw0mdqgvguaq4850kc","n":"name11","sq":1,"sr":"github","u":"https://gist.github.com/name11/1"},"vts":1234567890010}}
-/user/name12@github {"kid":"kex1pdgn4kd5jfqptjsfqtks0yzy6wk9m0kzxphsd9yvzrdgadhrnuksyklezm","result":{"status":"ok","ts":1234567890015,"user":{"k":"kex1pdgn4kd5jfqptjsfqtks0yzy6wk9m0kzxphsd9yvzrdgadhrnuksyklezm","n":"name12","sq":1,"sr":"github","u":"https://gist.github.com/name12/1"},"vts":1234567890015}}
-/user/name13@github {"kid":"kex1jx3g5zm58q2e8fxeg62hjgyfy6hu3tvzezpekajyxkdeaw56fvaq46atcz","result":{"status":"ok","ts":1234567890020,"user":{"k":"kex1jx3g5zm58q2e8fxeg62hjgyfy6hu3tvzezpekajyxkdeaw56fvaq46atcz","n":"name13","sq":1,"sr":"github","u":"https://gist.github.com/name13/1"},"vts":1234567890020}}
-/user/name14@github {"kid":"kex1p0h0t20x08n28cf5lcncx7llxtrukh6agn4qn09su4pt444ycrxq2x9f8c","result":{"status":"ok","ts":1234567890025,"user":{"k":"kex1p0h0t20x08n28cf5lcncx7llxtrukh6agn4qn09su4pt444ycrxq2x9f8c","n":"name14","sq":1,"sr":"github","u":"https://gist.github.com/name14/1"},"vts":1234567890025}}
+/user/name11@github {"kid":"kex1v6l8uvev0fznxv4an5987lds2h6utmc6q6k6vmvckw0mdqgvguaq4850kc","result":{"status":"ok","ts":1234567890011,"user":{"k":"kex1v6l8uvev0fznxv4an5987lds2h6utmc6q6k6vmvckw0mdqgvguaq4850kc","n":"name11","sq":1,"sr":"github","u":"https://gist.github.com/name11/1"},"vts":1234567890011}}
+/user/name12@github {"kid":"kex1pdgn4kd5jfqptjsfqtks0yzy6wk9m0kzxphsd9yvzrdgadhrnuksyklezm","result":{"status":"ok","ts":1234567890017,"user":{"k":"kex1pdgn4kd5jfqptjsfqtks0yzy6wk9m0kzxphsd9yvzrdgadhrnuksyklezm","n":"name12","sq":1,"sr":"github","u":"https://gist.github.com/name12/1"},"vts":1234567890017}}
+/user/name13@github {"kid":"kex1jx3g5zm58q2e8fxeg62hjgyfy6hu3tvzezpekajyxkdeaw56fvaq46atcz","result":{"status":"ok","ts":1234567890023,"user":{"k":"kex1jx3g5zm58q2e8fxeg62hjgyfy6hu3tvzezpekajyxkdeaw56fvaq46atcz","n":"name13","sq":1,"sr":"github","u":"https://gist.github.com/name13/1"},"vts":1234567890023}}
+/user/name14@github {"kid":"kex1p0h0t20x08n28cf5lcncx7llxtrukh6agn4qn09su4pt444ycrxq2x9f8c","result":{"status":"ok","ts":1234567890029,"user":{"k":"kex1p0h0t20x08n28cf5lcncx7llxtrukh6agn4qn09su4pt444ycrxq2x9f8c","n":"name14","sq":1,"sr":"github","u":"https://gist.github.com/name14/1"},"vts":1234567890029}}
diff --git a/user/search.go b/user/search.go
index 720219e..8bdf39e 100644
--- a/user/search.go
+++ b/user/search.go
@@ -3,7 +3,6 @@ package user
import (
"context"
"encoding/json"
- "strings"
"github.com/keys-pub/keys"
"github.com/keys-pub/keys/docs"
@@ -59,62 +58,61 @@ func (u *Store) searchUsers(ctx context.Context, query string, limit int) ([]*Se
return results, nil
}
-func (u *Store) searchKIDs(ctx context.Context, query string, limit int) ([]*SearchResult, error) {
- logger.Infof("Searching kid %q", query)
- iter, err := u.ds.DocumentIterator(ctx, indexKID, docs.Prefix(query))
- if err != nil {
- return nil, err
+// Search for users.
+func (u *Store) Search(ctx context.Context, req *SearchRequest) ([]*SearchResult, error) {
+ logger.Infof("Search users, query=%q, limit=%d", req.Query, req.Limit)
+ limit := req.Limit
+ if limit == 0 {
+ limit = 100
}
- results := make([]*SearchResult, 0, limit)
- for {
- doc, err := iter.Next()
+ // Check if query is for key identifier
+ kid, err := keys.ParseID(req.Query)
+ if err == nil {
+ res, err := u.findKID(ctx, kid)
if err != nil {
return nil, err
}
- if doc == nil {
- break
- }
- if len(results) >= limit {
- break
- }
- var keyDoc keyDocument
- if err := json.Unmarshal(doc.Data, &keyDoc); err != nil {
- return nil, err
+ if res != nil {
+ return []*SearchResult{res}, nil
}
+ }
- results = append(results, &SearchResult{
- KID: keyDoc.KID,
- Result: keyDoc.Result,
- Field: "kid",
- })
+ res, err := u.searchUsers(ctx, req.Query, limit)
+ if err != nil {
+ return nil, err
}
- iter.Release()
- logger.Infof("Found %d user results", len(results))
- return results, nil
+ return res, nil
}
-// Search for users.
-func (u *Store) Search(ctx context.Context, req *SearchRequest) ([]*SearchResult, error) {
- logger.Infof("Search users, query=%q, limit=%d", req.Query, req.Limit)
- limit := req.Limit
- if limit == 0 {
- limit = 100
+func (u *Store) findKID(ctx context.Context, kid keys.ID) (*SearchResult, error) {
+ res, err := u.Get(ctx, kid)
+ if err != nil {
+ return nil, err
+ }
+ if res != nil {
+ return &SearchResult{
+ KID: kid,
+ Result: res,
+ Field: "kid",
+ }, nil
}
- res, err := u.searchUsers(ctx, req.Query, limit)
+ rkid, err := u.lookupRelated(ctx, kid)
if err != nil {
return nil, err
}
- // Search kid's if prefix is kex1
- if strings.HasPrefix(req.Query, "kex1") {
- resKIDs, err := u.searchKIDs(ctx, req.Query, limit-len(res))
- if err != nil {
- return nil, err
- }
- res = append(res, resKIDs...)
+ res, err = u.Get(ctx, rkid)
+ if err != nil {
+ return nil, err
}
-
- return res, nil
+ if res != nil {
+ return &SearchResult{
+ KID: kid,
+ Result: res,
+ Field: "kid",
+ }, nil
+ }
+ return nil, nil
}
diff --git a/user/search_test.go b/user/search_test.go
index 6af9de7..4e060d6 100644
--- a/user/search_test.go
+++ b/user/search_test.go
@@ -61,8 +61,8 @@ func TestSearchUsers(t *testing.T) {
require.Equal(t, "github", results[0].Result.User.Service)
require.Equal(t, "https://gist.github.com/alice/1", results[0].Result.User.URL)
require.Equal(t, 1, results[0].Result.User.Seq)
- require.Equal(t, int64(1234567890028), results[0].Result.VerifiedAt)
- require.Equal(t, int64(1234567890028), results[0].Result.Timestamp)
+ require.Equal(t, int64(1234567890033), results[0].Result.VerifiedAt)
+ require.Equal(t, int64(1234567890033), results[0].Result.Timestamp)
// Search "kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"
results, err = ust.Search(ctx, &user.SearchRequest{Query: "kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"})
@@ -71,8 +71,8 @@ func TestSearchUsers(t *testing.T) {
require.NotNil(t, results[0].Result)
require.Equal(t, alice.ID(), results[0].Result.User.KID)
- // Search "kex132yw8h"
- results, err = ust.Search(ctx, &user.SearchRequest{Query: "kex132yw8h"})
+ // Search "kbx1rvd43h2sag2tvrdp0duse5p82nvhpjd6hpjwhv7q7vqklega8atshec5ws"
+ results, err = ust.Search(ctx, &user.SearchRequest{Query: "kbx1rvd43h2sag2tvrdp0duse5p82nvhpjd6hpjwhv7q7vqklega8atshec5ws"})
require.NoError(t, err)
require.Equal(t, 1, len(results))
require.NotNil(t, results[0].Result)
@@ -365,7 +365,7 @@ func TestSearchUsersRequestErrors(t *testing.T) {
require.NotNil(t, results[0].Result)
require.Equal(t, keys.ID("kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"), results[0].Result.User.KID)
require.Equal(t, user.StatusConnFailure, results[0].Result.Status)
- require.Equal(t, int64(1234567890006), results[0].Result.Timestamp)
+ require.Equal(t, int64(1234567890007), results[0].Result.Timestamp)
require.Equal(t, int64(1234567890003), results[0].Result.VerifiedAt)
// List by status
@@ -408,6 +408,7 @@ func TestSearchUsersRequestErrors(t *testing.T) {
}
func TestExpired(t *testing.T) {
+ var err error
ds := docs.NewMem()
scs := keys.NewSigchainStore(ds)
@@ -416,20 +417,12 @@ func TestExpired(t *testing.T) {
ust := testStore(t, ds, scs, req, clock)
ctx := context.TODO()
- ids, err := ust.Expired(ctx, time.Hour)
- require.NoError(t, err)
- require.Equal(t, 0, len(ids))
-
- alice := keys.NewEdX25519KeyFromSeed(testSeed(0x01))
-
- _, err = ust.Update(ctx, alice.ID())
- require.NoError(t, err)
-
- ids, err = ust.Expired(ctx, time.Hour)
+ ids, err := ust.Expired(ctx, time.Hour, time.Hour*24*60)
require.NoError(t, err)
require.Equal(t, 0, len(ids))
// Add alice@github
+ alice := keys.NewEdX25519KeyFromSeed(testSeed(0x01))
testSaveUser(t, ust, scs, alice, "alice", "github", clock, req)
_, err = ust.Update(ctx, alice.ID())
@@ -445,13 +438,24 @@ func TestExpired(t *testing.T) {
require.Equal(t, int64(1234567890002), results[0].Result.VerifiedAt)
require.Equal(t, int64(1234567890002), results[0].Result.Timestamp)
- ids, err = ust.Expired(ctx, time.Hour)
+ ids, err = ust.Expired(ctx, time.Hour, time.Hour*24*60)
require.NoError(t, err)
require.Equal(t, 0, len(ids))
- ids, err = ust.Expired(ctx, time.Millisecond)
+ // Test expired
+ clock.Add(time.Hour * 2)
+
+ ids, err = ust.Expired(ctx, time.Hour, time.Hour*24*60)
require.NoError(t, err)
+ require.Equal(t, 1, len(ids))
require.Equal(t, []keys.ID{alice.ID()}, ids)
+
+ // Test max age
+ clock.Add(time.Hour * 24 * 30)
+
+ ids, err = ust.Expired(ctx, time.Hour, time.Hour*24*7)
+ require.NoError(t, err)
+ require.Equal(t, 0, len(ids))
}
func testSaveUser(t *testing.T, ust *user.Store, scs keys.SigchainStore, key *keys.EdX25519Key, name string, service string, clock tsutil.Clock, mock *request.MockRequestor) *keys.Statement {
diff --git a/user/store.go b/user/store.go
index fb3e862..fbfb995 100644
--- a/user/store.go
+++ b/user/store.go
@@ -217,9 +217,17 @@ func (u *Store) removeUser(ctx context.Context, user *User) error {
return nil
}
+// indexKID is collection for key identifiers.
const indexKID = "kid"
+
+// indexUser is collection for user@service.
const indexUser = "user"
+// indexRKL is collection for reverse key lookups.
+const indexRKL = "rkl"
+
+// TODO: Remove document from indexes if failed for a long time?
+
func (u *Store) index(ctx context.Context, keyDoc *keyDocument) error {
// Remove existing if different
existing, err := u.get(ctx, indexKID, keyDoc.KID.String())
@@ -242,12 +250,24 @@ func (u *Store) index(ctx context.Context, keyDoc *keyDocument) error {
}
logger.Debugf("Data to index: %s", string(data))
+ // Index for kid
kidPath := docs.Path(indexKID, keyDoc.KID.String())
logger.Infof("Indexing kid %s", kidPath)
if err := u.ds.Set(ctx, kidPath, data); err != nil {
return err
}
+ // Index for rkl
+ rk, err := keys.Convert(keyDoc.KID, keys.X25519Public)
+ if err != nil {
+ return err
+ }
+ rklPath := docs.Path(indexRKL, rk.ID())
+ if err := u.ds.Set(ctx, rklPath, []byte(keyDoc.KID.String())); err != nil {
+ return err
+ }
+
+ // Index for user
if keyDoc.Result != nil {
index := false
if keyDoc.Result.VerifiedAt == 0 {
@@ -281,6 +301,22 @@ func indexName(user *User) string {
return fmt.Sprintf("%s@%s", user.Name, user.Service)
}
+func (u *Store) lookupRelated(ctx context.Context, kid keys.ID) (keys.ID, error) {
+ path := docs.Path(indexRKL, kid.String())
+ doc, err := u.ds.Get(ctx, path)
+ if err != nil {
+ return "", err
+ }
+ if doc == nil {
+ return "", nil
+ }
+ rkid, err := keys.ParseID(string(doc.Data))
+ if err != nil {
+ return "", err
+ }
+ return rkid, nil
+}
+
// Status returns KIDs that match a status.
func (u *Store) Status(ctx context.Context, st Status) ([]keys.ID, error) {
iter, err := u.ds.DocumentIterator(context.TODO(), indexKID)
@@ -312,7 +348,7 @@ func (u *Store) Status(ctx context.Context, st Status) ([]keys.ID, error) {
}
// Expired returns KIDs that haven't been checked in a duration.
-func (u *Store) Expired(ctx context.Context, dt time.Duration) ([]keys.ID, error) {
+func (u *Store) Expired(ctx context.Context, dt time.Duration, maxAge time.Duration) ([]keys.ID, error) {
iter, err := u.ds.DocumentIterator(context.TODO(), indexKID)
if err != nil {
return nil, err
@@ -333,6 +369,12 @@ func (u *Store) Expired(ctx context.Context, dt time.Duration) ([]keys.ID, error
if keyDoc.Result != nil {
ts := tsutil.ConvertMillis(keyDoc.Result.Timestamp)
+ // If verifiedAt age is too old skip it
+ vts := tsutil.ConvertMillis(keyDoc.Result.VerifiedAt)
+ if !vts.IsZero() && u.clock.Now().Sub(vts) > maxAge {
+ continue
+ }
+
if ts.IsZero() || u.clock.Now().Sub(ts) > dt {
kids = append(kids, keyDoc.Result.User.KID)
}