feat: device list and revoke with the jti followed through refresh

GET /devices lists the caller's confirmed and used device sessions (PHP's
fields, device_name from the stored name or the user agent, is_current
for the session the request's token belongs to). DELETE /devices/{id}
marks the session revoked and blacklists the device token's jti for its
whole refresh window in one transaction. POST /refresh hands a used
session's jwt_jti over to the refreshed token (classes.HandOverDeviceJTI),
so a device that refreshed before the revoke is signed out too.
This commit is contained in:
2026-10-26 10:19:25 +01:00
parent 4e3e5b05b5
commit 3b22134292
7 changed files with 353 additions and 9 deletions

View File

@@ -1,12 +1,14 @@
package classes
import (
"context"
"crypto/rand"
"encoding/base64"
"strings"
"time"
"git.golem15.com/golem15/summercms/modules/backpack"
"gorm.io/gorm"
)
// DeviceShortCodeAlphabet is the alphabet of device short codes (PHP's
@@ -126,3 +128,17 @@ func DeviceDisplayName(stored *string, userAgent string) string {
}
return "Unknown Device"
}
// HandOverDeviceJTI moves a used device session from the jti of a session
// token to the jti of the token that refreshed it, and stamps its
// last_activity_at, so revoking the device still reaches the token the
// device holds now. POST /refresh calls it after a successful refresh;
// empty values do nothing.
func HandOverDeviceJTI(ctx context.Context, db *gorm.DB, oldJTI, newJTI string) error {
if db == nil || oldJTI == "" || newJTI == "" || oldJTI == newJTI {
return nil
}
return db.WithContext(ctx).Exec(`UPDATE golem15_user_device_auth_sessions
SET jwt_jti = ?, last_activity_at = NOW(), updated_at = NOW()
WHERE jwt_jti = ? AND status = 'used'`, newJTI, oldJTI).Error
}