wroofauth/internal/machines/session.go
2023-10-20 16:08:37 +00:00

192 lines
4.4 KiB
Go

package machines
import (
"context"
"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(ctx context.Context) *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(ctx context.Context) 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(ctx context.Context) 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(ctx context.Context, identification string) error {
if s.State != SessionState_EMPTY {
return ErrIllegalStateAction // This step may only run on EMPTY sessions
}
user, err := user.GetByIdentification(ctx, identification)
if err != nil {
return err
}
if user == nil {
panic("no error, but nil user")
}
s.User = user
//TODO: Check for SAML
s.State = SessionState_UNAUTHENTICATED
return nil
}
func (s *Session) HandlePassword(ctx context.Context, 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(ctx)
}
s.State = SessionState_AWAITING_FACTOR
return nil
}
// TODO: Passkey action
func (s *Session) HandleTOTP(ctx context.Context, 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(ctx)
}
func (s *Session) HandleLock(ctx context.Context) error {
if !s.IsAnyAuthenticated() {
return ErrIllegalStateAction
}
// TODO: Handle Lock
return nil
}
func (s *Session) HandleLogout(ctx context.Context) error {
if !s.IsAnyAuthenticated() {
return ErrIllegalStateAction
}
// TODO: Handle Logout
return nil
}
func (s *Session) Destroy(ctx context.Context) error {
// TODO: Destroy Session
return nil
}