108 lines
2.7 KiB
Go
108 lines
2.7 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"forge.cadoles.com/Cadoles/emissary/internal/auth"
|
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
|
"github.com/lestrrat-go/jwx/v2/jws"
|
|
"github.com/lestrrat-go/jwx/v2/jwt"
|
|
"github.com/pkg/errors"
|
|
"gitlab.com/wpetit/goweb/logger"
|
|
)
|
|
|
|
const DefaultAcceptableSkew = 5 * time.Minute
|
|
|
|
type Authenticator struct {
|
|
repo datastore.AgentRepository
|
|
acceptableSkew time.Duration
|
|
}
|
|
|
|
// Authenticate implements auth.Authenticator.
|
|
func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth.User, error) {
|
|
authorization := r.Header.Get("Authorization")
|
|
if authorization == "" {
|
|
return nil, errors.WithStack(auth.ErrUnauthenticated)
|
|
}
|
|
|
|
rawToken := strings.TrimPrefix(authorization, "Bearer ")
|
|
if rawToken == "" {
|
|
return nil, errors.WithStack(auth.ErrUnauthenticated)
|
|
}
|
|
|
|
token, err := jwt.Parse([]byte(rawToken), jwt.WithVerify(false))
|
|
if err != nil {
|
|
logger.Debug(ctx, "could not parse jwt token", logger.CapturedE(errors.WithStack(err)))
|
|
return nil, errors.WithStack(auth.ErrUnauthenticated)
|
|
}
|
|
|
|
rawThumbprint, exists := token.Get(keyThumbprint)
|
|
if !exists {
|
|
return nil, errors.WithStack(auth.ErrUnauthenticated)
|
|
}
|
|
|
|
thumbrint, ok := rawThumbprint.(string)
|
|
if !ok {
|
|
logger.Debug(ctx, "unexpected claim value", logger.F("claim", rawThumbprint), logger.F("value", rawThumbprint))
|
|
return nil, errors.WithStack(auth.ErrUnauthenticated)
|
|
}
|
|
|
|
agents, _, err := a.repo.Query(
|
|
ctx,
|
|
datastore.WithAgentQueryThumbprints(thumbrint),
|
|
datastore.WithAgentQueryLimit(1),
|
|
)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
if len(agents) != 1 {
|
|
logger.Debug(ctx, "unexpected number of found agents", logger.F("total", len(agents)))
|
|
return nil, errors.WithStack(auth.ErrUnauthenticated)
|
|
}
|
|
|
|
agent, err := a.repo.Get(
|
|
ctx,
|
|
agents[0].ID,
|
|
)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
_, err = jwt.Parse(
|
|
[]byte(rawToken),
|
|
jwt.WithKeySet(agent.KeySet.Set, jws.WithRequireKid(false)),
|
|
jwt.WithValidate(true),
|
|
jwt.WithAcceptableSkew(a.acceptableSkew),
|
|
)
|
|
if err != nil {
|
|
logger.Error(ctx, "could not parse jwt", logger.CapturedE(errors.WithStack(err)))
|
|
return nil, errors.WithStack(auth.ErrUnauthenticated)
|
|
}
|
|
|
|
contactedAt := time.Now().UTC()
|
|
|
|
agent, err = a.repo.Update(ctx, agent.ID, datastore.WithAgentUpdateContactedAt(contactedAt))
|
|
if err != nil {
|
|
return nil, errors.WithStack(auth.ErrUnauthenticated)
|
|
}
|
|
|
|
user := &User{
|
|
agent: agent,
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func NewAuthenticator(repo datastore.AgentRepository, acceptableSkew time.Duration) *Authenticator {
|
|
return &Authenticator{
|
|
repo: repo,
|
|
acceptableSkew: acceptableSkew,
|
|
}
|
|
}
|
|
|
|
var _ auth.Authenticator = &Authenticator{}
|