package auth import ( "crypto/rsa" "fmt" "io/ioutil" "time" "github.com/pquerna/ffjson/ffjson" "strings" "bitbucket.org/nemt/nemt-portal-api/application/viewmodel" "bitbucket.org/nemt/nemt-portal-api/infra/config" "bitbucket.org/nemt/nemt-portal-api/infra/errors" jwt "github.com/dgrijalva/jwt-go" "github.com/labstack/echo" ) const ( // AppKeyHeaderName is the header name for application keys AppKeyHeaderName = "App" // AppTokenHeaderName is the header name for application token AppTokenHeaderName = "Token" // TokenExpiration is the auth token default expiration time TokenExpiration = 24 * time.Hour ) var ( // TokenSigningMethod is the auth token signing algorithm TokenSigningMethod = jwt.SigningMethodRS256 ) // GetAppKeyFromContext returns the application key from request header func GetAppKeyFromContext(ctx echo.Context) (appKey string, err error) { appKey = ctx.Request().Header.Get(AppKeyHeaderName) if strings.TrimSpace(appKey) == "" { return "", errors.NewNotAuthorizedError("Application key not found") } return appKey, nil } // ValidateAppKey validates the presence and validity of App key func ValidateAppKey(ctx echo.Context, cfg *config.Config) (err error) { appKey, err := GetAppKeyFromContext(ctx) if err != nil { return errors.Wrap(err) } if appKey != cfg.HTTP.Auth.AppKey { return errors.NewNotAuthorizedError("Invalid API Key") } return nil } // GetCertPrivateKey returns the private key for the token authentication certificate func GetCertPrivateKey(path string) (pk *rsa.PrivateKey, err error) { data, err := ioutil.ReadFile(path) if err != nil { return nil, errors.Wrap(err) } pk, err = jwt.ParseRSAPrivateKeyFromPEM(data) if err != nil { return nil, errors.Wrap(err) } return pk, nil } // GetCertPublicKey returns the public key for the token authentication certificate func GetCertPublicKey(path string) (pk *rsa.PublicKey, err error) { privateKey, err := GetCertPrivateKey(path) if err != nil { return nil, errors.Wrap(err) } return &privateKey.PublicKey, nil } // GenerateToken creates a token based on certificate func GenerateToken(cfg *config.Config, data interface{}) (tokenString string, err error) { key, _ := GetCertPrivateKey(cfg.HTTP.Auth.CertificatePath) claims := jwt.MapClaims{ "iat": time.Now().Unix(), "exp": time.Now().Add(TokenExpiration).Unix(), "iss": "BDC", "sub": "NEMT", "data": data, } token := jwt.NewWithClaims(TokenSigningMethod, claims) tokenString, err = token.SignedString(key) if err != nil { return tokenString, errors.Wrap(err) } return tokenString, nil } func GetTokenDetail(ctx echo.Context, cfg *config.Config) (interface{}, error) { key, _ := GetCertPublicKey(cfg.HTTP.Auth.CertificatePath) tokenString := ctx.Request().Header.Get("Token") tokenString = strings.Replace(tokenString, "Bearer ", "", -1) token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return key, nil }) if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { return claims["data"], nil } else { return nil, err } } func GetUserDetail(ctx echo.Context, cfg *config.Config) (viewmodel.User, error) { key, _ := GetCertPublicKey(cfg.HTTP.Auth.CertificatePath) tokenString := ctx.Request().Header.Get("Token") tokenString = strings.Replace(tokenString, "Bearer ", "", -1) token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return key, nil }) if err != nil { return viewmodel.User{}, err } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { byteData, _ := ffjson.Marshal(claims["data"]) user := viewmodel.User{} err = ffjson.Unmarshal(byteData, &user) if err != nil { return viewmodel.User{}, err } else { return user, nil } } else { return viewmodel.User{}, err } }