123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- package indieauth
- import (
- "crypto/sha256"
- "encoding/base64"
- "fmt"
- "net/http"
- "net/url"
- "strings"
- "time"
- "github.com/andybalholm/cascadia"
- "github.com/pkg/errors"
- "golang.org/x/net/html"
- )
- func createAuthRequest(authDestination, userID, displayName, accessToken, baseServer string) (*Request, error) {
- authURL, err := url.Parse(authDestination)
- if err != nil {
- return nil, errors.Wrap(err, "unable to parse IndieAuth destination")
- }
- authEndpointURL, err := getAuthEndpointFromURL(authURL.String())
- if err != nil {
- return nil, errors.Wrap(err, "unable to get IndieAuth endpoint from destination URL")
- }
- baseServerURL, err := url.Parse(baseServer)
- if err != nil {
- return nil, errors.Wrap(err, "unable to parse local owncast base server URL")
- }
- callbackURL := *baseServerURL
- callbackURL.Path = "/api/auth/indieauth/callback"
- codeVerifier := randString(50)
- codeChallenge := createCodeChallenge(codeVerifier)
- state := randString(20)
- responseType := "code"
- clientID := baseServerURL.String() // Our local URL
- codeChallengeMethod := "S256"
- redirect := *authEndpointURL
- q := authURL.Query()
- q.Add("response_type", responseType)
- q.Add("client_id", clientID)
- q.Add("state", state)
- q.Add("code_challenge_method", codeChallengeMethod)
- q.Add("code_challenge", codeChallenge)
- q.Add("me", authURL.String())
- q.Add("redirect_uri", callbackURL.String())
- redirect.RawQuery = q.Encode()
- return &Request{
- Me: authURL,
- UserID: userID,
- DisplayName: displayName,
- CurrentAccessToken: accessToken,
- Endpoint: authEndpointURL,
- ClientID: baseServer,
- CodeVerifier: codeVerifier,
- CodeChallenge: codeChallenge,
- State: state,
- Redirect: &redirect,
- Callback: &callbackURL,
- Timestamp: time.Now(),
- }, nil
- }
- func getAuthEndpointFromURL(urlstring string) (*url.URL, error) {
- htmlDocScrapeURL, err := url.Parse(urlstring)
- if err != nil {
- return nil, errors.Wrap(err, "unable to parse URL")
- }
- if htmlDocScrapeURL.Scheme != "https" {
- return nil, fmt.Errorf("url must be https")
- }
- r, err := http.Get(htmlDocScrapeURL.String()) // nolint:gosec
- if err != nil {
- return nil, err
- }
- defer r.Body.Close()
- scrapedHTMLDocument, err := html.Parse(r.Body)
- if err != nil {
- return nil, errors.Wrap(err, "unable to parse html at remote auth host")
- }
- authorizationEndpointTag := cascadia.MustCompile("link[rel=authorization_endpoint]").MatchAll(scrapedHTMLDocument)
- if len(authorizationEndpointTag) == 0 {
- return nil, fmt.Errorf("url does not support indieauth")
- }
- for _, attr := range authorizationEndpointTag[len(authorizationEndpointTag)-1].Attr {
- if attr.Key == "href" {
- u, err := url.Parse(attr.Val)
- if err != nil {
- return nil, errors.Wrap(err, "unable to parse authorization endpoint")
- }
- // If it is a relative URL we an fill in the missing components
- // by using the original URL we scraped, since it is the same host.
- if u.Scheme == "" {
- u.Scheme = htmlDocScrapeURL.Scheme
- }
- if u.Host == "" {
- u.Host = htmlDocScrapeURL.Host
- }
- return u, nil
- }
- }
- return nil, fmt.Errorf("unable to find href value for authorization_endpoint")
- }
- func createCodeChallenge(codeVerifier string) string {
- sha256hash := sha256.Sum256([]byte(codeVerifier))
- encodedHashedCode := strings.TrimRight(base64.URLEncoding.EncodeToString(sha256hash[:]), "=")
- return encodedHashedCode
- }
|