auth/internal/domain/token.go

182 lines
4.1 KiB
Go
Raw Normal View History

package domain
import (
2021-12-29 20:08:30 +00:00
"fmt"
"testing"
"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"
"source.toby3d.me/toby3d/auth/internal/random"
)
2021-12-29 20:08:30 +00:00
type (
// Token describes the data of the token used by the clients.
Token struct {
CreatedAt time.Time
Expiry time.Time
ClientID *ClientID
Me *Me
Scope Scopes
AccessToken string
RefreshToken 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
2022-01-29 17:50:45 +00:00
Issuer *ClientID
2022-02-17 21:53:39 +00:00
Subject *Me
Scope Scopes
2022-01-29 17:50:45 +00:00
Secret []byte
Algorithm string
NonceLength int
2021-12-29 20:08:30 +00:00
}
)
// DefaultNewTokenOptions describes the default settings for NewToken.
//nolint: gochecknoglobals, gomnd
2021-12-29 20:08:30 +00:00
var DefaultNewTokenOptions = NewTokenOptions{
Expiration: 0,
Scope: nil,
Issuer: nil,
Subject: nil,
Secret: nil,
Algorithm: "HS256",
NonceLength: 32,
}
2022-01-29 17:50:45 +00:00
// NewToken create a new token by provided options.
2022-02-17 21:53:39 +00:00
//nolint: funlen,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)
}
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 {
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 {
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
}
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,
CreatedAt: now,
Expiry: now.Add(opts.Expiration),
Me: opts.Subject,
RefreshToken: "", // TODO(toby3d)
Scope: opts.Scope,
}, nil
}
2022-01-29 17:50:45 +00:00
// TestToken returns valid random generated token for tests.
//nolint: gomnd // testing domain can contains non-standart values
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 {
tb.Fatal(err)
2022-01-30 17:49:25 +00:00
}
2021-10-17 20:13:14 +00:00
tkn := jwt.New()
2021-12-29 20:08:30 +00:00
cid := TestClientID(tb)
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,
ScopeProfile,
ScopeEmail,
2021-12-29 20:08:30 +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)
}
accessToken, err := jwt.Sign(tkn, jwa.HS256, []byte("hackme"))
2022-01-30 17:49:25 +00:00
if err != nil {
tb.Fatal(err)
2022-01-30 17:49:25 +00:00
}
return &Token{
CreatedAt: now.Add(-1 * time.Hour),
Expiry: now.Add(1 * time.Hour),
ClientID: cid,
Me: me,
Scope: scope,
AccessToken: string(accessToken),
RefreshToken: "", // TODO(toby3d)
}
}
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 ""
}
return "Bearer " + t.AccessToken
2021-12-29 20:08:30 +00:00
}