indieauth

package module
v3.3.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2023 License: MIT Imports: 17 Imported by: 1

README

indieauth

An IndieAuth toolkit in Go.

Go Report Card Documentation Codecov

This repository contains a set of tools to help you implement IndieAuth, both server and client, in Go.

Usage

Check the documentation.

Example

This repository contains two demos as examples: server and client.

Contribute

Just open an issue or a pull request.

License

MIT License © Henrique Dias

Documentation

Overview

This package provides a set of tools to help you implement an IndieAuth client or server, according to the specification.

Index

Examples

Constants

View Source
const (
	AuthorizationEndpointRel string = "authorization_endpoint"
	TokenEndpointRel         string = "token_endpoint"
	IndieAuthMetadataRel     string = "indieauth-metadata"
)

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")
	ErrInvalidIssuer error = errors.New("issuer does not match")
)
View Source
var (
	ErrInvalidRedirectURI         error = errors.New("redirect_uri is invalid")
	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")
	ErrWrongCodeVerifierLength    error = errors.New("code_verifier length must be between 43 and 128 characters long")  // RFC 7636, section 4.1
	ErrWrongCodeChallengeLength   error = errors.New("code_challenge length must be between 43 and 128 characters long") // RFC 7636, section 4.2
)
View Source
var (
	ErrInvalidProfileURL       error = errors.New("invalid profile URL")
	ErrInvalidClientIdentifier error = errors.New("invalid client identifier")

	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.

func IsValidCodeChallengeMethod

func IsValidCodeChallengeMethod(ccm string) bool

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

func IsValidProfileURL

func IsValidProfileURL(profile string) error

IsValidProfileURL validates the profile URL according to the specification.

func ValidateCodeChallenge

func ValidateCodeChallenge(ccm, cc, ver string) bool

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

The caller is responsible for handling cases where the length of the code challenge or code verifier fall outside the range permitted by RFC 7636.

Types

type AuthInfo

type AuthInfo struct {
	Metadata
	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 an IndieAuth client. As a client, you want to authenticate other users to log into your website. An example of how to use the client library can be found in the examples/client/ directory.

func NewClient

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 state 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) DiscoverLinkEndpoint

func (c *Client) DiscoverLinkEndpoint(urlStr, rel string) (string, error)

DiscoverLinkEndpoint discovers as given endpoint identified by rel.

func (*Client) DiscoverMetadata

func (c *Client) DiscoverMetadata(urlStr string) (*Metadata, error)

DiscoverMetadata discovers the IndieAuth metadata of the provided URL, such as the authorization and token endpoints. This code is partially based on webmention.DiscoverEndpoint.

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 per specification. Please note that this action consumes the code.

func (*Client) GetOAuth2

func (c *Client) GetOAuth2(m *Metadata) *oauth2.Config

GetOAuth2 returns an oauth2.Config based on the given endpoints. This can be used to get an http.Client. See the documentation of 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 Metadata

type Metadata struct {
	Issuer                                     string   `json:"issuer"`
	AuthorizationEndpoint                      string   `json:"authorization_endpoint"`
	TokenEndpoint                              string   `json:"token_endpoint"`
	IntrospectionEndpoint                      string   `json:"introspection_endpoint"`
	IntrospectionEndpointAuthMethodsSupported  []string `json:"introspection_endpoint_auth_methods_supported"`
	RevocationEndpoint                         string   `json:"revocation_endpoint"`
	RevocationEndpointAuthMethodsSupported     []string `json:"revocation_endpoint_auth_methods_supported"`
	ScopesSupported                            []string `json:"scopes_supported"`
	ResponseTypesSupported                     []string `json:"response_types_supported"`
	GrantTypesSupported                        []string `json:"grant_types_supported"`
	ServiceDocumentation                       []string `json:"service_documentation"`
	CodeChallengeMethodsSupported              []string `json:"code_challenge_methods_supported"`
	AuthorizationResponseIssParameterSupported bool     `json:"authorization_response_iss_parameter_supported"`
	UserInfoEndpoint                           string   `json:"userinfo_endpoint"`
}

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

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

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.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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