wroofauth/internal/machines/session.go
2023-10-19 11:44:03 +00:00

176 lines
4 KiB
Go

package machines
import (
"errors"
"strings"
"git.1in9.net/raider/wroofauth/internal/entities/user"
)
var (
ErrIllegalStateAction = errors.New("illegal action for this state")
ErrIllegalState = errors.New("illegal state")
)
type SessionState string
const (
SessionState_EMPTY SessionState = "EMPTY"
SessionState_UNAUTHENTICATED SessionState = "UNAUTHENTICATED"
SessionState_AWAITING_FACTOR SessionState = "AWAITING_FACTOR"
SessionState_AUTHENTICATED_PENDING SessionState = "AUTHENTICATED_PENDING"
SessionState_AUTHENTICATED_FULLY SessionState = "AUTHENTICATED_FULLY"
SessionState_AUTHENTICATED_PASSWORD_CHANGE SessionState = "AUTHENTICATED_PASSWORD_CHANGE"
SessionState_AUTHENTICATED_2FA_ENROLL SessionState = "AUTHENTICATED_2FA_ENROLL"
SessionState_AUTHENTICATED_REVIEW_TOS SessionState = "AUTHENTICATED_REVIEW_TOS"
SessionState_AUTHENTICATED_REVIEW_RECOVERY SessionState = "AUTHENTICATED_REVIEW_RECOVERY"
)
type AuthenticationMethod string
const (
AuthenticationMethod_NONE = "NONE"
AuthenticationMethod_PASSWORD = "PASSWORD"
)
type SecondFactor string
const (
SecondFactor_NONE = "NONE"
SecondFactor_TOTP = "TOTP"
)
type Session struct {
State SessionState
AuthenticationMethod AuthenticationMethod
SecondFactor SecondFactor
User *user.User
}
func NewSession() *Session {
return &Session{
State: SessionState_EMPTY,
AuthenticationMethod: AuthenticationMethod_NONE,
SecondFactor: SecondFactor_NONE,
}
}
// s.Validate checks if the session is in a valid state
func (s *Session) Validate() error {
if s.IsAnyAuthenticated() {
if s.User == nil {
// We can only be here if a user is set
return ErrIllegalState
}
if s.AuthenticationMethod == AuthenticationMethod_NONE {
// We can not be authenticated without any way we got here
return ErrIllegalState
}
if s.SecondFactor == SecondFactor_NONE && s.User.Needs2FA() {
// User has either just enabled 2FA, or something went horribly wrong.
// Either way, throw away the session!
return ErrIllegalState
}
}
return nil
}
func (s *Session) IsAnyAuthenticated() bool {
return strings.HasPrefix(string(s.State), "AUTHENTICATED_")
}
func (s *Session) performPreflight() error {
// TODO: Do Preflight Checks.
// TODO: Do PASSWORD_EXPIRED check
// TODO: Do 2FA-Force-Enrolment check
// TODO: Do TOS check
// TODO: Do Reveiw Recovery check
// TODO: Do Flag check
// Preflight ok.
s.State = SessionState_AUTHENTICATED_FULLY
return nil
}
func (s *Session) HandleIdentification(identification string) error {
if s.State != SessionState_EMPTY {
return ErrIllegalStateAction // This step may only run on EMPTY sessions
}
// TODO: Handle Identification
return nil
}
func (s *Session) HandlePassword(password string) error {
if s.State != SessionState_UNAUTHENTICATED {
return ErrIllegalStateAction // This step may only run on UNAUTHENTICATED sessions
}
err := s.User.CheckPassword(password)
if err != nil {
return err
}
// Password Ok.
s.AuthenticationMethod = AuthenticationMethod_PASSWORD
if !s.User.Needs2FA() {
// No 2fa, jump to AUTHENTICATED_PENDING for preflight
s.State = SessionState_AUTHENTICATED_PENDING
return s.performPreflight()
}
s.State = SessionState_AWAITING_FACTOR
return nil
}
// TODO: Passkey action
func (s *Session) HandleTOTP(otp string) error {
if s.State != SessionState_AWAITING_FACTOR {
return ErrIllegalStateAction
}
err := s.User.ValidateTOTP(otp)
if err != nil {
return err
}
// OTP Ok.
s.SecondFactor = SecondFactor_TOTP
// Good to go for preflight.
s.State = SessionState_AUTHENTICATED_PENDING
return s.performPreflight()
}
func (s *Session) HandleLock() error {
if !s.IsAnyAuthenticated() {
return ErrIllegalStateAction
}
// TODO: Handle Lock
return nil
}
func (s *Session) HandleLogout() error {
if !s.IsAnyAuthenticated() {
return ErrIllegalStateAction
}
// TODO: Handle Logout
return nil
}
func (s *Session) Destroy() error {
// TODO: Destroy Session
return nil
}