961e841bd7
errs.Class should not contain "error" in the name, since that causes a lot of stutter in the error logs. As an example a log line could end up looking like: ERROR node stats service error: satellitedbs error: node stats database error: no rows Whereas something like: ERROR nodestats service: satellitedbs: nodestatsdb: no rows Would contain all the necessary information without the stutter. Change-Id: I7b7cb7e592ebab4bcfadc1eef11122584d2b20e0
118 lines
2.9 KiB
Go
118 lines
2.9 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package authorization
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"path"
|
|
|
|
"github.com/zeebo/errs"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"storj.io/common/errs2"
|
|
)
|
|
|
|
// ErrEndpoint is the default error class for the authorization endpoint.
|
|
var ErrEndpoint = errs.Class("authorization endpoint")
|
|
|
|
// Endpoint provides a http endpoint for interacting with an authorization service.
|
|
type Endpoint struct {
|
|
log *zap.Logger
|
|
service *Service
|
|
server http.Server
|
|
listener net.Listener
|
|
}
|
|
|
|
// NewEndpoint creates a authorization endpoint.
|
|
func NewEndpoint(log *zap.Logger, service *Service, listener net.Listener) *Endpoint {
|
|
mux := http.NewServeMux()
|
|
endpoint := &Endpoint{
|
|
log: log,
|
|
listener: listener,
|
|
service: service,
|
|
server: http.Server{
|
|
Addr: listener.Addr().String(),
|
|
Handler: mux,
|
|
},
|
|
}
|
|
|
|
mux.HandleFunc("/v1/authorizations/", endpoint.handleAuthorization)
|
|
|
|
return endpoint
|
|
}
|
|
|
|
// Run starts the endpoint HTTP server and waits for the context to be
|
|
// cancelled or for `Close` to be called.
|
|
func (endpoint *Endpoint) Run(ctx context.Context) (err error) {
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
var group errgroup.Group
|
|
group.Go(func() error {
|
|
<-ctx.Done()
|
|
return endpoint.server.Shutdown(context.Background())
|
|
})
|
|
group.Go(func() error {
|
|
defer cancel()
|
|
err := endpoint.server.Serve(endpoint.listener)
|
|
if errs2.IsCanceled(err) || errors.Is(err, http.ErrServerClosed) {
|
|
err = nil
|
|
}
|
|
return err
|
|
})
|
|
|
|
return group.Wait()
|
|
}
|
|
|
|
// Close closes the endpoint HTTP server.
|
|
func (endpoint *Endpoint) Close() error {
|
|
return endpoint.server.Close()
|
|
}
|
|
|
|
func (endpoint *Endpoint) handleAuthorization(writer http.ResponseWriter, httpReq *http.Request) {
|
|
var err error
|
|
ctx := httpReq.Context()
|
|
mon.Task()(&ctx)(&err)
|
|
|
|
if httpReq.Method != http.MethodPut {
|
|
msg := fmt.Sprintf("unsupported HTTP method: %s", httpReq.Method)
|
|
// NB: err set for `mon.Task` call.
|
|
err = ErrEndpoint.New("%s", msg)
|
|
http.Error(writer, msg, http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
userID := path.Base(httpReq.URL.Path)
|
|
if userID == "authorizations" || userID == "" {
|
|
msg := "missing user ID body"
|
|
err = ErrEndpoint.New("%s", msg)
|
|
http.Error(writer, msg, http.StatusUnprocessableEntity)
|
|
return
|
|
}
|
|
|
|
token, err := endpoint.service.GetOrCreate(ctx, userID)
|
|
if err != nil {
|
|
msg := "error creating authorization"
|
|
err = ErrEndpoint.Wrap(err)
|
|
endpoint.log.Error(msg, zap.Error(err))
|
|
http.Error(writer, msg, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
writer.WriteHeader(http.StatusCreated)
|
|
if _, err = writer.Write([]byte(token.String())); err != nil {
|
|
msg := "error writing response"
|
|
err = ErrEndpoint.Wrap(err)
|
|
endpoint.log.Error(msg, zap.Error(err))
|
|
// NB: status cannot be changed and the resource *was* created.
|
|
http.Error(writer, msg, http.StatusCreated)
|
|
return
|
|
}
|
|
}
|