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 }