2021-09-29 20:35:39 +00:00
|
|
|
package domain
|
|
|
|
|
|
|
|
import (
|
2021-12-29 20:08:30 +00:00
|
|
|
"fmt"
|
2023-01-14 21:27:37 +00:00
|
|
|
"net/http"
|
2021-09-29 20:35:39 +00:00
|
|
|
"testing"
|
2021-10-13 21:53:31 +00:00
|
|
|
"time"
|
|
|
|
|
2022-06-09 19:14:21 +00:00
|
|
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
|
|
|
"github.com/lestrrat-go/jwx/v2/jwt"
|
2021-09-29 20:35:39 +00:00
|
|
|
|
2023-01-14 21:27:37 +00:00
|
|
|
"source.toby3d.me/toby3d/auth/internal/common"
|
2022-03-13 10:58:34 +00:00
|
|
|
"source.toby3d.me/toby3d/auth/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-02-17 15:11:27 +00:00
|
|
|
CreatedAt time.Time
|
|
|
|
Expiry time.Time
|
2023-01-14 21:27:37 +00:00
|
|
|
ClientID ClientID
|
|
|
|
Me Me
|
2022-02-17 15:11:27 +00:00
|
|
|
AccessToken string
|
|
|
|
RefreshToken string
|
2023-07-06 23:11:53 +00:00
|
|
|
Scope Scopes
|
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 {
|
2023-01-14 21:27:37 +00:00
|
|
|
Issuer ClientID
|
|
|
|
Subject Me
|
2023-07-06 23:11:53 +00:00
|
|
|
Algorithm string
|
2022-02-17 15:11:27 +00:00
|
|
|
Scope Scopes
|
2022-01-29 17:50:45 +00:00
|
|
|
Secret []byte
|
2023-07-06 23:11:53 +00:00
|
|
|
Expiration time.Duration
|
2023-01-14 21:27:37 +00:00
|
|
|
NonceLength uint8
|
2021-12-29 20:08:30 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2022-02-01 17:27:48 +00:00
|
|
|
// DefaultNewTokenOptions describes the default settings for NewToken.
|
2022-12-26 14:09:34 +00:00
|
|
|
//
|
|
|
|
//nolint:gochecknoglobals,gomnd
|
2021-12-29 20:08:30 +00:00
|
|
|
var DefaultNewTokenOptions = NewTokenOptions{
|
2022-02-01 17:27:48 +00:00
|
|
|
Expiration: 0,
|
|
|
|
Scope: nil,
|
2023-01-14 21:27:37 +00:00
|
|
|
Issuer: ClientID{},
|
|
|
|
Subject: Me{},
|
2022-02-17 15:11:27 +00:00
|
|
|
Secret: nil,
|
|
|
|
Algorithm: "HS256",
|
|
|
|
NonceLength: 32,
|
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-12-26 14:09:34 +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()
|
|
|
|
|
2023-01-16 10:29:40 +00:00
|
|
|
for key, val := range map[string]any{
|
2022-02-01 17:27:48 +00:00
|
|
|
"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
|
|
|
|
2023-01-14 21:27:37 +00:00
|
|
|
if opts.Issuer.clientID != 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-06-09 19:14:21 +00:00
|
|
|
accessToken, err := jwt.Sign(tkn, jwt.WithKey(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{
|
2022-02-17 15:11:27 +00:00
|
|
|
AccessToken: string(accessToken),
|
|
|
|
ClientID: opts.Issuer,
|
|
|
|
CreatedAt: now,
|
|
|
|
Expiry: now.Add(opts.Expiration),
|
|
|
|
Me: opts.Subject,
|
|
|
|
RefreshToken: "", // TODO(toby3d)
|
|
|
|
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-12-26 14:09:34 +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,
|
2022-02-25 15:29:48 +00:00
|
|
|
ScopeProfile,
|
|
|
|
ScopeEmail,
|
2021-12-29 20:08:30 +00:00
|
|
|
}
|
2021-10-13 21:53:31 +00:00
|
|
|
|
2023-01-16 10:29:40 +00:00
|
|
|
for key, val := range map[string]any{
|
2022-02-01 17:27:48 +00:00
|
|
|
// 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-06-09 19:14:21 +00:00
|
|
|
accessToken, err := jwt.Sign(tkn, jwt.WithKey(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{
|
2022-02-17 15:11:27 +00:00
|
|
|
CreatedAt: now.Add(-1 * time.Hour),
|
|
|
|
Expiry: now.Add(1 * time.Hour),
|
2023-01-14 21:27:37 +00:00
|
|
|
ClientID: *cid,
|
|
|
|
Me: *me,
|
2022-02-17 15:11:27 +00:00
|
|
|
Scope: scope,
|
|
|
|
AccessToken: string(accessToken),
|
|
|
|
RefreshToken: "", // TODO(toby3d)
|
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
|
|
|
|
}
|
|
|
|
|
2023-01-14 21:27:37 +00:00
|
|
|
r.Header.Set(common.HeaderAuthorization, t.String())
|
2021-12-29 20:08:30 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|