forked from tangled.org/core
this repo has no description

appview: router: no-@ handles and flattened dids

Introduces two new user routing options: handles without @'s will
redirect to their @'d counterparts, and did-plc-foobar (a flattened
did) will redirect to did:plc:foobar.

These can now be used as valid Go modules.

Changed files
+179 -1
appview
+1 -1
appview/state/pull.go
···
secret, err := db.GetRegistrationKey(s.db, f.Knot)
if err != nil {
-
log.Printf("failed to get registration key: %w", err)
+
log.Printf("failed to get registration key: %v", err)
return types.MergeCheckResponse{
Error: "failed to check merge status: this knot is unregistered",
}
+64
appview/state/router.go
···
import (
"net/http"
+
"regexp"
"strings"
"github.com/go-chi/chi/v5"
···
if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") {
s.UserRouter().ServeHTTP(w, r)
} else {
+
// Check if the first path element is a valid handle without '@' or a flattened DID
+
pathParts := strings.SplitN(pat, "/", 2)
+
if len(pathParts) > 0 {
+
if isHandleNoAt(pathParts[0]) {
+
// Redirect to the same path but with '@' prefixed to the handle
+
redirectPath := "@" + pat
+
http.Redirect(w, r, "/"+redirectPath, http.StatusFound)
+
return
+
} else if isFlattenedDid(pathParts[0]) {
+
// Redirect to the unflattened DID version
+
unflattenedDid := unflattenDid(pathParts[0])
+
var redirectPath string
+
if len(pathParts) > 1 {
+
redirectPath = unflattenedDid + "/" + pathParts[1]
+
} else {
+
redirectPath = unflattenedDid
+
}
+
http.Redirect(w, r, "/"+redirectPath, http.StatusFound)
+
return
+
}
+
}
s.StandardRouter().ServeHTTP(w, r)
}
})
return router
+
}
+
+
func isHandleNoAt(s string) bool {
+
// ref: https://atproto.com/specs/handle
+
re := regexp.MustCompile(`^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`)
+
return re.MatchString(s)
+
}
+
+
func unflattenDid(s string) string {
+
if !isFlattenedDid(s) {
+
return s
+
}
+
+
parts := strings.SplitN(s[4:], "-", 2) // Skip "did-" prefix and split on first "-"
+
if len(parts) != 2 {
+
return s
+
}
+
+
return "did:" + parts[0] + ":" + parts[1]
+
}
+
+
// isFlattenedDid checks if the given string is a flattened DID.
+
// A flattened DID is a DID with the :s swapped to -s to satisfy certain
+
// application requirements, such as Go module naming conventions.
+
func isFlattenedDid(s string) bool {
+
// Check if the string starts with "did-"
+
if !strings.HasPrefix(s, "did-") {
+
return false
+
}
+
+
// Split the string to extract method and identifier
+
parts := strings.SplitN(s[4:], "-", 2) // Skip "did-" prefix and split on first "-"
+
if len(parts) != 2 {
+
return false
+
}
+
+
// Reconstruct as a standard DID format
+
// Example: "did-plc-xyz-abc" becomes "did:plc:xyz-abc"
+
reconstructed := "did:" + parts[0] + ":" + parts[1]
+
re := regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`)
+
+
return re.MatchString(reconstructed)
}
func (s *State) UserRouter() http.Handler {
+114
appview/state/router_test.go
···
+
package state
+
+
import "testing"
+
+
func TestUnflattenDid(t *testing.T) {
+
unflattenedMap := map[string]string{
+
"did-plc-abcdefghijklmnopqrstuvwxyz": "did:plc:abcdefghijklmnopqrstuvwxyz",
+
"did-plc-1234567890": "did:plc:1234567890",
+
"did-key-z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
+
"did-plc-abcdefghi-jklmnopqr-stuvwxyz": "did:plc:abcdefghi-jklmnopqr-stuvwxyz",
+
"plc-abcdefghijklmnopqrstuvwxyz": "plc-abcdefghijklmnopqrstuvwxyz",
+
"didplc-abcdefghijklmnopqrstuvwxyz": "didplc-abcdefghijklmnopqrstuvwxyz",
+
"": "",
+
"did-": "did-",
+
"did:plc:abcdefghijklmnopqrstuvwxyz": "did:plc:abcdefghijklmnopqrstuvwxyz",
+
"did-invalid$format:something": "did-invalid$format:something",
+
}
+
+
tests := []struct {
+
name string
+
input string
+
expected string
+
}{}
+
+
for _, tc := range isFlattenedDidTests {
+
tests = append(tests, struct {
+
name string
+
input string
+
expected string
+
}{
+
name: tc.name,
+
input: tc.input,
+
expected: unflattenedMap[tc.input],
+
})
+
}
+
+
for _, tc := range tests {
+
t.Run(tc.name, func(t *testing.T) {
+
result := unflattenDid(tc.input)
+
if result != tc.expected {
+
t.Errorf("unflattenDid(%q) = %q, want %q", tc.input, result, tc.expected)
+
}
+
})
+
}
+
}
+
+
var isFlattenedDidTests = []struct {
+
name string
+
input string
+
expected bool
+
}{
+
{
+
name: "valid flattened DID",
+
input: "did-plc-abcdefghijklmnopqrstuvwxyz",
+
expected: true,
+
},
+
{
+
name: "valid flattened DID with numbers",
+
input: "did-plc-1234567890",
+
expected: true,
+
},
+
{
+
name: "valid flattened DID with special characters",
+
input: "did-key-z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
+
expected: true,
+
},
+
{
+
name: "valid flattened DID with dashes",
+
input: "did-plc-abcdefghi-jklmnopqr-stuvwxyz",
+
expected: true,
+
},
+
+
{
+
name: "doesn't start with did-",
+
input: "plc-abcdefghijklmnopqrstuvwxyz",
+
expected: false,
+
},
+
{
+
name: "no hyphen after did",
+
input: "didplc-abcdefghijklmnopqrstuvwxyz",
+
expected: false,
+
},
+
{
+
name: "empty string",
+
input: "",
+
expected: false,
+
},
+
{
+
name: "only did-",
+
input: "did-",
+
expected: false,
+
},
+
{
+
name: "standard DID format, not flattened",
+
input: "did:plc:abcdefghijklmnopqrstuvwxyz",
+
expected: false,
+
},
+
{
+
name: "invalid reconstructed DID format",
+
input: "did-invalid$format:something",
+
expected: false,
+
},
+
}
+
+
func TestIsFlattenedDid(t *testing.T) {
+
for _, tc := range isFlattenedDidTests {
+
t.Run(tc.name, func(t *testing.T) {
+
result := isFlattenedDid(tc.input)
+
if result != tc.expected {
+
t.Errorf("isFlattenedDid(%q) = %v, want %v", tc.input, result, tc.expected)
+
}
+
})
+
}
+
}