fork of indigo with slightly nicer lexgen

identity SDK: match spec better w/r/t handle normalization and extraction from DID doc alsoKnownAs (#1105)

- tries harder to find a valid handle in alsoKnownAs list (doesn't stop
at the first `at://` if it was not a valid handle)
- normalizes handles when extracting from DID docs / identities
- ensures that bi-directional handle comparisons are done with
normalized handles
- ensures that caching is done with normalized version of handles
- adds some tests for all this

authored by bnewbold.net and committed by GitHub f2ad2426 5641d3c2

+1
atproto/identity/apidir/apidir.go
··· 153 153 } 154 154 155 155 func (dir *APIDirectory) ResolveHandle(ctx context.Context, handle syntax.Handle) (syntax.DID, error) { 156 + handle = handle.Normalize() 156 157 var body handleBody 157 158 u := dir.Host + "/xrpc/com.atproto.identity.resolveHandle?handle=" + handle.String() 158 159
+1
atproto/identity/base_directory.go
··· 55 55 if err != nil { 56 56 return nil, fmt.Errorf("could not verify handle/DID match: %w", err) 57 57 } 58 + // NOTE: DeclaredHandle() returns a normalized handle, and we already normalized 'h' above 58 59 if declared != h { 59 60 return nil, fmt.Errorf("%w: %s != %s", ErrHandleMismatch, declared, h) 60 61 }
+2
atproto/identity/cache_directory.go
··· 93 93 } 94 94 95 95 func (d *CacheDirectory) ResolveHandle(ctx context.Context, h syntax.Handle) (syntax.DID, error) { 96 + h = h.Normalize() 96 97 if h.IsInvalidHandle() { 97 98 return "", fmt.Errorf("can not resolve handle: %w", ErrInvalidHandle) 98 99 } ··· 251 252 if err != nil { 252 253 return nil, hit, fmt.Errorf("could not verify handle/DID mapping: %w", err) 253 254 } 255 + // NOTE: DeclaredHandle() returns a normalized handle, and we already normalized 'h' above 254 256 if declared != h { 255 257 return nil, hit, fmt.Errorf("%w: %s != %s", ErrHandleMismatch, declared, h) 256 258 }
+2
atproto/identity/handle.go
··· 177 177 var dnsErr error 178 178 var did syntax.DID 179 179 180 + handle = handle.Normalize() 181 + 180 182 if handle.IsInvalidHandle() { 181 183 return "", fmt.Errorf("can not resolve handle: %w", ErrInvalidHandle) 182 184 }
+5 -1
atproto/identity/identity.go
··· 198 198 func (i *Identity) DeclaredHandle() (syntax.Handle, error) { 199 199 for _, u := range i.AlsoKnownAs { 200 200 if strings.HasPrefix(u, "at://") && len(u) > len("at://") { 201 - return syntax.ParseHandle(u[5:]) 201 + hdl, err := syntax.ParseHandle(u[5:]) 202 + if err != nil { 203 + continue 204 + } 205 + return hdl.Normalize(), nil 202 206 } 203 207 } 204 208 return "", ErrHandleNotDeclared
+70
atproto/identity/identity_test.go
··· 1 + package identity 2 + 3 + import ( 4 + "encoding/json" 5 + "io" 6 + "os" 7 + "testing" 8 + 9 + "github.com/stretchr/testify/assert" 10 + ) 11 + 12 + // Tests parsing and normalizing handles from DID documents 13 + func TestHandleExtraction(t *testing.T) { 14 + assert := assert.New(t) 15 + f, err := os.Open("testdata/did_plc_doc.json") 16 + if err != nil { 17 + t.Fatal(err) 18 + } 19 + defer f.Close() 20 + 21 + docBytes, err := io.ReadAll(f) 22 + if err != nil { 23 + t.Fatal(err) 24 + } 25 + 26 + var doc DIDDocument 27 + err = json.Unmarshal(docBytes, &doc) 28 + assert.NoError(err) 29 + 30 + { 31 + ident := ParseIdentity(&doc) 32 + hdl, err := ident.DeclaredHandle() 33 + assert.NoError(err) 34 + assert.Equal("atproto.com", hdl.String()) 35 + } 36 + 37 + { 38 + doc.AlsoKnownAs = []string{ 39 + "at://BLAH.com", 40 + "at://other.org", 41 + } 42 + ident := ParseIdentity(&doc) 43 + hdl, err := ident.DeclaredHandle() 44 + assert.NoError(err) 45 + assert.Equal("blah.com", hdl.String()) 46 + } 47 + 48 + { 49 + doc.AlsoKnownAs = []string{ 50 + "https://http.example.com", 51 + "at://under_example_com", 52 + "at://correct.EXAMPLE.com", 53 + "at://other.example.com", 54 + } 55 + ident := ParseIdentity(&doc) 56 + hdl, err := ident.DeclaredHandle() 57 + assert.NoError(err) 58 + assert.Equal("correct.example.com", hdl.String()) 59 + } 60 + 61 + { 62 + doc.AlsoKnownAs = []string{ 63 + "https://http.example.com", 64 + } 65 + ident := ParseIdentity(&doc) 66 + _, err := ident.DeclaredHandle() 67 + assert.Error(err) 68 + assert.Equal("handle.invalid", ident.Handle.String()) 69 + } 70 + }
+1 -1
atproto/identity/mock_directory.go
··· 26 26 27 27 func (d *MockDirectory) Insert(ident Identity) { 28 28 if !ident.Handle.IsInvalidHandle() { 29 - d.Handles[ident.Handle] = ident.DID 29 + d.Handles[ident.Handle.Normalize()] = ident.DID 30 30 } 31 31 d.Identities[ident.DID] = ident 32 32 }
+5
atproto/identity/mock_directory_test.go
··· 65 65 66 66 _, err = c.ResolveDID(ctx, syntax.DID("did:plc:abc999")) 67 67 assert.ErrorIs(err, ErrDIDNotFound) 68 + 69 + // handle lookups should be case-insensitive 70 + _, err = c.ResolveHandle(ctx, syntax.Handle("handle.EXAMPLE.com")) 71 + assert.NoError(err) 72 + assert.Equal(id1.DID, did) 68 73 }
+2
atproto/identity/redisdir/redis_directory.go
··· 156 156 157 157 func (d *RedisDirectory) ResolveHandle(ctx context.Context, h syntax.Handle) (syntax.DID, error) { 158 158 start := time.Now() 159 + h = h.Normalize() 159 160 if h.IsInvalidHandle() { 160 161 return "", fmt.Errorf("can not resolve handle: %w", identity.ErrInvalidHandle) 161 162 } ··· 359 360 if err != nil { 360 361 return nil, hit, err 361 362 } 363 + // NOTE: DeclaredHandle() returns a normalized handle, and we already normalized 'h' above 362 364 if declared != h { 363 365 return nil, hit, identity.ErrHandleMismatch 364 366 }
+3
cmd/bluepages/handlers.go
··· 74 74 func (srv *Server) resolveIdentityFromHandle(c echo.Context, handle syntax.Handle) error { 75 75 ctx := c.Request().Context() 76 76 77 + handle = handle.Normalize() 78 + 77 79 did, err := srv.dir.ResolveHandle(ctx, handle) 78 80 if err != nil && errors.Is(err, identity.ErrHandleNotFound) { 79 81 return c.JSON(404, GenericError{ ··· 110 112 } 111 113 112 114 ident := identity.ParseIdentity(&doc) 115 + // NOTE: 'handle' was resolved above, and 'DeclaredHandle()' returns a normalized handle 113 116 declHandle, err := ident.DeclaredHandle() 114 117 if err != nil || declHandle != handle { 115 118 return c.JSON(400, GenericError{