Skip to content

Commit

Permalink
implemented api tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
maxencehenneron committed Apr 26, 2019
1 parent 38f863c commit 72d1e53
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 14 deletions.
113 changes: 113 additions & 0 deletions controllers/token.go
@@ -0,0 +1,113 @@
package controllers

import (
"net/http"

"github.com/uploadexpress/app/services/config"

"github.com/gin-gonic/gin"
"github.com/uploadexpress/app/helpers"
"github.com/uploadexpress/app/helpers/params"
"github.com/uploadexpress/app/models"
"github.com/uploadexpress/app/store"
)

// TokenController holds all controller functions related to the Token entity
type TokenController struct{}

// NewTokenController instantiates of the controller
func NewTokenController() TokenController {
return TokenController{}
}

// CreateToken to create a new Token
func (tc TokenController) CreateToken(c *gin.Context) {
token := &models.Token{}

if err := c.BindJSON(token); err != nil {
c.AbortWithError(http.StatusBadRequest, helpers.ErrorWithCode("invalid_input", "Failed to bind the body data", err))
return
}

token.Ip = c.ClientIP()
if err := store.CreateToken(c, token); err != nil {
c.Error(err)
c.Abort()
return
}

secret := config.GetString(c, "jwt_secret")
accessToken, err := helpers.GenerateApiToken(secret, store.Current(c).Id, token.Id)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, helpers.ErrorWithCode("token_generation_failed", "Could not generate the access token", err))
return
}

// Only sent once
token.Token = accessToken

c.JSON(http.StatusCreated, token)
}

// GetToken from id (in context)
func (tc TokenController) GetToken(c *gin.Context) {
token, err := store.FindTokenById(c, c.Param("id"))

if err != nil {
c.AbortWithError(http.StatusNotFound, helpers.ErrorWithCode("Token_not_found", "The token does not exist", err))
return
}

c.JSON(http.StatusOK, token)
}

// GetAllTokens to get all Tokens
func (tc TokenController) GetAllTokens(c *gin.Context) {
tokens, err := store.GetAllTokens(c)
if err != nil {
c.Error(err)
c.Abort()
return
}

c.JSON(http.StatusOK, tokens)
}

//UpdateToken updates the Token entity
func (tc TokenController) UpdateToken(c *gin.Context) {
newToken := models.Token{}

err := c.BindJSON(&newToken)
if err != nil {
c.AbortWithError(http.StatusBadRequest, helpers.ErrorWithCode("invalid_input", "Failed to bind the body data", err))
return
}

_, err = store.FindTokenById(c, c.Param("id"))
if err != nil {
c.AbortWithError(http.StatusInternalServerError, helpers.ErrorWithCode("Token_not_found", "Failed to find Token id", err))
return
}

err = store.UpdateToken(c, c.Param("id"), params.M{"$set": newToken})
if err != nil {
c.Error(err)
c.Abort()
return
}

c.JSON(http.StatusOK, nil)
}

// DeleteToken to delete an existing Token
func (Tc TokenController) DeleteToken(c *gin.Context) {
err := store.DeleteToken(c, c.Param("id"))

if err != nil {
c.Error(err)
c.Abort()
return
}

c.JSON(http.StatusOK, nil)
}
57 changes: 49 additions & 8 deletions helpers/jwt.go
Expand Up @@ -24,11 +24,27 @@ func GenerateAccessToken(secret string, subject string) (*string, error) {
return &accessString, nil
}

func ValidateJwtToken(token string, secret string, audience string) (jwt.MapClaims, error) {
func GenerateApiToken(secret string, subject, creator string) (*string, error) {
access := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": creator,
"sub": subject,
"aud": "api",
"iat": time.Now().Unix(),
})

accessString, err := access.SignedString([]byte(secret))
if err != nil {
return nil, err
}

return &accessString, nil
}

func GetParsedToken(token, secret string) (*jwt.Token, error) {
rawToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
// validate the alg
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}

return []byte(secret), nil
Expand All @@ -37,6 +53,29 @@ func ValidateJwtToken(token string, secret string, audience string) (jwt.MapClai
return nil, err
}

return rawToken, nil
}

func GetTokenAudience(token, secret string) (string, error) {
rawToken, err := GetParsedToken(token, secret)
if err != nil {
return "", err
}

claims, ok := rawToken.Claims.(jwt.MapClaims)
if !ok {
return "", errors.New("could not parse the claims")
}

return claims["aud"].(string), nil
}

func ValidateJwtToken(token string, secret string, audience string) (jwt.MapClaims, error) {
rawToken, err := GetParsedToken(token, secret)
if err != nil {
return nil, err
}

//validate signature
if !rawToken.Valid {
return nil, errors.New("token in invalid (wrong signature)")
Expand All @@ -47,17 +86,19 @@ func ValidateJwtToken(token string, secret string, audience string) (jwt.MapClai
return nil, errors.New("could not parse the claims")
}

//validate exp
tokenExp := claims["exp"].(float64)
if tokenExp < float64(time.Now().Unix()) {
return nil, errors.New("token in invalid (expired)")
}

//validate aud
tokenAud := claims["aud"].(string)
if tokenAud != audience {
return nil, errors.New("token in invalid (wrong audience)")
}

//validate exp
if tokenAud != "api" { // api tokens don't expire
tokenExp := claims["exp"].(float64)
if tokenExp < float64(time.Now().Unix()) {
return nil, errors.New("token in invalid (expired)")
}
}

return claims, nil
}
49 changes: 45 additions & 4 deletions middlewares/auth.go
Expand Up @@ -21,16 +21,57 @@ func AuthMiddleware() gin.HandlerFunc {
return
}

token := authHeaderParts[1]
secret := config.GetString(c, "jwt_secret")
claims, err := helpers.ValidateJwtToken(authHeaderParts[1], secret, "access")
tokenAudience, err := helpers.GetTokenAudience(token, secret)
if err != nil {
c.AbortWithError(http.StatusBadRequest, helpers.ErrorWithCode("invalid_token", "the given token is invalid", err))
return
}

user, _ := store.FindUserById(c, claims["sub"].(string))
c.Set(store.CurrentKey, user)
if tokenAudience == "api" {
if validateApiToken(token, secret, c) {
c.Next()
}
return
}

if validateAccessToken(token, secret, c) {
c.Next()
}
}
}

func validateAccessToken(token, secret string, c *gin.Context) bool {
claims, err := helpers.ValidateJwtToken(token, secret, "access")
if err != nil {
c.AbortWithError(http.StatusBadRequest, helpers.ErrorWithCode("invalid_token", "the given token is invalid", err))
return false
}

c.Next()
user, err := store.FindUserById(c, claims["sub"].(string))
if err != nil {
c.AbortWithError(http.StatusBadRequest, helpers.ErrorWithCode("unknown_user", "the given user is invalid", err))
return false
}

c.Set(store.CurrentKey, user)

return true
}

func validateApiToken(token, secret string, c *gin.Context) bool {
claims, err := helpers.ValidateJwtToken(token, secret, "api")
if err != nil {
c.AbortWithError(http.StatusBadRequest, helpers.ErrorWithCode("invalid_token", "the given token is invalid", err))
return false
}

_, err = store.FindTokenById(c, claims["iss"].(string))
if err != nil {
c.AbortWithError(http.StatusBadRequest, helpers.ErrorWithCode("unknown_token", "the token is invalid or expired", err))
return false
}

return true
}
23 changes: 23 additions & 0 deletions models/token.go
@@ -0,0 +1,23 @@
package models

import (
"time"

"gopkg.in/mgo.v2/bson"
)

type Token struct {
Id string `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
Ip string `json:"ip" bson:"ip"`
CreationDate int64 `json:"creation_date" bson:"creation_date"`
Token *string `json:"token,omitempty" bson:"-"`
}

func (token *Token) BeforeCreate() error {
token.Id = bson.NewObjectId().Hex()
token.CreationDate = time.Now().Unix()
return nil
}

const TokensCollection = "tokens"
14 changes: 12 additions & 2 deletions server/router.go
Expand Up @@ -61,11 +61,11 @@ func (a *API) SetupRouter() {
uploader.POST("/", uploaderController.Create)
uploader.PUT("/:upload_id/complete", uploaderController.CompleteUpload)
uploader.GET("/:upload_id/file/:file_id/upload_url", uploaderController.CreatePreSignedRequest)
uploader.POST("/:upload_id/mail", uploaderController.SendMail)
uploader.Use(authMiddleware)
uploader.GET("/", uploaderController.Index)
uploader.DELETE("/:upload_id/", uploaderController.DeleteUpload)
uploader.POST("/:upload_id/background", uploaderController.AttachBackground)
uploader.POST("/:upload_id/mail", uploaderController.SendMail)
}

downloader := v1.Group("/downloader")
Expand All @@ -81,14 +81,24 @@ func (a *API) SetupRouter() {
{
settingsController := controllers.NewSettingsController()
settings.GET("/", settingsController.Index)
uploader.Use(authMiddleware)
settings.Use(authMiddleware)
settings.PUT("/", settingsController.Edit)
settings.POST("/logo/", settingsController.CreateLogo)
settings.POST("/background/", settingsController.CreateBackground)
settings.DELETE("/background/:id/", settingsController.DeleteBackground)
settings.DELETE("/logo/", settingsController.DeleteLogo)

}

tokens := v1.Group("/tokens")
{
tokensController := controllers.NewTokenController()
tokens.Use(authMiddleware)
tokens.POST("/", tokensController.CreateToken)
tokens.GET("/", tokensController.GetAllTokens)
tokens.PUT("/:id", tokensController.UpdateToken)
tokens.DELETE("/:id", tokensController.DeleteToken)
}
}

router.PUT("/upload/:upload_name", uploaderController.CreateDirectUpload)
Expand Down

0 comments on commit 72d1e53

Please sign in to comment.