Skip to content
Snippets Groups Projects
supertoken.go 7.35 KiB
Newer Older
  • Learn to ignore specific revisions
  • Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    package supertoken
    
    import (
    	"database/sql/driver"
    	"fmt"
    	"time"
    
    
    	"github.com/jmoiron/sqlx"
    	"github.com/zachmann/mytoken/internal/supertoken/event"
    	event2 "github.com/zachmann/mytoken/internal/supertoken/event/pkg"
    
    	"github.com/zachmann/mytoken/internal/db"
    	"github.com/zachmann/mytoken/internal/utils"
    
    
    	"github.com/zachmann/mytoken/internal/model"
    
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	"github.com/dgrijalva/jwt-go"
    	uuid "github.com/satori/go.uuid"
    	"github.com/zachmann/mytoken/internal/config"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	response "github.com/zachmann/mytoken/internal/endpoints/token/super/pkg"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	"github.com/zachmann/mytoken/internal/jws"
    	"github.com/zachmann/mytoken/internal/supertoken/capabilities"
    	"github.com/zachmann/mytoken/internal/supertoken/restrictions"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	"github.com/zachmann/mytoken/internal/utils/issuerUtils"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    )
    
    // SuperToken is a mytoken SuperToken
    type SuperToken struct {
    
    	Issuer               string                    `json:"iss"`
    	Subject              string                    `json:"sub"`
    	ExpiresAt            int64                     `json:"exp,omitempty"`
    	NotBefore            int64                     `json:"nbf"`
    	IssuedAt             int64                     `json:"iat"`
    	ID                   uuid.UUID                 `json:"jti"`
    	Audience             string                    `json:"aud"`
    	OIDCSubject          string                    `json:"oidc_sub"`
    	OIDCIssuer           string                    `json:"oidc_iss"`
    	Restrictions         restrictions.Restrictions `json:"restrictions,omitempty"`
    	Capabilities         capabilities.Capabilities `json:"capabilities"`
    	SubtokenCapabilities capabilities.Capabilities `json:"subtoken_capabilities,omitempty"`
    	jwt                  string
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    }
    
    
    func (st *SuperToken) VerifyID() bool {
    	if len(st.ID.String()) == 0 {
    		return false
    	}
    	return true
    }
    
    func (st *SuperToken) VerifySubject() bool {
    	if len(st.Subject) == 0 {
    		return false
    	}
    	if st.Subject != issuerUtils.CombineSubIss(st.OIDCSubject, st.OIDCIssuer) {
    		return false
    	}
    	return true
    }
    
    func (st *SuperToken) VerifyCapabilities(required ...capabilities.Capability) bool {
    	if st.Capabilities == nil || len(st.Capabilities) == 0 {
    		return false
    	}
    	for _, c := range required {
    		if !st.Capabilities.Has(c) {
    			return false
    		}
    	}
    	return true
    }
    
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    // NewSuperToken creates a new SuperToken
    
    func NewSuperToken(oidcSub, oidcIss string, r restrictions.Restrictions, c, sc capabilities.Capabilities) *SuperToken {
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	now := time.Now().Unix()
    	st := &SuperToken{
    
    		ID:                   uuid.NewV4(),
    		IssuedAt:             now,
    		NotBefore:            now,
    		Issuer:               config.Get().IssuerURL,
    		Subject:              issuerUtils.CombineSubIss(oidcSub, oidcIss),
    		Audience:             config.Get().IssuerURL,
    		OIDCIssuer:           oidcIss,
    		OIDCSubject:          oidcSub,
    		Capabilities:         c,
    		SubtokenCapabilities: sc,
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	}
    	if len(r) > 0 {
    		st.Restrictions = r
    		exp := r.GetExpires()
    		if exp != 0 {
    			st.ExpiresAt = exp
    		}
    		nbf := r.GetNotBefore()
    		if nbf != 0 && nbf > now {
    			st.NotBefore = nbf
    		}
    	}
    	return st
    }
    
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    func (st *SuperToken) ExpiresIn() uint64 {
    	now := time.Now().Unix()
    	expAt := st.ExpiresAt
    
    	if expAt > 0 && expAt > now {
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    		return uint64(expAt - now)
    	}
    	return 0
    }
    
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    func (st *SuperToken) Valid() error {
    	standardClaims := jwt.StandardClaims{
    		Audience:  st.Audience,
    		ExpiresAt: st.ExpiresAt,
    		Id:        st.ID.String(),
    		IssuedAt:  st.IssuedAt,
    		Issuer:    st.Issuer,
    		NotBefore: st.NotBefore,
    		Subject:   st.Subject,
    	}
    	if err := standardClaims.Valid(); err != nil {
    		return err
    	}
    	if ok := standardClaims.VerifyIssuer(config.Get().IssuerURL, true); !ok {
    
    		return fmt.Errorf("invalid issuer")
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	}
    	if ok := standardClaims.VerifyAudience(config.Get().IssuerURL, true); !ok {
    
    		return fmt.Errorf("invalid Audience")
    	}
    	if ok := st.VerifyID(); !ok {
    		return fmt.Errorf("invalid id")
    	}
    	if ok := st.VerifySubject(); !ok {
    		return fmt.Errorf("invalid subject")
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	}
    	return nil
    }
    
    
    // ToSuperTokenResponse returns a SuperTokenResponse for this token. It requires that jwt is set or that the jwt is passed as argument; if not passed as argument ToJWT must have been called earlier on this token to set jwt. This is always the case, if the token has been stored.
    
    func (st *SuperToken) toSuperTokenResponse(jwt string) response.SuperTokenResponse {
    
    	res := st.toTokenResponse()
    	res.SuperToken = jwt
    	res.SuperTokenType = model.ResponseTypeToken
    	return res
    }
    
    func (st *SuperToken) toShortSuperTokenResponse() (response.SuperTokenResponse, error) {
    	shortToken := utils.RandASCIIString(config.Get().Features.ShortTokens.Len)
    	if _, err := db.DB().Exec(`INSERT INTO ShortSuperTokens (short_token, ST_id) VALUES(?,?)`, shortToken, st.ID); err != nil {
    		return response.SuperTokenResponse{}, err
    	}
    	res := st.toTokenResponse()
    	res.SuperToken = shortToken
    	res.SuperTokenType = model.ResponseTypeShortToken
    	return res, nil
    }
    
    func (st *SuperToken) toTokenResponse() response.SuperTokenResponse {
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	return response.SuperTokenResponse{
    
    		ExpiresIn:            st.ExpiresIn(),
    		Restrictions:         st.Restrictions,
    		Capabilities:         st.Capabilities,
    		SubtokenCapabilities: st.SubtokenCapabilities,
    
    func CreateTransferCode(stid uuid.UUID, networkData model.NetworkData) (string, uint64, error) {
    	transferCode := utils.RandASCIIString(config.Get().Features.TransferCodes.Len)
    	expiresIn := config.Get().Features.TransferCodes.ExpiresAfter
    	exp := uint64(expiresIn)
    	if err := db.Transact(func(tx *sqlx.Tx) error {
    		if _, err := tx.Exec(`INSERT INTO TransferCodes (transfer_code, ST_id, expires_in, new_st) VALUES(?,?,?,0)`, transferCode, stid, expiresIn); err != nil {
    			return err
    		}
    		return nil
    	}); err != nil {
    		return transferCode, exp, err
    	}
    	if err := event.LogEvent(&event2.Event{
    		Type: event2.STEventTransferCodeCreated,
    	}, stid, networkData); err != nil {
    		return transferCode, exp, err
    	}
    	return transferCode, exp, nil
    }
    
    func (st *SuperToken) ToTokenResponse(responseType model.ResponseType, networkData model.NetworkData, jwt string) (response.SuperTokenResponse, error) {
    	switch responseType {
    	case model.ResponseTypeShortToken:
    		if config.Get().Features.ShortTokens.Enabled {
    			return st.toShortSuperTokenResponse()
    		}
    	case model.ResponseTypeTransferCode:
    		transferCode, expiresIn, err := CreateTransferCode(st.ID, networkData)
    		res := st.toTokenResponse()
    		res.TransferCode = transferCode
    		res.SuperTokenType = model.ResponseTypeTransferCode
    		res.ExpiresIn = expiresIn
    		return res, err
    	}
    	return st.toSuperTokenResponse(jwt), nil
    }
    
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    // ToJWT returns the SuperToken as JWT
    func (st *SuperToken) ToJWT() (string, error) {
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	if st.jwt != "" {
    		return st.jwt, nil
    	}
    	var err error
    	st.jwt, err = jwt.NewWithClaims(jwt.GetSigningMethod(config.Get().Signing.Alg), st).SignedString(jws.GetPrivateKey())
    	return st.jwt, err
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    }
    
    // Value implements the driver.Valuer interface.
    func (st *SuperToken) Value() (driver.Value, error) {
    	return st.ToJWT()
    }
    
    // Scan implements the sql.Scanner interface.
    func (st *SuperToken) Scan(src interface{}) error {
    	tmp, err := ParseJWT(src.(string))
    	if err != nil {
    		return err
    	}
    	*st = *tmp
    	return nil
    }
    
    func ParseJWT(token string) (*SuperToken, error) {
    	tok, err := jwt.ParseWithClaims(token, &SuperToken{}, func(t *jwt.Token) (interface{}, error) {
    		return jws.GetPublicKey(), nil
    	})
    	if err != nil {
    		return nil, err
    	}
    
    	if st, ok := tok.Claims.(*SuperToken); ok && tok.Valid {
    		return st, nil
    	}
    
    	return nil, fmt.Errorf("token not valid")
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    }