auth/internal/token/delivery/http/token_http.go

136 lines
3.2 KiB
Go

package http
import (
"bytes"
"strings"
"github.com/fasthttp/router"
json "github.com/goccy/go-json"
http "github.com/valyala/fasthttp"
"golang.org/x/xerrors"
"source.toby3d.me/website/indieauth/internal/common"
"source.toby3d.me/website/indieauth/internal/domain"
"source.toby3d.me/website/indieauth/internal/token"
)
type (
RevocationRequest struct {
Action string
Token string
}
//nolint: tagliatelle
VerificationResponse struct {
Me string `json:"me"`
ClientID string `json:"client_id"`
Scope string `json:"scope"`
}
RevocationResponse struct{}
RequestHandler struct {
tokener token.UseCase
}
)
const (
Action string = "action"
ActionRevoke string = "revoke"
)
func NewRequestHandler(tokener token.UseCase) *RequestHandler {
return &RequestHandler{
tokener: tokener,
}
}
func (h *RequestHandler) Register(r *router.Router) {
r.GET("/token", h.Read)
r.POST("/token", h.Update)
}
func (h *RequestHandler) Read(ctx *http.RequestCtx) {
ctx.SetContentType(common.MIMEApplicationJSON)
ctx.SetStatusCode(http.StatusOK)
rawToken := ctx.Request.Header.Peek(http.HeaderAuthorization)
t, err := h.tokener.Verify(ctx, string(bytes.TrimPrefix(rawToken, []byte("Bearer "))))
if err != nil {
if xerrors.Is(err, token.ErrRevoke) {
ctx.Error(http.StatusMessage(http.StatusUnauthorized), http.StatusUnauthorized)
} else {
ctx.Error(err.Error(), http.StatusBadRequest)
}
return
}
if t == nil {
ctx.Error(http.StatusMessage(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
if err := json.NewEncoder(ctx).Encode(&VerificationResponse{
ClientID: t.ClientID,
Me: t.Me,
Scope: strings.Join(t.Scopes, " "),
}); err != nil {
ctx.Error(err.Error(), http.StatusInternalServerError)
}
}
func (h *RequestHandler) Update(ctx *http.RequestCtx) {
if strings.EqualFold(string(ctx.FormValue(Action)), ActionRevoke) {
h.Revocation(ctx)
}
}
func (h *RequestHandler) Revocation(ctx *http.RequestCtx) {
ctx.SetContentType(common.MIMEApplicationJSON)
ctx.SetStatusCode(http.StatusOK)
encoder := json.NewEncoder(ctx)
req := new(RevocationRequest)
if err := req.bind(ctx); err != nil {
ctx.Error(err.Error(), http.StatusBadRequest)
return
}
if err := h.tokener.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 (r *RevocationRequest) bind(ctx *http.RequestCtx) error {
if r.Action = string(ctx.FormValue(Action)); !strings.EqualFold(r.Action, ActionRevoke) {
return domain.Error{
Code: "invalid_request",
Description: "request MUST contain 'action' key with value 'revoke'",
URI: "https://indieauth.spec.indieweb.org/#token-revocation-request",
Frame: xerrors.Caller(1),
}
}
if r.Token = string(ctx.FormValue("token")); r.Token == "" {
return domain.Error{
Code: "invalid_request",
Description: "request MUST contain the 'token' key with the valid access token as its value",
URI: "https://indieauth.spec.indieweb.org/#token-revocation-request",
Frame: xerrors.Caller(1),
}
}
return nil
}