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) }