auth_utility.go 3.29 KB
package restutility

import (
	"errors"
	"time"
	"crypto/sha256"
	"crypto/rand"
	"encoding/hex"
	"strings"
	"github.com/dgrijalva/jwt-go"
)

const OneDay  = time.Hour*24
const OneWeek = OneDay*7
const saltSize = 32
const appName  = "korisnicki-centar"
const secret   = "korisnicki-centar-api"

type Token struct {
	TokenString string `json:"token"`
}

type TokenClaims struct {
	Username string `json:"username"`
	Role string     `json:"role"`
	jwt.StandardClaims
}

type CredentialsStruct struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

func GenerateSalt() (string, error) {
	salt := ""
	
	rawsalt := make([]byte, saltSize)
	_, err := rand.Read(rawsalt)
	if err != nil {
		return "", err
	}
	salt = hex.EncodeToString(rawsalt)
	return salt, nil
}

func HashMessage(message string, presalt string) (string, string, error) {
	hash, salt := "", ""
	var err error

	// chech if message is presalted
	if presalt == "" {
		salt, err = GenerateSalt()
		if err != nil {
			return "", "", err
		}
	} else {
		salt = presalt
	}

	// convert strings to raw byte slices
	rawmessage := []byte(message)
	rawsalt, err := hex.DecodeString(salt)
	if err != nil {
		return "", "", err
	}
	rawdata := make([]byte, len(rawmessage) + len(rawsalt))
	rawdata = append(rawdata, rawmessage...)
	rawdata = append(rawdata, rawsalt...)

	// hash message + salt
	hasher := sha256.New()
	hasher.Write(rawdata)
	rawhash := hasher.Sum(nil)
	hash = hex.EncodeToString(rawhash)
	return hash, salt, nil
}

func IssueAPIToken(username, role string) (Token, error) {
	var apiToken Token
	var err error

	if err != nil {
		return Token{}, err
	}

	claims := TokenClaims{
		username,
		role,
		jwt.StandardClaims{
			ExpiresAt: (time.Now().Add(OneWeek)).Unix(),
			Issuer:    appName,
		},
	}

	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	apiToken.TokenString, err = jwtToken.SignedString([]byte(secret))
	if err != nil {
		return Token{}, err
	}
	return apiToken, nil
}

func RefreshAPIToken(tokenString string) (Token, error) {
	var newToken Token
	tokenString = strings.TrimPrefix(tokenString, "Bearer ")
	token, err := parseTokenFunc(tokenString)
	if err != nil {
		return Token{}, err
	}

	// type assertion
	claims, ok := token.Claims.(*TokenClaims)
	if !ok || !token.Valid {
		return Token{}, errors.New("token is not valid")
	}

	claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix()
	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	newToken.TokenString, err = jwtToken.SignedString([]byte(secret))
	if err != nil {
		return Token{}, err
	}

	return newToken, nil
}

func ParseAPIToken(tokenString string) (*TokenClaims, error) {
	if ok := strings.HasPrefix(tokenString, "Bearer"); ok {
		tokenString = strings.TrimPrefix(tokenString, "Bearer ")
	} else {
		return &TokenClaims{}, errors.New("Authorization header is incomplete")
	}

	token, err := parseTokenFunc(tokenString)
	if err != nil {
		return &TokenClaims{}, err
	}

	// type assertion
	claims, ok := token.Claims.(*TokenClaims)
	if !ok || !token.Valid {
		return &TokenClaims{}, errors.New("token is not valid")
	}
	return claims, nil
}

func parseTokenFunc(tokenString string) (*jwt.Token, error) {
	token, err := jwt.ParseWithClaims(tokenString,
		&TokenClaims{},
		func(token *jwt.Token) (interface{}, error) {
			return []byte(secret), nil
		},
	)
	return token, err
}