Skip to content
Snippets Groups Projects
Commit 1d8b85be4337 authored by Christophe de Vienne's avatar Christophe de Vienne
Browse files

TokenOptions: if secret is missing, a random one is set

parent 8f17e465eba0
No related branches found
No related tags found
No related merge requests found
Pipeline #121134 failed
package auth
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"net/http"
"time"
......@@ -4,9 +5,10 @@
"encoding/hex"
"errors"
"fmt"
"net/http"
"time"
"github.com/rs/zerolog"
"gopkg.in/dgrijalva/jwt-go.v3"
)
......@@ -36,7 +38,7 @@
//
//nolint:lll
type TokenOptions struct {
SecretOpt func(string) error `long:"token-secret" env:"AUTH_TOKEN_SECRET" ini-name:"secret" required:"t" description:"hex HMAC256/AES256 secret for signing/verifying JWT & crypting (32 bytes)"`
SecretOpt string `long:"token-secret" env:"AUTH_TOKEN_SECRET" ini-name:"secret" description:"hex HMAC256/AES256 secret for signing/verifying JWT & crypting (32 bytes)"`
Secret []byte `no-flag:"t"`
Expiration time.Duration `no-flag:"t"`
ExpirationOpt func(string) error `long:"token-expiration" ini-name:"expiration" required:"false" description:"Expiration time of the generated tokens" default:"5m"`
......@@ -47,6 +49,7 @@
CachePurgeDelayOpt func(string) error `long:"token-cache-purge-delay" ini-name:"cache-purge-delay" required:"false" description:"Delay between token cache purges" default:"10m"`
cookieBasename string
log func() *zerolog.Logger
}
func durationOption(tgt *time.Duration) func(string) error {
......@@ -61,19 +64,4 @@
}
}
func hexBytesOption(size int, tgt *[]byte) func(string) error {
return func(s string) error {
b, err := hex.DecodeString(s)
if err != nil {
return fmt.Errorf("invalid hex string: %w", err)
}
if len(b) != size {
return fmt.Errorf("invalid option size. Expected %d bytes, got: %d", size, len(b))
}
*tgt = b
return nil
}
}
// NewTokenOptions creates a TokenOptions.
......@@ -79,4 +67,4 @@
// NewTokenOptions creates a TokenOptions.
func NewTokenOptions(cookieBasename string) *TokenOptions {
func NewTokenOptions(cookieBasename string, getLog func() *zerolog.Logger) *TokenOptions {
options := TokenOptions{
cookieBasename: cookieBasename,
......@@ -81,5 +69,6 @@
options := TokenOptions{
cookieBasename: cookieBasename,
log: getLog,
}
options.ExpirationOpt = durationOption(&options.Expiration)
options.CachePurgeDelayOpt = durationOption(&options.CachePurgeDelay)
......@@ -83,7 +72,6 @@
}
options.ExpirationOpt = durationOption(&options.Expiration)
options.CachePurgeDelayOpt = durationOption(&options.CachePurgeDelay)
options.SecretOpt = hexBytesOption(32, &options.Secret)
return &options
}
......@@ -93,6 +81,33 @@
SetExpiresAt(time.Time)
}
func (t TokenOptions) MakeToken(
// EnsureSecret makes sure a valid secret is configured.
func (t *TokenOptions) EnsureSecret(strict bool) error {
if len(t.Secret) == 32 {
return nil
}
if t.SecretOpt == "" {
if strict {
return fmt.Errorf("token-secret is not configured")
}
t.log().Warn().Msg("no secret configured. A random secret will be used, meaning authenticated session will not survice a server restart. To avoid this, please set the token-secret option")
t.Secret = make([]byte, 32)
_, _ = rand.Read(t.Secret)
} else {
b, err := hex.DecodeString(t.SecretOpt)
if err != nil {
return fmt.Errorf("'secret' is not a valid hex string: %w", err)
}
if len(b) != 32 {
return fmt.Errorf("'secret' has in invalid size. Expected %d bytes, got: %d", 32, len(b))
}
t.Secret = b
}
return nil
}
func (t *TokenOptions) MakeToken(
claims Claims, withCookie bool,
) (string, *http.Cookie, error) {
......@@ -97,5 +112,8 @@
claims Claims, withCookie bool,
) (string, *http.Cookie, error) {
if err := t.EnsureSecret(false); err != nil {
return "", nil, err
}
expiration := t.Expiration
if withCookie {
expiration *= 2
......@@ -123,7 +141,10 @@
return stoken, cookie, nil
}
func (t TokenOptions) ParseToken(tokenString string, claims Claims) error {
func (t *TokenOptions) ParseToken(tokenString string, claims Claims) error {
if err := t.EnsureSecret(false); err != nil {
return err
}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) {
return t.Secret, nil
})
......
......@@ -168,15 +168,6 @@
})
}
func WithTokenOptions[E any]() Option[E] {
return func(program *Program[E]) {
program.TokenOptions = auth.NewTokenOptions(program.Name)
if _, err := program.Parser.AddGroup("Token", "Token Options", program.TokenOptions); err != nil {
panic(err)
}
}
}
func WithDatabase[E any](migrateSource source.Driver) Option[E] {
return func(program *Program[E]) {
program.hasDB = true
......
package cmd
import (
"crypto/rand"
"encoding/hex"
"fmt"
"github.com/justinas/alice"
......@@ -4,5 +8,6 @@
"github.com/justinas/alice"
"github.com/rs/zerolog"
"orus.io/orus-io/go-orusapi/auth"
)
......@@ -5,7 +10,23 @@
"orus.io/orus-io/go-orusapi/auth"
)
func WithTokenOptions[E any]() Option[E] {
return func(program *Program[E]) {
program.TokenOptions = auth.NewTokenOptions(
program.Name,
func() *zerolog.Logger {
return &program.Logger
},
)
if _, err := program.Parser.AddGroup("Token", "Token Options", program.TokenOptions); err != nil {
panic(err)
}
SetupGenerateAuthSecretCmd(program)
}
}
func WithAuthMiddleware[E any](middleware any) Option[E] {
return func(program *Program[E]) {
program.authMiddlewares = append(program.authMiddlewares, getMiddleware[E](middleware))
......@@ -22,3 +43,32 @@
},
)
}
type GenerateAuthSecretCmd[E any] struct {
program *Program[E]
}
func (cmd *GenerateAuthSecretCmd[E]) Execute([]string) error {
secret := make([]byte, 32)
if _, err := rand.Read(secret); err != nil {
panic(err)
}
_, err := fmt.Println(hex.EncodeToString(secret))
return err
}
func SetupGenerateAuthSecretCmd[E any](program *Program[E]) *GenerateAuthSecretCmd[E] {
cmd := GenerateAuthSecretCmd[E]{
program: program,
}
if _, err := program.Parser.AddCommand(
"generate-auth-secret", "Generate a proper auth secret", "", &cmd,
); err != nil {
program.Logger.Fatal().Err(err).Msg("could not init generate-auth-secret command")
}
return &cmd
}
......@@ -38,6 +38,9 @@
}
func (cmd *ServeCmd[E]) Execute([]string) error {
if err := cmd.program.TokenOptions.EnsureSecret(false); err != nil {
return err
}
if err := cmd.program.EnsureDB(cmd.AutoMigrate); err != nil {
return err
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment