use hyphens as the encode for dids

This commit is contained in:
Evan Jarrett
2026-04-07 21:47:51 -05:00
parent 21b6f6301a
commit a68477033a
3 changed files with 31 additions and 18 deletions
+7
View File
@@ -299,6 +299,13 @@ func (nr *NamespaceResolver) Repository(ctx context.Context, name reference.Name
identityStr := parts[0]
imageName := parts[1]
// 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 {
identityStr = decoded
}
// Resolve identity to DID, handle, and PDS endpoint
did, handle, pdsEndpoint, err := atproto.ResolveIdentity(ctx, identityStr)
if err != nil {
+20 -14
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"log/slog"
"net/http"
"net/url"
"strings"
"time"
@@ -277,18 +276,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// parseBasicAuthDID fixes DID usernames that are mangled by HTTP Basic Auth.
// Basic Auth splits on the first colon, so "did:plc:abc123" as a username
// produces username="did" and the rest gets prepended to the password.
//
// This handles two cases:
// 1. URL-encoded DIDs (did%3Aplc%3Aabc123) — decoded back to did:plc:abc123
// 2. Raw DIDs — reconstructed from the mangled username + password
// 1. Hyphen-encoded DIDs (did-plc-abc123) — converted to did:plc:abc123.
// This is the recommended format for tools like helm that reject colons in usernames.
// 2. Raw DIDs split by BasicAuth — "did:plc:abc123" gets split on the first colon
// into username="did", password="plc:abc123:<real-password>". Reconstructed here.
func parseBasicAuthDID(username, password string) (string, string) {
// Case 1: URL-encoded DID (e.g., did%3Aplc%3Aabc123)
if decoded, err := url.QueryUnescape(username); err == nil && decoded != username {
if strings.HasPrefix(decoded, "did:") {
return decoded, password
}
// Case 1: Hyphen-encoded DID (e.g., did-plc-abc123 or did-web-example.com)
if did, ok := DecodeDIDFromHyphens(username); ok {
return did, password
}
// Case 2: Raw DID was split by BasicAuth on the first colon
@@ -298,15 +295,11 @@ func parseBasicAuthDID(username, password string) (string, string) {
}
if strings.HasPrefix(password, "plc:") {
// did:plc:<base32-id> — the ID is a single segment (no colons)
// password = "plc:<id>:<real-password>"
rest := strings.TrimPrefix(password, "plc:")
if idx := strings.Index(rest, ":"); idx > 0 {
return "did:plc:" + rest[:idx], rest[idx+1:]
}
} else if strings.HasPrefix(password, "web:") {
// did:web:<hostname> — hostname uses dots not colons
// password = "web:<hostname>:<real-password>"
rest := strings.TrimPrefix(password, "web:")
if idx := strings.Index(rest, ":"); idx > 0 {
return "did:web:" + rest[:idx], rest[idx+1:]
@@ -315,3 +308,16 @@ 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
}
+4 -4
View File
@@ -659,15 +659,15 @@ func TestParseBasicAuthDID(t *testing.T) {
wantPassword: "mypassword",
},
{
name: "URL-encoded did:plc",
username: "did%3Aplc%3Aabc123",
name: "hyphen-encoded did:plc",
username: "did-plc-abc123",
password: "mypassword",
wantUsername: "did:plc:abc123",
wantPassword: "mypassword",
},
{
name: "URL-encoded did:web",
username: "did%3Aweb%3Aexample.com",
name: "hyphen-encoded did:web",
username: "did-web-example.com",
password: "mypassword",
wantUsername: "did:web:example.com",
wantPassword: "mypassword",