51 lines
1.6 KiB
Go
51 lines
1.6 KiB
Go
|
|
package lastfm
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/navidrome/navidrome/core/auth"
|
||
|
|
)
|
||
|
|
|
||
|
|
const (
|
||
|
|
linkTokenScope = "lastfm-link"
|
||
|
|
linkTokenTTL = 5 * time.Minute
|
||
|
|
)
|
||
|
|
|
||
|
|
// createLinkToken issues a signed token binding the Last.fm callback to the
|
||
|
|
// user who initiated the OAuth flow. It travels back through Last.fm via the
|
||
|
|
// `cb` URL in place of the previously-trusted raw `uid` query parameter.
|
||
|
|
func createLinkToken(userID string) (string, error) {
|
||
|
|
claims := map[string]any{
|
||
|
|
"uid": userID,
|
||
|
|
"scope": linkTokenScope,
|
||
|
|
"exp": time.Now().Add(linkTokenTTL).UTC().Unix(),
|
||
|
|
}
|
||
|
|
return auth.EncodeToken(claims)
|
||
|
|
}
|
||
|
|
|
||
|
|
// verifyLinkToken validates a signed link token and returns the encoded user ID.
|
||
|
|
// It enforces both the signature/expiry (via the underlying JWT verifier) and a
|
||
|
|
// dedicated scope claim, preventing tokens minted for other purposes (e.g. a
|
||
|
|
// regular session JWT) from being accepted here.
|
||
|
|
func verifyLinkToken(tokenStr string) (string, error) {
|
||
|
|
token, err := auth.DecodeAndVerifyToken(tokenStr)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
// jwtauth treats a token without `exp` as non-expiring; require it
|
||
|
|
// explicitly so an accidental regression cannot mint permanent tokens.
|
||
|
|
if exp, ok := token.Expiration(); !ok || exp.IsZero() {
|
||
|
|
return "", errors.New("link token missing expiration")
|
||
|
|
}
|
||
|
|
var scope string
|
||
|
|
if err := token.Get("scope", &scope); err != nil || scope != linkTokenScope {
|
||
|
|
return "", errors.New("invalid link token scope")
|
||
|
|
}
|
||
|
|
var uid string
|
||
|
|
if err := token.Get("uid", &uid); err != nil || uid == "" {
|
||
|
|
return "", errors.New("invalid link token user ID")
|
||
|
|
}
|
||
|
|
return uid, nil
|
||
|
|
}
|