125 lines
3.2 KiB
Go
125 lines
3.2 KiB
Go
package models
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"fmt"
|
|
|
|
"encoding/json"
|
|
"github.com/go-webauthn/webauthn/webauthn"
|
|
"github.com/gofrs/uuid"
|
|
"github.com/supabase/auth/internal/crypto"
|
|
"github.com/supabase/auth/internal/storage"
|
|
"time"
|
|
)
|
|
|
|
type Challenge struct {
|
|
ID uuid.UUID `json:"challenge_id" db:"id"`
|
|
FactorID uuid.UUID `json:"factor_id" db:"factor_id"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
VerifiedAt *time.Time `json:"verified_at,omitempty" db:"verified_at"`
|
|
IPAddress string `json:"ip_address" db:"ip_address"`
|
|
Factor *Factor `json:"factor,omitempty" belongs_to:"factor"`
|
|
OtpCode string `json:"otp_code,omitempty" db:"otp_code"`
|
|
WebAuthnSessionData *WebAuthnSessionData `json:"web_authn_session_data,omitempty" db:"web_authn_session_data"`
|
|
}
|
|
|
|
type WebAuthnSessionData struct {
|
|
*webauthn.SessionData
|
|
}
|
|
|
|
func (s *WebAuthnSessionData) Scan(value interface{}) error {
|
|
if value == nil {
|
|
s.SessionData = nil
|
|
return nil
|
|
}
|
|
|
|
// Handle byte and string as a precaution, in postgres driver, json/jsonb should be returned as []byte
|
|
var data []byte
|
|
switch v := value.(type) {
|
|
case []byte:
|
|
data = v
|
|
case string:
|
|
data = []byte(v)
|
|
default:
|
|
panic(fmt.Sprintf("unsupported type for web_authn_session_data: %T", value))
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
s.SessionData = nil
|
|
return nil
|
|
}
|
|
if s.SessionData == nil {
|
|
s.SessionData = &webauthn.SessionData{}
|
|
}
|
|
return json.Unmarshal(data, s.SessionData)
|
|
|
|
}
|
|
|
|
func (s *WebAuthnSessionData) Value() (driver.Value, error) {
|
|
if s == nil || s.SessionData == nil {
|
|
return nil, nil
|
|
}
|
|
return json.Marshal(s.SessionData)
|
|
}
|
|
|
|
func (ws *WebAuthnSessionData) ToChallenge(factorID uuid.UUID, ipAddress string) *Challenge {
|
|
id := uuid.Must(uuid.NewV4())
|
|
return &Challenge{
|
|
ID: id,
|
|
FactorID: factorID,
|
|
IPAddress: ipAddress,
|
|
WebAuthnSessionData: &WebAuthnSessionData{
|
|
ws.SessionData,
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
func (Challenge) TableName() string {
|
|
tableName := "mfa_challenges"
|
|
return tableName
|
|
}
|
|
|
|
// Update the verification timestamp
|
|
func (c *Challenge) Verify(tx *storage.Connection) error {
|
|
now := time.Now()
|
|
c.VerifiedAt = &now
|
|
return tx.UpdateOnly(c, "verified_at")
|
|
}
|
|
|
|
func (c *Challenge) HasExpired(expiryDuration float64) bool {
|
|
return time.Now().After(c.GetExpiryTime(expiryDuration))
|
|
}
|
|
|
|
func (c *Challenge) GetExpiryTime(expiryDuration float64) time.Time {
|
|
return c.CreatedAt.Add(time.Second * time.Duration(expiryDuration))
|
|
}
|
|
|
|
func (c *Challenge) SetOtpCode(otpCode string, encrypt bool, encryptionKeyID, encryptionKey string) error {
|
|
c.OtpCode = otpCode
|
|
if encrypt {
|
|
es, err := crypto.NewEncryptedString(c.ID.String(), []byte(otpCode), encryptionKeyID, encryptionKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.OtpCode = es.String()
|
|
}
|
|
return nil
|
|
|
|
}
|
|
|
|
func (c *Challenge) GetOtpCode(decryptionKeys map[string]string, encrypt bool, encryptionKeyID string) (string, bool, error) {
|
|
if es := crypto.ParseEncryptedString(c.OtpCode); es != nil {
|
|
bytes, err := es.Decrypt(c.ID.String(), decryptionKeys)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
|
|
return string(bytes), encrypt && es.ShouldReEncrypt(encryptionKeyID), nil
|
|
}
|
|
|
|
return c.OtpCode, encrypt, nil
|
|
|
|
}
|