Improve browser caching with signed JWT photo URLs
* JWT tokens will now expire on the 10th of the next month, to produce consistent values for a period of time and aid with browser caching.
This commit is contained in:
parent
77a9d9a7fd
commit
2262edfe09
|
@ -131,7 +131,7 @@ func Landing() http.HandlerFunc {
|
|||
Gender: Gender(currentUser),
|
||||
VIP: isShy, // "shy accounts" use the "VIP" status for special icon in chat
|
||||
Rules: rules,
|
||||
RegisteredClaims: encryption.StandardClaims(currentUser.ID, currentUser.Username, 5*time.Minute),
|
||||
RegisteredClaims: encryption.StandardClaims(currentUser.ID, currentUser.Username, time.Now().Add(5*time.Minute)),
|
||||
}
|
||||
token, err := encryption.SignClaims(claims, []byte(config.Current.BareRTC.JWTSecret))
|
||||
if err != nil {
|
||||
|
|
|
@ -14,11 +14,11 @@ import (
|
|||
// It will include values for Subject (username), Issuer (site title), ExpiresAt, IssuedAt, NotBefore.
|
||||
//
|
||||
// If the userID is >0, the ID field is included.
|
||||
func StandardClaims(userID uint64, username string, expires time.Duration) jwt.RegisteredClaims {
|
||||
func StandardClaims(userID uint64, username string, expiresAt time.Time) jwt.RegisteredClaims {
|
||||
claim := jwt.RegisteredClaims{
|
||||
Subject: username,
|
||||
Issuer: config.Title,
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expires)),
|
||||
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"code.nonshy.com/nonshy/website/pkg/encryption"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/utility"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
|
@ -29,7 +30,7 @@ func VisibleAvatarURL(user, currentUser *models.User) string {
|
|||
|
||||
// SignedPhotoURL returns a URL path to a photo's filename, signed for the current user only.
|
||||
func SignedPhotoURL(user *models.User, filename string) string {
|
||||
return createSignedPhotoURL(user.ID, user.Username, filename, config.SignedPhotoJWTExpires, false)
|
||||
return createSignedPhotoURL(user.ID, user.Username, filename, false)
|
||||
}
|
||||
|
||||
// SignedPublicAvatarURL returns a signed URL for a user's public square avatar image, which has
|
||||
|
@ -38,7 +39,7 @@ func SignedPhotoURL(user *models.User, filename string) string {
|
|||
// The primary use case is for the chat room: users are sent into chat with their avatar URL,
|
||||
// and it must be viewable to all users for a long time.
|
||||
func SignedPublicAvatarURL(filename string) string {
|
||||
return createSignedPhotoURL(0, "@", filename, config.SignedPublicAvatarJWTExpires, true)
|
||||
return createSignedPhotoURL(0, "@", filename, true)
|
||||
}
|
||||
|
||||
// SignedPhotoClaims are a JWT claims object used to sign and authenticate image (direct .jpg) links.
|
||||
|
@ -60,13 +61,21 @@ func FilenameHash(filename string) string {
|
|||
}
|
||||
|
||||
// Common function to create a signed photo URL with an expiration.
|
||||
func createSignedPhotoURL(userID uint64, username string, filename string, expires time.Duration, anyone bool) string {
|
||||
func createSignedPhotoURL(userID uint64, username string, filename string, anyone bool) string {
|
||||
|
||||
claims := SignedPhotoClaims{
|
||||
FilenameHash: FilenameHash(filename),
|
||||
Anyone: anyone,
|
||||
RegisteredClaims: encryption.StandardClaims(userID, username, expires),
|
||||
}
|
||||
// Claims expire on the 10th of next month.
|
||||
var (
|
||||
expiresAt = utility.NextMonth(time.Now(), 10)
|
||||
claims = SignedPhotoClaims{
|
||||
FilenameHash: FilenameHash(filename),
|
||||
Anyone: anyone,
|
||||
RegisteredClaims: encryption.StandardClaims(userID, username, expiresAt),
|
||||
}
|
||||
)
|
||||
|
||||
// Lock the date stamps for a consistent JWT value for caching.
|
||||
claims.IssuedAt = nil
|
||||
claims.NotBefore = nil
|
||||
|
||||
log.Debug("createSignedPhotoURL(%s): %+v", filename, claims)
|
||||
|
||||
|
|
|
@ -7,6 +7,22 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// NextMonth takes an input time (usually time.Now) and will return the next month on the given day.
|
||||
//
|
||||
// Example, NextMonth from any time in April should return e.g. May 10th.
|
||||
func NextMonth(now time.Time, day int) time.Time {
|
||||
var (
|
||||
year, month, _ = now.Date()
|
||||
nextMonth = month + 1
|
||||
)
|
||||
if nextMonth > 12 {
|
||||
nextMonth = 1
|
||||
year++
|
||||
}
|
||||
|
||||
return time.Date(year, nextMonth, day, 0, 0, 0, 0, now.Location())
|
||||
}
|
||||
|
||||
// FormatDurationCoarse returns a pretty printed duration with coarse granularity.
|
||||
func FormatDurationCoarse(duration time.Duration) string {
|
||||
// Negative durations (e.g. future dates) should work too.
|
||||
|
|
|
@ -7,6 +7,53 @@ import (
|
|||
"code.nonshy.com/nonshy/website/pkg/utility"
|
||||
)
|
||||
|
||||
func TestNextMonth(t *testing.T) {
|
||||
var tests = []struct {
|
||||
Now string
|
||||
Day int
|
||||
Expect string
|
||||
}{
|
||||
{
|
||||
Now: "1995-08-01",
|
||||
Day: 15,
|
||||
Expect: "1995-09-15",
|
||||
},
|
||||
{
|
||||
Now: "2006-12-15",
|
||||
Day: 11,
|
||||
Expect: "2007-01-11",
|
||||
},
|
||||
{
|
||||
Now: "2006-12-01",
|
||||
Day: 15,
|
||||
Expect: "2007-01-15",
|
||||
},
|
||||
{
|
||||
Now: "2007-01-15",
|
||||
Day: 29, // no leap day
|
||||
Expect: "2007-03-01",
|
||||
},
|
||||
{
|
||||
Now: "2004-01-08",
|
||||
Day: 29, // leap day
|
||||
Expect: "2004-02-29",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
now, err := time.Parse(time.DateOnly, test.Now)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d: parse error: %s", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
actual := utility.NextMonth(now, test.Day).Format("2006-01-02")
|
||||
if actual != test.Expect {
|
||||
t.Errorf("Test #%d: expected %s but got %s", i, test.Expect, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatDurationCoarse(t *testing.T) {
|
||||
var tests = []struct {
|
||||
In time.Duration
|
||||
|
|
Loading…
Reference in New Issue
Block a user