diff --git a/backend/internal/oauth/client.go b/backend/internal/oauth/client.go
index 843c1a0..c0b0768 100644
--- a/backend/internal/oauth/client.go
+++ b/backend/internal/oauth/client.go
@@ -180,6 +180,42 @@ func (c *Client) ResolveDIDToPDS(ctx context.Context, did string) (string, error
return "", fmt.Errorf("no PDS found in DID document")
}
+func (c *Client) ResolveDIDToHandle(ctx context.Context, did string) (string, error) {
+ var docURL string
+ if strings.HasPrefix(did, "did:plc:") {
+ docURL = config.Get().PLCResolveURL(did)
+ } else if strings.HasPrefix(did, "did:web:") {
+ domain := strings.TrimPrefix(did, "did:web:")
+ docURL = fmt.Sprintf("https://%s/.well-known/did.json", domain)
+ } else {
+ return "", fmt.Errorf("unsupported DID method: %s", did)
+ }
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, docURL, nil)
+ if err != nil {
+ return "", err
+ }
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+
+ var doc struct {
+ AlsoKnownAs []string `json:"alsoKnownAs"`
+ }
+ if err := json.NewDecoder(resp.Body).Decode(&doc); err != nil {
+ return "", err
+ }
+
+ for _, aka := range doc.AlsoKnownAs {
+ if handle := strings.TrimPrefix(aka, "at://"); handle != aka {
+ return handle, nil
+ }
+ }
+ return "", fmt.Errorf("no handle found in DID document")
+}
+
func (c *Client) GetAuthServerMetadata(ctx context.Context, pds string) (*AuthServerMetadata, error) {
resourceURL := fmt.Sprintf("%s/.well-known/oauth-protected-resource", strings.TrimSuffix(pds, "/"))
resp, err := http.Get(resourceURL)
diff --git a/backend/internal/oauth/handler.go b/backend/internal/oauth/handler.go
index 8eb60f7..8abeb9c 100644
--- a/backend/internal/oauth/handler.go
+++ b/backend/internal/oauth/handler.go
@@ -446,6 +446,14 @@ func (h *Handler) HandleCallback(w http.ResponseWriter, r *http.Request) {
_ = newNonce
+ if pending.Handle == "" {
+ if resolved, herr := client.ResolveDIDToHandle(ctx, tokenResp.Sub); herr == nil {
+ pending.Handle = resolved
+ } else {
+ logger.Error("Failed to resolve handle for %s: %v", tokenResp.Sub, herr)
+ }
+ }
+
sessionID := generateSessionID()
expiresAt := time.Now().Add(7 * 24 * time.Hour)
diff --git a/extension/src/components/popup/App.tsx b/extension/src/components/popup/App.tsx
index 0a342fc..f3c04a7 100644
--- a/extension/src/components/popup/App.tsx
+++ b/extension/src/components/popup/App.tsx
@@ -171,6 +171,15 @@ export function App() {
}
}
+ async function handleSignIn() {
+ try {
+ await browser.permissions.request({ origins: [`${new URL(apiUrl).origin}/*`] });
+ } catch {
+ /* empty */
+ }
+ await browser.tabs.create({ url: `${apiUrl}/login` });
+ }
+
async function loadCurrentTab() {
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
if (tab?.url) {
@@ -474,7 +483,7 @@ export function App() {
Annotate, highlight, and bookmark the web with your AT Protocol identity.