2021-09-29 20:35:39 +00:00
|
|
|
package domain
|
|
|
|
|
|
|
|
import (
|
2021-12-29 20:08:30 +00:00
|
|
|
"fmt"
|
2021-09-29 20:35:39 +00:00
|
|
|
"testing"
|
2021-10-13 21:53:31 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/lestrrat-go/jwx/jwa"
|
|
|
|
"github.com/lestrrat-go/jwx/jwt"
|
2021-12-29 20:08:30 +00:00
|
|
|
http "github.com/valyala/fasthttp"
|
2021-09-29 20:35:39 +00:00
|
|
|
|
2022-01-04 17:31:33 +00:00
|
|
|
"source.toby3d.me/website/indieauth/internal/random"
|
2021-09-29 20:35:39 +00:00
|
|
|
)
|
|
|
|
|
2021-12-29 20:08:30 +00:00
|
|
|
type (
|
|
|
|
// Token describes the data of the token used by the clients.
|
|
|
|
Token struct {
|
2022-01-29 17:50:45 +00:00
|
|
|
Scope Scopes
|
2021-12-29 20:08:30 +00:00
|
|
|
ClientID *ClientID
|
|
|
|
Me *Me
|
2022-01-29 17:50:45 +00:00
|
|
|
AccessToken string
|
2021-12-29 20:08:30 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// NewTokenOptions contains options for NewToken function.
|
2021-12-29 20:08:30 +00:00
|
|
|
NewTokenOptions struct {
|
|
|
|
Expiration time.Duration
|
|
|
|
Scope Scopes
|
2022-01-29 17:50:45 +00:00
|
|
|
Issuer *ClientID
|
2021-12-29 20:08:30 +00:00
|
|
|
Subject *Me
|
2022-01-29 17:50:45 +00:00
|
|
|
Secret []byte
|
|
|
|
Algorithm string
|
|
|
|
NonceLength int
|
2021-12-29 20:08:30 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2022-02-01 17:27:48 +00:00
|
|
|
// DefaultNewTokenOptions describes the default settings for NewToken.
|
|
|
|
//nolint: gochecknoglobals, gomnd
|
2021-12-29 20:08:30 +00:00
|
|
|
var DefaultNewTokenOptions = NewTokenOptions{
|
|
|
|
Algorithm: "HS256",
|
2022-02-01 17:27:48 +00:00
|
|
|
Expiration: 0,
|
|
|
|
Issuer: nil,
|
2022-01-12 18:04:40 +00:00
|
|
|
NonceLength: 32,
|
2022-02-01 17:27:48 +00:00
|
|
|
Scope: nil,
|
|
|
|
Secret: nil,
|
|
|
|
Subject: nil,
|
2021-09-29 20:35:39 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// NewToken create a new token by provided options.
|
2022-02-01 17:27:48 +00:00
|
|
|
//nolint: cyclop
|
2021-12-29 20:08:30 +00:00
|
|
|
func NewToken(opts NewTokenOptions) (*Token, error) {
|
|
|
|
if opts.NonceLength == 0 {
|
|
|
|
opts.NonceLength = DefaultNewTokenOptions.NonceLength
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.Algorithm == "" {
|
|
|
|
opts.Algorithm = DefaultNewTokenOptions.Algorithm
|
|
|
|
}
|
|
|
|
|
|
|
|
now := time.Now().UTC().Round(time.Second)
|
|
|
|
|
|
|
|
nonce, err := random.String(opts.NonceLength)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot generate nonce: %w", err)
|
|
|
|
}
|
|
|
|
|
2022-02-01 17:27:48 +00:00
|
|
|
tkn := jwt.New()
|
|
|
|
|
|
|
|
for key, val := range map[string]interface{}{
|
|
|
|
"nonce": nonce,
|
|
|
|
"scope": opts.Scope,
|
|
|
|
jwt.IssuedAtKey: now,
|
|
|
|
jwt.NotBeforeKey: now,
|
|
|
|
jwt.SubjectKey: opts.Subject.String(),
|
|
|
|
} {
|
|
|
|
if err = tkn.Set(key, val); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to set JWT token field: %w", err)
|
|
|
|
}
|
|
|
|
}
|
2021-12-29 20:08:30 +00:00
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
if opts.Issuer != nil {
|
2022-02-01 17:27:48 +00:00
|
|
|
if err = tkn.Set(jwt.IssuerKey, opts.Issuer.String()); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to set JWT token field: %w", err)
|
|
|
|
}
|
2022-01-29 17:50:45 +00:00
|
|
|
}
|
|
|
|
|
2021-12-29 20:08:30 +00:00
|
|
|
if opts.Expiration != 0 {
|
2022-02-01 17:27:48 +00:00
|
|
|
if err = tkn.Set(jwt.ExpirationKey, now.Add(opts.Expiration)); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to set JWT token field: %w", err)
|
|
|
|
}
|
2021-12-29 20:08:30 +00:00
|
|
|
}
|
2021-09-29 20:35:39 +00:00
|
|
|
|
2022-02-01 17:27:48 +00:00
|
|
|
accessToken, err := jwt.Sign(tkn, jwa.SignatureAlgorithm(opts.Algorithm), opts.Secret)
|
2021-12-29 20:08:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot sign a new access token: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Token{
|
|
|
|
AccessToken: string(accessToken),
|
|
|
|
ClientID: opts.Issuer,
|
|
|
|
Me: opts.Subject,
|
|
|
|
Scope: opts.Scope,
|
2022-02-01 17:27:48 +00:00
|
|
|
}, nil
|
2021-09-29 20:35:39 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// TestToken returns valid random generated token for tests.
|
2022-02-01 17:27:48 +00:00
|
|
|
//nolint: gomnd // testing domain can contains non-standart values
|
2021-09-29 20:35:39 +00:00
|
|
|
func TestToken(tb testing.TB) *Token {
|
|
|
|
tb.Helper()
|
|
|
|
|
2022-01-12 18:04:40 +00:00
|
|
|
nonce, err := random.String(22)
|
2022-01-30 17:49:25 +00:00
|
|
|
if err != nil {
|
2022-02-01 17:27:48 +00:00
|
|
|
tb.Fatal(err)
|
2022-01-30 17:49:25 +00:00
|
|
|
}
|
2021-10-17 20:13:14 +00:00
|
|
|
|
2022-02-01 17:27:48 +00:00
|
|
|
tkn := jwt.New()
|
2021-12-29 20:08:30 +00:00
|
|
|
cid := TestClientID(tb)
|
2022-01-29 13:49:27 +00:00
|
|
|
me := TestMe(tb, "https://user.example.net/")
|
2021-12-29 20:08:30 +00:00
|
|
|
now := time.Now().UTC().Round(time.Second)
|
2022-01-08 10:53:58 +00:00
|
|
|
scope := Scopes{
|
2021-12-29 20:08:30 +00:00
|
|
|
ScopeCreate,
|
|
|
|
ScopeDelete,
|
2022-01-08 10:53:58 +00:00
|
|
|
ScopeUpdate,
|
2021-12-29 20:08:30 +00:00
|
|
|
}
|
2021-10-13 21:53:31 +00:00
|
|
|
|
2022-02-01 17:27:48 +00:00
|
|
|
for key, val := range map[string]interface{}{
|
|
|
|
// NOTE(toby3d): required
|
|
|
|
jwt.IssuerKey: cid.String(),
|
|
|
|
jwt.SubjectKey: me.String(),
|
|
|
|
jwt.ExpirationKey: now.Add(1 * time.Hour),
|
|
|
|
jwt.NotBeforeKey: now.Add(-1 * time.Hour),
|
|
|
|
jwt.IssuedAtKey: now.Add(-1 * time.Hour),
|
|
|
|
// TODO(toby3d): jwt.AudienceKey
|
|
|
|
// TODO(toby3d): jwt.JwtIDKey
|
|
|
|
// NOTE(toby3d): optional
|
|
|
|
"scope": scope,
|
|
|
|
"nonce": nonce,
|
|
|
|
} {
|
|
|
|
_ = tkn.Set(key, val)
|
|
|
|
}
|
2021-10-13 21:53:31 +00:00
|
|
|
|
2022-02-01 17:27:48 +00:00
|
|
|
accessToken, err := jwt.Sign(tkn, jwa.HS256, []byte("hackme"))
|
2022-01-30 17:49:25 +00:00
|
|
|
if err != nil {
|
2022-02-01 17:27:48 +00:00
|
|
|
tb.Fatal(err)
|
2022-01-30 17:49:25 +00:00
|
|
|
}
|
2021-10-13 21:53:31 +00:00
|
|
|
|
2021-09-29 20:35:39 +00:00
|
|
|
return &Token{
|
2021-12-29 20:08:30 +00:00
|
|
|
ClientID: cid,
|
|
|
|
Me: me,
|
|
|
|
Scope: scope,
|
2021-10-13 21:53:31 +00:00
|
|
|
AccessToken: string(accessToken),
|
2021-09-29 20:35:39 +00:00
|
|
|
}
|
|
|
|
}
|
2021-12-29 20:08:30 +00:00
|
|
|
|
|
|
|
// SetAuthHeader writes an Access Token to the request header.
|
2022-01-12 18:04:40 +00:00
|
|
|
func (t Token) SetAuthHeader(r *http.Request) {
|
2021-12-29 20:08:30 +00:00
|
|
|
if t.AccessToken == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Header.Set(http.HeaderAuthorization, t.String())
|
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// String returns string representation of token.
|
2022-01-12 18:04:40 +00:00
|
|
|
func (t Token) String() string {
|
2021-12-29 20:08:30 +00:00
|
|
|
if t.AccessToken == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2022-02-01 17:27:48 +00:00
|
|
|
return "Bearer " + t.AccessToken
|
2021-12-29 20:08:30 +00:00
|
|
|
}
|