This repository has been archived on 2023-11-13. You can view files and clone it, but cannot push or open issues or pull requests.
imagini/internal/auth/auth.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
}