PHP's device flow on golem15_user_device_auth_sessions (PHP's columns,
the token stored as its SHA-256, plus jwt_jti): POST /devices/initiate
answers a 64-character token, an XXXX-XXXX short code, the configurable
auth_url (golem15.user.device_auth.auth_url) and the expiry; a signed-in
user authorizes it with POST /devices/authorize (short_code or token,
one guarded pending-to-confirmed UPDATE); the device's first poll of
GET /devices/status/{token} after that receives a session JWT through one
guarded confirmed-to-used UPDATE, so exactly one poll gets it. Used and
revoked sessions never turn into expired; each initiate deletes a bounded
batch of stale sessions.
67 lines
2.8 KiB
Go
67 lines
2.8 KiB
Go
package models
|
|
|
|
import "time"
|
|
|
|
// Device authorization session statuses, PHP's DeviceAuthSession constants.
|
|
// pending becomes confirmed when a signed-in user authorizes the device,
|
|
// confirmed becomes used on the device's first poll (which receives the
|
|
// session token), used becomes revoked when the user revokes the device,
|
|
// and pending or confirmed become expired once expires_at passes. used and
|
|
// revoked never become expired.
|
|
const (
|
|
DeviceStatusPending = "pending"
|
|
DeviceStatusConfirmed = "confirmed"
|
|
DeviceStatusUsed = "used"
|
|
DeviceStatusExpired = "expired"
|
|
DeviceStatusRevoked = "revoked"
|
|
)
|
|
|
|
// DeviceAuthSession is one QR or short-code device authorization
|
|
// (golem15_user_device_auth_sessions). TokenHash is the SHA-256 hex of the
|
|
// device token (column token, PHP's name); the token itself is never
|
|
// stored. JWTJTI is the jti of the session token the device received,
|
|
// followed through refresh.
|
|
type DeviceAuthSession struct {
|
|
ID uint `gorm:"column:id;primaryKey"`
|
|
TokenHash string `gorm:"column:token" json:"-"`
|
|
ShortCode *string `gorm:"column:short_code" json:"-"`
|
|
UserID *uint `gorm:"column:user_id"`
|
|
Status string `gorm:"column:status"`
|
|
ExpiresAt *time.Time `gorm:"column:expires_at"`
|
|
DeviceIP *string `gorm:"column:device_ip"`
|
|
DeviceUserAgent *string `gorm:"column:device_user_agent"`
|
|
DeviceName *string `gorm:"column:device_name"`
|
|
AuthIP *string `gorm:"column:auth_ip"`
|
|
ConfirmedAt *time.Time `gorm:"column:confirmed_at"`
|
|
UsedAt *time.Time `gorm:"column:used_at"`
|
|
SessionID *string `gorm:"column:session_id"`
|
|
LastActivityAt *time.Time `gorm:"column:last_activity_at"`
|
|
JWTJTI *string `gorm:"column:jwt_jti" json:"-"`
|
|
CreatedAt time.Time `gorm:"column:created_at"`
|
|
UpdatedAt time.Time `gorm:"column:updated_at"`
|
|
}
|
|
|
|
func (DeviceAuthSession) TableName() string { return "golem15_user_device_auth_sessions" }
|
|
|
|
func (DeviceAuthSession) Fillable() []string {
|
|
return []string{
|
|
"token", "short_code", "user_id", "status", "expires_at", "device_ip",
|
|
"device_user_agent", "device_name", "auth_ip", "confirmed_at", "used_at",
|
|
"session_id", "last_activity_at", "jwt_jti",
|
|
}
|
|
}
|
|
|
|
func (DeviceAuthSession) Hidden() []string { return []string{"token", "short_code", "jwt_jti"} }
|
|
|
|
// ExpiredAt reports whether a pending or confirmed session is past
|
|
// expires_at at now. A used, revoked or expired session, and one without
|
|
// expires_at, is never newly expired.
|
|
func (s DeviceAuthSession) ExpiredAt(now time.Time) bool {
|
|
if s.Status != DeviceStatusPending && s.Status != DeviceStatusConfirmed {
|
|
return false
|
|
}
|
|
return s.ExpiresAt != nil && !s.ExpiresAt.After(now)
|
|
}
|
|
|
|
func init() { Register(DeviceAuthSession{}) }
|