auth/internal/auth/delivery/http/auth_http_schema.go

195 lines
6.2 KiB
Go

package http
import (
"errors"
"net/http"
"strings"
"source.toby3d.me/toby3d/auth/internal/domain"
"source.toby3d.me/toby3d/form"
)
type (
AuthAuthorizationRequest struct {
// The client URL.
ClientID domain.ClientID `form:"client_id"`
// The redirect URL indicating where the user should be
// redirected to after approving the request.
RedirectURI domain.URL `form:"redirect_uri"`
// The URL that the user entered.
Me domain.Me `form:"me"`
// The hashing method used to calculate the code challenge.
CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
// Indicates to the authorization server that an authorization
// code should be returned as the response.
ResponseType domain.ResponseType `form:"response_type"` // code
// A parameter set by the client which will be included when the
// user is redirected back to the client. This is used to
// prevent CSRF attacks. The authorization server MUST return
// the unmodified state value back to the client.
State string `form:"state"`
// The code challenge as previously described.
CodeChallenge string `form:"code_challenge,omitempty"`
// A space-separated list of scopes the client is requesting,
// e.g. "profile", or "profile create". If the client omits this
// value, the authorization server MUST NOT issue an access
// token for this authorization code. Only the user's profile
// URL may be returned without any scope requested.
Scope domain.Scopes `form:"scope,omitempty"`
}
AuthVerifyRequest struct {
ClientID domain.ClientID `form:"client_id"`
Me domain.Me `form:"me"`
RedirectURI domain.URL `form:"redirect_uri"`
CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
ResponseType domain.ResponseType `form:"response_type"`
Authorize string `form:"authorize"`
CodeChallenge string `form:"code_challenge,omitempty"`
State string `form:"state"`
Provider string `form:"provider"`
Scope domain.Scopes `form:"scope[],omitempty"`
}
AuthExchangeRequest struct {
GrantType domain.GrantType `form:"grant_type"` // authorization_code
// The client's URL, which MUST match the client_id used in the
// authentication request.
ClientID domain.ClientID `form:"client_id"`
// The client's redirect URL, which MUST match the initial
// authentication request.
RedirectURI domain.URL `form:"redirect_uri"`
// The authorization code received from the authorization
// endpoint in the redirect.
Code string `form:"code"`
// The original plaintext random string generated before
// starting the authorization request.
CodeVerifier string `form:"code_verifier"`
}
AuthExchangeResponse struct {
Me domain.Me `json:"me"`
Profile *AuthProfileResponse `json:"profile,omitempty"`
}
AuthProfileResponse struct {
Email *domain.Email `json:"email,omitempty"`
Photo *domain.URL `json:"photo,omitempty"`
URL *domain.URL `json:"url,omitempty"`
Name string `json:"name,omitempty"`
}
)
func NewAuthAuthorizationRequest() *AuthAuthorizationRequest {
return &AuthAuthorizationRequest{
ClientID: domain.ClientID{},
CodeChallenge: "",
CodeChallengeMethod: &domain.CodeChallengeMethodUnd,
Me: domain.Me{},
RedirectURI: domain.URL{},
ResponseType: domain.ResponseTypeUnd,
Scope: make(domain.Scopes, 0),
State: "",
}
}
//nolint:cyclop
func (r *AuthAuthorizationRequest) bind(req *http.Request) error {
indieAuthError := new(domain.Error)
if err := form.Unmarshal([]byte(req.URL.Query().Encode()), r); err != nil {
if errors.As(err, indieAuthError) {
return indieAuthError
}
return domain.NewError(domain.ErrorCodeInvalidRequest, err.Error(),
"https://indieauth.net/source/#authorization-request")
}
if r.ResponseType == domain.ResponseTypeID {
r.ResponseType = domain.ResponseTypeCode
}
return nil
}
func NewAuthVerifyRequest() *AuthVerifyRequest {
return &AuthVerifyRequest{
Authorize: "",
ClientID: domain.ClientID{},
CodeChallenge: "",
CodeChallengeMethod: &domain.CodeChallengeMethodUnd,
Me: domain.Me{},
Provider: "",
RedirectURI: domain.URL{},
ResponseType: domain.ResponseTypeUnd,
Scope: make(domain.Scopes, 0),
State: "",
}
}
//nolint:funlen,cyclop
func (r *AuthVerifyRequest) bind(req *http.Request) error {
indieAuthError := new(domain.Error)
if err := req.ParseForm(); err != nil {
return domain.NewError(domain.ErrorCodeInvalidRequest, err.Error(),
"https://indieauth.net/source/#authorization-request")
}
if err := form.Unmarshal([]byte(req.PostForm.Encode()), r); err != nil {
if errors.As(err, indieAuthError) {
return indieAuthError
}
return domain.NewError(domain.ErrorCodeInvalidRequest, err.Error(),
"https://indieauth.net/source/#authorization-request")
}
// NOTE(toby3d): backwards-compatible support.
// See: https://aaronparecki.com/2020/12/03/1/indieauth-2020#response-type
if r.ResponseType == domain.ResponseTypeID {
r.ResponseType = domain.ResponseTypeCode
}
r.Provider = strings.ToLower(r.Provider)
if !strings.EqualFold(r.Authorize, "allow") && !strings.EqualFold(r.Authorize, "deny") {
return domain.NewError(domain.ErrorCodeInvalidRequest, "cannot validate verification request",
"https://indieauth.net/source/#authorization-request")
}
return nil
}
func (r *AuthExchangeRequest) bind(req *http.Request) error {
indieAuthError := new(domain.Error)
if err := req.ParseForm(); err != nil {
return domain.NewError(domain.ErrorCodeInvalidRequest, err.Error(),
"https://indieauth.net/source/#authorization-request")
}
if err := form.Unmarshal([]byte(req.PostForm.Encode()), r); err != nil {
if errors.As(err, indieAuthError) {
return indieAuthError
}
return domain.NewError(domain.ErrorCodeInvalidRequest, "cannot validate verification request",
"https://indieauth.net/source/#redeeming-the-authorization-code")
}
return nil
}