auth/main.go
package auth
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"fmt"
"log"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/gomodule/oauth1/oauth"
"github.com/google/uuid"
"golang.org/x/oauth2"
"github.com/yasshi2525/RushHour/config"
)
// Auther enc/dec sensitive data
type Auther struct {
baseURL string
state string
salt string
block cipher.Block
twitterClient *oauth.Client
githubConf *oauth2.Config
googleConf *oauth2.Config
}
// OAuthInfo represents infomation from OAuth App
type OAuthInfo struct {
Handler *Auther
DisplayName string
Image string
LoginID string
OAuthToken string
OAuthSecret string
IsEnc bool
}
// JWTInfo is requirements to create json web token
type JWTInfo struct {
ID uint
Name string
Image string
Admin bool
Hue int
}
// GetAuther initiates cipher, connection
func GetAuther(conf config.CnfAuth) (*Auther, error) {
a := &Auther{
baseURL: conf.BaseURL,
state: conf.State,
salt: conf.Salt,
}
key := "0123456789abcdef"
if len(conf.Key) == 16 {
key = conf.Key
} else {
log.Printf("auth.key %s must be 16 length. set to 0123456789abcdef", conf.Key)
}
var err error
if a.block, err = aes.NewCipher([]byte(key)); err != nil {
return nil, err
}
a.initTwitter(conf.Twitter)
a.initGoogle(conf.Google)
a.initGitHub(conf.GitHub)
return a, nil
}
// IsValid returns whether OAuthToken exists or not
func (i *OAuthInfo) IsValid() bool {
if i.IsEnc {
return i.Handler.Decrypt(i.OAuthToken) != ""
}
return i.OAuthToken != ""
}
// Enc returns encrypted info
func (i *OAuthInfo) Enc() (*OAuthInfo, error) {
if i.IsEnc {
return nil, fmt.Errorf("%v is already encrypted", i)
}
return &OAuthInfo{
Handler: i.Handler,
LoginID: i.Handler.Encrypt(i.LoginID),
OAuthToken: i.Handler.Encrypt(i.OAuthToken),
OAuthSecret: i.Handler.Encrypt(i.OAuthSecret),
DisplayName: i.Handler.Encrypt(i.DisplayName),
Image: i.Handler.Encrypt(i.Image),
IsEnc: true,
}, nil
}
// Dec returns decrypted info
func (i *OAuthInfo) Dec() (*OAuthInfo, error) {
if !i.IsEnc {
return nil, fmt.Errorf("%v is already decrypted", i)
}
return &OAuthInfo{
Handler: i.Handler,
LoginID: i.Handler.Decrypt(i.LoginID),
OAuthToken: i.Handler.Decrypt(i.OAuthToken),
OAuthSecret: i.Handler.Decrypt(i.OAuthSecret),
DisplayName: i.Handler.Decrypt(i.DisplayName),
Image: i.Handler.Decrypt(i.Image),
IsEnc: false,
}, nil
}
// Digest returns hash value of plain
func (a *Auther) Digest(plain string) string {
mac := hmac.New(sha512.New, []byte(a.salt))
mac.Write([]byte(plain))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
// Encrypt returns AES and Base64 encoded string.
func (a *Auther) Encrypt(plain string) string {
encrypter, iv := a.genEncrypter()
padPlain := pad([]byte(plain))
enc := make([]byte, len(padPlain))
encrypter.CryptBlocks(enc, []byte(padPlain))
// combine iv after enc
enc = append(enc, iv...)
return base64.StdEncoding.EncodeToString(enc)
}
// Decrypt returns plain text from AES and Base64 encoded string.
func (a *Auther) Decrypt(enc64 string) string {
if enc64 == "" {
return ""
}
if combined, err := base64.StdEncoding.DecodeString(enc64); err != nil {
panic(err)
} else {
// trim iv after enc
piv := len(combined) - a.block.BlockSize()
enc := combined[:piv]
iv := combined[piv:]
plain := make([]byte, len(enc))
cipher.NewCBCDecrypter(a.block, iv).CryptBlocks(plain, enc)
return string(unpad(plain))
}
}
// BuildJWT returns JSON Web Token of player
func (a *Auther) BuildJWT(o *JWTInfo) (string, error) {
url := a.baseURL
now := time.Now()
exp := now.Add(time.Hour)
uu := uuid.New()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": url,
"sub": "AccessToken",
"aud": url,
"exp": exp.Unix(),
"nbf": now.Unix(),
"iat": now.Unix(),
"jti": uu.String(),
fmt.Sprintf("%s/id", url): o.ID,
fmt.Sprintf("%s/name", url): o.Name,
fmt.Sprintf("%s/image", url): o.Image,
fmt.Sprintf("%s/admin", url): o.Admin,
fmt.Sprintf("%s/hue", url): o.Hue,
})
res, err := token.SignedString([]byte(a.salt))
if err != nil {
return "", err
}
return res, nil
}
func (a *Auther) genEncrypter() (cipher.BlockMode, []byte) {
iv := make([]byte, a.block.BlockSize())
if _, err := rand.Read(iv); err != nil {
panic(err.Error())
}
return cipher.NewCBCEncrypter(a.block, iv), iv
}
func pad(b []byte) []byte {
padSize := aes.BlockSize - (len(b) % aes.BlockSize)
pad := bytes.Repeat([]byte{byte(padSize)}, padSize)
return append(b, pad...)
}
func unpad(b []byte) []byte {
padSize := int(b[len(b)-1])
return b[:len(b)-padSize]
}