fix validation on dids with hyphens

This commit is contained in:
Evan Jarrett
2026-04-07 22:26:21 -05:00
parent 03759713e9
commit 9033d74a19
4 changed files with 55 additions and 14 deletions
+1 -1
View File
@@ -302,7 +302,7 @@ func (nr *NamespaceResolver) Repository(ctx context.Context, name reference.Name
// Support hyphen-encoded DIDs in image paths (e.g., did-plc-abc123/repo:tag)
// OCI reference grammar doesn't allow colons in path components, so DIDs must
// be encoded with hyphens instead: did:plc:abc123 → did-plc-abc123
if decoded, ok := token.DecodeDIDFromHyphens(identityStr); ok {
if decoded, ok := auth.DecodeDIDFromHyphens(identityStr); ok {
identityStr = decoded
}
+19
View File
@@ -56,6 +56,19 @@ func ParseScope(scopes []string) ([]AccessEntry, error) {
return access, nil
}
// DecodeDIDFromHyphens converts a hyphen-encoded DID back to colon-separated form.
// "did-plc-abc123" → "did:plc:abc123", "did-web-example.com" → "did:web:example.com"
// Returns the decoded DID and true if the input matched, or ("", false) otherwise.
func DecodeDIDFromHyphens(s string) (string, bool) {
if strings.HasPrefix(s, "did-plc-") {
return "did:plc:" + strings.TrimPrefix(s, "did-plc-"), true
}
if strings.HasPrefix(s, "did-web-") {
return "did:web:" + strings.TrimPrefix(s, "did-web-"), true
}
return "", false
}
// ValidateAccess checks if the requested access is allowed for the user
// For ATCR, users can only push to repositories under their own handle/DID
func ValidateAccess(userDID, userHandle string, access []AccessEntry) error {
@@ -79,6 +92,12 @@ func ValidateAccess(userDID, userHandle string, access []AccessEntry) error {
repoOwner := parts[0]
// Decode hyphen-encoded DIDs (e.g., did-plc-abc123 → did:plc:abc123)
// Image paths use hyphens because colons are parsed as transport separators
if decoded, ok := DecodeDIDFromHyphens(repoOwner); ok {
repoOwner = decoded
}
// Check if user is trying to access their own repository
// They can use either their handle or DID
if repoOwner != userHandle && repoOwner != userDID {
+34
View File
@@ -413,6 +413,40 @@ func TestValidateAccess_DIDAndHandleBothWork(t *testing.T) {
}
}
func TestValidateAccess_HyphenEncodedDID(t *testing.T) {
userDID := "did:plc:pddp4xt5lgnv2qsegbzzs4xg"
userHandle := "evan.atcr.io"
// Hyphen-encoded DID should match (this is how DIDs appear in image paths)
access := []AccessEntry{
{
Type: "repository",
Name: "did-plc-pddp4xt5lgnv2qsegbzzs4xg/charts/loom",
Actions: []string{"pull", "push"},
},
}
err := ValidateAccess(userDID, userHandle, access)
if err != nil {
t.Errorf("Expected no error for hyphen-encoded DID, got: %v", err)
}
// did:web hyphen-encoded
webDID := "did:web:example.com"
accessWeb := []AccessEntry{
{
Type: "repository",
Name: "did-web-example.com/myapp",
Actions: []string{"push"},
},
}
err = ValidateAccess(webDID, "example.com", accessWeb)
if err != nil {
t.Errorf("Expected no error for hyphen-encoded did:web, got: %v", err)
}
}
func TestValidateAccess_MixedActionsAndOwnership(t *testing.T) {
userDID := "did:plc:alice123"
userHandle := "alice.bsky.social"
+1 -13
View File
@@ -284,7 +284,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// into username="did", password="plc:abc123:<real-password>". Reconstructed here.
func parseBasicAuthDID(username, password string) (string, string) {
// Case 1: Hyphen-encoded DID (e.g., did-plc-abc123 or did-web-example.com)
if did, ok := DecodeDIDFromHyphens(username); ok {
if did, ok := auth.DecodeDIDFromHyphens(username); ok {
return did, password
}
@@ -309,15 +309,3 @@ func parseBasicAuthDID(username, password string) (string, string) {
return username, password
}
// DecodeDIDFromHyphens converts a hyphen-encoded DID back to colon-separated form.
// "did-plc-abc123" → "did:plc:abc123", "did-web-example.com" → "did:web:example.com"
// Returns the decoded DID and true if the input matched, or ("", false) otherwise.
func DecodeDIDFromHyphens(s string) (string, bool) {
if strings.HasPrefix(s, "did-plc-") {
return "did:plc:" + strings.TrimPrefix(s, "did-plc-"), true
}
if strings.HasPrefix(s, "did-web-") {
return "did:web:" + strings.TrimPrefix(s, "did-web-"), true
}
return "", false
}