186 lines
5.2 KiB
Go
186 lines
5.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/crypto/bcrypt"
|
|
log "github.com/sirupsen/logrus"
|
|
"gorm.io/gorm"
|
|
"reichard.io/imagini/internal/db"
|
|
"reichard.io/imagini/internal/config"
|
|
"reichard.io/imagini/internal/models"
|
|
"reichard.io/imagini/internal/session"
|
|
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/lestrrat-go/jwx/jwa"
|
|
"github.com/lestrrat-go/jwx/jwt"
|
|
)
|
|
|
|
type AuthManager struct {
|
|
DB *db.DBManager
|
|
Config *config.Config
|
|
Session *session.SessionManager
|
|
}
|
|
|
|
func NewMgr(db *db.DBManager, c *config.Config) *AuthManager {
|
|
session := session.NewMgr()
|
|
return &AuthManager{
|
|
DB: db,
|
|
Config: c,
|
|
Session: session,
|
|
}
|
|
}
|
|
|
|
func (auth *AuthManager) AuthenticateUser(creds models.APICredentials) bool {
|
|
// By Username
|
|
foundUser, err := auth.DB.User(models.User{Username: creds.User})
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
foundUser, err = auth.DB.User(models.User{Email: creds.User})
|
|
}
|
|
|
|
// Error Checking
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
log.Warn("[auth] User not found: ", creds.User)
|
|
return false
|
|
} else if err != nil {
|
|
log.Error(err)
|
|
return false
|
|
}
|
|
|
|
log.Info("[auth] Authenticating user: ", foundUser.Username)
|
|
|
|
// Determine Type
|
|
switch foundUser.AuthType {
|
|
case "Local":
|
|
return authenticateLocalUser(foundUser, creds.Password)
|
|
case "LDAP":
|
|
return authenticateLDAPUser(foundUser, creds.Password)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (auth *AuthManager) ValidateJWTToken(userJWT string) bool {
|
|
byteUserJWT := []byte(userJWT)
|
|
|
|
serverToken, err := jwt.ParseBytes(byteUserJWT, jwt.WithVerify(jwa.HS256, auth.Config.JWTSecret))
|
|
if err != nil {
|
|
fmt.Println("failed to parse payload: ", err)
|
|
}
|
|
|
|
uid, ok := serverToken.Get("uid");
|
|
if !ok {
|
|
fmt.Println("failed to acquire uid")
|
|
}
|
|
|
|
userID := fmt.Sprintf("%v", uid)
|
|
userKey := auth.Session.Get(userID)
|
|
|
|
userToken, err := jwt.ParseBytes(byteUserJWT, jwt.WithVerify(jwa.HS256, userKey))
|
|
if err != nil {
|
|
fmt.Println("failed to parse payload: ", err)
|
|
}
|
|
_ = userToken
|
|
|
|
// TODO:
|
|
// - Get User ID from UNVALIDATED token
|
|
// - Lookup user key, concat with server key
|
|
// - Validate with concatted user & server key
|
|
// validatedToken, err := jwt.ParseBytes(byteUserJWT, jwt.WithVerify(jwa.HS256, concatKey))
|
|
// if err != nil {
|
|
// fmt.Printf("failed to parse payload: %s\n", err)
|
|
// }
|
|
|
|
// userToken := auth.Session.Get(userID)
|
|
// log.Info("[auth] DEBUG: ", userToken)
|
|
|
|
return false
|
|
}
|
|
|
|
func (auth *AuthManager) RevokeRefreshToken() {
|
|
|
|
}
|
|
|
|
func (auth *AuthManager) ValidateRefreshToken(refreshToken, deviceID string) bool {
|
|
// Acquire Device
|
|
deviceUUID, err := uuid.Parse(deviceID)
|
|
device := models.Device{Base: models.Base{UUID: deviceUUID}}
|
|
foundDevice, err := auth.DB.Device(device)
|
|
|
|
// Validate Expiration
|
|
expTime, err := time.Parse(time.RFC3339, foundDevice.RefreshExp)
|
|
if expTime.Before(time.Now()) {
|
|
return false
|
|
}
|
|
|
|
// Validate Token
|
|
bRefreshToken :=[]byte(refreshToken)
|
|
err = bcrypt.CompareHashAndPassword([]byte(foundDevice.RefreshToken), bRefreshToken)
|
|
if err == nil {
|
|
log.Info("[auth] Refresh Token validation succeeded: ", foundDevice.UUID)
|
|
return true
|
|
}
|
|
log.Warn("[auth] Refresh Token validation failed: ", foundDevice.UUID)
|
|
return false
|
|
}
|
|
|
|
func (auth *AuthManager) UpdateRefreshToken(deviceID string) error {
|
|
// TODO:
|
|
// - Remove Refresh token from Session AND DB
|
|
// - Call CreateRefreshToken
|
|
return nil
|
|
}
|
|
|
|
func (auth *AuthManager) CreateRefreshToken(deviceID string) (string, error) {
|
|
// TODO:
|
|
// - Create regular bcrypt password
|
|
// - Create Expiration (Depends on Device Type)
|
|
// - Store in DB: DeviceID, ValidUntil
|
|
|
|
generatedToken := uuid.New().String()
|
|
hashedRefreshToken, err := bcrypt.GenerateFromPassword([]byte(generatedToken), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
log.Error(err)
|
|
return "", err
|
|
}
|
|
_ = string(hashedRefreshToken)
|
|
|
|
return "", nil
|
|
}
|
|
|
|
func (auth *AuthManager) CreateJWTAccessToken(user, role, deviceID string) (string, error) {
|
|
// Create New Token
|
|
tm := time.Now()
|
|
t := jwt.New()
|
|
t.Set(`did`, deviceID) // Device ID
|
|
t.Set(`role`, role) // User Role (Admin / User)
|
|
t.Set(jwt.SubjectKey, user) // User ID
|
|
t.Set(jwt.AudienceKey, `imagini`) // App ID
|
|
t.Set(jwt.IssuedAtKey, tm) // Issued At
|
|
t.Set(jwt.ExpirationKey, tm.Add(time.Minute * 30)) // 30 Minute Access Key
|
|
|
|
// Validate Token Creation
|
|
_, err := json.MarshalIndent(t, "", " ")
|
|
if err != nil {
|
|
fmt.Printf("failed to generate JSON: %s\n", err)
|
|
return "", err
|
|
}
|
|
|
|
// Use Server Key
|
|
byteKey := []byte(auth.Config.JWTSecret)
|
|
|
|
// Sign Token
|
|
signed, err := jwt.Sign(t, jwa.HS256, byteKey)
|
|
if err != nil {
|
|
log.Printf("failed to sign token: %s", err)
|
|
return "", err
|
|
}
|
|
|
|
// Return Token
|
|
return string(signed), nil
|
|
}
|