Skip to content
Snippets Groups Projects
revocationEndpoint.go 6.47 KiB
Newer Older
package revocation

import (

	"github.com/gofiber/fiber/v2"
	"github.com/jmoiron/sqlx"
	"github.com/oidc-mytoken/utils/utils/jwtutils"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
Gabriel Zachmann's avatar
Gabriel Zachmann committed
	"github.com/oidc-mytoken/api/v0"
	"github.com/oidc-mytoken/server/internal/config"
	"github.com/oidc-mytoken/server/internal/db"
	helper "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/mytokenrepohelper"
	"github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/transfercoderepo"
	"github.com/oidc-mytoken/server/internal/model"
	"github.com/oidc-mytoken/server/internal/mytoken"
	eventService "github.com/oidc-mytoken/server/internal/mytoken/event"
	event "github.com/oidc-mytoken/server/internal/mytoken/event/pkg"
	mytokenPkg "github.com/oidc-mytoken/server/internal/mytoken/pkg"
	"github.com/oidc-mytoken/server/internal/mytoken/universalmytoken"
	"github.com/oidc-mytoken/server/internal/utils/ctxutils"
	"github.com/oidc-mytoken/server/internal/utils/errorfmt"
	"github.com/oidc-mytoken/server/internal/utils/logger"
// HandleRevoke handles requests to the revocation endpoint
func HandleRevoke(ctx *fiber.Ctx) error {
	rlog := logger.GetRequestLogger(ctx)
	rlog.Debug("Handle revocation request")
Gabriel Zachmann's avatar
Gabriel Zachmann committed
	req := api.RevocationRequest{}
	if err := ctx.BodyParser(&req); err != nil {
		return model.ErrorToBadRequestErrorResponse(err).Send(ctx)
	}
	rlog.WithField("parsed request", fmt.Sprintf("%+v", req)).WithField(
		"body", string(ctx.Body()),
	).Trace("Parsed revocation request")
	if req.Token == "" {
		req.Token = ctx.Cookies("mytoken")
		if req.Token == "" {
			return model.Response{
				Status:   fiber.StatusBadRequest,
				Response: model.BadRequestError("no token given"),
			}.Send(ctx)
		}
		if req.MOMID == "" {
	if req.MOMID != "" {
		errRes := revokeByID(rlog, req, ctxutils.ClientMetaData(ctx))
		if errRes != nil {
			return errRes.Send(ctx)
		}
		return ctx.SendStatus(fiber.StatusNoContent)
	errRes := revokeAnyToken(rlog, nil, req.Token, req.OIDCIssuer, req.Recursive)
	if errRes != nil {
		return errRes.Send(ctx)
	}
	if clearCookie {
		return model.Response{
			Status: fiber.StatusNoContent,
Gabriel Zachmann's avatar
Gabriel Zachmann committed
			Cookies: []*fiber.Cookie{
				{
					Name:     "mytoken",
					Value:    "",
					Path:     "/api",
					Expires:  time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
					Secure:   config.Get().Server.Secure,
					HTTPOnly: true,
					SameSite: "Strict",
				},
			},
	return ctx.SendStatus(fiber.StatusNoContent)
}

func revokeByID(rlog log.Ext1FieldLogger, req api.RevocationRequest, clientMetadata *api.ClientMetaData) *model.
	Response {
	token, err := universalmytoken.Parse(rlog, req.Token)
	if err != nil {
		return model.ErrorToBadRequestErrorResponse(err)
	}
	authToken, err := mytokenPkg.ParseJWT(token.JWT)
	if err != nil {
		return model.ErrorToBadRequestErrorResponse(err)
	}
	isParent, err := helper.MOMIDHasParent(rlog, nil, req.MOMID, authToken.ID)
	if err != nil {
		return model.ErrorToInternalServerErrorResponse(err)
	}
	if !isParent && !authToken.Capabilities.Has(api.CapabilityRevokeAnyToken) {
		return &model.Response{
			Status: fiber.StatusForbidden,
			Response: api.Error{
				Error: api.ErrorStrInsufficientCapabilities,
				ErrorDescription: fmt.Sprintf(
					"The provided token is neither a parent of the the token to be revoked"+
						" nor does it have the '%s' capability", api.CapabilityRevokeAnyToken.Name,
				),
	same, err := helper.CheckMytokensAreForSameUser(rlog, nil, req.MOMID, authToken.ID)
	if err != nil {
		return model.ErrorToInternalServerErrorResponse(err)
	}
	if !same {
		return &model.Response{
			Status: fiber.StatusForbidden,
			Response: api.Error{
				Error:            api.ErrorStrInvalidGrant,
				ErrorDescription: "The provided token cannot be used to revoke this mom_id",
	if err = db.Transact(
		rlog, func(tx *sqlx.Tx) error {
			if err = helper.RevokeMT(rlog, tx, req.MOMID, req.Recursive); err != nil {
				return err
			}
			return eventService.LogEvent(
				rlog, tx, eventService.MTEvent{
					Event: event.FromNumber(event.RevokedOtherToken, fmt.Sprintf("mom_id: %s", req.MOMID)),
					MTID:  authToken.ID,
				}, *clientMetadata,
			)
		},
	); err != nil {
		return model.ErrorToInternalServerErrorResponse(err)
	}
func revokeAnyToken(
	rlog log.Ext1FieldLogger, tx *sqlx.Tx, token, issuer string, recursive bool,
) (errRes *model.Response) {
	if jwtutils.IsJWT(token) { // normal Mytoken
		return revokeMytoken(rlog, tx, token, issuer, recursive)
	} else if len(token) < api.MinShortTokenLen { // Transfer Code
		return revokeTransferCode(rlog, tx, token, issuer)
	} else { // Short Token
		shortToken := transfercoderepo.ParseShortToken(token)
		var valid bool
Gabriel Zachmann's avatar
Gabriel Zachmann committed
		if err := db.RunWithinTransaction(
			rlog, tx, func(tx *sqlx.Tx) error {
				jwt, v, err := shortToken.JWT(rlog, tx)
Gabriel Zachmann's avatar
Gabriel Zachmann committed
				valid = v
				if err != nil {
					return err
				}
				token = jwt
				return shortToken.Delete(rlog, tx)
Gabriel Zachmann's avatar
Gabriel Zachmann committed
			},
		); err != nil {
			rlog.Errorf("%s", errorfmt.Full(err))
			return model.ErrorToInternalServerErrorResponse(err)
		return revokeMytoken(rlog, tx, token, issuer, recursive)
func revokeMytoken(rlog log.Ext1FieldLogger, tx *sqlx.Tx, jwt, issuer string, recursive bool) (errRes *model.Response) {
	mt, err := mytokenPkg.ParseJWT(jwt)
	if err != nil {
	if issuer != "" && mt.OIDCIssuer != issuer {
		return &model.Response{
			Status:   fiber.StatusBadRequest,
			Response: model.BadRequestError("token not for specified issuer"),
	return mytoken.RevokeMytoken(rlog, tx, mt.ID, jwt, recursive, mt.OIDCIssuer)
func revokeTransferCode(rlog log.Ext1FieldLogger, tx *sqlx.Tx, token, issuer string) (errRes *model.Response) {
	transferCode := transfercoderepo.ParseTransferCode(token)
Gabriel Zachmann's avatar
Gabriel Zachmann committed
	err := db.RunWithinTransaction(
		rlog, tx, func(tx *sqlx.Tx) error {
			revokeMT, err := transferCode.GetRevokeJWT(rlog, tx)
			if err != nil {
				return err
			}
Gabriel Zachmann's avatar
Gabriel Zachmann committed
			if revokeMT {
				jwt, valid, err := transferCode.JWT(rlog, tx)
Gabriel Zachmann's avatar
Gabriel Zachmann committed
				if err != nil {
					return err
				}
				if valid { // if !valid the jwt field could not be decrypted correctly, so we can skip that,
					// but still delete the TransferCode
					errRes = revokeAnyToken(rlog, tx, jwt, issuer, true)
Gabriel Zachmann's avatar
Gabriel Zachmann committed
					if errRes != nil {
						return errors.New("placeholder")
					}
			return transferCode.Delete(rlog, tx)
Gabriel Zachmann's avatar
Gabriel Zachmann committed
		},
	)
	if err != nil && errRes == nil {
		rlog.Errorf("%s", errorfmt.Full(err))
		return model.ErrorToInternalServerErrorResponse(err)