+1
-1
appview/state/pull.go
+1
-1
appview/state/pull.go
···
122
122
123
123
secret, err := db.GetRegistrationKey(s.db, f.Knot)
124
124
if err != nil {
125
-
log.Printf("failed to get registration key: %w", err)
125
+
log.Printf("failed to get registration key: %v", err)
126
126
return types.MergeCheckResponse{
127
127
Error: "failed to check merge status: this knot is unregistered",
128
128
}
+64
appview/state/router.go
+64
appview/state/router.go
···
2
2
3
3
import (
4
4
"net/http"
5
+
"regexp"
5
6
"strings"
6
7
7
8
"github.com/go-chi/chi/v5"
···
15
16
if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") {
16
17
s.UserRouter().ServeHTTP(w, r)
17
18
} else {
19
+
// Check if the first path element is a valid handle without '@' or a flattened DID
20
+
pathParts := strings.SplitN(pat, "/", 2)
21
+
if len(pathParts) > 0 {
22
+
if isHandleNoAt(pathParts[0]) {
23
+
// Redirect to the same path but with '@' prefixed to the handle
24
+
redirectPath := "@" + pat
25
+
http.Redirect(w, r, "/"+redirectPath, http.StatusFound)
26
+
return
27
+
} else if isFlattenedDid(pathParts[0]) {
28
+
// Redirect to the unflattened DID version
29
+
unflattenedDid := unflattenDid(pathParts[0])
30
+
var redirectPath string
31
+
if len(pathParts) > 1 {
32
+
redirectPath = unflattenedDid + "/" + pathParts[1]
33
+
} else {
34
+
redirectPath = unflattenedDid
35
+
}
36
+
http.Redirect(w, r, "/"+redirectPath, http.StatusFound)
37
+
return
38
+
}
39
+
}
18
40
s.StandardRouter().ServeHTTP(w, r)
19
41
}
20
42
})
21
43
22
44
return router
45
+
}
46
+
47
+
func isHandleNoAt(s string) bool {
48
+
// ref: https://atproto.com/specs/handle
49
+
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])?$`)
50
+
return re.MatchString(s)
51
+
}
52
+
53
+
func unflattenDid(s string) string {
54
+
if !isFlattenedDid(s) {
55
+
return s
56
+
}
57
+
58
+
parts := strings.SplitN(s[4:], "-", 2) // Skip "did-" prefix and split on first "-"
59
+
if len(parts) != 2 {
60
+
return s
61
+
}
62
+
63
+
return "did:" + parts[0] + ":" + parts[1]
64
+
}
65
+
66
+
// isFlattenedDid checks if the given string is a flattened DID.
67
+
// A flattened DID is a DID with the :s swapped to -s to satisfy certain
68
+
// application requirements, such as Go module naming conventions.
69
+
func isFlattenedDid(s string) bool {
70
+
// Check if the string starts with "did-"
71
+
if !strings.HasPrefix(s, "did-") {
72
+
return false
73
+
}
74
+
75
+
// Split the string to extract method and identifier
76
+
parts := strings.SplitN(s[4:], "-", 2) // Skip "did-" prefix and split on first "-"
77
+
if len(parts) != 2 {
78
+
return false
79
+
}
80
+
81
+
// Reconstruct as a standard DID format
82
+
// Example: "did-plc-xyz-abc" becomes "did:plc:xyz-abc"
83
+
reconstructed := "did:" + parts[0] + ":" + parts[1]
84
+
re := regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`)
85
+
86
+
return re.MatchString(reconstructed)
23
87
}
24
88
25
89
func (s *State) UserRouter() http.Handler {
+114
appview/state/router_test.go
+114
appview/state/router_test.go
···
1
+
package state
2
+
3
+
import "testing"
4
+
5
+
func TestUnflattenDid(t *testing.T) {
6
+
unflattenedMap := map[string]string{
7
+
"did-plc-abcdefghijklmnopqrstuvwxyz": "did:plc:abcdefghijklmnopqrstuvwxyz",
8
+
"did-plc-1234567890": "did:plc:1234567890",
9
+
"did-key-z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
10
+
"did-plc-abcdefghi-jklmnopqr-stuvwxyz": "did:plc:abcdefghi-jklmnopqr-stuvwxyz",
11
+
"plc-abcdefghijklmnopqrstuvwxyz": "plc-abcdefghijklmnopqrstuvwxyz",
12
+
"didplc-abcdefghijklmnopqrstuvwxyz": "didplc-abcdefghijklmnopqrstuvwxyz",
13
+
"": "",
14
+
"did-": "did-",
15
+
"did:plc:abcdefghijklmnopqrstuvwxyz": "did:plc:abcdefghijklmnopqrstuvwxyz",
16
+
"did-invalid$format:something": "did-invalid$format:something",
17
+
}
18
+
19
+
tests := []struct {
20
+
name string
21
+
input string
22
+
expected string
23
+
}{}
24
+
25
+
for _, tc := range isFlattenedDidTests {
26
+
tests = append(tests, struct {
27
+
name string
28
+
input string
29
+
expected string
30
+
}{
31
+
name: tc.name,
32
+
input: tc.input,
33
+
expected: unflattenedMap[tc.input],
34
+
})
35
+
}
36
+
37
+
for _, tc := range tests {
38
+
t.Run(tc.name, func(t *testing.T) {
39
+
result := unflattenDid(tc.input)
40
+
if result != tc.expected {
41
+
t.Errorf("unflattenDid(%q) = %q, want %q", tc.input, result, tc.expected)
42
+
}
43
+
})
44
+
}
45
+
}
46
+
47
+
var isFlattenedDidTests = []struct {
48
+
name string
49
+
input string
50
+
expected bool
51
+
}{
52
+
{
53
+
name: "valid flattened DID",
54
+
input: "did-plc-abcdefghijklmnopqrstuvwxyz",
55
+
expected: true,
56
+
},
57
+
{
58
+
name: "valid flattened DID with numbers",
59
+
input: "did-plc-1234567890",
60
+
expected: true,
61
+
},
62
+
{
63
+
name: "valid flattened DID with special characters",
64
+
input: "did-key-z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
65
+
expected: true,
66
+
},
67
+
{
68
+
name: "valid flattened DID with dashes",
69
+
input: "did-plc-abcdefghi-jklmnopqr-stuvwxyz",
70
+
expected: true,
71
+
},
72
+
73
+
{
74
+
name: "doesn't start with did-",
75
+
input: "plc-abcdefghijklmnopqrstuvwxyz",
76
+
expected: false,
77
+
},
78
+
{
79
+
name: "no hyphen after did",
80
+
input: "didplc-abcdefghijklmnopqrstuvwxyz",
81
+
expected: false,
82
+
},
83
+
{
84
+
name: "empty string",
85
+
input: "",
86
+
expected: false,
87
+
},
88
+
{
89
+
name: "only did-",
90
+
input: "did-",
91
+
expected: false,
92
+
},
93
+
{
94
+
name: "standard DID format, not flattened",
95
+
input: "did:plc:abcdefghijklmnopqrstuvwxyz",
96
+
expected: false,
97
+
},
98
+
{
99
+
name: "invalid reconstructed DID format",
100
+
input: "did-invalid$format:something",
101
+
expected: false,
102
+
},
103
+
}
104
+
105
+
func TestIsFlattenedDid(t *testing.T) {
106
+
for _, tc := range isFlattenedDidTests {
107
+
t.Run(tc.name, func(t *testing.T) {
108
+
result := isFlattenedDid(tc.input)
109
+
if result != tc.expected {
110
+
t.Errorf("isFlattenedDid(%q) = %v, want %v", tc.input, result, tc.expected)
111
+
}
112
+
})
113
+
}
114
+
}