indieauth

package module
v1.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 17, 2022 License: MIT Imports: 20 Imported by: 1

README

IndieAuth Toolkit for Go

Go Report Card Documentation

This repository contains a set of tools to help you implement IndieAuth, both server and client, in Go. The documentation can be found here. Please note that there may be bugs. Feel free to use it and send PRs with improvements.

Usage

Some examples:

License

MIT © Henrique Dias

Documentation

Overview

This package provides a set of tools to help you implement IndieAuth, both as a Server or as a Client, according to the spec: https://indieauth.spec.indieweb.org

Index

Examples

Constants

View Source
const (
	AuthorizationEndpointRel string = "authorization_endpoint"
	TokenEndpointRel         string = "token_endpoint"
)

Variables

View Source
var (
	ErrCodeNotFound  error = errors.New("code not found")
	ErrStateNotFound error = errors.New("state not found")
	ErrInvalidState  error = errors.New("state does not match")
)
View Source
var (
	ErrInvalidCodeChallengeMethod error = errors.New("invalid code challenge method")
	ErrInvalidGrantType           error = errors.New("grant_type must be authorization_code")
	ErrNoMatchClientID            error = errors.New("client_id differs")
	ErrNoMatchRedirectURI         error = errors.New("redirect_uri differs")
	ErrPKCERequired               error = errors.New("pkce is required, not provided")
	ErrCodeChallengeFailed        error = errors.New("code challenge failed")
	ErrInvalidResponseType        error = errors.New("response_type must be code")
	ErrWrongCodeChallengeLenght   error = errors.New("code_challenge length must be between 43 and 128 charachters long")
)
View Source
var (
	ErrInvalidScheme   error = errors.New("scheme must be either http or https")
	ErrEmptyPath       error = errors.New("path must not be empty")
	ErrInvalidPath     error = errors.New("path cannot contain single or double dots")
	ErrInvalidFragment error = errors.New("fragment must be empty")
	ErrUserIsSet       error = errors.New("user and or password must not be set")
	ErrPortIsSet       error = errors.New("port must not be set")
	ErrIsIP            error = errors.New("profile cannot be ip address")
	ErrIsNonLoopback   error = errors.New("client id cannot be non-loopback ip")
)
View Source
var CodeChallengeMethods = []string{
	"plain", "S256",
}

CodeChallengeMethods are the code challenge methods that are supported by this package.

View Source
var ErrNoEndpointFound = fmt.Errorf("no endpoint found")

ErrNoEndpointFound is returned when no endpoint can be found for a certain target URL.

Functions

func CanonicalizeURL

func CanonicalizeURL(urlStr string) string

CanonicalizeURL checks if a URL has a path, and appends a path "/"" if it has no path.

Example
fmt.Println(CanonicalizeURL("example.com"))
Output:

https://example.com/

func IsValidClientIdentifier

func IsValidClientIdentifier(identifier string) error

IsValidClientIdentifier validates a client identifier according to the specification. https://indieauth.spec.indieweb.org/#client-identifier

func IsValidCodeChallengeMethod

func IsValidCodeChallengeMethod(ccm string) bool

IsValidCodeChallengeMethod returns whether the provided code challenge method is or is not valid.

func IsValidProfileURL

func IsValidProfileURL(profile string) error

IsValidProfileURL validates the profile URL according to the specification. https://indieauth.spec.indieweb.org/#user-profile-url

func ValidateCodeChallenge

func ValidateCodeChallenge(ccm, cc, ver string) bool

ValidateCodeChallenge validates a code challenge against it code verifier. Right now, we support "plain" and "S256" code challenge methods.

Types

type AuthInfo

type AuthInfo struct {
	Endpoints
	Me           string
	State        string
	CodeVerifier string
}

type AuthenticationRequest

type AuthenticationRequest struct {
	RedirectURI         string
	ClientID            string
	Scopes              []string
	State               string
	CodeChallenge       string
	CodeChallengeMethod string
}

type Client

type Client struct {
	Client *http.Client

	ClientID    string
	RedirectURL string
}

Client is a IndieAuth client. As a client, you want to authenticate other users to log into onto your website.

First, create a client with the correct client ID and callback URL.

client := NewClient("https://example.com/", "https://example.com/callback", nil)

Then, obtain the user's profile URL via some method, such as an HTTP form. Optionally, canonicalize the value (see CanonicalizeURL for more information).

profile = CanonicalizeURL(profile)

Then, validate the profile URL according to the specification.

err = IsValidProfileURL(profile)
if err != nil {
	// Do something
}

Obtain the authentication information and redirect URL:

authData, redirect, err := client.Authenticate(profile, "the scopes you need")
if err != nil {
	// Do something
}

The client should now store authData because it will be necessary to verify the callback. You can store it, for example, in a database or cookie. Then, redirect the user:

http.Redirect(w, r, redirect, http.StatusSeeOther)

In the callback handler, you should obtain authData according to the method you defined. Then, call ValidateCallback to obtain the code:

code, err := client.ValidateCallback(authData, r)
if err != nil {
	// Do something
}

Now that you have the code, you have to redeem it. You can either use FetchProfile to redeem it by the users' profile or GetToken.

func NewClient added in v0.1.3

func NewClient(clientID, redirectURL string, httpClient *http.Client) *Client

NewClient creates a new Client from the provided clientID and redirectURL. If no httpClient is given, http.DefaultClient will be used.

func (*Client) Authenticate

func (c *Client) Authenticate(profile, scope string) (*AuthInfo, string, error)

Authenticate takes a profile URL and the desired scope, discovers the required endpoints, generates a random scope and code challenge (using method SHA256), and builds the authorization URL. It returns the authorization info, redirect URI and an error.

The returned AuthInfo should be stored by the caller of this function in such a way that it can be retrieved to validate the callback.

func (*Client) DiscoverEndpoint added in v0.1.7

func (s *Client) DiscoverEndpoint(urlStr, rel string) (string, error)

DiscoverEndpoint discovers as given endpoint identified by rel.

func (*Client) DiscoverEndpoints

func (s *Client) DiscoverEndpoints(urlStr string) (*Endpoints, error)

DiscoverEndpoints discovers the authorization and token endpoints for the provided URL. This code is partially based on https://github.com/willnorris/webmention/blob/main/webmention.go.

func (*Client) FetchProfile

func (c *Client) FetchProfile(i *AuthInfo, code string) (*Profile, error)

FetchProfile fetches the user profile, exchanging the authentication code from their authentication endpoint, as described in the link below. Please note that this action consumes the code.

https://indieauth.spec.indieweb.org/#profile-url-response

func (*Client) GetOAuth2 added in v0.1.6

func (c *Client) GetOAuth2(e *Endpoints) *oauth2.Config

GetOAuth2 returns an oauth2.Config based on the given endpoints. This can be used to get an http.Client See https://pkg.go.dev/golang.org/x/oauth2 for more details.

func (*Client) GetToken

func (c *Client) GetToken(i *AuthInfo, code string) (*oauth2.Token, *oauth2.Config, error)

GetToken exchanges the code for an oauth2.Token based on the provided information. It returns the token and an oauth2.Config object which can be used to create an http client that uses the token on future requests.

Note that token.Raw may contain other information returned by the server, such as "Me", "Profile" and "Scope".

token, oauth2, err := client.GetToken(authData, code)
if err != nil {
	// Do something
}
httpClient := oauth2.Client(context.Background(), token)

You can now use httpClient to make requests to, for example, a Micropub endpoint. They are authenticated with token. See https://pkg.go.dev/golang.org/x/oauth2 for more details.

func (*Client) ValidateCallback

func (c *Client) ValidateCallback(i *AuthInfo, r *http.Request) (string, error)

ValidateCallback validates the callback request by checking if the code exists and if the state is valid according to the provided AuthInfo.

type Endpoints

type Endpoints struct {
	Authorization string
	Token         string
}

type Profile

type Profile struct {
	Me      string `json:"me"`
	Profile struct {
		Name  string `json:"name"`
		URL   string `json:"url"`
		Photo string `json:"photo"`
		Email string `json:"email"`
	} `json:"profile"`
}

func ProfileFromToken

func ProfileFromToken(token *oauth2.Token) *Profile

ProfileFromToken retrieves the extra information from the token and creates a profile based on it. Note that the profile may be nil in case no information can be retrieved.

type Server

type Server struct {
	Client      *http.Client
	RequirePKCE bool
}

func NewServer added in v0.1.5

func NewServer(requirePKCE bool, httpClient *http.Client) *Server

NewServer creates a new Server that from the given options. If no httpClient is given, http.DefaultClient will be used.

func (*Server) ParseAuthorization

func (s *Server) ParseAuthorization(r *http.Request) (*AuthenticationRequest, error)

ParseAuthorization parses an authorization request and returns all the collected information about the request.

func (*Server) ValidateTokenExchange added in v0.1.3

func (s *Server) ValidateTokenExchange(authRequest *AuthenticationRequest, r *http.Request) error

ValidateTokenExchange validates the token exchange request according to the provided authentication request and returns an error.

Please note that you need to fetch the authentication code yourself from the request.

_ = r.ParseForm()
code := r.Form.Get("code")

The code was provided by you at a previous stage. Thus, you will need to use it to rebuild the AuthenticationRequest data. The AuthenticationRequest does not need to have the scope or state set for this validation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL