♻️ Improved Error usage
This commit is contained in:
parent
4ce47440aa
commit
7cb23125c8
|
@ -30,59 +30,59 @@ func NewClientID(raw string) (*ClientID, error) {
|
|||
clientID := http.AcquireURI()
|
||||
if err := clientID.Parse(nil, []byte(raw)); err != nil {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: err.Error(),
|
||||
URI: "https://indieauth.net/source/#client-identifier",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
scheme := string(clientID.Scheme())
|
||||
if scheme != "http" && scheme != "https" {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "client identifier URL MUST have either an https or http scheme",
|
||||
URI: "https://indieauth.net/source/#client-identifier",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
path := string(clientID.PathOriginal())
|
||||
if path == "" || strings.Contains(path, "/.") || strings.Contains(path, "/..") {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "client identifier URL MUST contain a path component and MUST NOT contain " +
|
||||
"single-dot or double-dot path segments",
|
||||
URI: "https://indieauth.net/source/#client-identifier",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
if clientID.Hash() != nil {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "client identifier URL MUST NOT contain a fragment component",
|
||||
URI: "https://indieauth.net/source/#client-identifier",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
if clientID.Username() != nil || clientID.Password() != nil {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "client identifier URL MUST NOT contain a username or password component",
|
||||
URI: "https://indieauth.net/source/#client-identifier",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
domain := string(clientID.Host())
|
||||
if domain == "" {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "client host name MUST be domain name or a loopback interface",
|
||||
URI: "https://indieauth.net/source/#client-identifier",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,11 +98,11 @@ func NewClientID(raw string) (*ClientID, error) {
|
|||
|
||||
if !ip.IsLoopback() && ip.Compare(localhostIPv4) != 0 && ip.Compare(localhostIPv6) != 0 {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "client identifier URL MUST NOT be IPv4 or IPv6 addresses except for IPv4 " +
|
||||
"127.0.0.1 or IPv6 [::1]",
|
||||
URI: "https://indieauth.net/source/#client-identifier",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ type Email struct {
|
|||
}
|
||||
|
||||
var ErrEmailInvalid error = Error{
|
||||
Code: "invalid_request",
|
||||
Description: "cannot unmarshal email input",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "cannot parse email",
|
||||
}
|
||||
|
||||
func NewEmail(src string) (*Email, error) {
|
||||
|
|
|
@ -2,39 +2,185 @@ package domain
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
http "github.com/valyala/fasthttp"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Error describes the data of a typical error.
|
||||
//nolint: tagliatelle
|
||||
type Error struct {
|
||||
Code string `json:"error"`
|
||||
Description string `json:"error_description,omitempty"`
|
||||
URI string `json:"error_uri,omitempty"`
|
||||
Frame xerrors.Frame `json:"-"`
|
||||
type (
|
||||
// Error describes the format of a typical IndieAuth error.
|
||||
Error struct {
|
||||
// A single error code.
|
||||
Code ErrorCode `json:"error"`
|
||||
|
||||
// Human-readable ASCII text providing additional information, used to
|
||||
// assist the client developer in understanding the error that occurred.
|
||||
Description string `json:"error_description,omitempty"` //nolint: tagliatelle
|
||||
|
||||
// A URI identifying a human-readable web page with information about
|
||||
// the error, used to provide the client developer with additional
|
||||
// information about the error.
|
||||
URI string `json:"error_uri,omitempty"` //nolint: tagliatelle
|
||||
|
||||
// REQUIRED if a "state" parameter was present in the client
|
||||
// authorization request. The exact value received from the
|
||||
// client.
|
||||
State string `json:"-"`
|
||||
|
||||
frame xerrors.Frame `json:"-"`
|
||||
}
|
||||
|
||||
// ErrorCode represent error code described in RFC 6749.
|
||||
ErrorCode struct {
|
||||
uid string
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorCodeUndefined ErrorCode = ErrorCode{uid: ""}
|
||||
|
||||
// RFC 6749 section 4.1.2.1: The resource owner or authorization server
|
||||
// denied the request.
|
||||
ErrorCodeAccessDenied ErrorCode = ErrorCode{uid: "access_denied"}
|
||||
|
||||
// RFC 6749 section 5.2: Client authentication failed (e.g., unknown
|
||||
// client, no client authentication included, or unsupported
|
||||
// authentication method).
|
||||
//
|
||||
// The authorization server MAY return an HTTP 401 (Unauthorized) status
|
||||
// code to indicate which HTTP authentication schemes are supported.
|
||||
//
|
||||
// If the client attempted to authenticate via the "Authorization"
|
||||
// request header field, the authorization server MUST respond with an
|
||||
// HTTP 401 (Unauthorized) status code and include the
|
||||
// "WWW-Authenticate" response header field matching the authentication
|
||||
// scheme used by the client.
|
||||
ErrorCodeInvalidClient ErrorCode = ErrorCode{uid: "invalid_client"}
|
||||
|
||||
// RFC 6749 section 5.2: The provided authorization grant (e.g.,
|
||||
// authorization code, resource owner credentials) or refresh token is
|
||||
// invalid, expired, revoked, does not match the redirection URI used in
|
||||
// the authorization request, or was issued to another client.
|
||||
ErrorCodeInvalidGrant ErrorCode = ErrorCode{uid: "invalid_grant"}
|
||||
|
||||
// RFC 6749 section 4.1.2.1: The request is missing a required
|
||||
// parameter, includes an invalid parameter value, includes a parameter
|
||||
// more than once, or is otherwise malformed.
|
||||
//
|
||||
// RFC 6749 section 5.2: The request is missing a required parameter,
|
||||
// includes an unsupported parameter value (other than grant type),
|
||||
// repeats a parameter, includes multiple credentials, utilizes more
|
||||
// than one mechanism for authenticating the client, or is otherwise
|
||||
// malformed.
|
||||
ErrorCodeInvalidRequest ErrorCode = ErrorCode{uid: "invalid_request"}
|
||||
|
||||
// RFC 6749 section 4.1.2.1: The requested scope is invalid, unknown, or
|
||||
// malformed.
|
||||
//
|
||||
// RFC 6749 section 5.2: The requested scope is invalid, unknown,
|
||||
// malformed, or exceeds the scope granted by the resource owner.
|
||||
ErrorCodeInvalidScope ErrorCode = ErrorCode{uid: "invalid_scope"}
|
||||
|
||||
// RFC 6749 section 4.1.2.1: The authorization server encountered an
|
||||
// unexpected condition that prevented it from fulfilling the request.
|
||||
// (This error code is needed because a 500 Internal Server Error HTTP
|
||||
// status code cannot be returned to the client via an HTTP redirect.)
|
||||
ErrorCodeServerError ErrorCode = ErrorCode{uid: "server_error"}
|
||||
|
||||
// RFC 6749 section 4.1.2.1: The authorization server is currently
|
||||
// unable to handle the request due to a temporary overloading or
|
||||
// maintenance of the server. (This error code is needed because a 503
|
||||
// Service Unavailable HTTP status code cannot be returned to the client
|
||||
// via an HTTP redirect.)
|
||||
ErrorCodeTemporarilyUnavailable ErrorCode = ErrorCode{uid: "temporarily_unavailable"}
|
||||
|
||||
// RFC 6749 section 4.1.2.1: The client is not authorized to request an
|
||||
// authorization code using this method.
|
||||
//
|
||||
// RFC 6749 section 5.2: The authenticated client is not authorized to
|
||||
// use this authorization grant type.
|
||||
ErrorCodeUnauthorizedClient ErrorCode = ErrorCode{uid: "unauthorized_client"}
|
||||
|
||||
// RFC 6749 section 5.2: The authorization grant type is not supported
|
||||
// by the authorization server.
|
||||
ErrorCodeUnsupportedGrantType ErrorCode = ErrorCode{uid: "unsupported_grant_type"}
|
||||
|
||||
// RFC 6749 section 4.1.2.1: The authorization server does not support
|
||||
// obtaining an authorization code using this method.
|
||||
ErrorCodeUnsupportedResponseType ErrorCode = ErrorCode{uid: "unsupported_response_type"}
|
||||
)
|
||||
|
||||
// String returns a string representation of the error code.
|
||||
func (ec ErrorCode) String() string {
|
||||
return ec.uid
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the error code into its string representation in JSON.
|
||||
func (ec ErrorCode) MarshalJSON() ([]byte, error) {
|
||||
return []byte(strconv.QuoteToASCII(ec.uid)), nil
|
||||
}
|
||||
|
||||
// Error returns a string representation of the error, satisfying the error
|
||||
// interface.
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprint(e)
|
||||
}
|
||||
|
||||
// Format prints the stack as error detail.
|
||||
func (e Error) Format(s fmt.State, r rune) {
|
||||
xerrors.FormatError(e, s, r)
|
||||
}
|
||||
|
||||
// FormatError prints the receiver's error, if any.
|
||||
func (e Error) FormatError(p xerrors.Printer) error {
|
||||
p.Print(e.Description)
|
||||
p.Print(e.Code)
|
||||
|
||||
if e.URI != "" {
|
||||
p.Print(" (", e.URI, ")")
|
||||
if e.Description != "" {
|
||||
p.Print(": ", e.Description)
|
||||
}
|
||||
|
||||
if !p.Detail() {
|
||||
return e
|
||||
}
|
||||
|
||||
e.Frame.Format(p)
|
||||
e.frame.Format(p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReirectURI sets fasthttp.QueryArgs with the request state, code,
|
||||
// description and error URI in the provided fasthttp.URI.
|
||||
func (e Error) SetReirectURI(u *http.URI) {
|
||||
if u == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range map[string]string{
|
||||
"error": e.Code.String(),
|
||||
"error_description": e.Description,
|
||||
"error_uri": e.URI,
|
||||
"state": e.State,
|
||||
} {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
u.QueryArgs().Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// NewError creates a new Error with the stack pointing to the function call
|
||||
// line.
|
||||
//
|
||||
// If no code or ErrorCodeUndefined is provided, ErrorCodeAccessDenied will be
|
||||
// used instead.
|
||||
func NewError(code ErrorCode, description string) *Error {
|
||||
if code == ErrorCodeUndefined {
|
||||
code = ErrorCodeAccessDenied
|
||||
}
|
||||
|
||||
return &Error{
|
||||
Code: code,
|
||||
Description: description,
|
||||
URI: "",
|
||||
State: "",
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package domain_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"source.toby3d.me/website/indieauth/internal/domain"
|
||||
)
|
||||
|
||||
func ExampleNewError() {
|
||||
fmt.Printf("%v", domain.NewError(domain.ErrorCodeInvalidRequest, "client_id MUST be provided"))
|
||||
// Output: invalid_request: client_id MUST be provided
|
||||
}
|
|
@ -23,77 +23,77 @@ func NewMe(raw string) (*Me, error) {
|
|||
me := http.AcquireURI()
|
||||
if err := me.Parse(nil, []byte(raw)); err != nil {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: err.Error(),
|
||||
URI: "https://indieauth.net/source/#user-profile-url",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
scheme := string(me.Scheme())
|
||||
if scheme != "http" && scheme != "https" {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "profile URL MUST have either an https or http scheme",
|
||||
URI: "https://indieauth.net/source/#user-profile-url",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
path := string(me.PathOriginal())
|
||||
if path == "" || strings.Contains(path, "/.") || strings.Contains(path, "/..") {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "profile URL MUST contain a path component (/ is a valid path), MUST NOT " +
|
||||
"contain single-dot or double-dot path segments",
|
||||
URI: "https://indieauth.net/source/#user-profile-url",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
if me.Hash() != nil {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "profile URL MUST NOT contain a fragment component",
|
||||
URI: "https://indieauth.net/source/#user-profile-url",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
if me.Username() != nil || me.Password() != nil {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "profile URL MUST NOT contain a username or password component",
|
||||
URI: "https://indieauth.net/source/#user-profile-url",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
domain := string(me.Host())
|
||||
if domain == "" {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "profile host name MUST be a domain name",
|
||||
URI: "https://indieauth.net/source/#user-profile-url",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
if _, port, _ := net.SplitHostPort(domain); port != "" {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "profile MUST NOT contain a port",
|
||||
URI: "https://indieauth.net/source/#user-profile-url",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
if net.ParseIP(domain) != nil {
|
||||
return nil, Error{
|
||||
Code: "invalid_request",
|
||||
Code: ErrorCodeInvalidRequest,
|
||||
Description: "profile MUST NOT be ipv4 or ipv6 addresses",
|
||||
URI: "https://indieauth.net/source/#user-profile-url",
|
||||
Frame: xerrors.Caller(1),
|
||||
frame: xerrors.Caller(1),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue