258 lines
5.8 KiB
Go
258 lines
5.8 KiB
Go
package http
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/fasthttp/router"
|
|
json "github.com/goccy/go-json"
|
|
http "github.com/valyala/fasthttp"
|
|
"golang.org/x/xerrors"
|
|
|
|
"source.toby3d.me/toby3d/form"
|
|
"source.toby3d.me/toby3d/middleware"
|
|
"source.toby3d.me/website/indieauth/internal/common"
|
|
"source.toby3d.me/website/indieauth/internal/domain"
|
|
"source.toby3d.me/website/indieauth/internal/token"
|
|
)
|
|
|
|
type (
|
|
ExchangeRequest struct {
|
|
ClientID *domain.ClientID `form:"client_id"`
|
|
RedirectURI *domain.URL `form:"redirect_uri"`
|
|
GrantType domain.GrantType `form:"grant_type"`
|
|
Code string `form:"code"`
|
|
CodeVerifier string `form:"code_verifier"`
|
|
}
|
|
|
|
RevokeRequest struct {
|
|
Action domain.Action `form:"action"`
|
|
Token string `form:"token"`
|
|
}
|
|
|
|
TicketRequest struct {
|
|
Action domain.Action `form:"action"`
|
|
Ticket string `form:"ticket"`
|
|
}
|
|
|
|
//nolint: tagliatelle
|
|
ExchangeResponse struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
Scope string `json:"scope"`
|
|
Me string `json:"me"`
|
|
}
|
|
|
|
//nolint: tagliatelle
|
|
VerificationResponse struct {
|
|
Me *domain.Me `json:"me"`
|
|
ClientID *domain.ClientID `json:"client_id"`
|
|
Scope domain.Scopes `json:"scope"`
|
|
}
|
|
|
|
RevocationResponse struct{}
|
|
|
|
RequestHandler struct {
|
|
tokens token.UseCase
|
|
// TODO(toby3d): tickets ticket.UseCase
|
|
}
|
|
)
|
|
|
|
func NewRequestHandler(tokens token.UseCase /*, tickets ticket.UseCase*/) *RequestHandler {
|
|
return &RequestHandler{
|
|
tokens: tokens,
|
|
// tickets: tickets,
|
|
}
|
|
}
|
|
|
|
func (h *RequestHandler) Register(r *router.Router) {
|
|
chain := middleware.Chain{
|
|
middleware.LogFmt(),
|
|
}
|
|
|
|
r.GET("/token", chain.RequestHandler(h.handleValidate))
|
|
r.POST("/token", chain.RequestHandler(h.handleAction))
|
|
}
|
|
|
|
func (h *RequestHandler) handleValidate(ctx *http.RequestCtx) {
|
|
ctx.SetContentType(common.MIMEApplicationJSONCharsetUTF8)
|
|
ctx.SetStatusCode(http.StatusOK)
|
|
|
|
encoder := json.NewEncoder(ctx)
|
|
|
|
t, err := h.tokens.Verify(ctx, strings.TrimPrefix(string(ctx.Request.Header.Peek(http.HeaderAuthorization)),
|
|
"Bearer "))
|
|
if err != nil || t == nil {
|
|
ctx.SetStatusCode(http.StatusUnauthorized)
|
|
encoder.Encode(&domain.Error{
|
|
Code: "unauthorized_client",
|
|
Description: err.Error(),
|
|
Frame: xerrors.Caller(1),
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
encoder.Encode(&VerificationResponse{
|
|
ClientID: t.ClientID,
|
|
Me: t.Me,
|
|
Scope: t.Scope,
|
|
})
|
|
}
|
|
|
|
func (h *RequestHandler) handleAction(ctx *http.RequestCtx) {
|
|
ctx.SetContentType(common.MIMEApplicationJSONCharsetUTF8)
|
|
|
|
encoder := json.NewEncoder(ctx)
|
|
|
|
switch {
|
|
case ctx.PostArgs().Has("grant_type"):
|
|
h.handleExchange(ctx)
|
|
case ctx.PostArgs().Has("action"):
|
|
action, err := domain.ParseAction(string(ctx.PostArgs().Peek("action")))
|
|
if err != nil {
|
|
ctx.SetStatusCode(http.StatusBadRequest)
|
|
encoder.Encode(domain.Error{
|
|
Code: "invalid_request",
|
|
Description: err.Error(),
|
|
Frame: xerrors.Caller(1),
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
switch action {
|
|
case domain.ActionRevoke:
|
|
h.handleRevoke(ctx)
|
|
case domain.ActionTicket:
|
|
h.handleTicket(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *RequestHandler) handleExchange(ctx *http.RequestCtx) {
|
|
ctx.SetContentType(common.MIMEApplicationJSONCharsetUTF8)
|
|
|
|
encoder := json.NewEncoder(ctx)
|
|
|
|
req := new(ExchangeRequest)
|
|
if err := req.bind(ctx); err != nil {
|
|
ctx.SetStatusCode(http.StatusBadRequest)
|
|
encoder.Encode(err)
|
|
|
|
return
|
|
}
|
|
|
|
token, err := h.tokens.Exchange(ctx, token.ExchangeOptions{
|
|
ClientID: req.ClientID,
|
|
RedirectURI: req.RedirectURI,
|
|
Code: req.Code,
|
|
CodeVerifier: req.CodeVerifier,
|
|
})
|
|
if err != nil {
|
|
ctx.SetStatusCode(http.StatusBadRequest)
|
|
encoder.Encode(&domain.Error{
|
|
Description: err.Error(),
|
|
Frame: xerrors.Caller(1),
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
encoder.Encode(&ExchangeResponse{
|
|
AccessToken: token.AccessToken,
|
|
TokenType: "Bearer",
|
|
Scope: token.Scope.String(),
|
|
Me: token.Me.String(),
|
|
})
|
|
}
|
|
|
|
func (h *RequestHandler) handleRevoke(ctx *http.RequestCtx) {
|
|
ctx.SetContentType(common.MIMEApplicationJSONCharsetUTF8)
|
|
ctx.SetStatusCode(http.StatusOK)
|
|
|
|
encoder := json.NewEncoder(ctx)
|
|
|
|
req := new(RevokeRequest)
|
|
if err := req.bind(ctx); err != nil {
|
|
ctx.Error(err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
}
|
|
|
|
if err := h.tokens.Revoke(ctx, req.Token); err != nil {
|
|
ctx.Error(err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
}
|
|
|
|
if err := encoder.Encode(&RevocationResponse{}); err != nil {
|
|
ctx.Error(err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func (h *RequestHandler) handleTicket(ctx *http.RequestCtx) {
|
|
ctx.SetContentType(common.MIMEApplicationJSONCharsetUTF8)
|
|
ctx.SetStatusCode(http.StatusOK)
|
|
|
|
encoder := json.NewEncoder(ctx)
|
|
|
|
req := new(TicketRequest)
|
|
if err := req.bind(ctx); err != nil {
|
|
ctx.SetStatusCode(http.StatusBadRequest)
|
|
encoder.Encode(err)
|
|
|
|
return
|
|
}
|
|
|
|
/* TODO(toby3d)
|
|
token, err := h.tickets.Redeem(ctx, req.Ticket)
|
|
if err != nil {
|
|
ctx.SetStatusCode(http.StatusInternalServerError)
|
|
encoder.Encode(domain.Error{
|
|
Description: err.Error(),
|
|
Frame: xerrors.Caller(1),
|
|
})
|
|
|
|
return
|
|
}
|
|
*/
|
|
|
|
encoder.Encode(ExchangeResponse{})
|
|
}
|
|
|
|
func (r *ExchangeRequest) bind(ctx *http.RequestCtx) error {
|
|
if err := form.Unmarshal(ctx.PostArgs(), r); err != nil {
|
|
return domain.Error{
|
|
Code: "invalid_request",
|
|
Description: err.Error(),
|
|
Frame: xerrors.Caller(1),
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *RevokeRequest) bind(ctx *http.RequestCtx) error {
|
|
if err := form.Unmarshal(ctx.PostArgs(), r); err != nil {
|
|
return domain.Error{
|
|
Code: "invalid_request",
|
|
Description: err.Error(),
|
|
Frame: xerrors.Caller(1),
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *TicketRequest) bind(ctx *http.RequestCtx) error {
|
|
if err := form.Unmarshal(ctx.PostArgs(), r); err != nil {
|
|
return domain.Error{
|
|
Code: "invalid_request",
|
|
Description: err.Error(),
|
|
Frame: xerrors.Caller(1),
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|