~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
135 lines · 4.5 KB Raw History
package atproto

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/bluesky-social/indigo/atproto/syntax"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/oauthclient"
)

type accountMismatchError struct {
	text string
}

func (e *accountMismatchError) Error() string {
	return "marketidentity: " + e.text
}

type accountUnverifiedError struct {
	err error
}

func (e *accountUnverifiedError) Error() string {
	return "marketidentity: the oauth session account is not verified: " + e.err.Error()
}

func (e *accountUnverifiedError) Unwrap() error {
	return e.err
}

func accountKey(id *v1alpha1.MarketIdentity) string {
	return strings.Join([]string{strings.TrimSpace(id.Spec.Did), strings.ToLower(strings.TrimSpace(id.Spec.Handle)),
		strings.TrimRight(strings.TrimSpace(id.Spec.PdsURL), "/")}, "\x00")
}

func (a *Accounts) checkAccount(ctx context.Context, id *v1alpha1.MarketIdentity, did, host, authServer, claimed string) (string, error) {
	did = strings.TrimSpace(did)
	resolved := a.handleOf(ctx, did)
	got := describeAccount(did, firstNonEmpty(resolved, claimed))
	if want := strings.TrimSpace(id.Spec.Did); want != "" && want != did {
		return "", &accountMismatchError{text: fmt.Sprintf("the oauth session is for %s, not the configured did %s; sign in as %s",
			got, want, want)}
	}
	want := strings.TrimRight(strings.TrimSpace(id.Spec.PdsURL), "/")
	host = strings.TrimRight(strings.TrimSpace(host), "/")
	if want != "" && host != "" && want != host {
		if want != strings.TrimRight(strings.TrimSpace(authServer), "/") {
			return "", &accountMismatchError{text: fmt.Sprintf("the oauth session of %s is on the PDS %s, neither the configured pdsUrl %s nor its authorization server; sign in again",
				got, host, want)}
		}
		if err := a.entrywayAccount(ctx, did, host, want); err != nil {
			if mismatch := (*accountMismatchError)(nil); errors.As(err, &mismatch) {
				return "", err
			}
			return "", &accountUnverifiedError{err: err}
		}
	}
	if raw := strings.TrimSpace(id.Spec.Handle); raw != "" {
		handle, err := syntax.ParseHandle(raw)
		if err != nil {
			return "", &accountUnverifiedError{err: fmt.Errorf("the configured handle %q: %w", raw, err)}
		}
		if a.oauth.Directory == nil {
			return "", &accountUnverifiedError{err: errors.New("no identity directory resolves the configured handle " + raw)}
		}
		ident, err := a.oauth.Directory.LookupHandle(ctx, handle.Normalize())
		if err != nil {
			return "", &accountUnverifiedError{err: fmt.Errorf("resolving the configured handle %s: %w", raw, err)}
		}
		if ident.DID.String() != did {
			return "", &accountMismatchError{text: fmt.Sprintf("the oauth session is for %s, not %s (%s); sign in as %s",
				got, handle.Normalize(), ident.DID, handle.Normalize())}
		}
		if resolved == "" {
			resolved = ident.Handle.Normalize().String()
		}
	}
	return firstNonEmpty(resolved, claimed), nil
}

func (a *Accounts) entrywayAccount(ctx context.Context, did, host, entryway string) error {
	if a.oauth.Directory == nil {
		return errors.New("no identity directory resolves " + did + " to check its PDS against the entryway " + entryway)
	}
	parsed, err := syntax.ParseDID(did)
	if err != nil {
		return err
	}
	ident, err := a.oauth.Directory.LookupDID(ctx, parsed)
	if err != nil {
		return fmt.Errorf("resolving %s: %w", did, err)
	}
	if pds := strings.TrimRight(strings.TrimSpace(ident.PDSEndpoint()), "/"); pds != host {
		return &accountMismatchError{text: fmt.Sprintf("the oauth session of %s is on the PDS %s, but %s names the PDS %q; sign in again", did, host, did, pds)}
	}
	authServer, _, _, err := oauthclient.DiscoverAuthServer(ctx, a.oauthHTTP(), host)
	if err != nil {
		return err
	}
	if authServer != entryway {
		return &accountMismatchError{text: fmt.Sprintf("the PDS %s of %s names the authorization server %s, not the configured pdsUrl %s; sign in again",
			host, did, authServer, entryway)}
	}
	return nil
}

func (a *Accounts) handleOf(ctx context.Context, did string) string {
	if a.oauth.Directory == nil {
		return ""
	}
	parsed, err := syntax.ParseDID(did)
	if err != nil {
		return ""
	}
	ident, err := a.oauth.Directory.LookupDID(ctx, parsed)
	if err != nil || ident.Handle == "" || ident.Handle.IsInvalidHandle() {
		return ""
	}
	return ident.Handle.Normalize().String()
}

func describeAccount(did, handle string) string {
	did, handle = strings.TrimSpace(did), strings.TrimSpace(handle)
	switch {
	case did == "":
		return handle
	case handle == "" || handle == did:
		return did
	}
	return handle + " (" + did + ")"
}